目录结构
1.G1垃圾回收器概述
2.设定内存大小
3.新生代垃圾回收
4.老年代垃圾回收
5.大对象回收分配策略
6. 混合垃圾回收 (Mixed-GC)
7. 总结
Parnew + CMS 的痛点: 无论是新生代还是老年代的垃圾回收,都会或多或少产生"stop the world" ,对系统运行有一定影响。而且不是可控的,只能优化。
废话不多说,先上一张总图:
G1 垃圾回收器可以同时回收新生代和老年代的对象,一个人负责所有。
内存结构: 把java堆内存拆分为多个大小相等的Regin,新生代可能包含了某些块,老年代可能包含某些块。
特点: 可以设置垃圾回收的预期停顿时间
比如我们可以指定G1 在垃圾回收时候可以保证,在一小时内垃圾回收导致的"stop the world"时间不超过一分钟。
实现原理: 追踪每个 Region里的回收价值 (耗费时间,垃圾对象大小)
假设 G1通过追踪发现,1个Region中的垃圾对象由10m,回收需要1s,另一个垃圾对象20m,回收需要200ms。下一次执行垃圾回收时,回收掉上图中只需要200ms就能回收200mb垃圾的Region。
核心设计思路: 简单来说,G1可以做到让你来设定垃圾回收对系统的影响,通过把内存拆分为大量小Region,追踪每个Region可以回收对象的大小和预估时间,最后在垃圾回收的时候,尽量把垃圾回收对系统造成的影响控制在指定的时间范围内。有限时间内回收尽可能多的对象。
用 “-Xm” 和 “-Xmx” 设置堆内存的大小。
“-XX:+UseG1GC” 指定使用G1 垃圾回收器。
Jvm自动用堆大小除以2048
Jvm最多可以有 2048个Region ,Region的大小必须是2的倍数。比如1MB、2MB、4MB
比如堆大小是 4G=4096MB 除以 2048 那么每个Region大小是2MB。
一般保持这种默认的计算方式。如果通过手动方式指定: “-XX:G1HeapRegionSize”
刚开始的时候,默认新生代对堆内存的占比是5%,也就是200MB左右的内存,大概100个Region。
可以通过 “-XX:G1NewSizePercent” 设置新生代初始占比。
在系统运行中,Jvm会不停地给新生代增加更多的Region,但是最多新生代的占比不会超过 60%。
可以通过 "-XX:G1MaxNewSizePercent"改变。
新生代还是有 Eden 和 Survivor 划分的。
“-XX:SurvivorRation=8”,区分eden和 survivor比例。
比如新生代一开始有100个 Region,那么80个Region是eden,两个Survivor各占10个。
既然新生代也有 eden 和 survivor ,那么触发垃圾回收的机制都是类似的。
Jvm不断的给新生代加入更多的Region,直到新生代占据堆大小的最大比例 60%。
新生代大概占据1000个region了,还占满了对象,每个Survivor是100个大小。
这时候触发 新生代的Gc,G1就用之前的复制算法进行垃圾回收,进入"stop the world"。
eden中的存活对象放入 S1 的region中,接着回收掉eden中的对象。但是这个过程是有区别的,因为G1可以设定目标停顿时间。最多系统停顿时间。可以通过 “-XX:MaxGCPauseMills” 设定。默认值是 200ms。是每次" stop the world " 的停顿时间。
G1 就会通过之前说的,追踪每个Region回收时间,垃圾对象大小,保证Gc 停顿时间控制在指定范围内,尽可能多的回收一些对象。
老年代最多占据 40%的 Region,大概就是800个左右的Region。
对象进入老年代的条件
和之前几乎一模一样。
“-XX:MaxTenuringThreshold” 设置年龄,进入老年代。
此时会判断,比如1岁、2岁、3岁、4岁以上的对象大小综合超过了Survivor区的50%,此时4岁以上的对象会全部进入老年代,这就是动态年龄判断规则。
大对象不属于新生代和老年代,在G1里,新生代和老年代是动态的,不断变化的。比如一次垃圾回收后,eden里面的1000个region都空了,就可以让一些region存放大对象。
新、老年代在回收的时候,会顺带带着大对象Region一起回收。
条件:
G1 有一个参数(IHOP) “**-XX:InitiatingHeapoccupancyPercent **” 默认值 45%
如果老年代占据了堆内存的 45%的Region,此时就会触发 新生代+老年代+大对象 一起回收的混合回收。
过程:
ps: 在最后一个阶段"混合回收"的时候,会停止程序运行,但是为了不让程序过长时间停止,可以多次执行"混合回收"。
比如说,要回收100个Region先停止工作,执行一次混合回收,回收掉30个Region,接着恢复系统运行,然后再停止系统运行,再执行一次混合回收,回收掉30个。以此往复。有一个参数可以控制这个次数。
"-XX:G1MixedGCCountTarget " 默认值为8。
在一次混合回收中,最后一个阶段执行几次。
" -XX:G1HeapWastePercent " 默认值5% 。
也就是在全局标记结束后能够统计出所有Cset内可被回收的垃圾占整对的比例值,如果超过5%,那么就会触发之后的多轮Mixed GC,如果不超过,那么会在之后的某次Young GC中重新执行全局并发标记。可以尝试适当的调高此阈值,能够适当的降低Mixed GC的频率。
空闲的Region数量达到堆内存的5%就会停止回收,比如正常是8次回收,但是到第4次,空闲Region达到5%了,就不进行后续的混合回收了。
‘’-XX: G1MixedGCLiveThresholdPercnt" 默认值 85% 。
存活对象低于85%的 Region才可以进行回收。
如果一个对象存活对象多余85%,要把这些对象拷贝到别的Region,成本很高。
Mixed-Gc回收失败:
因为回收时,无论老年代还是年轻代执行复制算法,都要把存活对象拷贝到别的Region中,万一没有空闲的Region可以放存活对象了。就会失败。
一旦失败,立马切换为单线程垃圾回收,停止系统程序,进行标记、清理、整理。空闲出来一批Region。过程很慢。
当新生代Region大于 60 %进行新生代垃圾回收时,如果200ms回收后的Region是小于 survivor区的大小的话就不需要进入老年代了。
当老年代 Region大于 45%的时候才会去触发 mixed gc。
为什么要选择 G1 垃圾处理器?
G1在停顿时间上添加了预测机制,用户可以设置期望停顿时间,Stop The World时间相对可控。
G1的年轻代和老年代空间并不是固定的,当现有年轻代分区占满时,JVM会分配新的空闲Region加入到年轻代空间,老年代也是如此。还记得在CMS中出现的年轻代空间有大量浪费的情况吗?那将不再是问题。另一方面,由于无法保证每台机器的吞吐完全一致,不同机器的GC压力也是不一样的,该特性使得系统进程可以根据情况自行调整年轻代和老年代的空间大小,以应对不同的GC压力
G1 GC的回收过程中有内存整理,理论上不会产生内存碎片!
G1 很适合大内存的机器,因为如果是 ParNew+CMS,每次Gc都是内存快满了,此时一下子要回收对象太多,导致gc停顿时间太长。针对大内存机器,G1很适合。
如何尽量减少Mixed Gc 频率?
Mixed gc 触发条件是老年代到达 IHOP 设置的值。