JVM学习笔记
看的《深入理解Java虚拟机》
---------------Java内存区域-------------
一。运行时数据区域
1.Java虚拟机管理的内存包括的运行时数据区:程序计数器Program Couter Register,虚拟机栈VM Stack,本地方法栈Native Method Stack,堆Heap,方法区Method Area。
2.程序计数器:当前线程所执行的字节码的行号指示器。字节码解释器工作时通过改变计数器的值来选取下一条需要执行的字节码指令,分之、循环、跳转、异常处理、线程恢复等基础功能都需要依赖计数器完成。每条线程都有独立的程序计数器,各条线程之间的计数器互不影响,独立存储,这类内存区域为“线程私有”内存。若线程执行Java方法,计数器记录的是正在执行的虚拟机字节码地址,若是Native方法,计数器值为空。程序计数器的内存区域是唯一在JVM规范中没有规定任何OutOfMemoryError情况的区域。
3.Java虚拟机栈:线程私有的,生命周期与线程相同。VM栈是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧Stack Frame,用于存储局部变量表、操作栈、动态链接、方法出口等信息。每个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈从入栈到出栈的过程。局部变量表存放了编译器可知的各种基本数据、对象引用和returnAddress类型,所需的内存空间在编译期间完成分配,进入方法时,方法需要的帧中局部变量空间Slot是完全确定的不会改变。如果线程请求的栈深度大于VM允许,抛出StackOverflowError异常;如果VM栈动态扩展到无法申请到足够内存时抛出OutOfMemoryError异常。
4.本地方法栈:与虚拟机栈作用类似,区别是使用的是Native方法服务(Sun HotSpot虚拟机将两者合二为一)。同样抛出StackOverflowError和OutOfMemoryError异常。
5.Java堆:存放对象实例,几乎所有对象实例都在堆上分配。是JVM内存中最大一块,是所有线程共享的内存区域,在JVM启动时创建。堆是垃圾收集的主要区域,由分代收集算法,堆分为新生代和老年代。再细致分为Eden空间、From Survivor空间、To Survivor空间。从内存分配角度看,线程共享的堆总可划分多个线程私有的分配缓冲区TLAB。无论如何划分,存储的仍然是对象实例,划分目的是回收和分配内存。堆可以处于物理上不连续的内存空间,大小可扩展(通过-XmxJVM最大允许分配堆内存,-Xms堆的最小值,-Xmn年轻代大小)若堆中没有内存完成实例分配且堆无法扩展时,抛出OutOfMemoryError异常。
6.方法区:线程共享的内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区被称为永久代Permanent Generation。方法区垃圾回收主要针对常量池的回收和对类型的卸载。方法区无法满足内存分配需求时将抛出OutOfMemoryError。
7.运行时常量池:方法区的一部分,用于存放编译器的字面量和符号引用,将在类加载后放入。具备动态性,运行期间可放新常量。
8.class文件内容:类的版本、字段、方法、接口等描述信息和常量池。
9.直接内存:不是JVM数据区内容。eg.NIO类使用Native函数库直接分配堆外内存。
二。对象访问
10.涉及栈、堆、方法区上重要内存区域的关联关系。
11.eg.Object obj=new Object(); :“Object obj”反映到栈的本地变量表中作为一个reference类型数据出现。“new Object()”反映到堆中,形成存储了Object类型所有实例数据值的结构化内存,堆中还包括能查找到此对象类型数据(如对象类型、父类、实现接口、方法等)的地址信息,这些类型数据则存储在方法区中。
12.reference类型规定了一个指向对象的引用,访问方式有两种:使用句柄和直接指针。句柄:堆中划分出一块内存作为句柄池,reference存储的是对象的句柄信息,句柄中包含了对象实例数据和类型数据各自的具体地址信息。直接指针:reference直接存储对象地址。(Sun HotSpot选用直接指针)
---------------垃圾收集器与内存分配策略-------------
一。垃圾收集条件判断
13.简介:Java垃圾回收机制用于在空闲时间以不定时的方式动态回收无任何引用的对象占据的内存空间(注意:垃圾回收回收的是无任何引用的对象占据的内存空间而不是对象本身)。程序计数器、虚拟机栈、本地方法栈三区域随线程而生而灭,内存分配和回收具备确定性,方法结束或线程结束时内存自然回收。而Java堆和方法区则不一样,只有在程序处于运行期间时才能知道创建的对象,这部分内存的分配和回收是动态的,垃圾收集器关注的是这部分内存。
14.判断对象不再被使用:1.引用计数算法(微软的COM技术、Python语言使用):引用则计数器+1,引用失效-1,计数器为0不再被使用。但是,难以解决对象之间的相互引用问题。2.根搜索算法(Java、C#使用)(第一次标记):通过一系列名为“GC Roots”的对象作为起始点,从这些节点开始向下搜索走过的路径,成为引用链Reference Chain,当GC Roots没有引用链相连即GC Roots到某对象不可达则该对象不可用即可回收。
15.Java中可作为GC Roots的对象包括:栈帧中本地变量表中引用的对象,方法区中类静态属性引用的对象,方法区中的常态引用的对象,本地方法栈中Native方法引用的对象。
16.JDK1.2前对引用的定义:如果Reference类型数据中存储的数值代表另一块内存的起始地址,就称这块内存代表着一个引用。
17.JDK1.2后引用概念补充:将引用分为强引用Strong,软引用Soft,弱引用Weak,虚引用Phantom,引用强度依次减弱。
18.强引用:程序代码中普遍存在的。eg.“Object obj=new Object()”,只要强引用存在,垃圾收集器不会回收被引用的对象。
19.软引用:还有用但非必须的对象。软引用着的对象,在系统发生内存溢出异常之前,会被列进回收范围之中。提供了SoftReference类来实现软引用。
20.弱引用:描述非必须对象,强度比软引用更弱,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。垃圾收集器工作时,无论内存是否足够都会回收只被弱引用关联的对象。提供了WeakRererence类实现弱引用。
21.虚引用:最弱的引用关系,一个对象是否有虚引用的存在都不会对生存时间构成影响,无法通过虚引用获得对象实例。设置虚引用关联的目的是在该对象呗收集器回收时收到系统通知。提供了PhantomReference类实现虚引用。
22.垃圾收集器的执行逻辑:先是两次标记:第一次,通过根搜素;第二次,此对象是否有必要执行finalize()方法,加入待删除的对象队列,垃圾收集器对待删除队列再次标记。然后垃圾收集器把两次标记的对象回收了。
23.回收方法区(永久代):永久代的垃圾收集主要回收两部分内容:废弃常量和无用的类。废弃常量的回收与根搜索方法同。类则需要满足以下三个条件才可判为无用的类:该类所有实例已被回收即堆中不存在该类任何实例;加载该类的ClassLoader已经被回收;该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。JVM仅仅是可以回收满足上述3个条件的无用类,而非和对象一样,不使用了必然回收。是否回收,HopSpot虚拟机提供了-Xnoclassgc参数进行控制
二。垃圾回收算法
24.标记-清除算法Mark-Sweep:内容顾名思义,是最基础的收集算法。缺点:效率不高;标记清除后产生大量不连续的内存碎片,当需要分配较大对象是不得不提前触发垃圾收集动作获得足够连续内存。
25.复制算法Copying:内存分为容量大小相等两块,一块内存用完了就将存活的对象复制到另外一块,然后将已使用过的内存空间清理。缺点:内存缩小为原来的一半。现在的商业虚拟机都采用复制算法回收新生代,且将内存分配改进为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中的一块Survivor空间。HotSpot虚拟机默认Eden和Survivor大小比例是8:1。
26.标记-整理算法:针对老年代,标记过程仍顾名思义,后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
27.分代收集算法:根据对象的存活周期不同将内存划分为几块。把Java堆分为新生代和老年代,新生代中每次垃圾收集只有少量存活,选用复制算法,老年代因为对象存活率高,使用标记-清理或标记-整理算法。
三。垃圾收集器(-XX:+UseXxxGC使用)
28.概论:收集算法是内存回收的方法论,垃圾收集器是内存回收的具体实现。有7种作用于不同分代的收集器。
29.Serial收集器(串行GC):Client模式下默认的新生代收集器。单线程,暂停其他所有的工作线程,能与CMS收集器配合。外号“Stop the World”,进行垃圾收集时必须暂停其他所有的工作线程。可用的控制参数eg.-XX:SurvivorRatio生存者比率即新生代中Eden区与Survivor区比值、-XX:PretenureSizeThreshold新生代晋升老年代对象大小、-XX:HandlePromotionFailure是否允许新生代担保。
30.ParNew收集器(并行GC):Serial收集器的多线程版本。Server模式下虚拟机首选的新生代收集器,因为除Serial外唯一能与CMS收集器配合工作。使用-XX:ParallelGCThreads参数限制垃圾收集的线程数。
31.区别概念:并行和并发。并行:多条垃圾收集线程并行工作,但用户线程仍处于等待状态。并发:用户线程与垃圾收集线程同时执行(可交替执行),用户线程继续执行,而垃圾收集程序运行于另一个CPU上。
32.Parallel Scavenge收集器(并行GC):新生代收集器,使用复制算法,并行多线程。外号:“吞吐量优先收集器”目标是达到可控制的吞吐量Throughput()吞吐量是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间))。提供了-XX:MaxGCPauseMillis控制最大垃圾收集停顿时间、-XX:GCTimeRatio设置吞吐量大小。
33.Parallel Scavenge收集器的GC自适应调节策略:设置-XX:+UseAdaptiveSizePolicy,不许指定-Xmn、Eden与Survivor区比例等细节参数,JVM根据系统运行情况收集性能监控信息,动态调节参数以提供最合适的停顿时间或最大吞吐量。自适应调节策略也是Parallel Scavenge收集器与ParNew收集器重要区别。
34.Serial Old收集器(串行GC):单线程收集器,使用“标记-整理”算法,是Serial收集器老年代版本。
35.Parallel Old收集器(并行GC):使用多线程和“标记-整理”算法。在注重吞吐量及CPU资源敏感的场合,优先考虑Parallel Scavenge加Parallel Old收集器。
36.CMS(Concurrent Mark Sweep)收集器(并发GC):以获取最短回收停顿时间为目标的基于“标记-清除”算法的收集器(重视服务的响应速度)。分为四步标记:初始标记,并发标记,重新标记,并发清除。总体说,CMS收集器的内存回收过程是与用户线程一起并发执行。三个缺点:CMS收集器对CPU资源敏感;无法处理浮动垃圾;产生大量空间碎片。
37.G1收集器(并发GC):两方面改进CMS:基于“标记-整理”算法,不会产生空间碎片;精确控制停顿。G1收集器可以实现在基本不牺牲吞吐量的前提下完成低停顿的内存回收。之前收集器收集范围是整个新生或老年代,而G1将堆划分为多个大小固定的独立区域Region,根据垃圾堆积程度在后台维护优先列表,每次优先收集垃圾最多区域(Garbage First)。
38.总结分类:Young Generation:Serial、ParNew、ParallelScavenge;使用复制算法,分为Eden和Survivor。Tenured Generation:Serial Old、Parallel Old、CMS;使用“标记-清除”或“标记-整理”算法。Region:G1,采用“标记-整理”算法
39.配合使用:除Serial+Serial Old和Parallel Scavenge+Parallel Old 可配合外,还有Serial+CMS,ParNew+CMS,三种年轻代都可+Serial Old。注意:Parallel Scavenge不可搭配CMS。
四。内存分配与回收策略
40.概论:Java技术体系提倡的自动内存管理可归结为自动解决两个问题:给对象分配内存及回收分配给对象的内存。
41.大体:对象优先在Eden分配;大对象直接进入老年代(-XX:PretenureSizeThreshold参数令大于这个设置值对象直接进入老年代分配,避免在Eden及Survivor去大量内存拷贝);长期存活的对象进入老年代(-XX:MaxTenuringThreshold设置晋升老年代的年龄阈值);动态对象年龄判定(如果在Survivor空间中相同年龄所有对象总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可直接进入老年代);空间分配担保(老年代进行分配担保,让Survivor无法容纳的对象直接进入老年代)。
---------------类文件结构-------------
42.概述:Class文件格式采用类似于C语言结构体的伪结构存储,只有两种数据结构:基本数据类型的:无符号数;多个无符号数或其他表作为数据项构成的复合数据结构:表("_info"结尾)。整个Class文件本质上就是一张表。
43.常量池:Class文件结构中与其他项目关联最多的数据类型,也是Class文件空间占用最大的数据项目,是在Class文件中第一个出现的表类型数据项目。
44.访问标志:常量池结束后紧接着2个字节代表访问标志(access_flags),用于识别类或接口的访问信息。eg.Class是类或接口,是否定义public/abstrac/final等。
45.类索引、父类索引与接口索引集合;字段表集合;方法表集合;属性表集合。
---------------虚拟机类加载机制-------------
一。类加载过程
46.概述:类从被加载到JVM到卸载出内存为止,生命周期按顺序包括了:加载Loading、验证Verification、准备Preparation、解析Resolution、初始化Initialization、使用Using、卸载Unloading七个阶段。其中验证、准备和解析三部分被统称为连接Linking。
47.补充:解析阶段可能在初始化之后开始,因为Java语言的运行时绑定(动态绑定)。开始顺序是确定的,但所有阶段通常是互相交叉的进行。
48.开始类加载:JVM具体实现来自由把握。
49.必须立即对类初始化(在加载验证准备后):类进行主动引用的四种情况。1.遇到new、getstatic、putstatic、invokestatic这4条字节码指令时。eg.使用new实例化对象,读取或设置类的静态字段(final修饰的,已在编译器把结果放入常量池的静态字段除外),以及调用类的静态方法。2.使用java.lang.reflect包的方法进行反射调用的时候。3.当初始化一个类(不是接口)发现其父类还没进行过初始化,则会触发父类的初始化。4.JVM启动时用户执行的主类(包含main方法的类)。
50.不触发初始化:被动引用eg(P173开始):1.通过子类引用父类的静态字段,不会导致子类初始化。2.通过数组定义来引用类,不会触发此类的初始化。3.常量在编译阶段会存入调用类的常量池中,本质上没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。
51.类加载过程:加载、验证、准备、解析、初始化。
52.加载:完成三件事:1.通过类的全限定名获取定义此类的二进制字节流。2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据机构。3.在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些对象的访问入口。
53.加载阶段既可以使用系统提供的类加载器完成,也可以由用户自定义的类加载器完成。
54.验证阶段:确保Class文件的字节流中包含的信息符合JVM要求。完成四个阶段检验过程:文件格式验证(是否符合Class文件规范)、元数据验证(语义分析)、字节码验证(数据流和控制流分析)、符号引用验证(常量池符号引用信息进行匹配性校验)。
55.验证阶段对JVM是重要但不是必要阶段,可以通过-Xverify:none参数关闭大部分类验证措施。
56.准备阶段:正是为类变量分配内存并设置类变量初始值,内存将在方法区中进行分配(注意:1.进行内存分配仅包括类变量即static修饰的变量而不包括实例变量2.初始值通常情况是数据类型的零值或空值,赋初始值在初始化阶段完成)。
57.解析阶段:JVM将常量池内的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法四类符号引用进行。
58.初始化阶段:初始化类变量和其他资源,是执行类构造器<clinit>()方法的过程。编译过程第一次真正执行类中Java代码(字节码),是类加载过程的最后一步。
59.<clinit>()方法:编译器自动收集类中所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生。不需显示调用父类构造器,JVM保证父类的<clinit>()方法已经执行完毕。父类定义的静态语句块优于子类的变量赋值操作。
二。类加载器
60.类加载器:通过一个类的全限定名获取描述此类的二进制字节流。比较两个类是否相等,只有在两个类是由同一个类加载器加载的前提之下才有意义,否则即使来源于同一个Class文件,只要加载的类加载器不同,两个类就不相等。
61.三种系统提供的类加载器(Hot Spot):启动类加载器Bootstrap ClassLoader、扩展类加载器Extension ClassLoader(加载<JAVA_HOME>\lib\ext目录下所有类库)、应用程序类加载器Application ClassLoader(系统类加载器,加载ClassPath上指定的类库)。
62.双亲委派模型Parents Delegation Model(应用于几乎所有Java程序中):要求除顶层的启动类加载器外,其余类加载器都应当有自己的父类加载器。父子关系使用组合关系来复用父加载器代码。eg.启动类加载器-扩展类加载器-应用程序类加载器-自定义类加载器。
63.双亲委派模式工作过程:如果一个类加载器收到了类加载的请求,首先不会自己尝试加载这个类,而是把请求委派给父类加载器完成,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有父加载器搜索范围中没有找到所需类,子加载器才会尝试自己加载。
64.双亲委派模式优点:Java类随着它的类加载器一起具备了带有优先级的层次关系,保证Java程序的稳定运作。
65.违背双亲委派模式:父类加载器请求子类加载器完成类加载动作。eg.所有涉及SPI(Service Provider Interface)的加载:JNDI、JDBC等。
---------------虚拟机类加载机制-------------
一。运行时栈帧结构
66.栈帧Stack Frame概念:存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。栈帧用于支持虚拟机进行方法调用和方法执行的数据结构,是虚拟机栈的栈元素。每个方法从调用开始到执行完成的过程,对应着一个栈帧在虚拟机栈里入栈到出栈的过程。活动线程中,只有栈顶的栈帧是有效的,称为当前栈帧,所关联的方法称为当前方法。编译代码时,栈帧中需要多大局部变量表及多深的操作数栈已经确定。
67.局部变量表:以变量槽Slot为最小单位。注意:如果一个局部变量定义了但没有赋初值是不能编译通过的。
68.操作数栈:在方法的执行过程中,会有各种字节码指令向操作数栈中写入和提取内容,也就是入栈出栈操作。
69.栈帧之间数据共享:重叠区域为一栈帧的局部变量表共享区域和另一栈帧的操作数栈共享区域。
70.动态连接:每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。(Class文件常量池中大量符号引用,字节码中方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用一部分在类加载阶段或第一次使用时转化为直接引用,这种转化为静态转化。另外一部分将在每一次运行期间转化为直接引用,这部分为动态连接)
71.方法返回地址:方法退出过程实际上等同于吧当前栈帧出栈,因此退出可能执行:恢复上层方法的局部变量表和操作数栈,把返回值压入调用者栈帧的操作数栈中,调整PC计数器值以指向方法调用指令后面的一条指令。
二。方法调用
72.概论:方法调用不等同方法执行,唯一任务是确定调用哪一个方法,不涉及方法内部运行过程。(一切方法调用在Class文件中存储的只是符号引用,而不是方法在实际运行时内存布局的入口地址即直接引用。这个特性给Java带来了强大的动态扩展能力)
73.解析Resolution:调用目标在程序代码写好、编译器进行编译时就必须确定下来,这类方法的调用称为解析。静态方法、私有方法、实例构造区和父类方法都可以在解析阶段唯一确定,在类加载时候会把符号引用解析为该方法的直接引用,这些方法称为非虚方法(非虚方法还有被final修饰的方法)。解析调用是静态过程,在编译期间完全确定,在类装载的解析阶段把涉及的符号引用全部转变为可确定的直接引用。
74.静态类型与实际类型:eg.Human man=new Man();“Human”称为变量的静态类型,“Man”称为实际类型。静态类型是在编译器可知的,而实际类型在运行期才可确定,编译器在编译时不知道对象的实际类型。
75.分派:分为静态分派和动态分派,单分派和多分派。静态分派:依赖静态类型来定位方法执行类名,最典型应用是方法重载(P212)。发生在编译阶段,因此实际上不是由JVM执行的。动态分派:在运行期根据实际类型确定方法执行类名的分派过程。最典型应用是方法重写(P215)。单分派与多分派:根据分派基于多少种宗量(方法的接收者与方法的参数统称为方法的宗量)划分单分派和多分派。JDK1.6时期的Java语言是一门静态多分派、动态单分派的语言。
三。基于栈的字节码解释执行引擎
---------------Java内存模型与线程-------------
一。Java内存模型
76.主内存与工作内存
77.volatile型变量:当一个变量被定义为volatile后具备两种特性:1.保证次变量对所有线程的可见性(可见性指当一条线程修改了这个变量的值,新值对于其他线程来说是可以立即得知的。而普通变量不能做到这一点,变量值在线程间传递均需要通过主内存来完成)。volatile变量在各个线程的工作内存中不存在一致性问题,但是Java里运算并非原子操作,导致volatile变量的运算在并发下是不安全的。由于volatile变量只能保证可见性,我们仍要通过加锁(使用synchronized或java.util.concurrent中的原子类)保证原子性2.禁止指令重排序优化。普通变量仅仅会保证在方法执行过程中所有依赖赋值结果的地方都能获取到正确结果,而不能保证变量赋值操作的顺序与代码中执行顺序一致。
78.原子性:基本数据类型的访问读写具备原子性,在synchronized块之间的操作也具备原子性。
79.可见性:当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。除了volatile之外,还有两个Java关键字synchronized(对一个变量执行unlock操作前,把此变量同步回主内存中)和final(被final修饰的字段在构造器一旦完成初始化并且构造器没有把“this”的引用传递出去,那么在其他线程就能看见final字段的值)实现可见性。
80.有序性:volatile(第二个特性)和synchronized(一个变量在同一时刻只允许一条线程进行lock操作)关键字保证线程之间操作的有序性。
81.先行发生原则(一个操作时间上的先发生不代表这个操作先行发生):如果两个操作之间关系不在以下此列就没有顺序性保证,虚拟机可以随意重排序:程序书写次序,管程锁定(lock操作),volatile变量操作,线程启动、中断、终止,对象终结。
二。Java与线程
82.线程:线程比进程更轻量级的调度执行单位,把进程的资源分配和执行调度分开,各个线程可以共享进程资源(内存地址、文件I/O等),又可以独立调度。线程是CPU调度的最基本单位。
83.Java线程:被映射到系统的原生线程上实现,线程调度最终还是操作系统决定。
84.Java线程调度:线程调度指系统为线程分配处理器使用权的过程,主要调度方式有两种:协同式(线程的执行时间由线程本身控制,线程执行完毕主动通知系统切换另一个线程)和抢占式(每个线程由系统分配执行时间,线程的切换不由线程本身决定)。
85.线程状态:Java定义了6种线程状态,在任一时间点,一个线程有且只有其中一种状态:新建New,运行Running,无限期等待Waiting,限期等待Timed Waiting,阻塞Blocked,结束Terminated。6种状态在遇到特定事件时互相转换。
86.线程状态转换关系(P340):New遇start()转Running,Running遇wait()转Waiting,Running遇sleep()转Timed Waiting,Waiting/Timed Waiting遇notify()/notifyAll()转Running,Running和Blocked由synchronized控制相互转换,Running遇run()结束转Terminated。
三。线程安全
一。线程安全概念
87.线程安全定义:当多个线程访问一个对象时,如果不考虑这些线程在运行时的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的。(定义要求了线程安全的代码具备一个特征:代码本身封装了所有必要的正确性保障手段如互斥同步等,令调用者无须关心多线程问题)
88.线程安全程度:把操作共享的数据分为五类:不可变、绝对线程安全、相对线程安全、线程兼容、线程对立。
89.不可变:eg.把对象中带有状态的变量声明为final;枚举类型;java.lang.Number的部分子类等。
90.绝对线程安全:大部分都不是。
91.相对线程安全(通常意义的线程安全):调用时候不需要做额外的保障措施。eg.Vector,HashTable,Collections的synchronizedCollection()方法包装的集合等。
92.线程兼容(通常意义的线程不安全):对象本身不是线程安全的,但可以通过在调用端正确的使用同步手段保证对象在并发环境中安全使用。eg.ArrayList,HashMap等。
93.线程对立:调用端是否采取同步措施都无法在多线程环境下并发使用。eg.Thread类的suspend()和resume()方法:如果两个线程同时持有一个线程对象,一个尝试中断线程,一个尝试恢复线程,并发进行无论是否进行同步,目标线程都存在死锁风险。
二。线程安全实现办法
94.同步:多线程并发访问共享数据时,保证共享数据在同一时刻只被一条线程使用
95.互斥:实现同步的一种手段,临界区Cirtical Section、互斥量Mutex、信号量Semaphore都是主要的互斥实现方式。
96.互斥同步:最常见的并发正确性保障手段。互斥是因,同步是果;互斥是方法,同步是目的。最基本手段是synchronized关键字。
97.synchronized同步块:首先synchronized同步块对同一条线程来说是可重入的,不会出现自己把自己锁死的问题。其次,同步块在进入的线程执行完之前,会阻塞后面其他线程的进入。synchronized是Java语言的重量级操作,耗时长。
98.java.util.concurrent包中的重入锁RentrantLock:同样实现互斥线程同步,锁可绑定多个条件:等待可中断、可实现公平锁、锁可绑定多个条件。
99.非阻塞同步:先进行操作,如果共享数据有冲突,再进行其他补偿措施(最常见hi不断重试直到成功),这种乐观的并发策略不需要把线程挂起。需要操作和冲突检测具备原子性。
100.无同步方案:方法本身不涉及共享数据。
三。锁优化
101.锁优化技术:eg.适应性自旋Adaptive Spinning、锁消除Lock Elimination、锁粗化Lock Coarsening、轻量级锁Lightweight Locking、偏向锁Biased Locking等。