笔记:深入理解Java虚拟机-JVM高级特性与最佳实践

openjdk7 source : https://download.java.net/openjdk/jdk7

download: http://download.java.net/openjdk/jdk7/promoted/b147/openjdk-7-fcs-src-b147-27_jun_2011.zip

内存模型:https://www.cnblogs.com/nexiyi/p/java_memory_model_and_thread.html

目录

一、运行时数据区域

1、程序计数器(线程私有):当前线程所执行的字节码行号指示器。

2、java虚拟机栈(线程私有):描述的是java方法执行的内存模型。每个方法在执行的同时度会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接等信息。

3、本地方法栈

4、java堆(GC堆,垃圾收集器管理的主要区域)

5、方法区

二、对象的创建

三、对象的内存布局

1、mark word用于存储对象自身的运行时数据,如哈希码(HashCode)、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳、对象分代年龄。

2、class pointer:对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

四、访问对象方式

1、使用句柄方式:

2、使用直接指针

五、判断对象是否存活

1、可达性分析算法:以GC Roots 为起点,向下搜索,搜索过程所走的路径称为引用链,如果一个对象到GC Roots不存在引用链(从GC Roots到该对象不可达),则该对象是不可用的(等待回收)。

六、垃圾收集算法

1、标记-清除算法

2、复制算法

3、标记-整理算法

七、垃圾收集器

1、Serial收集器

2、ParNew收集器

3、Parallel Scavenge收集器

4、CMS收集器

5、G1收集器

八、GC日志

九、大对象

十、类加载

1、加载:

2、验证

3、准备

4、解析

5、类初始化阶段

十一、类加载器

十二、栈帧

1、局部变量表

2、操作数栈

十三、指令集

1、基于栈的指令集:

2、基于寄存器的指令集

十四、硬件的效率与一致性

十五、java内存模式

十六、volatile

1、保证此变量对所有线程的可见性(每次使用前度要刷新,即重新从主内存复制值至工作内存)

2、volatile无法保证java的运算原子操作。

十七、线程安全

1、final 不可变

十八、锁

1、轻量级锁

2、偏向锁

 

一、运行时数据区域

《笔记:深入理解Java虚拟机-JVM高级特性与最佳实践》

1、程序计数器(线程私有):当前线程所执行的字节码行号指示器。

    java虚拟机的多线程是通过线程轮流切换并分配处理器处理时间的方式实现的,在任何一个确定的时刻,一个处理器(一个内核)度只会执行一个线程中的指令。为了线程切换后能恢复到正确的执行位置,每个线程需要一个独立的程序计数器。唯一一个不会导致OutOfMemoryError的区域。

2、java虚拟机栈(线程私有):描述的是java方法执行的内存模型。每个方法在执行的同时度会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接等信息。

    局部变量表存放基本数据类型:boolean、byte、char、short、int、float、long、double、对象引用。

    局部变量表所需的内存空间在编译期间已完成分配,执行前大小已确定, 执行期间不会改变其大小。

    StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度。

    OutOfMemoryError:java虚拟机动态扩展是无法申请足够的内存。

结构图:

《笔记:深入理解Java虚拟机-JVM高级特性与最佳实践》

3、本地方法栈

与虚拟机栈区别:虚拟机栈为执行java方法服务,而本地方法栈则为虚拟机执行Native方法服务。

同样会导致StackOverflowError和OutOfMemoryError;

4、java堆(GC堆,垃圾收集器管理的主要区域)

所有线程共享。所有的对象实例以及数组度在堆上分配空间。

细分:新生代和老年代(Eden空间、From Survivor空间和To Survivor空间)

通过-Xmx和-Xms控制大小。

OutOfMemoryError:堆没有内存分配给实例,并无法再扩展。

5、方法区

所有线程共享。存储的数据:已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码。

-XX:MaxPermSize设置上限。

OutOfMemoryError:无法满足内存分配。

二、对象的创建

对象所需的内存大小在类加载完成后便完全确定。

为对象分配空间等同将一块确定大小的内存从java堆中划分出来。

分配内存方式一:指针碰撞

前提是java堆内存绝对规整,即已用过的内存放一边,空闲的放另一边,中间放一个指针作为分界点。分配内存时仅仅需将指针向空闲空间那边挪动一段与对象大小相等的距离。

分配内存方式二:空闲列表

已用内存与空闲内存相互交叉。虚拟机维护一个列表,记录哪些内存是可用的,分配时在列表上找到一块足够大的空间划分给对象实例,并更新列表。

内存分配不是线程安全:比如可能出现正在给对象A分配内存,指针还没来得及修改,对象B又同时使用原来的指针来分配内存。

解决内存分配线程安全问题:

