jvm的轻量级爽口讲解--内存管理子系统(俗称垃圾回收)〇肆

前情提要

jvm的轻量级爽口讲解–内存管理子系统(俗称垃圾回收)〇贰

前言

1
表示博客已经优化到博主比较满意的程度,图片加载问题已解决,jvm系列文章的封面也使用了自己设计的封面,首页菜单添加jvm和blog建站的专栏.虽然说的有点像枯燥的开发日志,但是能看到自己的东西越做越好真是由衷的发自内心的高兴,在这里博主祝大家端午节快乐,来口粽子,来口jvm小菜(恩,没毛病)

上次没吃完的一口粽子之安全点不够用?

上次我们讲了,jvm虚拟机一般是在并发标记回收时,通过设置安全点来实现用户线程的停顿,确切的说是主动式中断的安全点(设置一个轮询问标志,一旦发现论询标志为真时,就跑到附近的安全点去挂起.),但是遇到程序暂停执行的情况就不够用了,比如说线程的sleep和blocked状态,这个时候,他是无法响应程序的中断请求的.

因此我们还需要另一种方式,来让线程中断,这就是安全区域,其实完全就可以理解为被拉成线的安全点,当线程跑到安全区域时,会标识自己已进入,这样回收线程启动,就不会再管安全区域的对象,而在回收线程运行期间,安全区域的线程没有收到回收完毕的信号,是不会离开安全区域的,这就保证了安全区域的安全性(有点套娃)

刚蒸好的新粽子之新老年代如何同时收集?

之前我们介绍了"经典"(这里是新老年代分代,如果把到目前为止可以稳定使用定义为经典的话,需要再加上G1回收器)的回收器,都是新老年代分管,并进行组合使用的.但是那之后的新型回收器都是新老年代同时进行收集的,他们是如何做到呢?
让我们重新再回到理论阶段来解读

我们已知的分代理论出自分带收集理论,当时有两个假说做支撑:

强分代假说:绝大多数对象都是朝生夕灭的

弱分代价说:熬过多次垃圾回收的对象,就越难以消亡.

这和我们现在的垃圾回收流程相对应:

  1. 开始生成的对象会放到eden区当中,等eden区满,触发一次minor GC(有时候也叫young GC),熬过第一次回收的对象会放到survivor区.
  2. 等suvivor区满,再进行一次minor GC,并计算幸存对象熬过GC的次数,将熬过多次(默认15次,可用参数调节)幸存对象放到old区中.
  3. 老年代区域满了收集器触发Full GC,回收整个堆以及方法区内存.

讲到这里大家就要问了,老年代区域满了为什么不直接回收老年代区域的内存?

问的好!~~话说完全是你自己自问自答啊!!!~~这是因为可能会出现老年代引用新生代的对象,即出现跨代引用问题.
如果出现跨代引用,我们回收老年代的引用同时,势必要查询到引用到年轻代的对象,因此会连带年轻代的对象同时回收
,所以老一代的回收器都是old区域满了,进行一次FullGC.
(在这里多嘴一句,FullGC很容易和 MajorGCOldGC搞混,原因是CMS回收器之前是没有Old GC这个说法的,Old区满直接Full GC,目前只有CMS收集器能进行所谓的old GC,即只回收老年代的内存,所以CMS收集器出来之后,大家就old区满这个原因,混淆了old GC 和Full GC的说法,而Major GC更加说不清楚,各个资料各有个的说法)
所以就跨代引用问题,在这之后又出现了一个新的假说,叫做

  • 跨代引用假说: 存在相互引用关系的两个对象,是应该倾向于同时生存和同时消亡.

举个栗子,如果新生代存在跨代引用,那么回收的时候,老年代势必会使新生代对象得以存活,那么我们就不必在为了少量的跨带引用去回收老年代,我们只需要在新生代上建立一个全局的数据结构(叫做记忆集),当我们触发minor GC 的时候,只有包含跨带引用的小块内存里的对象才会被加入到GC Roots进行扫描回收.

基于记忆集我们也可以实现老年代的独立GC(CMS收集器).也可以实现新老年代的同时收集(G1收集器)

最后一口肉粽子之经典收集器的控制参数

