定义:
取代了CMS垃圾回收器。和CMS一样时并发的。
适用场景:
物理上分区,逻辑上分代。
相关JVM参数:
三个回收阶段,第一个是新生代回收,第二个是新生代+CM,第三个是混合回收。
当老年代内存超过阈值,会在新生代垃圾回收时进行并发标记。然后混合收集阶段会对新生代和老年代都进行收集。
Mixed GC:收集整个新生代和部分老年代的垃圾收集,目前只有G1有这种行为
新生代内存布局如上。
G1会把内存划分成大小相同的区域,每个区域都可以独立作为伊甸园,幸存区的,老年代。
白色的都是空闲区域,新创建的对象都在eden区,图上的E。
当Eden区被占满就会触发垃圾回收并STW(砸瓦鲁多)。
垃圾回收时把对象通过copy算法复制进幸存区。
当幸存区对象过多或是对象年龄达到阈值就又会触发新生代垃圾回收,幸存区对象部分晋升到老年代,不够年龄的对象会拷贝到另一个幸存区。
会对 E、S、o 进行全面垃圾回收
-XX:MaxGCPauseMillis=ms
混合收集时新生代垃圾回收和之前一样,该复制的赋值,该晋升的晋升。
老年代垃圾回收会把根据设定参数最大暂停时间进行有选择的垃圾回收: 那些内存超过阈值的老年代也会复制到别的老年代区域。堆内存较大,老年代垃圾回收因为复制时间较长,会超过最大暂停时间,所以G1会挑出回收价值最高部分(释放内存多)的区域。
SerialGC(串行)
ParallelGC(并行)
CMS (并发)
G1 (并发)
四个垃圾收集器在新生代内存不足时都会触发minor gc。
G1在垃圾回收速度跟不上垃圾产生的速度,并发收集失败,退化为多线程full GC。CMS是单线程full GC。
CMS和G1在回收速度高于产生速度时不会有Full GC。
在新生代垃圾回收查找根对象时,部分对象来自老年代。老年代存活对象非常多,要遍历查找的话效率低下,因此采用卡表的技术,将老年代区域再次细分。如果老年代其中有个对象应用了新生代对象,对应的卡标记为脏卡。
好处:找GC Root不需要遍历整个老年代,利于提高效率
新生代中有Remembered Set记录外部对它的引用,记录有哪些脏卡区域。
引用变更会由单独线程进行工作
在CMS中有并发标记阶段和重新标记阶段。
上图是并发标记阶段时对象的处理状态,黑色是已经处理完成,会被保留的,灰色是正在处理,白色的是尚未处理。
有引用的灰色和白色都会存活,没有引用的白色会被当垃圾回收。
在并发标记阶段处理时,用户线程改变了对c的引用,导致处理C时会将其标记成白色,结束后被当成垃圾回收。
别的情况:
c被处理完后,用户线程又改变了c的引用地址,但是A已经是黑色,不会再顺着A处理C. 所以并发标记结束后C依旧被认为是白色的,这是不对的。
这里要对对象的应用做进一步检查就是Remark(重新标记阶段)。
做法:
当对象引用发生改变时,写屏障代码将会执行。如上所示c被A引用后会将C放入一个队列并置为灰色。进入重新标记阶段会STW,然后将队列中对象进行检查,发现有强引用就会将其变成黑色。
写屏障技术,加入satb_mark_queue队列
jdk8中底层使用char数组存储每个String对象,如果有大量new String动作会导致内存占用提高,然后就要进行去重。除了使用之前的intern()方法进行去重。G1有了别的去重方法。
默认开启这个去重方法
条件如上:类的实例都被回收了,某个类加载器的所有类都不再使用。
jdk的类加载器一般不会被卸载,jdk的一般都是启动类加载器,扩展类加载器,应用的程序类加载器始终存在。对于自定义的类加载有卸载需求。
在G1的区域有四种区(伊甸园,幸存区,老年代区),还有这里的巨型对象区。
分布如下,可能占用多个region.
当如下图所示,某个的巨型对象没有被老年代引用时就可以回收了,目的就是尽早回收巨型对象。
G1 垃圾回收器也有一个full GC问题,垃圾回收速度跟不上垃圾产生速度也会退化为full GC。
避免方法:
提前让垃圾回收开始,让混合收集提前开始,减少full GC发生几率。
jdk9之前要设定老年内存占比阈值。
让空挡空间足够大,能够容纳并发阶段产生的浮动垃圾,避免full GC发生。???????
这句话有点问题??/
现在最新都是ZCG了.....还好大多数企业还是jdk8或者jdk11
JDK 11 Documentation - Books
使用该命令看到了虚拟机GC相关参数
jmap jconsle MAT等工具
垃圾回收调优近似众多调优的一个方向。GC的影响明显而已。
多个别的领域如下
cms g1 zgc低延迟,paralle1gc吞吐量
选择不同的垃圾回收器
GC频繁就要考虑是否代码有问题。
在resultSet里设置mysql查询会导致大量数据加载到堆内存,导致gc频繁,甚至导致内存溢出,应该加以限制。避免无用数据都放在java内存里
缓存实现可以使用如Redis等第三方缓存。
上面是排除所有代码问题,下面才是内存调优。
建议都要从新生代开始。
每个线程在伊甸园中都会有一块私有区域叫做TLAB。
new一个对象时会先检查在tlab缓冲区中有没有可用内存,有就优先在该区域进行分配。
对象分配也有线程安全问题,一段线程在分配某段内存时不能让别的线程也来用,会分配混乱。因此内存分配也要做并发安全保护(jvm实现)。
要减少线程在内存分配时的并发冲突就是用tlab,这样多个线程进行内存分配时也会有线程干扰。
正是因为大部分对选哪个用过即死才有Minor GC的时间远远低于FullGC
如何对新生代进行内存调优:
加大新生代内存????
上面参数用于设置新生代的初始和最大大小,设置太小导致执行大量小垃圾回收,设置太大,导致引发full GC,一次回收花费时间更多了。建议设置大于堆的四分之一,小于堆的二分之一 。
吞吐量 = 程序运行时间 / (程序运行时间 + 垃圾回收时间)
随着新生代空间变大,吞吐量会开始变小。因为后边垃圾回收时间也变大了。
总原则是将新生代空间调的尽可能大。
标记整理会产生很多碎片,整理的过程有很多需要判断碎片大小,移动垃圾位置。
原因之一:新生代垃圾的回收都是复制算法,分成标记和复制阶段,复制阶段花费时间更多,涉及内存块移动。 而新生代对象通常只有的少量对象可以存活。
理想内存
幸存区较小会由jvm动态调整晋升阈值,将对象提前晋升到老年代。
但是如果存活时间短的对象晋升到了老年代需要等到full GC才可以进行垃圾回收。
第一个是调整最大晋升阈值的参数
第二个是输出幸存区中的存活对象的年龄和占用空间的累计总和。
该参数是控制老年代在空间占用达到多少时进行CMS进行垃圾回收。
第一个就是增加新生代内存
案例2
单次暂停时间长需要分析是那一部分的时间长,可以查看GC日志。
CMS的阶段如下,查看日志可以看见每一阶段耗费的时间。
cms在重新标记阶段使用了写屏障+增量复制算法来提高效率的。
重新标记阶段耗费1~2s的话是因为重新标记阶段要扫描整个堆内存,还要找引用。
在重新标记之前可以先对新生代做一次垃圾回收,减少垃圾数量,使用下面的参数开启该功能。
-XX:+CMSScavengeBeforeRemark
案例三
CMS有可能由于空间不足导致并发失败或者空间碎片过大都会引发full GC。
如果GC日志里面没有并发失败和碎片过多的错误提示说明老年代空间充裕。
jdk1.7用的永久代作为方法区实现,jdk1.8用的元空间。
元空间用的操作系统的容量一般比较充裕,不会有元空间空间不足。
1.7以前永久代空间设小了就会触发整个区的fullGC。
定位到是永久代空间不足导致fullGC,所以应该增大永久代空间。