方式一:堆分配内存操作进行同步处理。实际上虚拟机采用CAS配上失败重试的方式保证更新操作的原子性。

方式二:将内存分配的动作按照线程划分在不同的空间中进行,每个线程在java堆中预先分配一小块内存空间(本地线程缓冲,TLAB),线程需要分配内存时就在自个的TLAB上分配,只有TLAB用完需分配新的TLAB时,才需要同步。

内存分配完成后,虚拟机将内存空间初始为零值(不包含对象头),接着将类的元数据信息、对象的哈希码、对象的GC分代存放至对象头。此时,对于虚拟机来说,对象已产生。对于java程序来说,对象创建才刚开始,因为init方法(也可认为构造函数)未执行。

三、对象的内存布局

对象的内存布局分为3块区域:对象头、实例数据、对齐填充。

《笔记:深入理解Java虚拟机-JVM高级特性与最佳实践》

对象头说明:

《笔记:深入理解Java虚拟机-JVM高级特性与最佳实践》

1、mark word用于存储对象自身的运行时数据,如哈希码(HashCode)、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳、对象分代年龄。

2、class pointer:对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

32位虚拟机:

对象未被锁定,mark word结构:

《笔记:深入理解Java虚拟机-JVM高级特性与最佳实践》

对象被锁定,mark word结构:

《笔记:深入理解Java虚拟机-JVM高级特性与最佳实践》

64位虚拟机:

mark word结构:
《笔记:深入理解Java虚拟机-JVM高级特性与最佳实践》

四、访问对象方式

1、使用句柄方式:

java堆必须划分一块内存作为句柄池,而对象引用存储对象的句柄地址,而句柄包含对象的实例数据和类型数据的地址信息。对象头无需使用class pointer保存类型信息。

《笔记:深入理解Java虚拟机-JVM高级特性与最佳实践》

2、使用直接指针

对象引用直接指向对象实例地址。对象头需使用class pointer保存类型信息。

《笔记:深入理解Java虚拟机-JVM高级特性与最佳实践》

五、判断对象是否存活

1、可达性分析算法:以GC Roots 为起点,向下搜索,搜索过程所走的路径称为引用链,如果一个对象到GC Roots不存在引用链(从GC Roots到该对象不可达),则该对象是不可用的(等待回收)。

《笔记:深入理解Java虚拟机-JVM高级特性与最佳实践》

六、垃圾收集算法

1、标记-清除算法

首先标记处所有需要回收的对象,然后再统一进行回收。

缺点:标记和清除两个过程效率度不高;标记清除后会产生大量不连续的碎片。

《笔记:深入理解Java虚拟机-JVM高级特性与最佳实践》

2、复制算法

将内存划分为大小相等的两块,每次仅使用一块(A),当这块内存(A)使用完,就将还存活的对象复制到另一块(B),然后这块(A)一次清除掉。

缺点:内存缩小原来的一般。

《笔记:深入理解Java虚拟机-JVM高级特性与最佳实践》

商用虚拟机采用此算法来回收新生代:

将内存划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor(A),当回收时,将Eden和Survivor中还存活的对象一次性复制到另外一块Survivor(B)空间上,然后清除Eden和被使用的Survivor(A)。Eden和Survivor比例8:1。

3、标记-整理算法

标记过程与标记-清除算法一致,但后续步骤不是直接对可回收的对象进行处理,而是让所有存活的对象度向一端移动,然后清除掉端边界以外的内存。

《笔记:深入理解Java虚拟机-JVM高级特性与最佳实践》

七、垃圾收集器

1、Serial收集器

单线程收集器,进行垃圾收集时会暂停所有的工作线程,直至收集结束。由虚拟机爱后台自动发起和自动完成,在用户不可见的情况下将用户正常工作的线程全部暂停。由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率。比较适合Clien模式下的虚拟机。目前的垃圾收集仅仅不断缩短用户停顿时间,并没有完全消除。

《笔记:深入理解Java虚拟机-JVM高级特性与最佳实践》

2、ParNew收集器

Serial收集器多线程版本,使用多条线程进行垃圾收集适合运行在Server模式下的虚拟机充当新生代的收集器。

《笔记:深入理解Java虚拟机-JVM高级特性与最佳实践》

3、Parallel Scavenge收集器

CMS等收集器关注点是尽可能的缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器目的时达到一个可控制的吞吐量。吞吐量是CPU用于运行用户线程的时间与CPU总消耗的时间比值。吞吐量=运行用户线程时间/(运行用户线程时间+垃圾收集时间)。

停顿时间越短越适合需要与用户交互的程序,能提升用户体验。

高吞吐量则可以高效率的利用CPU时间,尽快完成任务,比较合适后台运算。

-XX:MaxGCPauseMillis:最大垃圾收集时间       -XX:GCTimeRatio:吞吐量大小

