JVM知识全景图
2021-03-15 17:49:35 35 举报
JVM知识可视化全景图(如果有错误/不完善的可以在评论中留言指正/补充),如要阅读配套文章,可点击图中左上角的链接跳转博客主页。
作者其他创作
大纲/内容
对象内存分配
GC算法
Shenandoah(并发)全局整理+局部复制
进行编译
标记复制算法
重度竞争、耗时过长、自旋过多等
STW(Stop-The-World):在GC发生时,所有用户线程都需要挂起停止,对于用户而言就是整个程序卡住不能动了,直到等到GC完成只会用户线程才能重新恢复执行。至于为什么需要停止用户线程也很好理解,GC发生时,GC线程和用户线程操作的是同一块内存区域,如果用户线程不停止,GC线程前脚刚标记完一块区域,用户线程就在这块区域创建了一个对象,当回收的时候,可能这个对象还是存活的,那么就会导致该对象被清除了,对于业务来说会出现很大影响,存在线程安全问题。
验证文件格式/元数据/字节码/符号引用等
GC标志
值初始化
Bootstrap类加载器
五、最后执行<init>函数,也就是类的构造函数,主要是对属性赋值。
垃圾回收算法
子类构造方法
当出现大量线程(CPU核数一半+)同时竞争轻量级锁或一个线程CAS10次(默认是十次,可以通过-XX:PreBlockSpin调整)以上时轻量级锁发生膨胀,升级到重量级锁
分配担保:当发生GC时,一个S区空间无法储存eden区和另外一个S区存活对象时,这些对象直接被转移到老年代,这个过程就是空间分配担保。JDK8以后,在进行Minor GC前,如果老年代的连续空间大于新生代对象大小总和或历次晋升的平均大小,则进行Minor GC,否则进行Full GC。
一、调优指标:① 降低回收STW延迟 ②提升最大吞吐量 ③减少内存占用量二、调优原则: ① MinorGC回收原则: 每次MinorGC都要尽可能回收更多垃圾对象,以减少应用程序发生Full GC的频率。 ② GC内存最大化原则:处理吞吐量和低延迟问题时候,垃圾处理器能使用的内存越大,垃圾收集的效果越好,应用程序也会越来越流畅。 ③ GC调优3选2原则: 在性能属性里面,吞吐量、延迟度、内存占用率,我们只能选择其中两个进行调优,不可三者兼得。三、JVM调优的常用参数: -Xms:堆最小空间 -XX:+PrintGC:在控制台输出GC信息 -Xmx:堆最大空间 -XX:+PrintTLAB:可以查看TLAB的使用情况 -Xss:虚拟机栈大小 -XX:+UseSpining:开启自旋锁 -XX:NewRatio: Old/New的比例 -XX:MaxGCPauseMillis:设置每次年轻代垃圾回收最长时间 -Xmn:年轻代大小,调整会影响老年代大小,官方建议为堆大小的3/8 -XX:EliminateAllocations:开启标量替换 -XX:SurvivorRatio:调整Survivor区和Eden区的大小比例 -XX:+DoEscapeAnalysis:开启逃逸分析 -XX:MetaspaceSize:元空间初始化大小,JVM-64bit默认20.75M +XX:+EliminateLocks:开启同步消除 -XX:MaxMetaspaceSize:元空间最大大小,逻辑限制为物理内存上限 -XX:+PrintEliminateAllocations:查看标量替换情况 -XX:PretenureSizeThreshold:大对象直接进入老年代的阈值(字节为单位) -server:代表以server模式运行虚拟机 -XX:MaxTenuringThreshold:进入老年代的分代年龄阈值 -client:代表以client模式运行虚拟机 -XX:-XX:TargetSurvivorRatio:动态年龄判断比例设置 -XX:ThreadStackSize:设置线程栈默认大小 -verbose:gc:输出JVM的gc情况 -XX:-UseTLAB:开启TLAB线程本地空间 -XX:+PrintGCDetails:输出GC详细信息 -Xnoclassgc:关闭垃圾回收机制 -XX:+PrintGCTimeStamps:输出GC的时间戳(以基准时间的形式) -XX:TLABWasteTargetPercent:TLAB与Eden区的占比 -XX:+PrintTenuringDistribution:输出对象GC年龄信息 -XX:+CollectGen0First:FullGC是否先Young GC -XX:+PrintHeapAtGC:在进行GC的前后打印出堆的信息 XX:+UseAdaptiveSizePolicy:自适应Survivor和Eden大小 -Xloggc:路径:日志文件的输出路径 -XX:GCTimeRatio:设置GC时间占程序运行时间的百分比 -XX:+UseG1GC:用G1垃圾收集器(其他垃圾收集器也是一样,更换名字即可) -XX:NewSize:设置年轻代的初始大小 -XX:+PrintCommandLineFlags -version:输出默认的垃圾回收器 -XX:PermSize:设置年老代的初始大小
Survivor1 1/10(To/s1)
热点探测
基本数据类型数值
内存完整:①复制②标整③标清内存利用:①标整②标清③复制
虚拟机栈(Stack)
类加载子系统
是否偏向锁
空闲列表:与指针碰撞一样,空闲列表同样是Java在为新对象分配堆内存时的一种内存分配方式,一般适用于CMS等一些会产生内存碎片、堆内存不完整的垃圾收集器。分配过程:堆中的已用内存和空闲内存相互交错,JVM通过维护一张内存列表记录可用的空闲内存块信息,当创建新对象需要分配内存时,从列表中找到一个足够大的内存块分配给对象实例,并同步更新列表上的记录,当GC收集器发生GC时,也会将已回收的内存更新到内存列表。
2bit
4bit
Parellel Old(并行)整理
二、创建一个对象所需要的内存在类加载完成时就能确定,内存分配是指在堆中划出一块和对象大小的对应内存出来,具体的分配方式根据堆内存的整齐性决定,而堆内存的整齐性则由当前程序采用GC机制决定。分配方式:①指针碰撞(堆整齐) ②空闲列表(堆不整齐) 分配出现并发情况解决方案:①CAS自旋 ②TLAB本地内存
线程私有区
运行时常量池
用于存放int、long、short、float、double、char、byte八种基本数据类型数据,存放以变量槽(slot)为最小单位(32bit/位),double、long这两种基本数据类型需要2个slot存储,所以会出现线程安全问题;基本数据类型存储数值本身,引用数据类型存储指向堆内存的引用指针
根据验证后的类信息初始化类结构变量
31bit
锁标志位
栈帧1
1
否
不管能不能分配下都是在Eden区进行分配,因为TLAB指的是Hostpot对于new对象的优化,因为会出现同时多个线程在创建(new)对象,那么假设如果两个线程都看上了同一块内存,就会在这里浪费很多争抢的时间,所以Hostpot为每一个线程都在eden区中分配了一块专享的空间供线程使用。
验证
后台线程执行编译
不分代回收GC器
TLAB:Thread Local Allocation Buffer在Eden区为每个线程开辟的一个缓冲空间,线程在创建对象时如果该缓冲区的大小能够承载对象大小则直接在该区域分配对象,能够避免多个线程同时创建对象时由于竞争同一块堆内存时产生的资源消耗
1 1
与虚拟机栈相似,但是本地方法栈是为虚拟机的Native方法提供服务;Hotspot将虚拟机栈与本地方法栈合二为一
重量级锁
是
........
CMS(并发)清除
1bit
局部变量表
本地内存
标记整理算法
TLAB(线程本地内存) 1
HashCode
Application(app)类加载器
32bit/位虚拟机对象头信息
安全点:无论是在GC中还是线程安全中都会出现安全点(SafePoint)这个概念,当我们需要阻塞一个线程时都需要在安全点停止,简单说安全点就是指当线程运行到这类位置时,堆对象状态是确定一致的,当前线程停止后,JVM可以安全地进行操作,如GC、偏向锁撤销等。安全点的定义:①循环结束的末尾段 ②方法调用之后 ③抛出异常的位置 ④方法返回之前当JVM需要发生GC、偏向锁撤销等操作时如果让所有线程到达安全点阻塞?①主动式中断(JVM采用的中断方式):不中断线程,而是设置一个标志然后让每个线程执行时主动轮询这个标志,当一个线程到达安全点后,发现中断标志为true时就自己中断挂起 ②抢断式中断:先中断所有线程,如果发现线程未执行到安全点则恢复线程让其运行到安全点位置。安全区域(SafeRegion):当一个线程处于中断或者休眠状态时就不能响应JVM的中断请求走到安全点区域挂起了,所以出现了安全区域的概念。安全区域是指一个线程执行到一段代码时,该区域的代码不会改变堆内对象的引用,在这区域内JVM可以安全地进行操作。当线程进入到该区域时需要先标识自己进入了,这样GC线程则不会管这些已标识的线程,当线程要离开这个区域时需要先判断GC Roots是否完成,如果完成了则往下执行,如果没有则需要原地等待到GC线程发出安全离开信息为止。
标记清除算法
新生代分配
类加载检测
returnAddress
锁信息
匿名偏向锁未开启
尝试栈上分配
逃逸分析:判断变量作用域是否存在于其他栈帧或者线程中。当一个对象的指针被多个方法或线程引用时,我们称这个指针发生了逃逸,判断逃逸的方法被称为逃逸分析。逃逸的作用域:①栈帧逃逸:当前方法内定义了一个局部变量逃出了当前方法/栈帧。 ②线程逃逸:当前方法内定义了一个局部变量逃出了当前线程能够被其他线程访问。全局变量赋值逃逸:当前对象被赋值给类属性、静态属性参数赋值逃逸:当前对象被当作参数传递给另一个方法方法返回值逃逸:当前对象被当做返回值return
同步消除:通过逃逸分析对一个对象进行逃逸判断之后,如果该对象为线程级作用域不可逃逸时,则代表当前这个对象只会有一个线程可访问。如果当前这个对象是被作为同步锁对象的,那么JVM会在编译时消除加锁和解锁的代码。决定能否同步消除(满足一个即可):①当前对象被分配在栈上。②当前对象的无法逃出线程作用域。
偏向锁
偏向锁已启动
字节码效验器
可以理解为PC寄存器,作为用于存储计算的临时数据存储区,当CPU执行load指令时能将数据加载进入操作数栈
①处于并列优先级的按照Java程序编写时代码的先后顺序加载。②前面两步为静态过程,程序运行过程中只会执行一次,之后再次创建该类或该子类的对象时并不会再次执行。
标记-压缩(整理)算法:标记阶段通过根可达算法标记出所有存活对象,整理/压缩阶段将所有存活对象挪动到内存的一端,然后对于边界之外的所有对象进行统一回收。缺点:①需要移动对象,所以效率也并不是特别高。优点:内存完整
其他概念/技术:(自行了解/写不下了)①三色指针②SATB③增量更新④有色指针⑤多标-漏标新代GC器解决漏标:CMS:写屏障+增量更新G1:SATB+写屏障ZGC:读屏障ZGC实现:有色指针+内存屏障
G1(并发)全局整理+局部复制
进行加载
1. 获取class字节码文件二进制字节流2. 将磁盘文件静态结构载入内存方法区转换为运行时数据结构<类信息>3. 将载入后的类信息进行组装,在堆空间中生成类对象(class),作为数据入口
0 1
语义分析
是否满足old代分配条件
类加载器
三、接着JVM会初始化分配好的内存,将其设为零值(不包括对象头,如果使用了TLAB,这一步会提前到内存分配阶段进行)。
子类变量/代码块
父类变量/代码块
使用
磁盘载入内存
指针碰撞:指针碰撞是Java在为对象分配堆内存时的一种内存分配方式,一般适用于Serial和ParNew等不会产生内存碎片、堆内存完整的的垃圾收集器。分配过程:堆中已用分配内存和为分配的空闲内存分别会处于不同的一侧,通过一个指针指向分界点区分,当JVM要为一个新的对象分配内存时,只需把指针往空闲的一端移动与对象大小相等的距离即可。
new
方法出口
Serial(串行)复制
元数据空间 | 方法区 | 永久代(Meta Space)
ZGC(并发)全局整理+局部复制
来回交换 同一时间内永远有一个为空
对象年龄
old
符号引用:类和结构全限定名字段名称和描述符方法名称和描述符
四、完成初始化操作后接着会对于对象的对象头进行设置:①mrakword:存储对象自身的运行时数据,如hashcode、GC分代年龄、锁标志、锁信息等;②klassword:类型指针,指向它对应的类元数据,VM用这个确定其属于哪个类的实例。
复制算法:将原有的内存区域一分为二,在同一时间内只会使用其中一块内存区域用来分配对象。在发生GC时,首先通过根可达算法判断存活对象,并且将所有的存活对象移动到另一块未使用的内存区域,最后对于前面的这块使用内存区域中的对象进行统一回收。缺点:①不适用于存活对象多的情况,移动对象对于空间/时间开销太大。②内存利用率太低,这种算法下无论何时永远有一半内存区域的浪费。优点:内存完整
JVM调优
markword
编译结束
轻量级锁
直接内存
方法/回边计数器+1/+n
无锁态
Unused
ParNew(并行)复制
注解抽象语法树
本地方法栈(Native Meathod Stack)
执行编译后的机器码
程序计数器(Program Counter Register)
young
连接
javac 源代码编译器
ParellelScavenge(并行)复制
JIT即时编译器字节码→JIT即时编译(代码体积增大)→执行→结果
方法执行
初始化
卸载
两个计数器相加是否超过阈值
线程共享区
对象成员分配内存后初始化顺序
wait
栈上分配:一般而言Java对象创建出来都是会在堆上进行内存分配,但其实并不是所有的对象都会在堆中分配,有时候也能够在栈上进行分配。不过想要将对象在栈上进行分配,需要先开启标量替换以及逃逸分析。决定一个对象能否在栈上分配的因素:①对象能够通过标量替换分解成一个个标量。②对象在栈帧级作用域不可逃逸。如果一个对象能够满足如上两点则代表该对象可以在栈上进行分配。
当第二个线程尝试获取锁,但是发现第一个线程还在执行,那么会将对象回归无锁态并将markword中除开锁标志位的信息全部copy到自己的栈内存中锁信息LockRecord中,然后再尝试利用CAS将对象头内markword中的指针指向自己栈内锁信息,能够替换成功代表获取到锁,替换失败继续自旋(小细节:如果此时之前持有偏向锁的线程来获取锁,优先CAS)
对象存活判断算法
标记-清除算法:标记阶段通过根可达算法标记出所有存活对象,然后在清除阶段对于所有未标记对象进行统一回收。缺点:①两个阶段效率都不高。②会产生大量的内存碎片。优点:简单
持有偏向锁线程的ID
抽象语法树
执行引擎(Execurtion engine)
class文件
栈帧2
词法分析
记录方法结束时的出栈地址(正常执行结束时的返回地址或者由于报错结束时的异常地址)
不管你当前对象是处于何种锁状态,只要你调用了Object.wait()方法,那么该锁会直接膨胀为重量级锁,因为wait方法实则还是依赖于monitor实现,所以不管什么时候调用wait都会使得对象头中markword内出现指向monitor的指针,而markword的改变对于用户线程是不可逆的,一旦出现monitor指针,就无法回到之前的锁状态
Extension(EXT)类加载器
在新生代达到一定条件的对象会进入年老代:1. s0与s1区域交换一次年龄+1 当对象年龄达到15岁进入(cms默认6岁)2. 大对象直接进入(可设置大小判断标准)3.s区相同年龄的对象大小总和超过s区空间一半,大于等于该年龄的所有对象直接进入
0 0
对象头
轻度竞争
当第一个线程试图获取锁资源时会先判断匿名偏向是否已开启,如果开启会先将markword内除开锁标志位的信息全部copy到自己的栈内存中锁信息LockRecord中并尝试利用自旋将自己的线程ID设置到markword中,以后每次该线程执行用改对象作为锁资源的代码无需加锁和放锁操作,只是在LR中加一个空的markword
父类静态变量/静态代码块
失败
堆空间(Heap) -- Full GC
指向监视器对象(Monitor)的指针
解释器解释
已用内存
Mojor GC
在程序执行过程中能够随着程序的需要生成并执行新的代码及称为即时编译。在目前的商用虚拟机里面都实现了JIT即时编译这种技术。虽然Java虚拟机规范中没有明确指出必须要实现它,但是JIT编译性能的好坏、代码优化程度的高低却是衡量一款商用虚拟机最关键的指标之一,它也是虚拟机中最核心且最能体现虚拟机技术水平的部分。 在HotSpot中Java程序通常情况下都是通过解释器(Interpreter)一边解释一边执行的模式运行代码的,当虚拟机检测到某个代码块或者方法体执行非常频繁时,会将这些检测到的代码判定为热点代码。而如果这些热点代码都通过解释器的模式执行,那么每次执行都需要用到解释器解释,为了提升热点代码执行效率,在运行时虚拟机会将这些代码编译成与当前本地平台+硬件相关的机器码并进行各层面的优化,同时也会将方法的调用地址指向编译后的机器码并且也会将编译后的机器码保存到磁盘,而这个编译热点代码的编译器被成为JIT(即时编译)器。 PS:当热点代码被检测到时,不管是代码块还是方法体,虚拟机都是以方法为最小单位进行编译。 HotSpot中实现的两种JIT器:ClientCompiler(C1):局部优化,追求编译速度/ServerCompiler(C2):充分优化,追求编译质量。会根据JVM运行模式来决定选择谁。
空(null)
字节码生成器
0
数组长度
实例对象
reference
对齐填充 - 8的整数倍
加载
编译器以方法为最小单元对于热点代码进行编译之后,原本的方法调用地址被替换为编译后的方法地址。
栈上替换
Minor GC
24bit
成功
类型指针
尝试TLAB分配
根可达算法
当前线程执行解释器代码
执行栈上替换
25bit
基于计数器实现的热点探测JVM给每个方法绑定了两个计数器:1.方法调用计数器:方法被调用执行一次时计数器+1。2.回边计数器:统计一个方法体内循环体循环的次数。
Eden 8/10
JVM启动后前四秒 new 出来的对象并未启动匿名偏向锁
23bit
记忆集(RememberSet):我们都知道在发生新生代GC时都会通过根可达算法先判断垃圾对象,之后再对非存活对象进行统一回收,但是如果有年老代对象引用了新生代对象,那么根据根可达算法的特性,年老代也会被加入扫描范围,这样下来一次新生代的GC代价太大。所以为了解决跨代引用的问题,在新生代引入了记录集的数据结构,记录从非收集区到收集区的引用指针集合,避免在通过根可达算法判断对象存活时把整个老年代加入扫描范围。GC时,GC器只需通过记忆集判断出某一块非收集区域是否存在指向收集区域的指针即可,无需进行详细的根搜索过程。记忆集可根据不同的记忆粒度实现:①字宽/字长精度:精确到每个字宽(32bit/64bit),每一个跨代引用指针②对象精度:精确到每个对象,对象的字段中包含跨代引用指针③卡精度:精准到每一块内存区域,内存区域中有对象存在跨代指针卡表:第三种精度的实现被称为卡表,也是HotSpot VM中记忆集的实现方式,卡表中记录中记忆集的记录精度、与堆内存区域的映射关系等。在HotSpot中卡表是使用一个字节数组实现:CARD_TABLE[this addredd >>9]=0,数组中每个元素对应着其标识的内存区域,称为卡页,hotSpot使用的卡页大小为2^9 即512字节,也就是说内存中每连续的512字节会被当作一个卡页作为卡表的一个元素。如果有年老代的对象引用了新生代的对象,那么该新生代对象所在区域对应的卡页元素设置为1,反之则为0。(G1以后的GC器不分代,所以G1以后的记忆集不是通过数组实现的,而是通过哈希表结构实现)。
本地方法库(Native Libraries)
执行类构造器<clinit>()生成class对象放入堆中
提交OSR编译请求
普通对象
语法分析
空闲内存
通过GCRoots(栈中变量/方法区静态变量/Monitor持有者对象/常驻异常对象/JVM内部引用/方法区常量)对象作为起始点向下搜索,搜索走过的路径被称做引用链,当一个对象到GCRoots没有任何引用链时即根不可达时该对象则判断为不可用可以被回收
子类静态变量/静态代码块
操作数栈
TLAB(线程本地内存) 2
当第二个线程试图获取锁资源时发现偏向锁已开启,那么会进行上一步操作,尝试将自己的线程ID设置到markword中,但是之前第一个线程已经赋值,对象头内存储的是第一个线程的ID,那么此时会判断是否是安全点(第一个线程是否已执行完毕),如果第一个线程已经执行完毕,那么会发生偏向锁撤销(将锁对象清除锁记录并回归无锁态),然后第二个线程重新cas自旋将自己的线程ID设置到markword中,重新偏向
Synchronized锁膨胀/升级过程
执行<init>
栈帧3
如果第一个线程试图获取锁时判断匿名偏向发现偏向锁未开启时会直接膨胀为轻量级锁,先将markword中除开锁标志位的信息全部copy到自己的栈内存中锁信息LockRecord中,然后再尝试利用CAS将对象头内markword中的指针指向自己栈内锁信息,能够替换成功代表获取到锁,替换失败继续自旋
Java对象创建过程
指针压缩:在64bit的虚拟机中为了提升内存的利用率,所以出现了指针压缩这一技术,指针压缩的技术会将Java程序中的所有引用指针(类型指针、堆引用指针、栈帧内变量引用指针等)都会压缩一半,而在Java中一个指针的大小是占一个字宽单位的,在64bit的虚拟机中一个字宽的大小为64bit,所以也就意味着在64位的虚拟机中,指针会从原本的64bit压缩为32bit的大小,而指针压缩这一技术在JDK1.7之后是默认开启的。指针压缩失效:指针压缩带来的好处是无可厚非,几乎能够为Java程序节省很大的内存空间,一般而言,如果不开启压缩的情况下对象内存需要14GB,在开启指针压缩之后几乎能够在10GB内存内分配下这些对象。但是压缩技术带来好处的同时,也存在非常大的弊端,因为指针通过压缩技术后被压缩到32bit,而32bit的指针最大寻址为32GB,也就代表着如果你的堆内存为32G时出现了OOM问题,你此时将内存扩充到48GB时仍有可能会出现OOM,因为内存超出32GB后,32bit的指针无法寻址,所有压缩的指针将会失效,发生指针膨胀。
指向栈中锁记录(Lock Record)的指针
.class 字节码文件
Survivor0 1/10(From/s0)
年老代(Old) 2/3
JVM启动后后四秒 new 出来的对象默认启动匿名偏向锁
由于OOP思想中存在多态的概念使得编译器在编译源代码时无法确定对象类型,只有在运行时才能确定对象,指向常量池中方法的引用
父类构造方法
解释器字节码→解释器执行→结果
Epoch
准备
是否编译?
class对象
垃圾回收 GC
解释执行
.java 源文件
匿名偏向
方法返回
类(class)信息
字面量:文本字符串final常量值基本类型数值....
双亲委派模型:当前类加载器需要加载xx类时不会自己直接去加载,而是尝试将加载这个类的任务向上传递交给父加载器去完成,如果父类加载器能加载则由父类加载器来完成而自己不会再去加载。(好处:外部想要替换JDK的系统类,篡改它的实现时,因为父类加载器已经加载过JDK的系统类子类加载器不会再加载,从而能够在一定程度上防止了危险代码的植入)
本地方法接口(Native Interface)
cms_free
由于CPU在执行的时候会存在线程时间片切换的概念,所以CPU执行指令的时候是会中断的,程序计数器会记录当前线程执行停止的字节码指令位置(行号),以便于再次切换到改线程时能够恢复到正确的执行位置而避免重新执行
字符串常量池
新生代(New) 1/3
分代回收GC器
属性方法类元信息(类型信息/类型的常量池/方法信息/字段信息/类加载器的引用信息/Class对象实例引用信息/方法表)
1 0
设置对象头
实例数据
JIT动态编译
个人技术博客:掘金-竹子爱熊猫(JVM专栏全系列已更完)地址:https://juejin.cn/user/862486453028888/posts可点击右下角的超链接快速跳转,主页有技术专栏列表 —>
如果是句柄引用reference则存储的是堆中句柄池内的句柄,如果是直接引用reference则存储的是堆中对象的内存地址。
常量池符号引用替换为直接引用<因为多态>
new
GC器 连线代表可搭配使用
动态链接
解析
直接引用:①直接指向内存地址的指针。②相对的偏移量(方法指针/指向实例变量) ③句柄池中间接指向内存地址的句柄符号引用:符号引用以一组符号来描述所引用的目标。符号引用可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。在类加载时的解析阶段,会有一步操作就是将class文件中的符号引用替换为直接引用,简单来说就是:在编译的时候一个每个Java类都会被编译成一个class文件,但在编译的时候因为类还没有被装载进内存,所以虚拟机并不知道所引用类的内存地址,所以就用符号引用来代替,而这个解析阶段就是为了把这个符号引用转化成为真正的地址。
obj@7fe2f3
SerialOld(串行)整理
热点代码缓存(hotCodeCache)JIT即时编译产物
User类加载器
G1承上启下:逻辑分代物理不分代
引用计数法
26bit
标量替换:使用标量替换聚合量(对象),对于一个对象进行分解,将其拆解成一个个小的标量的过程(需要先进行逃逸分析,不可逃逸的对象才能进行标量替换)。标量:不可分割的量,指基本数据类型和reference类型好处:①能够节省堆内存,因为进行标量替换之后的对象可以在栈上进行内存分配。②相对运行而言省去了去堆中查找对象引用的过程,速度会更快一些。③因为是分配在栈上,所以会随着方法结束和线程栈的弹出自动销毁,不需要GC的介入。
JIT(Just In Time Compiler)即时编译
方法入口
一、当遇到new指令时,VM首先会进行类加载检测:①检测new指令的参数是否能在常量池中定位类的符号引用。②检测这个符号引用是否进行过加载解析和初始化,没有则先对该类进行类加载。
64bit/位虚拟机对象头信息
运行时数据区
年老代分配
TLAB分配
引用级别:强引用 → 软引用 → 弱引用 → 虚引用强引用:通常通过new指令创建的对象都属于强引用类型,堆中的对象与栈中变量存在直接引用,对于这类存在强引用的对象。周所周知,当堆内存不足时,GC机制会被强制触发,但是如果GC器发现堆内对象都存在强引用时,GC器不会强制回收,而是触发OOM。所以一般情况下在编码的时候,如果确定一个对象不再使用之后可以显式的将对象引用清空:obj=null,这样能够方便GC在查找垃圾时直接发现它。软引用:软引用是指使用java.lang.ref.SoftReference<Object>(obj)类型修饰的对象,当一个对象只存在软引用时,在堆内存不足时,该引用级别的对象将被GC机制回收。当然在发生GC时如果堆内存还充足,那么是不会回收该引用级别的对象(可以用来实现缓存)。弱引用:弱引用是指使用java.lang.ref.WeakReference<Object>(obj)类型修饰的对象,与软引用的区别在于:弱引用类型对象的生命周期更短,因为弱引用类型的对象只要被GC发现,不管当前的堆内存资源是否紧张,都会被GC机制回收。不过因为GC线程的优先级比用户线程更低,所以一般不会立马发现弱引用类型对象,因此一般弱引用类型的对象也会有一段不短的存活周期。虚引用:虚引用是指使用java.lang.ref.PhantomReference<Object>(obj)类型修饰的对象,不过在使用虚引用的时候是需要配合ReferenceQueue引用队列才能联合使用。与其他的几种引用类型不同的是虚引用不会决定GC机制对一个对象的回收权,如果一个对象仅仅存在虚引用,那么GC机制将会把他当成一个没有任何引用类型的对象,随时随刻可以回收它。不过它还有个额外的用途:跟踪垃圾回收过程,也正是由于虚引用可以跟踪对象的回收时间,所以也可以将一些资源释放操作放置在虚引用中执行和记录。
偏向锁未启动
Epsilon:无作用GC器,当你的程序不需要GC时可以选择使用该GC器
0 条评论
回复 删除
下一页