JVM知识点总结
2023-02-07 11:37:41 0 举报
JVM知识点: Java内存模型、堆内存模型、垃圾回收、JIT
作者其他创作
大纲/内容
in
操作数栈
动态链接
本地方法栈
s0
线程B
虚拟机栈 FILO
sum
j
G1收集器和CMS的不同:1. 分区 -- 在JVM启动之初,收集器将整个堆内存分成大小相同的2000个区,区块的大小范围(1MB~32MB),因此老年代和新生代不再是物理连续的区域了,除此之外,对于大对象(大小大于region的一半)还会分配专门的区域H区。2. Minor GC -- 同样使用复制算法,将Eden区和Survivor区的对象复制到新的Survivor区域3. Major GC四个阶段 a) Initial Mark初始标记阶段:与CMS相似,在此阶段标记roots对象和第一层可达对象外。但G1的Initial Mark阶段是和Minor GC一同发生的,不用像CMS那样单独暂停应用程序。也就是说GC触发Minor GC时会将老年代的Initial Mark一并做了 b) Concurrent Mark并发标记阶段:和CMS做的事情也类似,只是额外多做了一件事情,G1会计算每个region的对象存活率,计算出的对象存活率用于后面cleanup阶段使用,如果region几乎没有对象存活,GC会在这个阶段就将其回收掉,这也是Garbage First名字的由来。 c) Remark重新标记阶段:和CMS做的事情一样,只是采用的算法更高效SATB(snapshot-at-the-beginning) d) Clean up/Copy阶段:挑选对象存活率低的region进行回收,这个阶段也是和minor gc一同发生的,Copy阶段伴随着STW事件发生4. Remembered Sets & Collection SetsG1需要分配一些额外的给remembered sets和collection setsRemembered Sets记录每个region里的对象引用情况,每个region都会存在一个RSet,以保证并行GC时的region里的对象都是不关联的,内存占比小于5%Collection Sets记录将要被回收的region列表,包括年轻代和老年代,以保证这些region在回收前不会再被使用,内存占比小于1%5. 优缺点总结:优点1:回收时有空间压缩的过程,减少碎片空间优点2:Major GC的很多工作是和Minor GC一起发生的,保证吞吐量的同时减少STW暂停时间发生,也间接减少了Full GC的发生优点3:可以预设置暂停时间缺点1:设置的暂停时间只能尽量达到,G1也是根据收集的数据预估需要回收多少region,不是非常的精准,而且在回收量预估上会花费资源缺点2:额外的空间消费在Remembered Sets和Collection Sets上缺点3:不适合大对象过多的情况6. 常用的启动参数-XX:+UseG1GC ---------------------- 启动GC收集器-XX:G1HeapRegionSize -------------- 设置region的大小-XX:G1ReservePercent -------------- 设置预留的空间比例避免晋升失败的情况,默认10-XX:InitiatingHeapOccupancyPercent FullGC的触发的空间使用比例,默认45-XX:NewRatio ------------------------- 新生代/老年代比例,默认2-XX:SurvivorRatio ---------------------- eden/survivor比例,默认8-XX:MaxTenuringThreashold ---------- 晋升的年龄边界,默认15-XX:ParallelGCThreads ----------------- 并行回收的线程数,默认是平台的CPU数量-XX:ConcGCThreads -------------------- 并发线程数,默认是平台的CPU数量
i
GC触发条件: - minor gc:eden区快满时触发 - old gc - mixed gc - full gc
方法区
新生代Young
meta space
栈帧
s1
JVM: Java Virtual Machine,我们编写的java代码会被编译器编译成class文件,而class文件如何能被不同的底层操作系统识别呢?JVM的作用就类似于翻译官,将class文件的字节码翻译成底层操作系统能识别的机器语言,是java能够\"跨平台\"的核心。JRE: Java Runtime Environment运行时环境,顾名思义也就是我们java代码运行时需要的环境,除了包括JVM之外,还包括java核心classes和一些类库,如lang包、util包等。这些类库中的一些是使用c/c++语言编写的。JDK: Java Development Kit开发工具,是我们开发Java代码时用到的工具包,除了包括JRE之外,还有一些其他的工具,如javac编译工具,javap解析字节码工具,jmap生成heap dump工具,jhat分析heap dump工具,jstack生成thread dump工具,jconsole用于可视化线程、内存监控工具......
2
复制回收算法:
this
老年代Old
To
Method1
虚拟机栈
From
3
栈顶
新生代比例分配为什么是8:1:1?1:1:1行吗?直接9:1呢?8:1:1是经过统计得出的最优比例,可以使内存利用率达到最大化。根据垃圾回收的经验可以知道eden区在回收时会有近90%以上的对象\"死亡\",只有很小一部分会被移到survivor区。所以如果设为1:1:1,eden区过小造成minor gc频繁触发降低性能,另外我们知道survivor中会至少一块区域为空,1:1:1就代表这1/3的空间被浪费。9:1的话也会有一个问题,我们知道minor gc使用的是复制算法,9:1的比例对eden的回收不会有影响(eden--survior),但是survior区的数据在minor gc时怎么回收呢?往哪里复制呢?如果survior区里的数据在minor gc时不被回收,导致survior区里面的\"死亡\"对象一直存在。当然我们也可以在minor gc时对survior区进行回收,活动的对象可以移动到old区,但是这样的话会造成到old区里面的年龄不足,失去了old区的原本设计。永久代和meta space的区别?- 1.8以前,永久代负责存储类信息、常量和静态变量,该区域大小是固定的,不足时会抛异常OutOfMemory: perm space;永久代存在虚拟机中- 1.8之后,一些常量的存储放到了Heap中,取代永久代的是meta space,它的特点是可扩容,不足时会自动扩容,当然也要设置最大可扩空间。metaspace不在虚拟机中而是在本地内存中
局部变量表
数据区(线程共享)
程序计数器
线程A
方法区MethodArea
Java运行时数据区
...
标记-清除算法:
JDK
出口
1.8
程序计数器:指向当前线程执行的字节码指令的地址(行号)。这样做的用处是多线程操作时,挂起的线程在重新激活后能够知道上次执行的位置。虚拟机栈: 存储当前线程执行方法时所需要的数据、指令、返回地址。因此一个线程独享一块虚拟机栈,栈中内存的单位是栈帧,栈中存放栈帧的数量是有上限的,由参数XSS定义,超出上限会抛StackOverflowError。每执行一个方法都会申请一个栈帧,一个栈帧里主要有这么几块: - 局部变量表:存储方法执行所需的变量,第0位是this对象,第1位开始先是方法的参数变量 - 操作数栈:方法进行运算的区域。如sum=i+j,先将i和j的值从局部变量表中加载到操作数栈里,然后执行加法操作,最后将结果sum返回到局部变量表 - 动态链接:实现运行时多态,存储父类引用指向具体的子类实例 - 出口:存储方法的返回地址,两种返回情况(正常return和异常)本地方法栈: 类似虚拟机栈,存储执行本地方法(native)时所需要的数据、指令、返回地址。方法区:存储类信息、常量(1.7之后有变化,String常量存在heap中)、静态变量、JIT(详见JIT说明)堆Heap:详见堆内存模型。
Method2
eden:s0:s1=8:1:1
垃圾回收算法:复制算法、标记-清除、标记-整理1. 复制算法:将内存分为From和To两部分,当From区满时,将From区中活动的对象复制到To区,清空From区,并From和To区角色互换。如何判断对象活动?从根引用开始递归遍历,深度优先搜索,被引用的对象即为活动对象,直接复制到To区。如何记录对象的新地址?forwording字段如何避免多次引用的对象被复制多次?COPIED字段记录对象是否被复制过,如果复制过则忽略,并改变引用的指向forwording新地址。2. 标记-清除算法:分为标记和清除两个阶段。标记阶段从根引用递归遍历,深度优先搜索,标记被引用的对象。清除阶段再次遍历,未标记的对象加入空闲链表,标记的对象去除标记。* 空闲链表表示该块区域可以被分配。3. 标记-整理算法:分三次遍历,第一次遍历标记活动对象,并根据活动情况设置对象的forwarding字段;第二次遍历根据forwarding值变更相关引用指向地址;第三次遍历移动对象,移动到forwarding的地址
回收机制:- 新生代Minor GC:发生在新生代的GC,通常使用复制算法进行回收,Stop-The-World事件会伴随着发生,每一次的GC使得Eden区和From区中的活动对象的年龄+1,到达一定年龄的对象(默认15)复制到老年代,未到年龄的对象复制到To区,之后清空Eden区和From区,并将From区和To区的角色互换。在执行机制上,JVM提供了串行GC(Serial GC)、并行回收GC(ParallelScavenge)和并行GC(ParNew) Serial GC:整个扫描和复制过程采用单线程的方式进行,适用于单CPU、新生代空间较小以及对暂停时间要求不高的应用上,是client级别默认的GC方式,通过-XX:+UseSerialGC强制指定。 ParNew GC: Serial GC的多线程版本,一般与老年代的并发GC(CMS)配合使用。 ParallelScavenge GC: 也是多线程并行回收器,是server级别默认采用的GC方式,通过-XX:+UseParallelGC强制指定,用-XX:ParallelGCThreads=4来指定并行线程数。这种回收器的目标是达到可控制吞吐量(程序运行时间/总时间),参数设置:MaxGCPauseMills和GCTimeRatio - 老年代Major GC: 采用标记算法进行回收,就是扫描并标记出活动的对象,回收未被标记对象,回收后对空出的空间要么进行合并、要么加入空闲链表以便下次进行分配。在执行机制上JVM提供了串行GC(Serial MSC)、并行GC(Parallel MSC)和并发GC(CMS)。 Serial MSC: client模式下默认的老年代GC方式,每次回收都会进行Compact,非常耗费时间,通过-XX:+UseSerialGC强制指定。 Parallel MSC: server模式下默认的老年代GC方式,吞吐量大但是GC响应很慢,通过-XX:UserParallelGC=N强制指定(N指定并行线程数)。 CMS: 多与新生代的ParNew GC配合使用,当老年代使用率到达一定阈值时触发 并发GC的几个特点: a) 大部分GC工作和应用线程并发执行,因此STW时间短,GC响应快,但是可能造成浮动垃圾(需要被回收的对象尚未回收) b) 没有copy和compact动作,高效率,但造成大量碎片空间。 CMS的执行顺序: 1. 初始标记Initial Mark(STW)标记GC roots 2. 并发标记Concurrent Marking与应用线程并发执行 3. 最终标记Remark(STW)标记步骤#2遗漏的对象 4. 并发清理与应用线程并发执行,回收未被标记的对象 5. Resetting 重置标记等待下次CMS GC触发 CMS可能引发的性能问题: a) Concurrent Mode Failure,由于浮动垃圾造成的老年代可用空间不足,新生代的对象晋升失败从而会触发Full GC b) 碎片空间过多,导致可用的连续空间不足以存放大对象从而会触发Full GC
eden
永久代Perm
堆内存模型
GC Roots:1. 虚拟机栈中引用的对象,如方法里的变量和参数2. 本地方法栈引用的对象3. 方法区中类的常量/静态变量引用的对象4. 活着的线程Alive Thread5. 用于同步的监控对象Monitor
JIT知识点
标记-整理算法
Heap
sum=i+j(1-2-3)
堆Heap
JRE
垃圾回收
JVM
1
。。。。。。
JVM、JRE、JDK
指令区(线程独享)
JIT是什么?一种动态编译器,全名Just In Time Compiler即时编译器。JIT的作用?随着程序运行的时间推移,一些运行频率高的代码(热点代码)会被JIT编译成本地机器指令,再进行一些优化后保存起来,下次执行时可以无需再翻译,提高代码执行效率。编译器的分类?Hot Spot中的编译器分两种,client(C1)和server(C2),两者的区别在于指令的进一步优化,client进行简单可靠的优化,主要关注\"局部性能优化\",编译时间更短;server会进行更深层的优化如\"全局优化手段\",并会根据概率选择一些大部分情况执行效率更高的激进优化,编译时间较长但质量更高。HotSpot代码执行的策略?我们知道,代码执行有两种方式:1)一种是解释执行,字节码被解释器Interpreter解释执行,省去编译时间但执行效率低2)一种是预编译执行,字节码被JIT编译器预编译之后保存起来,之后就可以直接执行这些代码,执行效率高。在JDK1.7之前,Hot Spot默认的代码执行策略是\"解释器和一种编译器配合使用\"Mixed Mode:a. 程序快速启动时,解释器发挥作用,省去编译时间立即执行程序b. 随着程序运行时间的推移,编译器逐渐发挥作用,热点代码被编译成本地指令保存起来,获取更高的执行效率。JDK根据自身版本和机器硬件性能自行选择使用哪个编译器。在JDK1.7之后,Hot Spot默认的代码执行策略是\"分层编译\"Tiered Compilation:在上面的方式中,由于编译器编译本地指令需要占用程序时间,而且解释器还要收集编译器所需的性能监控信息,解释执行的效率也会降低。为了平衡程序启动响应和执行效率,JDK1.6出现了分层编译的概念并在1.7中成为默认的策略。分层编译根据编译器编译、优化的规模和耗时,划分不同的编译层次:第0层,程序解释执行(没有编译),解释器不开启性能监控功能,可触发第1层编译第1层,C1编译,将字节码编译成本地指令,进行简单可靠的优化第2层,C2编译,将字节码编译成本地指令,但会启动一些编译耗时较长的优化,甚至会进行一些不可靠的激进优化。实施分层编译后,C1和C2会同时工作,许多代码被多次编译,C1获取更高的编译速度,C2获取更好的编译质量,而且在解释执行时解释器不需要再承担收集性能监控信息的任务。编译对象与触发条件?编译对象:“热点代码”1)被多次调用的方法,编译对象是方法2)被多次执行的循环体,编译对象依然是以整个方法为对象触发条件:- 基于计数器的热点探测:根据方法的执行次数设置阈值,需要额外开销维护计数器。- 基于采样的热点探测:周期性检查各个线程的栈顶,经常出现在栈顶的方法标为热点代码,不太精确HotSpot采用的方式为第一种,并准备了两类计数器:方法调用计数器(被调用时)和回边计数器(被调用后\"}\")方法调用计数器默认阈值client 1500;server 10000回边计数器默认阈值client 13995; server 10700
0 条评论
回复 删除
下一页