《深入理解Java虚拟机》知识点整理1

 2019-12-22 11:07  阅读(718)
文章分类:JVM

《深入理解Java虚拟机》这本书看了几遍,再整理一下自己觉得值得记录的知识点吧。欢迎补充。

第一章、第二章

1.Java程序设计语言、Java虚拟机、Java API类库这三部分称为JDK,JDK是用于支持Java程序开发的最小环境。Java API类库中的Java SE API子集和Java虚拟机两部分统称为JRE,它是支持Java程序运行的标准环境。

2.Java虚拟机包括程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区。

(1)程序计数器是一块较小的内存空间,可看作是当前线程所执行的字节码的行号指示器。在任何一个确定的时刻,一个处理器只会执行一条线程中的指令。如果线程执行的是一个Java方法,计数器记录的是正在执行的虚拟机字节码指令的地址;如果执行的是Native方法,计数器值为空。程序计数器区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError的区域。程序计数器线程私有。

(2)Java虚拟机栈是线程私有的,生命周期与线程相同。每个方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用到执行完成的过程,对应着一个栈帧在虚拟机栈中入栈到出栈的过程。局部变量表所需的内存空间在编译期间完成分配,进入方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。线程请求的栈深度大于虚拟机所允许的深度,抛出StackOverflowError异常;虚拟机栈动态扩展时无法申请到足够的内存,抛出OutOfMemoryError异常。在单个线程下,无论是由于栈帧太大还是虚拟机栈容量太小,当内存无法分配的时候,虚拟机都抛出StackOverflowError异常。

(3)本地方法栈为虚拟机使用到的Native方法服务,虚拟机栈为Java方法服务。

(4)Java堆是Java虚拟机中内存最大的一块。它被所有线程共享,在虚拟机启动时创建。Java堆唯一的目的是存放对象实例。它也是垃圾回收期管理的主要区域,因此也被称为GC堆。Java堆可被分为新生代和老年代,再细致一点的有Eden空间、From Survivor空间、To Survivor空间等。Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。

(5)方法区是线程共享的。存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。它也不需要连续内存和可以选择固定大小,可扩展,可不实现垃圾收集。方法区内存回收的主要目标是针对常量池的回收和对类型的卸载。运行时常量池是方法区的一部分。Class文件中的常量池用于存放编译期生成的各种字面量和符号引用,格式有严格的规定,但运行时常量池没有要求,比如可以存储翻译出的直接引用。另外,运行时常量池具备动态性,即并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可将新的常量放入池中。如String类的intern()方法。

3.对象的创建过程:

(1)虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。

(2)在类加载检查通过后,接下来虚拟机将为新生对象分配内存。有两种分配方式:①如果Java堆的内存是绝对规整的,所有用过的内存都放在一边,空闲的在另一边,中间放着一个指针作为分界点的指示器,分配内存的过程是把指针向空闲空间那边挪动一段与对象大小相等的距离。这种方法称为“指针碰撞”;②如果内存不是规整的,虚拟机会维护一个列表,记录哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象,并更新表上的记录,这种方法称为“空闲列表”。Java堆是否规整由所采用的垃圾收集器是否带有压缩整理功能决定。Serial、ParNew、Compact等收集器采用指针碰撞,CMS等采用空闲列表。另外,解决并发状态下创建对象过程中的安全问题有两种方案:①对分配内存空间的动作进行同步处理,采用CAS配上失败重试的方式保证更新操作的原子性;②把内存分配的动作按照线程划分在不同的空间之中进行,即每一个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(TLAB)。

(3)内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值。使用TLAB时该步骤可提前至TLAB分配时进行。这一操作保证了对象实例字段在Java代码中可以不赋初始值就直接使用。

(4)虚拟机对对象进行必要的设置。例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象的对象头中。

(5)执行方法,把对象按照程序员的意愿进行初始化。

4.对象的内存布局可以分为三块:对象头、实例数据、对齐填充。

(1)对象头包括两部分信息:第一部分用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。即Mark Word。第二部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。另外Java数组的对象头中还有一块记录数组长度的数据。

(2)实例数据是对象真正存储的有效信息,是代码中定义的各种类型的字段内容。它的存储顺序收到虚拟机分配策略参数和字段在Java源码中定义顺序的影响。

(3)对齐填充不是必然存在的,它仅仅起占位符的作用。因为对象大小必须是8字节的整数倍,实例数据未对齐时就需要对齐填充来补全。

