首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JVM(三)

JVM(三)

作者头像
小土豆Yuki
发布2021-08-06 14:58:51
2810
发布2021-08-06 14:58:51
举报
文章被收录于专栏:洁癖是一只狗洁癖是一只狗

到底是单线程垃圾回收好,还是多线程垃圾回收好

前面我知道,新生代的serial垃圾回收使用单线程,而parnew垃圾回收使用多线程.

首先我知道系统系统启动分为服务端模式和客户端模式,我们使用-server是指就是服务端,使用-client就是客户端,他们的区别就是如果在linux服务器上就是应该是服务端,如果是windows上的客户端程序就是客户端模式,

一般服务端模式就是一些,网站系统,电商系统,app后台等等,一般都是多CPU,因此使用parnew垃圾回收可以有效的利用多核,提高性能

但是如果我们在使用单线程的垃圾回收机制,不能充分利用多CPU,性能不是很好,

那么如果是一个java程序是一个客户端,还单线程的,但是要要用多线程的parnew线程去处理,会导致一个cpu,同时处理多个线程,导致频繁的上下文切换,反而会导致系统性能变差

因此不通的场景使用使用不通的垃圾回收器即可,运行在windows上的客户端,使用serial垃圾回收器,单CPU单线程垃圾回收即可,而服务端多核CPU,使用parnew收集即可,

JVM中的CMS垃圾回收机制

public class kafka2 {
private static ReplicaManager replicaManager = new ReplicaManager();
}
public class ReplicaManager {
private ReplicaFetcher replicaFetcher = new ReplicaFetcher();
}

CMS垃圾回收分为四个阶段如下

初始化标记

并发标记

重新标价

并发清除

初始化标记,仅仅会通过replicaManager这个静态变量代表的GC Roots,去标记他直接应用的对象ReplicaManager,这就是初始化标记,并且此时系统会停止工作如下图,工作比较简单,速度很快

并发标记,就是进行GC Roots追踪,就比如,replicaFetcer实例对象之类的老年代里面的对象,看他被谁引用了,然后看局部变量的relicaFetcer被是谁引用了,看到被kafka的静态变量replicaMannager引用,因此标记为存活对象,且此时系统照样可以创建新的对象,由于这部的进行GC Roots跟踪,比较耗时,

重新标记,由于第二阶段还会产生一部分新的对象,这部分对象就会在重新标记判断是否是存活的对象,然后进行标记,此时有的对象可能已经变成了垃圾对象,有的是存活对像,同时对外停止工作,不在产生新的对象,因为只有少量对象进行标记,因此速度很快

并发清除,就是最后进行清除工作,此时把前几步标记的垃圾对象进行清除,同时系统继续工作,因此这阶段很耗时

其实看完整个CMS垃圾回收机制之后,就会发现他已经尽可能的进行优化了,因为最耗时的即使对全老年代进行GC Roots追踪,标记那些需要回收,那些需要存活,但是第二阶段和第四阶段和系统并发执行,所以这两个阶段最耗时但是对系统没有影响,第一阶段和第三阶段需要STW,但是只是简单的标记而已速度很快,对系统影响也不大。

CMS垃圾回收机制的细节问题

首先我们知道,CMS垃圾回收机制在并发标记和并发清除同时进行垃圾回收和系统工作,这样就会导致一个问题,就是CPU竞争问题,消耗CPU资源

其次,我们的CMS在并发清除阶段,系统还会产生新的对象,因此此时还会产生一些浮动垃圾,虽然他们产生了垃圾,但是此时的并不会回收他们,而是等下一次,所以我可以得出,CMS垃圾回收阶段,还有预留一部分空间,让其他对象进入老年代,

具体什么时候触发老年代的CMS垃圾回收,是可以进行配置的,如下参数,JDK1.6默认的值是92%

“-XX:CMSInitiatingOccupancyFaction”

也就是说老年代占用92%,就会自动进行垃圾回收,预留8%的空间给并发回收期间,系统把新的对象放入老年代,

最后,我要注意的是,如果系统程序放入老年代的对象内存大于可用内存空间怎么办,这个时候就会发生Concurrent Mode Failure,并发垃圾回收失败,此时就会自动使用serial old垃圾回收器替代CMS,就是直接强行把系统程序STW,重新进行GC Roots追踪,标记全部的垃圾对象,然后进行一次性清理,之后恢复系统线程,当然我们要合理的设置CMS垃圾回收比例,尽量避免Concurrent mode Failure

CMS内存碎片的问题

CMS是采用标记-清除算法进行垃圾回收的,因此会产生内存碎片,由于内存碎片会导致频繁的Full GC,

因此有一个参数设置,

-XX:+UseCMSCompactAtFullCollection

他默认是打来,就是在Full GC之后,对内存的碎片进行整理,把存活的对象挪到一起,空出连续的内存空间,避免内存碎片

当然还有一个参数

“-XX:CMSFullGCsBeforeCompaction

他们的默认是0,就是执行了多少次Full GC之后进行内存碎片的整理,默认是每次都要一次内存整理。

为什么CMS垃圾回收慢

新生代垃圾收集块的原因是存活对象少,且直接从GC Roots触发就追踪那些对象是活的就行了,这个速度是非常快的

