JVM垃圾收集器
一.G1垃圾收集器
1.G1采用内存分区(Region)的思路,将内存划分为一个个相等大小的内存分区,回收时则以分区为单位进行回收,存活的对象复制到另一个空闲分区中。由于都是以相等大小的分区为单位进行操作,因此G1天然就是一种压缩方案(局部压缩),G1收集器(或者垃圾优先收集器)的设计初衷是为了尽量缩短处理超大堆(大于4GB)时产生的停顿。相对于CMS的优势而言是内存碎片的产生率大大降低。
2.G1虽然也是分代收集器,但整个内存分区不存在物理上的年轻代与老年代的区别,也不需要完全独立的survivor(to space)堆做复制准备。G1只有逻辑上的分代概念,或者说每个分区都可能随G1的运行在不同代之间前后切换;
1.分区
分区 Region
G1采用了分区(Region)的思路,将整个堆空间分成若干个大小相等的内存区域,每次分配对象空间将逐段地使用内存。因此,在堆的使用上,G1并不要求对象的存储一定是物理上连续的,只要逻辑上连续即可;每个分区也不会确定地为某个代服务,可以按需在年轻代和老年代之间切换。启动时可以通过参数-XX:G1HeapRegionSize=n可指定分区大小(1MB~32MB,且必须是2的幂),默认将整堆划分为2048个分区。默认年轻代堆的占比是5%,当年轻代的对象占有了5%时,并不会立刻出发GC,而是与我们自己设定的停顿时间进行比较,在运行过程中,jvm会不停的给年轻代增加更多的region,但是新生代的占比不会超过60%。年轻代中Eden和Survivor0和Survivor1对应的占比默认8:1:1。
2.巨型对象
巨型对象 Humongous Region
一个大小达到甚至超过分区大小一半的对象称为巨型对象(Humongous Object)。当线程为巨型分配空间时,不能简单在TLAB进行分配,因为巨型对象的移动成本很高,而且有可能一个分区不能容纳巨型对象。因此,巨型对象会直接在老年代分配,所占用的连续空间称为巨型分区(Humongous Region)。G1内部做了一个优化,一旦发现没有引用指向巨型对象,则可直接在年轻代收集周期中被回收。
巨型对象会独占一个、或多个连续分区,其中第一个分区被标记为开始巨型(StartsHumongous),相邻连续分区被标记为连续巨型(ContinuesHumongous)。由于无法享受Lab带来的优化,并且确定一片连续的内存空间需要扫描整堆,因此确定巨型对象开始位置的成本非常高,如果可以,应用程序应避免生成巨型对象。
G1垃圾收集器之所以将大对象放置到Humongous区,是为了让老年代区域能更多地放置一些小对象。
1.初始标记
标记一下GC Roots能直接关联到的对象,要停止线程但耗时很短。
2.并发标记
从GC Roots开始对堆中的对象进行可达性分析,找到存活的对象但耗时较长,可与用户线程并发执行。
3.最终标记
为了修正在并发标记阶段,因用户线程继续运行,而导致的标记变动的那部分对象,但要停止线程。
4.筛选回收
我们可以自己指定回收时间,对各个region的回收价值和成本进行排序,采用复制算法,回收时则以分区为单位进行回收,存活的对象复制到另一个空闲分区中,故不会产生内存碎片。
3.G1垃圾收集器参数
-XX:+UseG1GC 采用 Garbage First (G1) 收集器
-XX:InitiatingHeapOccupancyPercent=n
设置触发标记周期老年代堆内存占 Java 堆阈值。默认占用率是整个 Java 堆的 45%。默认值 45.,触发MixedGC。
4.G1垃圾收集分类
YoungGC
YoungGC并不是说年轻代的Eden区放满了就触发,而是会预计Eden区回收要多长时间去和自己设定的停止参数比较,如果差得远那么就继续申请年轻代的region,继续给新对象存放,直到某一次Eden区再次放满G1计算回收时间与设定接近,那么就会触发YoungGC。
MixedGC
当老年代的堆占有率达到参数设定的值(默认45%)时才会触发,回收所有young区域和根据期望的GC停顿时间来确定old区垃圾回收的优先顺序以及大对象区,正常情况下G1的垃圾收集先是做MixedGC,主要使用复制算法,需要把region中存活的对象拷贝到别的region中,拷贝过程如果没有足够的region去使用,就会触发一次Full GC
Full GC
停止系统程序,然后使用单线程进行标记、清理,而后空闲出来的region来供下一次MixedGC来使用,过程十分耗时。
5.G1适合的场景
1.50%以上的堆被存活对象占有
2.对象分配和晋升的速度变化非常大
3.垃圾回收的时间特别长,超过1秒
4.8GB以上的堆内存
5.停顿时间500ms以内
创作时间:2019年11月21日20:08:15于天目酒店