5.通过栈上的reference数据来操作堆上的具体对象。对象的访问定位方式有使用句柄和直接指针两种。

(1)句柄访问时Java堆中会划分出一块内存作为句柄池,reference中存储的是对象的句柄地址,句柄地址中包含了对象实例数据与类型数据各自的具体地址信息。

(2)使用直接指针访问,reference中存储的直接就是对象地址。

(3)两种方法区别:句柄访问时reference中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而reference本身不需要修改。直接访问的访问速度更快,节省了一次指针定位的时间开销。

第三章

1.判断对象是否存活:

(1)引用计数算法:给对象添加一个引用计数器,每当有一个地方引用它,计数器值加一,引用失效计数器值减一。计数器为零的对象就是不能再被使用的。但是它难以解决对象之间相互循环引用的问题。

(2)可达性分析算法:通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连即从GC Roots到这个对象不可达时,证明此对象是不可用的。可以作为GC Roots的对象包括:虚拟机栈(栈帧中的本地变量表)中引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象、本地方法栈中Native方法引用的对象。

2.引用类型:强引用、软引用、弱引用、虚引用。

(1)强引用是代码中普遍存在的,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。

(2)软引用是用来描述一些还有用但非必须的对象。在系统将要发生内存溢出异常前,会把这些对象列进回收范围中进行二次回收。

(3)弱引用也是描述非必须对象的,强度比软引用更弱一些。被弱引用关联的对象只能生存到下一次垃圾收集发生之前。

(4)虚引用是最弱的一种引用关系。无法通过虚引用来取得一个对象的实例。为一个对象设置虚引用关联的唯一目的是能在这个对象被收集器回收时收到一个系统通知。

3.两次标记:如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”。

如果这个对象被判定为有必要执行finalize()方法,那么这个对象将会放置在一个叫做F-Queue的队列中,并在稍后由一个虚拟机自动建立的、低优先级的Finalizer线程去执行它。只要对象重新与引用链上的任何一个对象建立关联,第二次标记时它就会被移出“即将回收”的集合,否则该对象就会被回收。另外任何一个对象的finalize()方法都只会被系统自动调用一次。

4.判断一个类为“无用的类”需要同时满足下列三个条件:①该类所以的实例都已经被回收,也就是Java堆中不存在该类的任何实例。②加载该类的ClassLoader已经被回收。③该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

5.垃圾回收算法:

(1)标记–清除算法。首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。这个算法效率不高,且会产生大量不连续的内存碎片,内存碎片太多可能会导致以后需要分配较大对象时无法找到足够的连续内存而提前触发另一次垃圾收集动作。

(2)复制算法。将可用内存按容量划分为大小相等的两块,每次只使用其中的一块,当这一块内存用完了,就将还存活的对象复制到另外一块上,然后再把已使用过的内存空间一次清理掉。这样每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。但这种算法将内存缩小为一半,代价太高。复制算法经常用来回收新生代,且空间分为一块大的Eden区和两块较小的Survivor区,每次使用Eden区和其中一块Survivor区。回收时将Eden和Survivor中存活的对象复制到另一块Survivor区上,清理掉Eden区和刚才的Survivor区。HotSpot虚拟机默认Eden和Survivor大小比例为8:1。如果另一块Survivor没有足够空间存放上次新生代的存活对象时,将通过分配担保机制进入老年代。

(3)标记–整理算法。老年代中通常采用此算法,标记之后,让所有存活的对象都向一端移动,然后直接清理掉边界以外的内存。

(4)分代收集算法。新生代采用复制算法,老年代采用标记清除或者标记整理算法。

6.垃圾收集器

(1)Serial收集器。最基本、发展历史最悠久。是单线程的,在进行垃圾收集时必须暂停其他所有工作线程,直到收集结束。是虚拟机运行在Client模式下的默认新生代收集器。没有线程交互的开销,简单高效。

(2)ParNew收集器。是Serial收集器的多线程版本。除了Serial收集器外只有它能与CMS收集器配合工作。

