JVM垃圾回收器

根据JVM不同版本的迭代,我们从最古老的垃圾回收器谈起,分别是早期的Serial和Serial Old,中期的Parallel Scavenge及Parallel Old,过渡期的ParNew和CMS,以及现在的G1,未来的ZGC。

这些垃圾回收器并不是同一时期的产物,也不会同时出现。

Serial和Serial Old
也叫Serial Young或Serial New,是一个工作在新生代的垃圾回收器,所以一般会有一个搭档负责老年代的垃圾回收,叫Serial Old,这种垃圾回收器是单线程的,不支持并发。
举例说明:

当开始垃圾回收时,所有用户线程必须全部暂停,这个动作叫做STW(Stop The World)。
随后垃圾回收器线程开始工作,标记并回收垃圾。
STW结束,用户线程恢复。

随着时间的发展,实际业务的变化,物理内存变得越来越大,这种工作模式很快就不能适应了。STW时间已经变得也来越长,它所造成的服务器卡顿,最高可以长达数十个小时。

Parallel Scavenge和Parallel Old
后来出现了Parallel Scavenge和Parallel Old,它们是多线程的垃圾回收器。
举例说明:

当开始垃圾回收时,所有用户线程必须全部暂停,依然触发了STW。
不过这次垃圾回收变成了多线程,对于多CPU的服务器来讲,提高了不少效率,但STW这个动作依然不可避免。

ParNew和CMS
后来就有了CMS,它支持并发。与它搭档的新生代回收器叫做ParNew,ParNew跟Parallel Scavenge几乎没有区别,就是为了搭配CMS做了一些调整。

CMS的并发工作流程:
这是用户线程,CMS介入也会触发STW。
不过,在初始标记垃圾的阶段,它只会标记根对象的第一层,因此,停顿时间比较短。

接下来进入真正的并发阶段,用户线程继续运行,CMS也在同时进行垃圾标记。

多线程的工作是极其复杂的,如果一边生产垃圾,一边标记垃圾,就会不可避免的产生错标、漏标的问题。

错标是如何产生的呢?
例如,当你调用了一个obj.close();方法,该方法是异步的,紧接着obj被设为null。
此时,CMS扫描到它,将obj标记为垃圾。

在500ms之后,close()方法当中,obj又将自身引用this传递给了其他对象,那么,obj从垃圾又回到了正常使用的状态。

但此时CMS并不知情,依然会把obj回收掉。

由于错标的存在,CMS又设计了一个阶段,叫做重新标记,它也会触发STW。

以多线程的方式重新修正错误的标记,然后再以并发的方式清理垃圾

此时,由于用户线程也在运行,新产生的垃圾就不能即使被清理,我们称之为浮动垃圾,它只能等待下一轮被回收。

G1
CMS解决了一些问题,但又带来了不少新的问题。从jdk-1.7后,就出现了一个叫做G1的垃圾回收器。不过,直到1.9以后,才变成jdk默认的垃圾回收器。
G1不需要任何搭档,一个人就可以管理整个堆内存,它的机制也更加复杂。
G1允许用户手动设置一个期望的STW时间,当然,这并不是说可以任意调整STW时间。G1也不保证一定能够准确的符合这个设置的时间,它只会尽量靠近这个时间。

它是如何做到的呢?
首先,G1将整个堆内存划分为若干相等大小的区域,区域的数量一般默认为2048。

它依然保留了E区,S区,Old区的概念。只不过,它们不再是物理上连续的空间。

当一块区域被年轻代使用时,它就属于年轻代。当清空回收后,又被老年代使用时,它就属于老年代。

因此,无论是年轻代还是老年代,它们的空间大小都不再是绝对固定的。

这不仅方便了扩展,而且当GC扫描内存时,它无需扫描整块内存,而是扫描特定区域的内存即可。大大提高了它所能支持的堆内存的大小。
G1的设计原本也是针对大内存使用的,因此,在它的设计当中,会有大量空间换时间的算法细节。

当G1进行垃圾回收时,它会根据设定的STW时间来调整策略,会将需要扫描的区域,进行价值排序。因为不同的区域垃圾数量不同,回收的时间也不太一样。如果设定了50ms,它就会尽可能的保证50ms的时间,优先回收一部分区域。

当然,如果时间设置的过低,也会带来另一个问题。回收了一部分,仍然有大量的垃圾区域没有清理。GC过后,没过多久又不够用了,GC的触发次数将会变得频繁。如果你的虚拟机频繁的切换手中的用户工作,把更多的时间用来做GC工作,吞吐量就下降了。

如果业务对用户的响应时间、计算要求都比较高,就必须在兼顾吞吐量情况下,来对GC策略进行调整。
表面上看,G1划分的区域,也会产生碎片。但G1在垃圾回收时,区域之间依然采用了复制算法,直接进行了碎片整理。
举例说明:
假设这几个区域要进行垃圾回收。

采用复制算法,直接将存活的对象赋值到新的区域内。这样就避免了产生碎片。

另外,G1还把大对象单独存放了,这个区域叫做H(humongous)。

在之前的GC算法中,当一个对象过于巨大,就会直接放入老年代。现在,它依然属于老年代,但存储是独立的。它在被回收之前,内存的位置始终固定不变。这样就避免了对老年代做垃圾回收整理时,频繁的移动大对象。

G1的工作流程跟CMS差不多。只不过,由于它不需要扫描全部内存,所以STW时间是非常短的。并且,在最终标记阶段,G1用三色标记法修正了CMS会出现错标的问题。

查看自己的JVM使用了哪种回收器的命令:java -XX:+PrintCommandLineFlags -version

可以看到这里有一个UseParallelGC,通过这张表来查看一下对应的垃圾回收器组合。

可以得知,我的jdk-1.8目前所使用的是Parallel Scavenge+Parallel Old组合
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小刘新鲜事儿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值