精神小伙儿探秘JVM(四)

HotSpot如何回收垃圾

HotSpot作为JVM中的大太子,对垃圾回收必定很有心得。今天咱们就来聊聊它是怎么处理垃圾的。

垃圾分类

上回咱们说过,JVM中的垃圾,按照存活时间不同,分为新生代和老年代。和垃圾分类原理一样,不同种类的垃圾,要用不同的垃圾收集器来回收。

新生代垃圾收集器  

针对新生代垃圾,主要包含三种垃圾收集器:Serial垃圾收集器,ParNew垃圾收集器,Parallel Scanvenge垃圾收集器。

Serial垃圾收集器:这种收集器采用单线程执行,当垃圾收集开始,就停止一切用户线程,这个操作非常凶猛,人称“Stop The World”。这种收集器适合客户端,因为需要单线程进行GC遍历,客户端对象一般都比较少,所以很适合使用这种方式。

ParNew垃圾收集器:工作机制和Serial没有任何区别。唯一的区别就是采用多线程的遍历方式,如果是多个CPU,这么做效率会提升。单个CPU,线程来回切来切去反而损耗了垃圾回收的性能。

Parellel Scanvenge垃圾收集器:这个收集器和上面的ParNew一样都采用了多线程的收集方式。不同点在于:ParNew追求降低用户的停顿时间,就是让用户尽量感受不到垃圾回收的耗时;而这种方式追求的是个新东西:CPU吞吐量。这是啥玩意?有个公式:吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)。从公式上可以看出,提高吞吐量,运行用户代码时间是不好改变的,但可以缩短垃圾收集时间。这个垃圾收集时间,表示的是整个的垃圾收集时间,比如一共进行了五次收集,这个垃圾收集时间就是这五次时间的总和。所以,就带来一个问题,如果通过降低GC次数来降低整个GC时间,次数降低导致每次需要回收的垃圾个数增加,最终导致整体垃圾时间上升;如果通过频繁运行GC降低单次GC回收垃圾的个数,整体的回收垃圾时间又会升高。所以,如果采用这个收集器,需要考虑:

1.参数GCTimeRadio:设置GC时间占总CPU时间的百分比;

2.参数MaxGCPauseMills:设置垃圾处理过程最久停顿时间;

3.参数UseAdaptiveSizePolicy:开启自适应策略。设置好堆大小和上面两个参数,JVM会通过自适应策略来调整新生代大小、Eden和Survior比例、对象进入老年代年龄,最终实现最大程度的接近我们设置的两个参数标准。

老年代垃圾收集器

说完年轻垃圾,来说说老年垃圾。和新生代垃圾收集类似,老年代也分为以下几种:

Serial Old垃圾收集器:就是Serial的老年版。也是单线程,也是遍历。唯一的区别:新年代采用的是复制算法,老年代采用标记整理算法。为啥?因为新年代对象生命周期短,不像老年代,实在太能活了,如果每次都复制,效率太低。所以采用排序整理,根据地址排序,地址范围以外的全部当成垃圾回收就OK,提高效率。

Parellel Old垃圾收集器:就是ParNew的老年版。

CMS垃圾收集器:这个收集器将低停顿做到了极致,方法就是将用户线程和GC线程同步执行,用户使用的时候,几乎感受不到卡顿。具体的执行流程如下:

1.初始标记:使用一个GC线程,对所有GC Roots直接关联的对象标记;

2.并发标记:使用多个标记线程,和用户线程并发执行,进行可达性分析,标记出所有的废弃对象;这步比较耗时;

3.重新标记:由于上一步用户线程还在执行,仍会产生废弃对象,所以在这步首先Stop The World,停掉全部用户线程,然后启动多个标记线程,把上次新出现的废弃对象标记出来;

4.并发清除:用一个GC线程,和用户线程并发执行,将之前所有的废弃对象全部清除。显而易见,这步最为耗时。

从上面可以看出,CMS有吞吐量低,无法处理浮动垃圾导致频繁full GC;使用标记清楚算法,会产生碎片。针对碎片问题,通过开启-XX:+UseCMSCompactAtFullCollection,每次full GC完成后,会进行一次零散内存的压缩整理,把碎片压缩到一块儿。-XX:CMSFullGCsBeforeCompaction这个参数的设置会告诉JVM,完成多少次full GC后,进行一次压缩整理。

G1通用垃圾收集器

最后一种。上面两种按代数分的收集器,那么,有没有一种不分新生代老年代的垃圾收集装置呢?有!它来了,就是G1通用垃圾收集器。

这种收集器,不分什么新生代老年代,就是分成一个个Region,其实就是一块块区域,完了估摸下每个区域大概能回收出多少垃圾,可着油水最多的先回收。总体上来说,它用的是标记-整理算法,对于没两个Region而言,它又是复制算法的使用。所以,这两种算法都不会导致碎片的产生。它的执行步骤如下:

1.初始标记:上来就Stop The World,完了用一个线程标记所有GC Roots直接关联的对象;

2.并发标记:用一个标记线程和用户线程并发执行,进行可达性分析;

3.最终标记:Stop The World,用多个线程并发标记;

4.筛选回收:还是Stop The World,完了启动多个线程回收废弃对象。

需要注意个问题:如果一个对象A,出现在Region一半,出现在Region另一半,那垃圾回收的时候,那道需要对所有堆内存来一遍可达性分析?不是,每个Region都有个Remembered Set来记录本区域中所有引用对象所在的区域,可达性分析时候用这个记录就能顺利找到对应的对象进行标记了。避免了遍历所有内存的浪费。

12点了。总算写完了。下回聊内存分配与回收的策略。今天是我31岁的生日,25岁参加工作,如今也有五年的时间了。一直做C++开发,现在想转Java,这条路注定会非常艰辛。但不管多难,是否真的是正确的,但实现这个目标,对我一定有非常大的意义。加油!奥利给!上诗!

三十一载人生路,白驹过隙如昨天;转型艰难多凶险,但求追梦勇向前!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

林小BA

请作者增肥

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

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

打赏作者

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

抵扣说明:

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

余额充值