(3)Parallel Scavenge收集器。复制算法,新生代收集,并行。目标是达到一个可控制的吞吐量(CPU用于运行用户代码的时间与CPU总消耗时间的比值)。最大垃圾收集停顿时间:-XX:MaxGCPauseMillis,GC停顿时间缩短是以牺牲吞吐量和新生代空间来换取的。吞吐量大小:-XX:GCTimeRatio,大于0小于100的整数,为垃圾收集时间占总时间的比率,相当于吞吐量的倒数。-XX:+UseAdaptiveSizePolicy打开后不需要手动指定新生代大小(-Xmm)、Eden与Survivor比例(-XX:SurvivorRatio)、晋升老年代对象大小(-XX:PretenureSizeThreshold)等参数,可以根据当前系统运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量,即GC自适应的调节策略。

(4)Serial Old收集器。Serial收集器的老年代版本,使用标记整理算法,给Client模式下的虚拟机使用。在Server模式下还可以:在JDK1.5以及之前的版本中与Parallel Scavenge收集器搭配使用;作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用。

(5)Parallel Old收集器。是Parallel Scavenge收集器的老年代版本,使用多线程和标记整理算法。注重吞吐量和CPU资源敏感的场合,可使用Parallel Scavenge + Parallel Old收集器的组合。

(6)CMS收集器。是以获取最短回收停顿时间为目标的收集器。基于标记清除算法。运行分为四个步骤:初始标记、并发标记、重新标记、并发清除。初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快;并发标记是进行GC Roots Tracing的过程;重新标记是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,该阶段停顿时间比初始标记长,远比并发标记短。并发标记与并发清除过程的收集器线程可以与用户线程一起工作,所以CMS大体上讲是与用户线程一起并发执行的。

优点:并发收集、低停顿。缺点:对CPU资源非常敏感;无法处理浮动垃圾,可能出现Concurrent Mode Failure失败导致另一次Full GC的产生,即无法处理标记过程之后用户线程并发产生的未标记的垃圾;因为是标记清除算法所以会产生大量空间碎片。

(7)G1收集器。是面向服务端应用的垃圾收集器。特点:并行与并发,分代收集,空间整理(整体基于标记整理算法,局部即Region间基于复制算法),可预测的停顿(根据每个Region垃圾堆积的价值大小维护一个优先列表,依据允许的收集时间优先回收价值大的Region,从而避免了进行Java堆全区域的垃圾收集)。另外,它将整个Java堆规划分为多个大小相等的独立区域(Region),新生代老年代都是一部分Region的集合。步骤分为:初始标记、并发标记、最终标记、筛选回收。初始标记仅仅只是标记一下GC Roots能直接关联到的对象,并且修改TAMS的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建对象,需要短暂停顿线程。并发标记是从GC Root开始对堆中对象进行可达性分析,找出存活的对象,耗时较长但可并发。最终标记阶段修正标记产生变动的部分,并将其记录在线程Remembered Set中,停顿线程但可并行。筛选回收阶段根据回收价值和成本排序,根据用户期望的GC停顿时间制定回收计划,并发,用户可控制时间。

7.垃圾收集器参数总结:

https://blog.csdn.net/MakeContral/article/details/79119050

JVM 参数配置及详解 -Xms -Xmx -Xmn -Xss 调优总结

https://blog.csdn.net/chen978616649/article/details/50380036/

8.Minor GC、Major GC、Full GC

Minor GC:指发生在新生代的垃圾收集,较频繁,回收速度也较快。

Major GC:老年代的GC,出现Major GC通常会出现至少一次Minor GC(非绝对)。速度比Minor GC慢十倍以上。

Full GC:Full GC是针对整个新生代、老年代、元空间(metaspace,java8以上版本取代perm gen)的全局范围的GC。Full GC不等于Major GC,也不等于Minor GC+Major GC,发生Full GC需要看使用了什么垃圾收集器组合,才能解释是什么样的垃圾回收。

第七章

1.虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。

2.类需要进行初始化的5种情况:

(1)遇到new、getstatic、putstatic或invokestatic这四条字节码指令;

(2)使用java.lang.reflect包的方法对类进行反射调用的时候;

(3)当初始化一个类的时候,如果发现其父类还没有进行过初始化,需要先触发其父类的初始化。但是如果是一个接口的话,并不要求其父类全部初始化过,只有真正使用到父接口的时候才会初始化;

(4)当虚拟机启动时,用户需要指定一个要执行的主类,虚拟机会先初始化这个主类;

(5)当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄对应的类没有初始化,则需要先触发其初始化。

3.生命周期:加载、验证、准备、解析、初始化、使用、卸载。验证、准备、解析三个部分统称为连接。

(1)加载有三个阶段:①通过一个类的全限定名来获取定义此类的二进制字节流;②将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;③在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

