JVM垃圾回收算法、垃圾收集器种类、常用垃圾收集器参数

对象是否可回收判断方式:

1.引用计数法:
对象每被引用一次,其引用计数就+1,不再引用时就-1,如果计数为0就表示对象可以回收了。引用计数法非常简单高效,是很多语⾔的资源回收选择,例如因⼈⼯智能⽽更加⽕热的Python,它就⽀持引⽤计数机制。Java并没有选择引⽤计数,是因为其存在⼀个基本的难题,也就是很难处理循环引⽤关系,比如A持有B,B持有A A a=new A(); B b =new B(); a.instance=b; b.instance=a; a=null;b=null; 这样引用计数都不为0,也就无法回收
2.可达性分析:
简单来说,就是将对象及其引⽤关系看作⼀个图,选定活动的对象作为GC Roots,然后跟踪引⽤链条,如果⼀个对象和GC Roots之间不存在引⽤链条,即认为是可回收对象。JVM会把虚拟机栈和本地⽅法栈中正在引⽤的对象、静态属性引⽤的对 象和常量,作为GC Roots。
具体哪种最优是要看场景的,业界有⼤规模实践中仅保留引⽤计数机制,以提⾼吞吐量的尝试。

垃圾回收算法:

1.标记-清除法:
先通过可达性分析标记出存活对象,剩余的就是需要清除的对象,然后进行清除。标记和清除两步效率都不高,而且空间利用率较低,清除法会产生大量的空间碎片,空间利用率不高,可能会导致剩余空间很大却无法分配较大对象内存的情况。下面的算法都是在此基础上进行了优化
2.复制法:
分配两块一样大小的内存空间,当其中一块内存不足时,将其中的有效对象标记出来复制到另一块空间中。
现在的商业虚拟机都采用这种方法进行回收新生代,但并不是将新生代分为两个一样大小的内存,由于考虑到98%的对象都是很快死亡的,所以将新生代分为 一个大的eden区和两个小的survivor区,每次只使用eden区和其中一个survivor区,回收时将这两个区中的存活对象复制到另外一个s区。默认情况eden区和s区的比例是8:1,这样只会浪费10%的空间而不是50%,主要是根据其普遍的存活率来设计的。考虑到有些情况下存活对象内存>s区大小,需要加入内存担保,保证在S区不够的情况下,还有内存可以容纳多出的这部分对象。
3.标记-移动法:
移动法是将有效的移向一边,无效的移向另一边,中间是一个分隔指针,然后将指针后的数据全部清除,这种方式在分配对象内存时很简单,只需要从分隔指针处开始分配即可,每次分配后,指针后移,保证存活对象在内存上连续分布
4.分代回收算法:
这个算法并没有什么新鲜的东西,只是根据java对象的生存规律(大部分对象都是朝生夕死)将堆分为新生代和老年代,对象都在新生代中创建,大部分也会在新生代中被回收,而留下的大年龄对象都被转移到老年代中,对于新生代,由于存活率很低,所以适合采用复制法,而老年代对象存活率高,采用标记清除法或者标记移动法进行回收。


垃圾收集器分类:

1.serial GC: 新生代收集算法,采用单线程、复制的方式执行GC,由于是单线程,适用于client模式,以及单CPU或者双CPU这种CPU资源很少的机器
2.serialOld GC:serial GC的老年代版本,采用标记-整理的方式进行老年代的回收,除了适用于client模式的jvm垃圾收集,还被用于server模式下的CMS收集器的备用收集器
3.parNew GC:是serial GC的多线程版本,称为并行GC(这里的并行意思是垃圾收集多线程并行,而不是垃圾收集与应用程序并行,应用程序依然需要暂停),曾经是新生代收集器的首选,除了可以并行之外,还因为新生代收集器中只有它可以与CMS收集器配合使用
4.parallel Scavenge GC:新生代收集器,也是采用复制算法,并行收集,那么它对于parNew GC的区别在于哪里呢,它的特点在于它的关注点是程序的吞吐率,所谓吞吐率就是 CPU运行用户代码的时间/(CPU运行用户代码的时间+GC的时间),直白点说就是如何使用最少的CPU资源进行GC是它的关注点;parallel Scavenge提供了一些用户友好的控制参数,用于控制吞吐率,
-XX:maxGcPauseMillis:表示最大暂停时间,也就是告诉虚拟机尽量在此时间内回收完毕
-XX:GCTimeRatio:指GC时间占总时间的比,也就是吞吐率的倒数
当然,这样并不意味着把maxGcPauseMillis设置的越小就越好,缩小maxGcPauseMillis是以程序吞吐率和新生代空间为代价的,当缩小时,虚拟机会自动调小新生代空间,这样时间是下来了,但是频率却变高了,吞吐率可能还会上升,所以这个值关键要适合;还有一个值得关注的参数是-XX:+useAdaptiveSizePolicy,表示虚拟机自适应调整参数,只适用于parallel Scanvenge GC,配置该参数后就无需指定新生代大小,eden与s区比例以及晋升年龄了,虚拟机会自动调整至合适的大小以便达到一个合适的吞吐率
5.parallelOld GC:是parallel Scavenge GC的老年代版本,采用标记整理的方式进行垃圾收集,jdk1.6才开始提供,在此之前parallel Scavenge GC就只能与Serial Old GC配合使用,它出现之后吞吐率优先GC才有了 一个名副其实的组合,也就是parallel Scavenge GC+parallelOld GC
6.CMS GC:基于标记清除算法,其关注点在于缩短GC时应用程序的停顿时间,这一点对于响应时间敏感的应用程序非常重要,所以现在CMS的使用非常广泛;CMS的收集步骤如下:
      1)初始标记(枚举GC Roots,需要暂停)
      2)并发标记(根据GC Roots找到需要清理的对象,无需暂停,并发进行)
      3)重新标记(对于那些在并发标记阶段引用情况发送变化的对象,重新进行标记,需要暂停)
      4)并发清除(最终的清除阶段,不需要暂停)