好的,新的理论假说已经聊清楚了,我们再回到之前讲的经典收集器.之前所介绍的经典收集器分为串行和并行?不!你没讲过(其实就是单核和多核并行)我们按收集器的顺序,再把收集器的调节参数讲一讲

参数 描述 所属收集器
-XX:SuvivorRatio 新生代中Eden区和Suvivor区域的容量比,默认数值为8,Eden:Survivor=8:1 Serial& ParNew&Parallel Scavenge
-XX:PretenureSizeThreshold 设置直接晋升到老年代的老年代的对象大小,设置这个参数后,大于这个参数的对象将在老年代分配,单位字节 所有经典收集器
-XX:MaxTenuringThreshold 设置晋升到老年代的年龄,每躲过一次Minor GC对象的年龄+1,默认此项不设置 所有经典收集器
-XX:+UseAdaptiveSizePolicy 动态调整java堆区域的大小以及进入老年代的年龄还有停顿时间和吞吐量,特别适合新手 Parallel Scavenge
-XX:GCTimeRatio GC时间站总时间的比率,默认为99, 即允许1%的GC时间 Parallel Scavenge
-XX:MaxGCPauseMillis 设置GC最大的停顿时间 Parallel Scavenge
-XX:CMSInitiatingOccupancyFraction 设置CMS老年代的空间被使用多少时进行收集,默认值为68% CMS
-XX:+UseCMSCompactAtFullCollection 完成垃圾回收后是否进行一次内存碎片整理 CMS
-XX:CMSFullGCsBeforeCompact 设置进行多少次垃圾回收之后再启动一次内存碎片整理 CMS

列完参数我们发现,大部分的调节参数集中在Paraellel Scavenge 收集器和CMS收集器当中,那我们再聊聊这俩个收集器

Parallel Scavenge 收集器

Parallel Scavenge 收集器,恩命名方式突然多了一个Scavenge一定有特殊之处,的确如此,Scavenge 收集器提供了两个重要的可控指标给我们,那就是吞吐量和停顿时间,这是之前提到过的,拿第一个来说,

  • 程序的吞吐量=运行用户代码的时间/(运行用户代码的时间+运行垃圾收集的时间)

而在Scavenge收集器当中的参数就是GCTimeRatio 表示GC回收的时间比率,正好和吞吐量取倒,比如说这个参数取19,那么他所占的用户时间比率为1/(1+19),即百分之五

而Scavenge的另一个参数便是,MaxGCPauseMills GC最大的停顿时间,这个没有什么可讲的,单位为毫秒

不过这个参数在使用的时候需要注意,停顿时间和吞吐量的参数设置是矛盾的,如果停顿时间设置比较小,虚拟机为了保持回收效率,会增加回收次数,这样回收次数*回收时间=总的回收时间,如果停顿时间设太短,回收次数将增加很多,其最后的结果势必会使吞吐量也降下来.

UseAdaptiveSizePolicy,这个参数说明的已经很详细,不再多赘述,如果你对收集器不了解,请务必把这项参数打开.

CMS收集器

CMS收集器,是一个跨时代意义的收集器,因为自它开始,之后的收集器都实现了并发标记并发收集的功能,它自己也如此,但需要注意的是即使并发收集也有一些停顿避免不了(初始标记GC Root对象时,也会产生停顿).好了,让我们来看看它的参数
CMSInitiatingOccupancyFraction 既然是并发执行,那么我们不能等老年代区满了再去执行,因为在回收过程中用户线程还在执行,我们需要预留一部分空间给用户线程,所以才有这个参数,jdk5 默认值为68,jdk6默认值为92,jdk6设置有些风险,因为在程序运行过程中,如果回收之后的老年代空间不足以用户使用,会出现"并发失败",这时会临时启用Serial Old收集器,势必会拖慢用户线程的运行.

+UseCMSCompactAtFullCollection 和 CMSFullGCsBeforeCompact 这两个参数同时讲,我们知道CMS收集器是基于标记-清除算法的,这可能会导致老年代没有连续的空间来存储大对象而导致full GC.所以就需要通过这两个参数来设置和整理(两个参数在jdk9开始废弃)

最后简单总结一下问题

  • 安全点有了,为什么需要安全区域?
  • 新生代老年代如何一同收集?
  • 经典收集器的参数?
  • 简单说一下Parallel Scavenge 收集器和CMS收集器的特征.