加载阶段与连接阶段如一部分字节码文件格式验证等部分是交叉进行的,但加载的开始时间总先于连接阶段。

(2)验证是连接阶段的第一步,是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。包括四个阶段:①文件格式验证,验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理。

可能包括以下验证:

1.是否以魔数0xCAFEBABE开头

2.主、次版本号是否在当前虚拟机处理范围之内

3.常量池的常量中是否有不被支持的常量类型(检查常量tag标志)

4.指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量

5.CONSTANT_Itf8_info 型的常量中是否有不符合UTF8编码的数据

6.Class文件中各个部分及文件本身是否有被删除的或附加的其他信息

②元数据验证,对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求。③字节码验证,通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。④符号引用验证,在解析阶段发生,对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验,以确保解析动作能正常执行。在无法通过时会抛出java.lang.IncompatibleClassChangeError异常的子类。

(3)准备阶段会正式为类变量分配内存并设置类变量初始值,这些变量所使用的内存都将在方法区中进行分配。需要注意,内存分配的仅包括类变量即被static修饰的变量,不包括实例变量。

(4)解析阶段失虚拟机将常量池内的符号引用替换为直接引用的过程。

符号引用是以一组符号来描述所引用的目标,符号可以是任何形式的字面量。直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。解析的动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符七类符号引用进行。

(5)初始化是类加载的最后一步,从这一步开始才真正执行类中定义的Java程序代码。

4.类加载器:实现“通过一个类的全限定名来获取描述此类的二进制字节流”这一动作的代码模块称为类加载器。分为两种:启动类加载器,使用C++实现,是虚拟机自身的一部分;所有其他的类加载器,由Java实现,独立于虚拟机的外部,都继承自抽象类java.lang.ClassLoader。

(1)启动类加载器将存放在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的类库加载到虚拟机内存中。不可直接引用。

(2)扩展类加载器,由sun.misc.Launcher$ExtClassLoader实现,负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库。可以直接使用。

(3)应用程序类加载器,由sun.misc.Launcher$AppClassLoader实现,也叫做系统类加载器,负责加载用户类路径ClassPath上所指定的类库。可以直接使用,是程序中默认的类加载器。

5.双亲委派模型:双亲委派模型要求除了顶层的启动加载类外,其余的类加载器都应当有自己的父类加载器。,这里类加载器之间的父子关系一般不会以继承的关系来实现,而是使用组合关系来复用父类加载器的代码。

工作过程:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都是应该传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

这样做的话,Java类随着它的类加载器一起具备了一种带有优先级的层级关系,最终都会委派给处于模型最顶端的启动类加载器进行加载。

第十二、十三章

1.内存间的八种操作:

lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态。

unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。

read(读取):作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用

load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。

use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。

assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。

store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。

write(写入):作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。

如果要把一个变量从主内存中复制到工作内存,就需要按顺序地执行read和load操作, 如果把变量从工作内存中同步回主内存中,就要按顺序地执行store和write操作。Java内存模型只要求上述操作必须按顺序执行,而没有保证必须是连续执行。

Java内存模型还规定了在执行上述八种基本操作时,必须满足如下规则:

(1)不允许read和load、store和write操作之一单独出现。

(2)不允许一个线程丢弃它的最近assign的操作,即变量在工作内存中改变了之后必须同步到主内存中。

(3)不允许一个线程无原因地(没有发生过任何assign操作)把数据从工作内存同步回主内存中。

(4)一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量。即就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。

(5)一个变量在同一时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。lock和unlock必须成对出现。

(6)如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行load或assign操作初始化变量的值。

(7)如果一个变量事先没有被lock操作锁定,则不允许对它执行unlock操作;也不允许去unlock一个被其他线程锁定的变量。

(8)对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(执行store和write操作)。

2.重排序:在执行程序时为了提高性能,编译器和处理器经常会对指令进行重排序。重排序分成三种类型:

(1)编译器优化的重排序。编译器在不改变单线程程序语义放入前提下,可以重新安排语句的执行顺序。

(2)指令级并行的重排序。现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。

(3)内存系统的重排序。由于处理器使用缓存和读写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。

为了保证内存的可见性,Java编译器在生成指令序列的适当位置会插入内存屏障指令来禁止特定类型的处理器重排序。Java内存模型把内存屏障分为LoadLoad、LoadStore、StoreLoad和StoreStore四种。

