JVM个人总结(四)
(个人对黑马程序员JVM完整教程总结,原视频地址https://www.bilibili.com/video/BV1yE411Z7AP)
垃圾回收
相关VM参数
含义 | 参数 |
---|---|
堆初始大小 | -Xms |
堆最大大小 | -Xmx 或 -XX:MaxHeapSize=size |
新生代大小 | -Xmn或(-XX:NewSize=size + -XX:MaxNewSize=size) |
幸存区比例(动态) | -XX:InitialSurvivorRatio=ratio和-XX:+UseAdaptiveSizePolicy |
幸存区比例 | -XX:SurvivorRatio=ratio |
晋升阈值 | -XX:MaxTenuringThreshold=threshold |
晋升详情 | -XX:+PrintTenuringDistribution |
GC详情 | -XX:+PrintGCDetails -verbose:gc |
FullGC 前 MinorGC | -XX:+ScavengeBeforeFullGC |
垃圾回收器
-
串行
单线程
堆内存较小,适合个人电脑
-
吞吐量优先
多线程
堆内存较大,多核CPU
让单位时间内,STW(stop the world)的时间最短
-
响应时间优先
多线程
堆内存较大,多核CPU
尽可能让STW的 单次 时间最短
串行
-XX:+UseSerialGC = Serial + SerialOld
垃圾回收时stop the world 其他线程会阻塞
吞吐量优先
①UseParallelGC:并行(parallel,多个垃圾回收器可以同时运行,但是不能和用户工作线程同时运行) 新生代
UseParallelOldGC:并行 老年 标记整理 (这两个开关两个连带开启)
②采用自适应的大小调整策略(动态去调整 伊甸园和幸存区比例、堆大小、晋升阈值等等)
③调整吞吐量的大小(例:垃圾回收时间不能超过总时间的%,如果不能达到目标,就会调整堆大小,致使垃圾回收次数下降,达到垃圾 回收时间下降,吞吐量提高)
(③④两者冲突,类似成反比)
④最大垃圾回收的暂停毫秒数
⑤控制垃圾回收线程数
响应时间优先 CMS
①UseConcMarkSweepGC(CMS): 并发(concurrent,垃圾回收在回收的同时其他的用户线程也能同时运行) 标记清除 老年代
UseParNewGC: 复制 新生代
SerialOld:(当CMS并发失败时(碎片过多造成空间不足),会退化到SerialOld,垃圾回收时间会飙升)标记整理 单线程 老年代
②初始标记时
ParallelGCThreads:并行垃圾回收线程数
ConcGCThreads:并发垃圾回收线程数
③执行CMS垃圾回收的内存占比(预留空间给其他)
④做一次新生代垃圾回收,新生代数量少了,之后扫描对象少了,能够减轻重新标记时的压力
G1
Garbage First
JDK7中正式支持
JDK9中默认使用(JDK9中废弃了CMS)
适用场景
- 同时注重吞吐量(Throughput)和低延迟(Low latency),默认的暂停目标是200ms
- 超大堆内存,会将堆划分为多个大小相等的Region
- 整体上是 标记+整理算法,两个区域之间是复制算法
相关JVM参数
-XX:+UseG1GC
-XX:G1HeapRegionSize=size
-XX:MaxGCPauseMillis=time
G1垃圾回收阶段
1)新生代收集Young Collection
整个UI内存被划分成一个个大小相同的区域,每个区域都可以独立作为 伊甸园,幸存区,老年代
2)新生代收集+并发标记 Young Collection + CM(Concurrent Mark)
- 在 Young GC 时会进行GC Root 初始标记
- 老年代占用堆空间比例达到阈值时,进行并发标记(不会STW),由JVM参数决定(-XX:InitiatingHeapOccupancyPercent=percent(默认45%))
3)混合收集 Mixed Collection
会对 E,S,O 进行全面垃圾回收
-
最终标记(Remark)会STW(标记在并发标记中遗漏的)
-
拷贝存活(Evacuate)会STW
-XX:MaxGCPauseMillis=ms
G1 会根据最大暂停时间,有选择的进行回收老年代(堆内存太大,老年代回收时间过长,所以只选择性回收垃圾多的区域)
Full GC 问题
-
Serial GC 串行
新生代内存不足发生的垃圾收集 - minor gc
老年代内存不足发生的垃圾收集 - full gc
-
ParallelGC 并行
新生代内存不足发生的垃圾收集 - minor gc
老年代内存不足发生的垃圾收集 - full gc
-
CMS
新生代内存不足发生的垃圾收集 - minor gc
老年代内存不足,并发失败后,退化到串行,才会有Full GC
-
G1
新生代内存不足发生的垃圾收集 - minor gc
老年代内存不足,在并发标记和混合收集时,如果 回收垃圾速度 跟不上 新垃圾产生的速度,并发收集失败,退化至串行收集,才会发生Full GC
Young Collection 跨代引用
新生代回收的跨代引用(老年代 引用 新生代 )问题
因为大部分 根对象 来源于 老年代,由于老年代存活的对象多,查找麻烦,所以将老年代再次细分成 CardTable卡表 (每个card 512B)
为了查到 新生代 的 根对象 (来源于老年代)
(例如:集合插入新的对象,集合在老年代,新的对象在新生代)
标记脏卡:将 有老年代引用新生代 的 卡 标记为脏卡
能够使在遍历根对象时,只需要遍历脏卡对象,减少搜索范围,大大提升效率
被引用的新生代中会有 Remembered Set 会记录 外部 对其的引用(记录对其引用的脏卡,就可以直接遍历其脏卡中的GC Root,提高效率)
concurrent refinement threads 更新 Remembered Set
在 引用 变更 时通过post-write barrier + dirty card queue,将脏卡的更新 信息保存在此queue中之后再由一条线程去更新
Remark 重新标记阶段
以下是并发标记中的处理阶段 :
黑色:已经处理完成的,结束时会存活
灰色:正在处理当中的,因为被强引用,最终会变为黑色存活
白色:未处理的,因为被强引用,最终也会变成黑色存活
由于该标记的环境是 并发 的
所以,Remark 重新标记 就是解决这个问题
加入 写屏障:当C对象的 引用 发生改变时,写屏障代码就会执行,将C加入到一个队列中,并变为灰色
在并发标记阶段结束后,再去判断该队列中的对象是否有被引用,再处理
JDK 8u20 字符串去重
优点:节省大量内存
缺点:略微多占用了cpu时间,新生代回收时间略微增加
-XX:+UseStringDeduplication
-
将所有新分配的字符串放入一个队列
-
当新生代回收时,G1并发检查是否有字符串重复
-
如果他们值一样,让它们引用同一个char[]
-
注意:与String.intern()不一样
String.intern()关注的是字符串对象
而字符串去重关注的是char[]
在JVM内部,使用了不同的字符串表
JDK 8u40 并发标记类卸载
- 所有对象都经过并发标记后,就能知道哪些类不再被使用,当一个类加载器的所有类都不再使用,则卸载它加载的所有类(只有自定义的会被卸载)
-XX:+ClassUnloadingWithConcurrentMark 默认启动
JDK 8u60 回收巨型对象
- 一个对象大于region的一半时,称之为巨型对象
- G1 不会对巨型对象进行拷贝
- 回收时被优先考虑
- G1会跟踪老年代所有incoming引用,这样老年代incoming引用为0的巨型对象就可以在新生代垃圾回收时处理掉(例如下图中①②引用没有后,该H就可以在新生代垃圾回收时处理)
JDK 9 并发标记起始时间的调整
-
并发标记 必须在堆空间沾满前完成,否则退化为 FullGC
-
JDK 9 之前需要使用 -XX:InitiatingHeapOccupancyPercent
-
JDK 9 可以动态调整
-XX:InitiatingHeapOccupancyPercent 用来设置初始值
进行数据采样并动态调整
总会添加一个安全的空档空间
g引用为0的巨型对象就可以在新生代垃圾回收时处理掉(例如下图中①②引用没有后,该H就可以在新生代垃圾回收时处理)
[外链图片转存中…(img-UQMBnaQ0-1639831561616)]
JDK 9 并发标记起始时间的调整
-
并发标记 必须在堆空间沾满前完成,否则退化为 FullGC
-
JDK 9 之前需要使用 -XX:InitiatingHeapOccupancyPercent
-
JDK 9 可以动态调整
-XX:InitiatingHeapOccupancyPercent 用来设置初始值
进行数据采样并动态调整
总会添加一个安全的空档空间