一:理论

image.png

分代收集理论

分治思想,依据特定选定不同的收集器,
一种设计思想

复制算法

新生代使用
就是一半空闲,将另一半存活的复制过去
太浪费空间了
image.png

标记清除算法

第一步:标记
第二步:清除
问题:1:效率问题,标记太多,效率太低,2:空间碎片问题
image.png

标记整理算法

针对老年代,
第一步:标记
第二补:整理
解决空间碎片问题

image.png

二:几种收集器

回收算法的实现

image.png

1:Serial收集器

串行,单线程
-XX:+UseSerialGC 新生代 复制算法
-XX:+UseSerialOldGC 老年代 整理算法 1.5以前与Pararllel搭配,CMS一定条件下的备胎
简单高效,
image.png

2:Parallel Scavenge收集器

Serial的多线程版本,默认收集线程数=CPU核数
侧重在吞吐量,高效利用CPU, 吞吐量:用户代码时间/CPU总耗时
JDK8默认
-XX:ParallelGCThreads 指定收集线程
-XX:+UseParallelGC 新生代 复制算法
-XX:+UseParallelOldGC 老年代 标记整理
image.png

3:ParNew

与Parallel类似,与CMS配合
-XX:+UseParNewGC
新生代 复制
老年代 标记整理
Server模式下的首选,
image.png

4:CMS

侧重STW时间,用户体验上
用户线程与收集线程可以同时工作
-XX:UseConMarkSweepGC 老年代
大致步骤

  1. 初始标记:STW, 标记gc-root直接应用的对象,很快
  2. 并发标记:从gc-root直接关联的对象遍历整个对象图,耗时久,不停顿用户线程,标记对象状态可能发生变化
  3. 重写标记:修正发生标记状态发生变化的对象,STW,会比较长,采用三色标记重新标记
  4. 并发清理:开启用户线程,GC线程对未标记区域清扫,新增对象标记黑色不处理
  5. 并发重置:重置本地GC过程的标记数据

优点:并发,低停顿
缺点:cpu敏感,会抢服务资源
浮动垃圾,下次GC会清理
空间碎片问题
-XX:+UseCMSCompactAtFullCollection标记清理后可以整理
会触发full gc 转变成Serial Old
image.png
CMS的相关核心参数

  1. -XX:+UseConcMarkSweepGC:启用cms
  2. -XX:ConcGCThreads:并发的GC线程数
  3. -XX:+UseCMSCompactAtFullCollection:FullGC之后做压缩整理(减少碎片)
  4. -XX:CMSFullGCsBeforeCompaction:多少次FullGC之后压缩一次,默认是0,代表每次FullGC后都会压缩一次
  5. -XX:CMSInitiatingOccupancyFraction: 当老年代使用达到该比例时会触发FullGC(默认是92,这是百分比)
  6. -XX:+UseCMSInitiatingOccupancyOnly:只使用设定的回收阈值(-XX:CMSInitiatingOccupancyFraction设定的值),如果不指定,JVM仅在第一次使用设定值,后续则会自动调整
  7. -XX:+CMSScavengeBeforeRemark:在CMS GC前启动一次minor gc,降低CMS GC标记阶段(也会对年轻代一起做标记,如果在minor gc就干掉了很多对垃圾对象,标记阶段就会减少一些标记时间)时的开销,一般CMS的GC耗时 80%都在标记阶段
  8. -XX:+CMSParallellnitialMarkEnabled:表示在初始标记的时候多线程执行,缩短STW
  9. -XX:+CMSParallelRemarkEnabled:在重新标记的时候多线程执行,缩短STW;

    三:JVM调优示例

  10. 每秒多少次请求

  11. 每次请求涉及多少对象
  12. 估算每秒大概多大对象产生
  13. 根据每秒大致多少对象产生,与设置对应参数

image.png
对于8G内存,一般给JVM4G, 一般参数如下
-Xms3072M -Xmx3072M -Xss1M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:SurvivorRatio=8