3.当一个变量定义为volatile之后,它将具备两种特性:

(1)可见性,这里的可见性是指当一条线程修改了这个变量的值,新值对于其他线程来说是可以立即得知的。普通变量的值在线程间传递需要通过主内存来完成。由于valatile只能保证可见性,在不符合以下两条规则的运算场景中,我们仍要通过加锁来保证原子性:

①运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值。

②变量不需要与其他的状态变量共同参与不变约束。

(2)禁止指令重排序,普通的变量仅仅会保证在该方法的执行过程中所有依赖赋值结果的地方都能获取到正确的结果,而不能保证变量赋值操作的顺序与程序代码中执行顺序一致。

volatile变量读操作的性能消耗与普通变量无区别,但写操作会慢一些。

另外,允许虚拟机将没有被volatile修饰的64位数据的读写操作分为两次32为的操作来进行,即允许虚拟机实现选择可以不保证64位数据类型的load、store、read和write这4个操作的原子性。

4.原子性、可见性和有序性

原子性:即一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

可见性:可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。Java内存模型是通过在变量修改后将新值同步会主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式来实现可见性,valatile特殊规则保障新值可以立即同步到主内存中。Synchronized是在对一个变量执行unlock之前,必须把变量同步回主内存中(执行store、write操作)。被final修饰的字段在构造器中一旦初始化完成,并且构造器没有把this的引用传递出去,那在其他线程中就能看见final字段的值。

有序性:即程序执行的顺序按照代码的先后顺序执行。

5.先行发生原则:

(1)程序次序规则:在一个线程内,按照程序代码顺序,书写在前面的操作先行发生于书写在后面的操作。

(2)管程锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作。这里必须强调的是同一个锁,而后面的是指时间上的先后顺序。

(3)Volatile变量规则:对一个volatile变量的写操作先行发生于后面对这个变量的读操作,这里的后面同样是指时间上的先后顺序。

(4)线程启动规则:Thread对象的start()方法先行发生于此线程的每一个动作。

(5)线程终止规则:线程中的所有操作都先行发生于对此线程的终止检测,可以通过Thread.joke()方法结束、ThradisAlive()的返回值等手段检测到线程已经终止执行。

(6)线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测到是否有中断发生。

(7)对象终结规则:一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始。

(8)传递性:如果操作A先行发生于操作B,操作B先行发生于操作C,那就可以得出操作A先行发生于操作C的结论。

6.线程调度:是指系统为线程分配处理器使用权的过程。

协同式调度:线程的执行时间由线程本身控制。实现简单,但线程执行时间不可控制。

抢占式调度:线程的执行时间由系统来分配,线程的切换不由线程本身决定。

7.状态转换

(1)新建(New):线程创建后尚未启动的状态。

(2)运行(Runable):线程可能正在执行,可能正在等待CPU为它分配执行时间。

(3)无限期等待(Waiting):不会被分配CUP执行时间,它们要等待被其他线程显式唤醒。

(4)限期等待(Timed Waiting):不会被分配CUP执行时间,它们无须等待被其他线程显式唤醒,一定时间会由系统自动唤醒。

(5)阻塞(Blocked):阻塞状态在等待着获取到一个排他锁,这个时间将在另一个线程放弃这个锁的时候发生;而等待状态就是在等待一段时间,或者唤醒动作的发生。

(6)结束(Terminated):已终止线程的线程状态,线程已经结束执行

8.Java中各种操作共享的数据分为5类:
(1)不可变:不可变的对象一定是线程安全的,无论是对象的方法实现还是方法的调用者,都不需要再采取任何的线程安全保障。

(2)绝对线程安全:不管运行时环境如何,都不需要任何额外的同步措施。

(3)相对线程安全:就是通常意义上所讲的线程安全,它需要保证对这个对象单独的操作是线程安全的,我们在调用的时候不需要做额外的保障措施,但是对于一些特定顺序的连续调用,就可能需要在调用端使用额外的同步手段来保证调用的正确性。

(4)线程兼容:对象本身并不是线程安全的,但是可以通过在调用端正确地使用同步手段来保证对象在并发环境中可以安全使用。

(5)线程对立:是指无论调用端是否采取了同步措施,都无法在多线程环境中并发使用的代码。

点赞(1)
版权归原创作者所有,任何形式转载请联系作者; Java 技术驿站 >> 《深入理解Java虚拟机》知识点整理1

相关推荐