4、CMS收集器

并发收集器,实现了垃圾收集器与用户线程同时工作(CPU切换执行),以获取最短回收停顿时间为目标的收集器。内存回收过程是与用户线程一起并发执行的。

《笔记:深入理解Java虚拟机-JVM高级特性与最佳实践》

缺点:

a、并发期间虽然不会导致用户线程停顿,但执行时需占用CPU资源,总吞吐量降低。

b、无法处理浮动垃圾,由于CMS并发收集阶段,用户线程还在运行,自然有新的垃圾产生,这一部分垃圾出现爱标记过程之后,CMS无法再当次收集中处理掉它们,只能留给下一次。可以看出,CMS需要预留部分空间提供给并发收集的程序使用。即不能等待内存几乎被填满了再进行收集。

c、由于CMS使用  标记-清除  算法实现,此算法收集时会产生大量碎片。-XX:+UserCMSCompactAtFullCollection:用于在CMS顶不住要进行FullGC时开启内存碎片的整理过程,但内存整理过程无法并发,导致停顿时间变长。

5、G1收集器

a、并发与并行,利用多个CPU缩短用户线程停顿时间。

b、分代收集

c、整体是  标记-整理  算法实现,局部是复制算法实现,即不会产生空间碎片。

d、可预测的停顿,因为G1将java堆划分为大小相等的region,回收region的时间可预算。

在G1之前的其他收集器收集的范围是这个新生代或老年代,而G1将java堆划分为多个大小相等的独立区域(Region),新生代与老年代不再是物理隔离,他们度是一部分Region。G1跟踪各个Region里垃圾堆积价值大小,并维护优先列表,每次根据允许的收集时间,优先回收价值最大的Region。

《笔记:深入理解Java虚拟机-JVM高级特性与最佳实践》

八、GC日志

《笔记:深入理解Java虚拟机-JVM高级特性与最佳实践》

33.125,100.67:GC发生时间

GC,Full GC:垃圾收集停顿类型     Full GC 说明GC发生了stop-the-world