最耗时的是并发标记和并发清除,都可以与用户线程并发执行,大大缩小了程序暂停时间。但是前面说了,标记清除算法存在空间碎片化问题,长时间运行时,不得不执行full gc将碎片空间整理为连续空间,如果堆空间很大,会导致无法忍受的停顿时间。对此CMS收集器专门添加了-XX:+UseCMSCompactAtFullCollection表示在CMS收集顶不住的时候进行一次空间整理,空间整理是无法并发进行的,需要较长的时间。还有一个参数-XX:CMSFullGcsBeforeCompact表示进行多少次不带压缩的垃圾收集就会进行一次带压缩的垃圾收集
7.G1 GC:兼顾吞吐量和停顿时间的GC实现,是Oracle JDK 9以后的默认GC选项。G1将内存空间分为逻辑上的多个region,每个region固定大小(可配置),年龄带在G1中依然存在,region块分为eden块、survivor块以及old块(还有未使用的块),G1进行垃圾收集的步骤与CMS类似,最后一步的并发清除有所不同,也是关键所在,G1会尽量选择可回收空间大的region块进行回收(为了避免长时间的停顿,会根据XX:MaxGCPauseMills设置的最大停顿时间来选择要回收的region块,而不是回收所有region块),将存活对象复制到比较空闲的region块中;

相⽐于CMS GC,G1未必能做到CMS在最好情况下的延时停顿,但是最差情况要好很多。 G1算法可以有效地避免内存碎⽚,尤其是当Java堆⾮常⼤的时候,G1的优势更加明显。 G1吞吐量和停顿表现都⾮常不错,并且仍然在不断地完善,与此同时CMS已经在JDK 9中被标记为废弃(deprecated), 所以G1 GC值得深⼊掌握。


GC的发展:

GC仍然处于⻜速发展之中,⽬前的默认选项G1 GC在不断的进⾏改进,很多我们原来认为的缺点,例如串⾏的Full GC、Card Table扫描的低效等,都已经被⼤幅改进,例如, JDK 10以后,Full GC已经是并⾏运⾏,在很多场景下,其表现还略优于Parallel GC的并⾏Full GC实现。

即使是Serial GC,虽然⽐较古⽼,但是简单的设计和实现未必就是过时的,它本身的开销,不管是GC相关数据结构的开销,还是线程的开销,都是⾮常⼩的,所以随着云计算的兴起,在Serverless等新的应⽤场景下,Serial GC找到了新的舞台。

⽐较不幸的是CMS GC,因为其算法的理论缺陷等原因,虽然现在还有⾮常⼤的⽤户群体,但是已经被标记为废弃,如果没有组织主动承担CMS的维护,很有可能会在未来版本移除

垃圾收集参数总结:

MaxTenuringThreshold参数表示新生代对象晋升到老年代的最大年龄,也就是经历的minorGC的次数,当对象经历的minorGC次数大于这个值时就会进入老年代,但是并不仅仅如此,虚拟机还可以进行动态对象年龄判断:如果Surivor区中某个年龄的对象占的内存大于Surivor区的一半,则大于或者等于该年龄的对象就会直接进入老年代
HandlePromotionFailure:在进行minorGC之前,会判断老年代的连续空间是否大于新生代总大小,如果小于就说明此次minorGC是有风险的,风险就是此次GC后晋升到老年代的对象大小大于老年代的剩余连续空间,会导致fullGC,此时会去查看HandlePromotionFailure参数是否允许担保失败,如果否则会先进行一次fullGC,如果是 则表示可以冒险,此时并不是直接进行GC而是再判断一下历次平均晋升对象大小是否小于老年代最大的连续空间大小,如果否也会先进行一次fullGC

 

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值