- 垃圾回收器调用垃圾回收线程
jdk7 update 4 之前
新生代
算法
- 复制算法,会
stop the world
一次
垃圾回收器
Serial
单线程ParNew
多线程- 可以通过
-XX:+UseParNewGC
启动 ParNew- 不过默认是server配置,即默认是parNew垃圾回收器
- 可以通过
-XX:ParallelGCThreads
调整 ParNew 线程数量- 默认是和cpu核心线程保持一致
老年代
算法
CMS 在执行一次垃圾回收的过程分为 4 个阶段
- 初始标记
- 并发标记
- 重新标记
- 并发清理
初始标记
- 让系统的工作线程全部停止,即
stop the world
- 标记所有 GC Roots直接引用的对象(比如类变量、方法局部变量)
-
并发标记
工作线程随意创建新对象
- 垃圾回收线程对 GC Roots 的直接关联对象遍历整个对象图,对包括初始标记中和当前新创建的对象进行 GC Roots 追踪,通过每个对象的引用地址查看哪些对象是存活的(专栏),标记存活对象和不可达对象
最耗时,但是回收线程和工作线程一起并发运行,不会对系统运行造成影响
重新标记
再次进入
stop the world
阶段- 重新标记 并发标记阶段 中新创建的对象,或者取消标记那些失去引用变成垃圾的对象,即修复并发标记期间,由于用户进程继续运作导致的标记产生变动的那部分对象的标记记录(即进行增量更新)
- 增量更新: 遍历对象图时,如果由于用户线程导致黑色对象引向了白色对象,那么会记录该变化,在并发扫描后,重新对此类黑色对象进行遍历,相当于黑色对象退化为灰色对象
- 增量更新: 遍历对象图时,如果由于用户线程导致黑色对象引向了白色对象,那么会记录该变化,在并发扫描后,重新对此类黑色对象进行遍历,相当于黑色对象退化为灰色对象
-
并发清理
工作线程正常运行,回收线程并发清除未被标记的对象
- 很耗时,但是也是并发运行,不会对系统运行操作影响
明确下 gc roots
- gc roots 可能会遍历到新生代和老年代,但是 cms 并发清除只会回收老年代
- 是两回事
cms 很耗性能
消耗 cpu 资源
在并发清理的时候,系统触发了 minor gc,在老年代新加入了一些对象,而这些对象很快没被引用,称为 浮动垃圾
在因为老年代内存占用达到一定比例,会自动执行 cms 垃圾回收
有一个参数
-XX:+UseCMSCompactAtFullCollection
默认打开- 即默认 full gc 后,会再次 STW,进行碎片整理
- 即默认 full gc 后,会再次 STW,进行碎片整理
-XX:CMSFullGCsBeforeCompaction
: 设置几次 full gc 后才进行碎片整理
优化方案
- survivor 空间要足够,避免因为动态年龄原因导致直接进入老年代
- 新生代的固定年龄参数,甚至可以设置小一点,让他们早点进老年代,避免在 survivor 区占位置
- 大对象直接进入老年代,对应的参数可以设置为 1mb
- 指定垃圾回收器
老年代
- 指定垃圾回收器
- 想下会发生 Concurrent Mode Failure 么
- 想下需要修改
-XX:+UseCMSCompactAtFullCollection
(full gc 后来一个 stw 进行碎片整理) 么 - 想下需要修改
-XX:CMSFullGCsBeforeCompaction
(几次 full gc 后才进行 stw 进行碎片整理) 么- 一般热销的时候,只要 full gc 能在活动之后很长一段时间才开始,那么 3和4的 stw 是无关紧要的,即不用改
- 一般热销的时候,只要 full gc 能在活动之后很长一段时间才开始,那么 3和4的 stw 是无关紧要的,即不用改
复习下 full gc 时机
- 第一是老年代可用内存小于新生代全部对象的大小
- jdk6 之前,如果没开启空间担保参数,会直接触发Full GC,所以一般空间担保参数都会打开;
- jdk6 之后,直接 full gc
- 第二是老年代可用内存小于历次新生代GC后进入老年代的平均对象大小,此时会提前Full GC;
- jdk6 之前,本步骤是判断空间担保参数为true的后续步骤
- jdk6 之后,并非后续步骤
- 第三是新生代Minor GC后的存活对象大于Survivor,那么就会进入老年代,此时老年代内存不足。
- 第四
-XX:CMSInitiatingOccupancyFaction
参数,(92%) 如果老年代可用内存大于历次新生代GC后进入老年代的对象平均大小,但是老年代已经使用的内存空间超过了这个参数指定的比例,也会自动触发Full GC - 第五补充 大对象或者动态年龄进入老年代,老年代空间不足,也会执行 full gc
G1
- g1 垃圾回收器将堆内存拆分为多个大小相等的 Region, 维护一个回收价值列表,建立一个可预测的时间停顿模型
- 年轻代和老年代是 逻辑上 的概念
- 即一部分 Region 是年轻代,一些是老年代
- 同一个 Region 属于哪一代并不是永久性的,是 g1 自动控制
- 可以设置一个垃圾回收的预期停顿时间(
一次stw时间?不是)-XX:MaxGCPauseMills
默认 200 ms- g1 会追踪到每个 Region里的回收价值
- 回收的对象大小和回收所需预估时间
- 在后台维护一个优先级列表
- 在垃圾回收的时候,尽量将垃圾回收时间控制在设置的预期停顿时间内,并且回收尽可能多的垃圾对象
- 即 选择最少回收时间和最多回收对象的 Region 进行垃圾回收
- g1 会追踪到每个 Region里的回收价值
Region 数量和大小
新生代占比 Region 数量
初始占比
- 默认是 5%,即如果有 2048 个 Region,那么新生代有 100 个左右
可以通过
-XX:G1NewSizePercent
指定新生代占比最大占比
在运行时, jvm 会不断为新生代增加 Region
- 默认新生代占有的 Region 不会超过 60%
- 可以通过
-XX:G1MaxNewSizePercent
来指定新生代最大占比
新生代占比 Region 的比例是动态的
- 如果不够会为新生代添加 Region
- 同理垃圾回收的时候 Region 会减少
Eden 和 Survivor 还是存在的
- 依然可以通过
-XX:SurvivorRatio=8
来指定 Eden 和 Survivor 比例- 上面会让占据新生代的 Region 中 80% 属于 Eden,20% 属于 Survivor
- jvm会动态增加减少新生代所占有的 Region, Eden 和 Survivor 各自的数量也会变化,比例也会动态变化
- 垃圾回收后,Region 会清空,但是不会直接让其不从属于所属代
新生代回收时机
- 和原来的流程差不多
- 新创建的对象优先在 Eden 区存放,所以 Eden 区占有的 Region 会不断增加,所以新生代占有的 Region 数量也会增加
- 为了维持和 Survivor 的比例,所以 Survivor 区占有的 Region 也会增加
- 新生代占有的 Region 数量达到阈值,即默认 60% 后,会触发 minor gc (算法还是复制算法),进行 stop the world
- 将 Eden 对应的 Region 中的存活对象放入 S1 对应的 Region 中,然后回收掉 Eden 对应的 Region 中的垃圾对象
- 由于可以通过
-XX:MaxGCPauseMills
设置目标 GC 停顿时间,默认 200 ms,那么 G1 会对每个 Region 回收价值追踪,追踪回收他需要多少时间,可以回收多少对象,从而回收一部分的 Region ( - 也就是即使某个 Region 满了,但是可能的回收时间太久,从而不会被回收
- 由于可以通过
对象什么时候进入老年代
- 老年代默认最多拥有 40% 的 Region 数量
- 对象躲过多次 minor gc ,达到一定年龄
-XX:MaxTenuringThreshold
设置,默认 15
- 动态年龄判定规则,发现某次 minor gc 后,存活对象会超过 Survivor 的 50%
- 如果年龄为1岁、2岁的对象的大小总数超过了 Survivor 的 50%,那么 包括2岁在内的及其以上的对象全部进入老年代
- 大对象会存储在专门的 大对象 Region (Humongous区域) ,而不是老年代了
- 当一个大对象超过一个 Region 大小的 50 %,会放入该大对象 Region
- 如果该大对象的大小超过一个 Region 的大小,那么会存放在 N 个连续的 Humongous 区域
- 位置位于那些暂时不属于新生代或者老年代的 Region,比如一开始或者垃圾回收后清空的 Region
- 在垃圾回收的时候,会顺便带上该大对象 Region 进行垃圾回收
- 当一个大对象超过一个 Region 大小的 50 %,会放入该大对象 Region
什么时候触发新生代+老年代混合垃圾回收 (Mixed GC)
- 理想情况下,默认新生代会占据堆内存 60%, 老年代占据 40%
- 但是老年代可能会多于 40%
- 有个参数
-XX:InitiatingHeapOccupancyPercent
默认 45%- 如果老年代占据了堆内存的的 45% 会触发混合垃圾回收
混合回收垃圾过程
- 初始标记,会进行 stw,标记 GC Roots 能直接引用的对象
- 比如方法区中类静态变量和栈帧中的局部变量中的 GC Roots 进行扫描,标记他们直接引用的对象
- 借助 minor gc 同步完成
- 并发标记,进行 GC Roots 追踪:从 GC Roots 开始对堆中的对象进行可达性分析,递归扫描整个堆中的对象图
- 同时 jvm 会对并发标记阶段中对象做出的一些修改(如新创建对象,或者某些对象变成了垃圾对象)进行记录
- 最终标记,会进行 stw,将根据并发标记阶段的对对象的修改记录(2.a阶段)进行最终标记
- 混合回收
- 计算老年代各个 Region 存活对象数量、存活对象占比,以及执行垃圾回收的预期性能和效率
- 进行 stw
- 选择部分 Region 进行回收(基于复制算法),需要对垃圾回收的停顿时间进行控制 (默认 200ms)
- 要回收的 Region 还有前提要求: 该 Region 中存活对象要低于 85% 才能进行回收 (可以通过
-XX:G1MixedGCLiveThresholdPercent
进行设置,默认 85%) - 选择任意多个 Region 构成回收集,复制待回收 Region 的存活对象到空的 Region,然后清除整个旧的 Region, 进入 stw ,由多条收集器线程并发完成
- 混合回收会执行多次,可以使用
-XX:G1MixedGCCountTarget
进行设置,默认 8 次-XX:MaxGCPauseMills
指定垃圾回收系统停顿时间,是 stw 时间,也就是有可能导致 200ms * 8 的停顿?- 8次的总共时间要在
-XX:MaxGCPauseMills
内,即默认 200ms 内
- 该收阶段,如果回收的 Region 达到堆内存的 5% (
-XX:G1HeapWastePercent
的默认值),就会停止混合回收,本次混合回收结束
- 要回收的 Region 还有前提要求: 该 Region 中存活对象要低于 85% 才能进行回收 (可以通过
混合回收的目标
- 老年代、新生代、大对象
失败导致的 full gc
- Mixed gc 时,年轻代和老年代都是基于复制算法进行回收,会需要将待回收的 Region 中的存活对象拷贝到别的 Region 中
- 如果拷贝的时候发现没有空闲 Region 来承载存活对象,会触发失败
- 会 stw,然后采用 Serial Old 垃圾回收器进行标记、清理和压缩整理,空闲一批 Region
注意事项
- ygc 失败 -> mgc, mgc失败 -> fgc (Sential Old)
- 每次 ygc 也会判断老年代是否足够/suvivor是否足够
- 参考之前的