[DefNew、[Tenured、[Perm发生GC的区域

[DefNew:3324k->152k(3712k)  ,该区域内存已用容量->GC后该内存区域已用容量(该内存区域总容量),后面的时间代表该区域GC所花的时间

3324k->152k(11904k):GC前java堆已用容量->GC后java堆已用容量(java堆总容量),后面的时间代表该区域GC所花的时间

a、垃圾收集器相关参数

《笔记:深入理解Java虚拟机-JVM高级特性与最佳实践》

《笔记:深入理解Java虚拟机-JVM高级特性与最佳实践》

九、大对象

-XX:PretenureSizeThreshold:令大于此值的对象直接在老年代总分配,避免在Eden和Survivor发生大量的内存复制。

如果对象在Eden产生,并经过此意Minor GC后还能存活,并且能被Survivor容纳,将被移至Survivor,并对象年龄设为1,随后每熬过一次MinorGC年龄就增加1,当达到15(默认阈值)时,将升为老年代。-XX:MaxTenuringThreshold设置阈值。

十、类加载

《笔记:深入理解Java虚拟机-JVM高级特性与最佳实践》

一切方法调用在Class文件里面存储的度只是符号引用,而不是入口地址(直接地址),所以需要解析阶段将符号引用转化为直接引用。

1、加载:

a、通过一个类的全限定名获取此类的二进制字节流

b、将这个字节流所代表的的惊天存储结构转化为方法区的运行时数据结构

c、在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口

2、验证

确保Class文件的字节流中包含的信息符合当前虚拟机要求,并不会危害虚拟机安全。

文件格式验证–>元数据验证–>字节码验证–>符号引用验证

3、准备

为类变量分配内存(在方法区分配)并设置类变量初始值(注意是类变量,即static修饰的变量)

public static int count = 123;   在准备阶段count为0,在初始化阶段count才被赋值为123

public final static int count =124;  特殊情况,被final修饰的变量在准备阶段就被赋值为124

4、解析

将常量池中的符号引用替换成直接引用。

符号引用:任何形式字面量,只能能无歧义定位到目标

《笔记:深入理解Java虚拟机-JVM高级特性与最佳实践》

直接引用:指针、相对偏移量或间接定位到目标的句柄

5、类初始化阶段

执行类构造器的过程。此时类变量已被初始化有默认值。初始化完成才被赋值为用户编码赋予的值。

十一、类加载器

每个类加载器度拥有独立的类名称空间。两个类相等的前提是这两个类必须有同一个类加载器加载的。

三类加载器:启动类加载器、扩展类加载器、应用程序类加载器

类加载器工作流程:

《笔记:深入理解Java虚拟机-JVM高级特性与最佳实践》

工作过程说明:

当一个类加载器收到类加载的请求时,它是先把这个请求委派为父类加载器去完成,所以所有请求最终会送到顶层启动类加载器,只有当父加载器反馈自己无法完成加载请求,子加载才会尝试自己加载。

十二、栈帧

用于支持虚拟机进行方法调用和方法执行的数据结构,是虚拟机栈的栈元素。每个方法从调用至执行完成对应一个栈帧入栈至出栈的过程。

栈帧包括局部变量表、操作数栈、动态链接、方法返回地址。在编译代码过程中已确定一个栈帧需要多大的空间。

《笔记:深入理解Java虚拟机-JVM高级特性与最佳实践》

1、局部变量表

存放方法参数和方法内定义的局部变量。大小在编译期间已确定。

2、操作数栈

先进后出,大小在编译期间已确定。作用:在方法执行过程中,会有各种字节码指令往操作数栈中写入和提取数据。如:执行其他方法时通过操作数栈来传递参数。

例子:整数加法字节码指令iadd,运算时会将操作数栈中栈顶的两个元素出栈并相加,然后将结果入栈。

十三、指令集

1、基于栈的指令集:

java编译器是基于栈(操作数栈)的指令集架构的

2、基于寄存器的指令集

PC是基于寄存器指令集的

栗子:1+1

基于栈的指令集:

iconst_1

iconst_1

iadd

istore_0

两条iconst_1指令连续将两个常量1压入栈,iadd把栈顶的两个值出栈、相加,然后将结果放回栈顶,最后istore_0把栈顶的值放到局部变量表的第0个slot中。

基于寄存器指令集:

mov eax ,1

add eax,1

mov指令将eax寄存器设为1,然后add将eax寄存器的值加1,再将结果保存至eax。

优缺点:

基于栈的指令集:可移植,寄存器指令集依赖于硬件,不可避免受到硬件的约束。

基于寄存器:速度快,基于栈的指令集频繁的操作内存。

十四、硬件的效率与一致性

处理器(CPU)运算速度非常快,比存储设备快几个数量级,为充分利用CPU,所以在他们之间加入高速缓存,运算时直接将内存数据复制到高速缓存,运算结束再从高速缓存同步内存。但是如果PC有多个处理器(CPU)(并发),每个CPU有独立的高速缓存,而所有高速缓存有共享同一个主内存,当从高速缓存同步数据会主内存时,无法确定以谁的缓存数据为准,这就可能发生一致性问题。解决方案:需要各个处理器访问缓存时遵循一些协议。如MSI。

《笔记:深入理解Java虚拟机-JVM高级特性与最佳实践》

十五、java内存模式

java内存模式定义了各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量的细节。

变量包括实例字段、静态字段和构成数据的对象元素,但不包括局部变量和方法参数(线程私有,不存在竞争)

java内存模式规定所有变量度存储在主内存,每条线程有独立的工作内存(类似处理器高速缓存),线程工作时需将数据从主内存复制到工作内存,线程对变量的所有操作必须在工作内存中进行,运算结束后再从工作内存同步至主内存。

《笔记:深入理解Java虚拟机-JVM高级特性与最佳实践》

主内存主要对应于堆,工作内存主要对应虚拟机栈。

十六、volatile

volatile变量只能保证可见性。

1、保证此变量对所有线程的可见性(每次使用前度要刷新,即重新从主内存复制值至工作内存)

2、volatile无法保证java的运算原子操作。

十七、线程安全

当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为度可以获取正确的结果

1、final 不可变

不可变的对象一定是线程安全的,无论是对象的方法实现还是方法的调用者,度不需要再采取任何的线程安全保障措施。

十八、锁

1、轻量级锁

前提同步对象没有被锁定,虚拟机首先将当前线程的栈帧建立一个名为锁记录的空间(Lock Record),用于存储锁对象的Mark Word,虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针。

《笔记:深入理解Java虚拟机-JVM高级特性与最佳实践》

如果更新失败,虚拟机首先会检查对象的Mark Word 是否指向当前线程的栈帧,如是说明当前线程已拥有这个对象的锁,直接进入同步块,否则说明锁对象已被其他线程占用。此时轻量级锁会升级为重量级锁。

2、偏向锁

轻量级锁是在无竞争的情况下使用CAS操作去消除同步使用互斥量,偏向锁是在无竞争的情况下把整个同步度消除,连CAS操作也去掉。

偏向第一个获取它的线程,如果该锁没被其他线程获取,则持有偏向锁的线程将永远不需要进行同步。

《笔记:深入理解Java虚拟机-JVM高级特性与最佳实践》

    原文作者:java虚拟机
    原文地址: https://blog.csdn.net/u012088516/article/details/81699914
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