1:优化动态年龄判断的问题
让s1区大于,每次minor gc的两倍,这样就不会有太多太年轻的对象直接进入老年代
2:估算minor gc时间,判断大概时间,调低年龄设置
让老对象尽早进入老年代
3:估算对象大小,比如1M,直接进入老年代
优化参数如下:
-Xms3072M -Xmx3072M -Xmn2048M -Xss1M -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M
-XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=5 -XX:PretenureSizeThreshold=1M

JDK8默认收集器是: -XX:UseParllelGC 和-XX:UseParallelOldGC ,内存大于4G时,推荐ParNew+CMS
-XX:UseParNewGC -XX:UseConMarkSweepGC

5:考虑老年代触发fullGC ,已经老年代的空间分配担保机制
6:考虑CMS fullGC 发生后,如果新生代对象过多,触发Serial Old 的问题
优化考虑CMS 参数如下

  1. -Xms3072M -Xmx3072M -Xmn2048M -Xss1M -XX:MetaspaceSize=384M -XX:MaxMetaspaceSize=384M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=5 -XX:PretenureSizeThreshold=1M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=92 -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=3

四:三色标记(CMS中使用)

应对CMS 并发标记过程中的,多标,漏标问题
gc root 可达的对象,按照是否访问过标记为三种颜色

  • 黑色:它的所有引用都扫描过。指向黑色的对象也无需重新扫描,黑色不会直接指向白色
  • 灰色:它被扫过,但至少还有一个引用没被扫描
  • 白色:未被垃圾收集器访问过,可达性分析前,都是白色,分析后仍是白色,就是不可达

    多标 浮动垃圾

    并发标记过程中
    本轮GC不会回收,下个周期清理
    并发标记,并发清理阶段产生的孤立新对象,直接标位黑色,不清理,也算一种浮动垃圾
    image.png

    漏标- 读写屏障

    解决漏标的方法:
    1:增量更新 Incremental Update
    2:原始快照 Snapshot At The Beginning
    增量更新:黑色指向白色后,就变灰了
    原始快照:灰色删除与白色的引用时,会记录下来,在从灰扫到白,把白标为黑,防止此次GC被删
    均是通过写屏障实现

写屏障

给对象成员变量赋值前后,加入处理

  1. /**
  2. * @param field 某对象的成员变量,如 a.b.d
  3. * @param new_value 新值,如 null
  4. */
  5. void oop_field_store(oop* field, oop new_value) {
  6. *field = new_value; // 赋值操作
  7. }

写屏障就是前后都做动作

  1. void oop_field_store(oop* field, oop new_value) {
  2. pre_write_barrier(field); // 写屏障-写前操作
  3. *field = new_value;
  4. post_write_barrier(field, value); // 写屏障-写后操作
  5. }

写屏障,记录成员变量原来的引用对象

  1. void pre_write_barrier(oop* field) {
  2. oop old_value = *field; // 获取旧值
  3. remark_set.add(old_value); // 记录原来的引用对象
  4. }

灰色集合一般通过栈,队列、缓存日志实现
采用广度、深度遍历
hotSoptVM 对并发标记 漏标的方案:

  • CMS:写屏障+增量更新
  • G1,Shennandoah:写屏障+SATB
  • ZGC:读屏障

我不太理解,我再品品

  1. 为什么G1SATBCMS用增量更新?
  2. 我的理解:SATB相对增量更新效率会高(当然SATB可能造成更多的浮动垃圾),因为不需要在重新标记阶段再次深度扫描被删除引用对象,而CMS对增量引用的根对象会做深度扫描,G1因为很多对象都位于不同的regionCMS就一块老年代区域,重新深度扫描对象的话G1的代价会比CMS高,所以G1选择SATB不深度扫描对象,只是简单标记,等到下一轮GC再深度扫描。

记忆集与卡表

记忆集:新生代里,记录从非收集区 到 收集区 的指针集合,避免吧整个老年代加入GCroot
卡表:记忆集的一种实现;
记录跨区引用的集合;
卡页:对应标识的内存区域一块特定大学的内存块;
一个卡页中可包含多个对象,只要有一个对象的字段存在跨代指针,其对应的卡表的元素标识就变成 1,表示该元素变脏,否则为0,GC时,只要筛选本收集区的卡表中变脏的元素加入GCRoots里