Java笔记之JVM原理

  1. JVM运行时数据区:方法区,虚拟机桟,本地方法桟,堆,程序计数器。
  2. 线程共享数据区:方法区,堆,执行引擎,本地库接口。
  3. 线程隔离数据区:虚拟机桟,本地方法桟,程序计数器。
  4. 程序计数器:字节码解释器、分支、循环、异常处理、跳转、线程恢复等基础功能都需要依赖这个计数器完成。如果线程执行的是java方法时,计数器记录的正在执行的虚拟机字节码指令地址,如果是Native方法,则计数器为空。此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
  5. java虚拟机桟:描述的是java方法执行的内存模型,每个方法在执行的时候都会创建一个桟帧,用于存储局部变量表、操作数桟、动态连接、方法出口等信息。在java虚拟机规范中,这个区域规定了两种异常状况:如果线程请求的桟深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果虚拟机桟可以动态扩展,如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。
  6. 局部变量表:存放了编译器可知的各种基本数据类型、对象引用和returnAddress类型。局部变量表所需的内存空间在编译期间完成分配,在方法运行期间不会改变,这也是局部变量在定义后需要初始化的根本原因。
  7. 本地方法桟:为虚拟机使用到的native方法服务,也会抛出StackOverflowError和OutOfMemoryError异常。有的虚拟机(例如Sun HotSpot)将本地方法桟和虚拟桟合二为一。
  8. Java堆:虚拟机所管理内存中最大的一块,用来存放对象实例。JVM规范要求所有的对象实例以及数组都要在堆上分配,但随着JIT编译器的发展与逃逸分析技术的成熟,桟上分配、标量替换优化技术的出现,全部在堆上分配也变得不那么“绝对”了。
  9. Java堆是垃圾收集器管理的主要区域。从内存回收的角度看:由于现在收集器基本采用分代收集算法,所以java堆可以细分为新生代和老年代,再细致一点的有Eden空间、From Survivor空间、To Survivor空间等;从内存分配角度看:线程共享的java堆中可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)。
  10. 根据JVM规范规定,java堆可以处于物理上不连续的内存空间,只要逻辑上连续即可。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。
  11. 方法区:用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。对于方法区,JVM规范也不要求内存上的连续,可以选择固定大小或者可以扩展,还可以选择不实现垃圾收集。虽然这样规定,也尽管对方法区的回收成绩不尽如意 ,但是对方法区进行垃圾回收还是有必要的。
  12. 使用永久代来实现方法区已为Hot SunSpot放弃,因为会更容易导致内存溢出问题,逐步采用Native Memory代替。
  13. 数据进入方法区并非进入“永久代”,对这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载。
  14. 运行时常量池:属于方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。一般来说,还会把翻译出来的直接引用也存储在运行时常量池中。
  15. 运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,java语言并不要求常量一定只有编译期才能产生,运行期间也可以将产生的常量放入常量池中,例如String类的intern()方法。当运行时常量池扩展时无法申请到内存时,也会抛出OutOfMemoryError异常。
  16. 直接内存:该内存区域并不是虚拟机运行时数据区的一部分,也不是JVM规范中定义的内存区域。该内存区域被频繁使用,也可能导致OutOfMemoryError异常出现。JDK1.4中加入的NIO类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数分配堆外内存,然后通过一个存储在java堆中的DirectByteBuffer对象作为这块内存的引用进行操作,这样避免了在Java堆和Native堆中来回复制数据,在一些场景中可以显著提高性能。
  17. 软件技术发展的瓶颈是硬件技术,当硬件技术获得突破时,软件技术可以达到质的发展。比如DMA专用总线技术和CAS硬件指令技术。NIO类的出现得益于DMA技术的发展,也是零拷贝(用户态到内核态不存在数据拷贝)实现的基础。java的CAS会使用现代处理器上提供的高效机器级别的原子指令,这些原子指令以原子方式对内存执行读-改-写操作,这是在多核处理器中实现同步的关键。
  18. X86架构:是微处理器执行的计算机语言指令集,也表示一套通用的计算机指令集合。为了保护数据和阻止恶意访问,X86体系引入了特权指令集和保护模式(R0-R3)。
  19. X86架构下,CPU的寄存器有2个bit位指定当前CPU所处的保护模式,所以保护模式提供4种特权级别,操作系统也会将内存划分为多个安全等级。
  20. Linux只用到了R0和R3。特权指令只有在最高特权级别R0下才能执行,而非特权指令可以在R0或R3下执行,但相同的非特权指令在不同保护模式下执行的行为不同。运行在CPU最高特权级别R0下的指令的状态称为“内核态”,最低特权级别R3下的指令状态称为“用户态”。
  21. 在非最高特权级别运行特权指令,则会触发异常跳转Trap机切换制到最高特权级,也就是从用户态切换到内核态。Trap机制触发时机:系统调用,异常,硬件中断。
  22. 操作系统由内核、系统调用、Shell、库函数几部分组成,其中Shell和库函数共同构成系统交互部分。
  23. 云计算的核心目的:提供标准化、云化的资源给上层应用。这类资源包括:虚拟机、虚拟存储、虚拟网络、应用运行时环境及依赖。
  24. 云原生是一种方法,用于构建和运行充分利用云计算模型优势的应用。包含一组应用模式,由微服务、容器、开发运维一体化、持续交付4方面组成。
  25. java对象的创建:在确保类加载完成之后,对象所需内存的大小便可完全确定,为对象分配空间的任务等同于把一块确定大小的内存从java堆划分出来。为对象分配空间有两种方式:指针碰撞和空闲列表。选择哪种方式由java堆是否规整决定,而java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。因此,在使用Serial、ParNew等带Compact过程的收集器时,系统采用指针碰撞的方式为对象分配空间,而使用CMS这种基于Mark-Sweep算法的收集器时,通常采用空闲列表的方式。
  26. 为java对象分配内存完成之后,jvm需要将分配到的内存空间都初始化为零值(不包括对象头),这也是在java类中全局变量不用手动赋值而可以使用的根本原因。
  27. 一般来说,虚拟机为对象赋零值后,一个java对象已经创建完毕,但为了创建出符合程序员意愿的java对象,在new指令执行完毕后又开始执行<init>实例构造方法。
  28. 类构造器<clinit>和实例构造器<init>中,分别收敛了关于类变量和实例变量的所有赋值逻辑。
  29. HotSpot虚拟机中,对象在内存中存储的布局可以分为3块:对象头、实例数据、对齐填充。
  30. java对象头:MarkWord,类型指针,数组对象的长度(为数组对象时)。
  31. MarkWord:对象哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,大小占一个字宽。
  32. 对象的定位:句柄或指针。
  33. 对象死活的判断:引用计数法(废弃),可达性分析算法。
  34. 引用类型与生存时间:强引用-永不回收、软应用-第二次回收、弱引用-下一次垃圾回收之前、虚引用-对回收时间不影响,仅用于被回收时收到一个系统通知。
  35. 在可达性分析算法中不可达的对象,不一定非死不可,确定回收一个对象,需经历两次标记过程。如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那么它将会被第一次标记并进行一次筛选,筛选条件是此对象是否需要执行其finalize()方法,如果对象没有覆盖finalize()方法或者系统已经调用过该方法,则没必要执行。虚拟机将需要执行finalize()方法的对象放置在一个叫做F-Queue的队列中,并在稍后由一个虚拟机创建的低优先级的Finalizer线程去执行它,但并不会等该线程执行完毕。稍后GC将对F-Queue队列中的对象进行第二次标记,如果对象在其finalize()方法中未能与GC Roots对象挂钩,意味着对象“自救”失败,等待被回收。
  36. GC Root对象:虚拟机桟中引用的对象、方法区中类属性引用的对象、方法区中常量引用的对象、本地方法桟中JNI(即一般说的Native方法)引用的对象。
  37. java中判定类可以回收需同时满足三个条件:该类所有的实例都已被回收;加载该类的ClassLoader已被回收;该类对应的java.lang.Class对象没有在任何地方被引用。
  38. 垃圾收集算法:标记-清除算法、复制算法、标记-整理算法、分代收集算法。
  39. java中的停机事件:GC进行时,必须停顿所有的java线程,目的是为了保证可达性分析快照的一致性,即分析时,系统中不存在对象引用关系的变化。在HotSpot的实现中,使用一组称为OopMap的数据结构来存放对象的引用。
  40. 实际上HotSpot并没有为每条指令都生成OopMap,只是在“特定的位置”记录了这些信息,这些位置称为安全点。
  41. 安全点的选定标准:以程序“是否具有让程序长时间执行的特征”为标准。最能满足该特征的有指令序列复用,例如方法调用、循环跳转、异常跳转等。
  42. GC发生时,需确保所有线程(除执行JNI调用的线程)到达安全点:抢先式中断、主动式中断。然而对处于sleep或blocked状态的线程,无法响应JVM的中断请求,使用安全域来解决。
  43. 安全域:引用关系不会发生变化的代码段,线程到达该区域时,会标识自己。
  44. 垃圾收集器:Serial(单线程)、ParNew(多线程版的Serial)、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1。
  45. GC日志:“[GC”和“[Full GC”表示垃圾收集的停顿类型,带Full的说明这次垃圾收集发生了“停机”事件,另外调用System.gc()方法进行的收集,也会发生“停机”事件;“3324K->152K(3712K)”表示“GC前该内存区域使用情况->GC后该内存区域使用情况(该内存区域总容量)”。
  46. 内存分配与回收策略:对象优先在新生代Eden区中分配;大对象直接进入老年代;长期存活的对象将进入老年代(至少经过15次GC后存活的对象)。
  47. JDK命令行工具:jps-虚拟机进程状况工具、jstat-虚拟机统计信息监视工具、jinfo-java配置信息工具、jmap-java内存映像工具、jhat-虚拟机堆转储快照分析工具、jstack-java堆栈跟踪工具、HSDIS-JIT生成代码反汇编。
  48. JDK可视化工具:JConsole-java监视与管理控制台、VisualVM-多合一故障处理工具。
  49. Class文件中包含了jvm指令集、符号表以及其他辅助信息。
  50. Java中的变量、关键字、运算符号的语义最终由多条字节码命令组合而成。
  51. Class文件数据结构:无符号数+表(表=无符号数+表)。
  52. Class文件格式:魔数、次版本号、主版本号、常量池数目、常量池表、访问标志位、当前类、父类、接口数目、接口集合、字段数目、字段表、方法数目、方法表、属性数目、属性表。
  53. 类文件常量池主要存放两大类常量:字面量,符号引用。字面量比较接近java中的常量,如文本字符串、final常量等;符号引用则属于编译原理范畴,包括三类常量:类和接口的全限定名、字段的名称和描述符、方法的名称和描述符。
  54. Class文件中没有各个方法、字段的最终内存布局信息,方法字段的符号引用要经过运行期转换才能得到内存入口地址,因此在编译期jvm无法使用这些方法字段。
  55. 方法源代码经编译后存放在属性表中一个名为code的属性里,如有泛型信息,也是经擦除的。
  56. 方法特征签名:一个方法中各个参数在常量池中的字段符号引用的集合。
  57. 在java语言中,任何类、接口、初始化方法或成员的泛型签名如果包含了类型变量或参数化类型,则属性表里的Signature属性会为它记录泛型签名信息。这也是编译时泛型信息经擦除后通过反射依旧能获得泛型信息的原因。
  58. 在实例方法的局部变量表中至少会存在一个指向当前对象实例的局部变量,局部变量表中也会预留出第一个slot位来存放对象实例的引用,this关键字间接的定位到该slot位,这也是this关键字能引用当前对象实例的根本原因。
  59. 字节码指令:操作码+操作数(大多数情况没有)。指令长度只有一个字节(0~255),JVM采用面向操作数栈而不是寄存器的架构,所以大多数指令都不包含操作数。常见指令有:加载和存储指令、运算指令、类型转换指令、对象创建与访问指令、操作数栈管理指令、控制转移指令、方法调用和返回指令、异常处理指令、同步指令等。
  60. 虚拟机的实现方式主要有两种:将输入的java虚拟机代码在加载或执行时翻译成另外一种虚拟机的指令集;将输入的java虚拟机的代码在加载或执行时翻译成宿主机CPU的本地指令集(即JIT代码生成技术)。
  61. JVM把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的java类型,这就是虚拟机的类加载机制。
  62. 区别于那些在编译时需要进行连接工作的语言,在Java语言里,类型的加载、连接和初始化过程都是在程序运行期间完成的,这种策略虽然会令类加载时稍微增加一些性能开销,但却为java语言带来高度的灵活性,例如java里天生可以动态扩展的语言特性就是依赖运行期间动态加载和动态连接特点实现的。
  63. 类的生命周期:加载-连接-初始化-使用-卸载,连接:验证-准备-解析。
  64. 类的主动引用:遇到new、getstatic、putstatic或invokestatic这4条字节码指令时;使用java.lang.reflect包的方法对类进行反射调用时;子类初始化时,先触发其父类初始化;当JVM启动时,主类(main()方法所在的类)进行初始化;当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果是REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄。
  65. 类的主动引用会导致类的初始化,被动引用不会。
  66. 典型被动引用场景:通过引用父类中定义的静态字段只会导致父类进行初始化而子类不会;通过数组定义来引用类,不会触发此类的初始化;常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。
  67. 接口类与普通类不同,接口也有初始化过程,编译器也会为其生成<clinit>,但接口类不允许static代码块出现,其次,接口进行初始化的时候并不要求其父类全部完成初始化,只有在真正使用到父接口的时候才会初始化。
  68. 类加载-加载阶段:通过类的全限定名获取相关的二进制字节流;将字节流所描述的静态存储结构转换为方法区的运行时数据结构;在内存中生成一个代表该类的Class对象(并不在堆上分配,HotSpot在方法区),作为方法区这个类的各种数据的访问入口。
  69. 数组类不通过类加载器创建,由JVM直接创建,但数组类与类加载器关系密切。
  70. 类加载-验证阶段:文件格式验证,确保二进制流能被正确解析并存储于方法区;对类的元数据信息进行语义检验,确保元数据符合java语言规范;字节码验证,通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的;符号引用验证:确保连接阶段的中的解析阶段正确进行。除了文件格式验证是针对二进制流的验证,其他验证阶段都是真的方法区中的数据的检验。
  71. 类加载-准备阶段:为类变量分配内存并设置初始值(零值);为final修饰的类变量赋自定义值。关于非final类型的类变量,在类加载准备阶段赋零值,类加载初始化阶段赋自定义值。
  72. 类加载-解析阶段:该阶段将虚拟机常量池内的符合应用替换为直接引用的过程。符号引用:任何形式的字面量,能够无歧义地定位到目标。直接引用:指向目标的指针、相对偏移量或一个能间接定位到对象目标的句柄。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。
  73. 类加载-初始化阶段:执行类构造器<clinit>()方法的过程。<clinit>()方法是由编译器自动收集类中所有类变量的赋值动作和静态语句块中的语句合并产生的。静态语句块只能访问定义在其前面的变量,不能访问定义在其后面的(会报非法向前引用错误),但是能够进行赋值操作。
  74. 在java虚拟机中,类的唯一性由类的全限定名和类加载器共同确定。
  75. 双亲委派模型:启动类类加载器<--扩展类类加载器<--应用程序类类加载器<--自定义类加载器。双亲委派模型保证了类的唯一性。
  76. 线程上下文类加载器:该加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类类加载器。
  77. 在OSGi环境下,类加载器不再是双亲委派模型的树状结构,而是进一步发展为更加复杂的网状结构,OSGi中的每一个模块(Bundle)都有一个自己的类加载器,更换一个Bundle时连同类加载器一起换掉已实现代码的热替换。
  78. 运行时桟帧:桟帧是用于支持虚拟机进行方法调用和方法执行的数据结构,是虚拟机桟的桟元素,存储了方法的局部变量表、操作数桟、动态连接和方法返回地址信息等。位于虚拟机桟桟顶的桟帧称为当前桟帧,相关联的方法称为当前方法,所有运行时字节码指令都只针对当前桟帧。
  79. 方法正常退出携带值,方法异常退出不携带值。
  80. 非虚方法及:静态方法、私有方法、实例构造器、父类方法以及final方法 。非虚方法会在类加载解析阶段,把符号引用替换为直接引用。非虚方法版本唯一。
  81. 方法调用字节码指令:invokestatic-静态方法,invokespecial-实例构造器、私有方法和父类方法,invokevirtual-虚方法,invokeinterface-接口方法,invokedynamic-更好地支持动态类型语言。
  82. Reflection与MethodHandle机制:都是在模拟方法调用,但Reflection是在模拟Java代码层次的方法调用,Reflection模拟的是在java一端的全面映像,包含了方法的签名、描述符以及方法属性表中各种属性的Java端表示方式,还包含执行权限等运行期信息。而MethodHandle是在模拟字节码层次的方法调用,仅仅包含与执行该方法相关的信息。通俗的讲,Reflection是重量级,MethodHandle是轻量级。
  83. invokedynamic与MethodHandle机制:作用一样,都是为了解决原有4条“invoke*”指令方法分派规则固化在虚拟机之中的问题,把如何查找目标方法的决定权从虚拟机转嫁到具体的用户代码之中,让用户有更高的自由度。
  84. 解释器:基于桟的字节码解释执行引擎。
  85. 现代经典编译原理思路:执行编译前先对源码进行此法分析和语法分析处理,把源码转化为抽象语法树(Abstract Syntax Tree,AST)。
  86. 对于一门具体的语言来说,词法分析、语法分析以至后面的优化器和目标代码生成器都可以选择独立于执行引擎,形成一个完整意义的编译器去实现,这类代表是C/C++语言。也可以把其中一部分步骤,例如生成抽象语法树之前的步骤,实现为一个半独立的编译器,这类代表是java语言。又或者把这些步骤和执行引擎全部集中封装在一个封闭的黑匣子之中,如大多数的JavaScript执行器。
  87. 编译期与编译器:前端编译器javac--将java文件编译为class文件,后端运行期编译器JIT--将字节码编译为机器码,静态提前编译器AOT--将java文件直接到机器码。
  88. final修饰的局部变量编译成class文件后无法看到final信息,对运行期没有影响,局部变量的不变性仅仅由编译器在编译期间保障。
  89. 虚拟机运行时不支持语法糖语法,在编译阶段会还原为基础语法结构。
  90. java语法糖:泛型与类型擦除,自动装箱、拆箱与遍历循环,条件编译,内部类,枚举类,断言语句等。
  91. 为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器称为即时编译器(Just In Time Compiler,JIT)。
  92. 热点代码:被多次调用的方法,JIT进行编译时,以整个方法作为编译对象;被多次执行的循环体,依旧以整个方法作为编译对象而不是循环体,由于编译发生在方法执行过程中,因此形象地称为桟上替换(On Stack Replacement,OSR编译),即方法桟帧还在桟上,方法就被替换了。
  93. 热点代码的检测:基于采样的热点探测,即周期性地检查各个线程的桟顶,经常出现在桟顶的方法为“热点方法”,这种探测好处是简单、高效,缺点是准确度不高(无法排除线程阻塞情况或其他外界干扰因素的影响);基于计数器的热点探测,虚拟机会为每个方法(甚至是代码块)建立计数器,来统计执行次数。HotSpot虚拟机采用第二种探测手段,并未每个方法准备了两类计数器:方法调用计数器、回边计数器。
  94. 方法调用计数器默认阀值,在Client模式下是1500次,在Server模式下是10000次。统计的是相对次数,即一段时间内被调用的次数,存在热度衰减。
  95. 回边计数器统计的是一个方法中循环体代码的执行次数,不存在热度衰减问题。在字节码中遇到控制流向后跳转的指令称为“回边”。回边计数器的建立就是为了触发OSR编译,Client模式下默认阀值是13995,Server模式下为10700。
  96. 默认情况下,无论是方法调用产生的即时编译请求,还是OSR编译请求,虚拟机在代码编译器还未完成之前,都仍然将按照解释方式继续执行,而编译动作则在后台的编译桟中进行。
  97. HotSpot中的即时编译器:Client Compiler(简称C1),Server Compiler(简称C2)。HotSpot采用的是解释器与编译器搭配使用的“混合模式”。
  98. Server Compiler会执行所有经典优化动作,如无用代码消除、循环展开、循环表达式外提,消除公共子表达式、常量传播、基本块重排序等,还会实施一些与java语言密切相关的优化技术,如范围检查消除、空值检查消除等。另外,还可能根据解释器或Client Compiler提供的性能监控信息,进行一些不稳定的激进优化,如守护内联、分支频率预测等。同时,解释器还可以作为编译器(C2)激进优化的一个“逃生门”。
  99. HotSpot采用分层编译的策略:0层-解释器,1层-C1,2层后-C2。
  100. 逃逸类型:方法逃逸以及线程逃逸等。如果能证明一个对象不会逃逸到方法或者线程外,则可以为这个变量进行一些高效的优化,如桟上分配,同步消除,标量替换等。
  101. Java内存模型(JMM)的主要目标:定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。JMM规定了所有的变量都存储在主内存中,每条线程有自己的工作内存,线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需通过主内存来完成。
  102. 内存间交互操作:lock-主-表示线程独占状态,read-主-将变量值从主内存传输到工作内存,load-工-将从主内存中得到的值放到工作内存的变量副本中,use-工-将工作内存中变量的值传递给执行引擎,assign-工-将从工作引擎接收到的值赋值给工作内存中的变量,store-工-将工作内存中的变量值传递到主内存中,write-主-将得到的工作内存的变量值存放到主内存变量中,unlock-主-释放锁定的变量。
  103. volatile内存语义:保证变量在各个线程之间的可见性;禁止指令重排序。
  104. CAS内存语义:除具有volatile的内存语义外,还具有保证对内存“读-改-写”的原子操作执行。
  105. JMM要求lock、read、load、use、assign、store、write、unlock这8个操作都具有原子性,但是对于64位的数据类型(long/double)却定义了相对宽松的规则:允许虚拟机将没有被volatile修饰的64位数据的读写操作划分为两次32位的操作来进行,即允许虚拟机实现选择可以不保证64位数据类型的load、store、read和write这4个操作的原子性,这点就是所谓的long和double的非原子协定。虽然JMM这样规定,但JVM却将64位数据的读写操作视为原子操作。
  106. JMM要解决的问题:原子性、可见性、有序性。
  107. 原子性:read、load、use、assign、store、write操作的原子性由JMM直接来保证,对于lock、unlock操作,尽管虚拟机未开发给用户直接使用,但是却提供了更高层次的字节码指令monitorenter和monitorexit来隐式地使用这两个操作,反映到java代码中就是同步块synchronized关键字,因此在synchronized块之间的操作也具备原子性。
  108. 可见性:JMM是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值这种依赖主内存作为传递媒介的方式来实现可见性的。java中能实现可见性的关键字:volatile,synchronized和final。
  109. 有序性:java语言提供了volatile和synchronized两个关键字来保证线程之间操作的有序性。volatile关键字本身包含了禁止指令重排序的语义;而synchronized则是由“一个变量在同一时刻只允许一条线程对其进行lock操作”这条规则获得的,这条规则决定了持有同一个锁的两个同步块只能串行地进入。
  110. 先行发生原则:程序次序规则、管程锁定规则、volatile变量规则、线程启动规则、线程终止规则、线程中断规则、对象终结规则、传递性。
  111. 线程模型及实现线程的主要方式:一对一的线程模型(windows,linux采用),使用内核线程实现;一对多的线程模型,使用用户线程实现;多对多的线程模型,使用用户线程加轻量级进程混合实现。
  112.  java线程的实现:操作系统支持的线程模型很大程度上决定了jvm中的线程映射。
  113. java线程调度:协同式线程调度(协程),执行时间由线程本身控制,存在线程阻塞问题;抢占是线程调度,由系统分配执行时间。
  114. java线程状态:新建、运行(包含就绪状态)、无限期等待、有限期等待、阻塞、终止。
  115. 线程安全:按照线程安全的“安全程度”由强至弱 来排序,可以将java语言中各种操作共享的数据分为以下5类:不可不、绝对线程安全、相对线程安全、线程兼容和线程对立。
  116. 锁优化:自旋锁与自适应自旋、锁消除、锁粗化、轻量级锁、偏向锁。
  117. 自旋锁:获取锁的线程在获取失败后,不立即挂起放弃CPU使用权,执行一个忙循环(自旋),默认自旋10次。
  118. 自适应自旋锁:自旋次数不固定,根据对同一个锁的以往自旋经历(需要监控辅助)作出判断:成功过-多自旋些次数,失败过-放弃自旋。
  119. 锁消除:主要依据逃逸分析数据的支持,JIT在运行时,对一些代码上要求同步,但是检测到不可能存在共享数据竞争的锁进行消除。
  120. 锁粗化:如果一系列的连续操作都对同一个对象反复加锁解锁,甚至加锁出现在循环体中,这种情况,虚拟机就会将同步范围扩大。
  121. 轻量级锁:轻量级是相对于使用操作系统互斥量来实现的传统锁而言的,因此传统的锁机制就称为重量级锁。轻量级锁并不是用来代替重量级锁的,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。
  122. 偏向锁:目的是消除数据在无竞争情况下的同步原语,进一步提高程序的运行性能。如果说轻量级锁是在无竞争的情况下使用CAS操作消除同步使用的互斥量,那偏向锁就是在无竞争情况下把整个同步都消除掉,连CAS操作都不做了。
  123. 锁的升级:无锁->偏向锁->轻量级锁->重量级锁。锁只能升级,不能降级。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值