但是老年代的CMS,存活的对象非常多,且要在零散的内存中找到垃圾对象,速度慢,并且并发清除之后,还要进行内存碎片的整理,最后万一预留的内存不够用还会发生Concurrent Mode Failure问题,使用serial old,导致STW,然后慢慢的重新回收一遍,因此老年代的CMS垃圾回收机制特别慢,

总结一下什么时候发生Full GC

  • 没有打来空间担保参数(-XX:HandlePromotionFailure),每次Minor GC前检查一次,此时如果老年代内存小于新生代对象总大小,发生Full GC,切记在JDK1.6以后就会不在使用
  • 每次Minir GC之前,检查老年代可用内存如果小于历史Minor GC升入老年代的平均对象大小,就会发生Full GC
  • 某次Minor GC存活的对象大于老年代可用空间,就会发生Full GC
  • 设置-XX:CMSInitiatingOccupancyFaction,比如92%,则老年代的内存使用超过92%,就会发生Full GC

最新的G1垃圾回收器的工作原理

我们前面知道了一般系统使用的垃圾收集器都是parNew+CMS进行回收垃圾的,但是他们组合有一个最大的痛点就是Stop the World,因此G1垃圾收集应运而生,

G1垃圾收集器可以同时收集老年代和年轻代,不需要两个垃圾收集器同时运行,他一个人完成多有的工作,

我们可以看到,G1包含了是有逻辑上的老年代和新生代,也就说region此刻是老年代,过一会可能就是新生代了,G1最大的特点就是设置一个垃圾回收的预期停顿时间,G1可以指定垃圾回收的时候,可以保证在一个小时有G1垃圾回收导致的Stop The Word时间,也就是停顿时间,不能超过1分钟

不管是我们如何优化参数,最终的目的就是减少GC,减少GC其实就是减少系统不可用的停顿时间,因此G1可以指定,一个时间内,垃圾回收导致的系统停顿时间不能超过多长时间,全有G1负责

G1是如何做到控制停顿时间的呢

如上图,在堆内存中,我们看到每个Regin有多少垃圾,每个垃圾回收需要多长时间,这个就是G1需要搞清楚的,然后G1在回收的时候,肯定是回收上面20ms就会能回收20M的垃圾

G1垃圾回收器的核心就是,通过被内存分成多个region,以及追踪region的垃圾和回收时长,尽量把垃圾回收对系统的影响控制在你指定的时间范围内,同时回收尽可能多的垃圾对象

要说明的就是region开始不属于任何人,然后接着分配给了新生代,然后垃圾回收之后,同一个region可能就会是老年代,存放长期存活的对象,实际上新生代和老年代各自的内存你区域是不停的变动的,有G1自动控制。

G1实现原理细节问题

G1会把内存最多分为2048个,每个region大小必须是2的倍数,比如堆内存是4G,那个每个region是2M,一共2048个,也可以用下面参数设置Region大小

-XX:G1HeapRegionSize

默认情况下新生代堆内存占比为5%,占据200M内存代销,大概就是100个region,可以使用- XX:G1NewSizePercent设置占比,随着系统的不断运行新生代会不断的分配对象,但是新生代最大不能超过60%,通过“- XX:G1MaxNewSizePercent,但是一旦进行垃圾回收,新生代也会减少

Region也是有新生代和老年代,而新生代也是分为Eden,Survivor,照样可以通过-XX:SurvivorRatio=8设置新生代的占比,比如有新生代100个Region,其中Eden就有80个,而survivor有20个region

新生代的垃圾回收和以前的回收基本一致,如下图

如果我的新生代随着系统的运行一致增长到60%时候,就会进行垃圾回收,此时新生代1200个region,其中1000个region已经被占满了,此时就会进行垃圾回收,

垃圾回收期间会使用复制算法回收,但是会产生Stop The World,把存活的对象放到S1,基本和以前一直,但是还是有区别的,G1可以设置目标GC停顿时间的,也就是G1执行GC最多可以让系统停顿多长时间,可以通过下面参数设置

-XX:MaxGCPauseMills 默认是200ms

最终G1,会跟踪region回收他需要多长时间,可以回收多少对象选择回收一部分的Region,保证G1停顿时间在控制执行范围内,尽可能多的回收一些对象

对象什么时候进入老年代

基本和以前说法一致

  • 在新生代躲过了很多次垃圾回收,达到一定年龄,-XX:MaxTenuringThreshold,
  • 新生代的存活对象超过Survivor的50%

但是我记得,大对象也会进入老年代,但是G1并不这样,他会有专门给大独享的region,同时我们知道新生代占60%,而老年代是40%,那么大对象放在那里呢,

首先我是如何判断大对象呢,其实就是一个对象超过一个region的50%就是大对象,但是有的对象可能很大,此时就会横跨多个region,

如何放大对象,比如新生代占据1200个region,但是每一次垃圾回收就让其中1000个region都空了,此时那么这个1000个region就可以不属于新生代,可以使用这个存放大对象,同时我们要只要到,新生代和老年代垃圾回收的收,同时回收大对象

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-08-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 洁癖是一只狗 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档