转载

JVM垃圾回收的Tips

整理了JVM垃圾回收的一些问题

为什么Young Generation适合使用复制算法

一句话:因为YGen的特点是大批对象快速死去,仅有少量对象存活。对于复制算法来说,每次复制的内容并不多,成本较低。

为什么是复制算法

一句话:算法简单,效率高,内存分配时也不需要考虑内存碎片等复杂情况,只需要移动指针,按照顺序分配即可 。虽然会浪费一定的空间,但放到合适的位置如YGen,则大小可控,因为YGen回收后的对象占用很少。

现在的商用虚拟机都采用这种算法来回收新生代,不过研究表明1:1复制的比例非常不科学,因此新生代的内存被划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden 和其中一块Survivor。 每次回收时,将Eden和Survivor中还存活着的对象一次性复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。

Survivor区的意义

一句话:作为Young Generation和Old Generation之间对象promotion的一个缓冲地带,扛不住了再给Old Generation。

如果没有Survivor,Eden每进行一次Minor GC,存活的对象就会进入老年代,老年代很快被填满就会进入Major GC。 由于老年代空间一般很大,所以进行一次GC耗时要长的多!尤其是频繁进行Full GC,对程序的响应和连接都会有影响! Survivor存在就是减少被送到老年代的对象,进而减少Full GC的发生。默认设置是经历了16次Minor GC还在新生代中存活的对象才会被送到老年代。

那为什么有两个Survivor

一句话:主要是为了解决内存碎片化和效率问题,内存碎片多会影响大对象的分配,导致频繁GC,复制简单,效率高。

如果只有一个Survivor时,每触发一次Minor GC都会有数据从Eden放到Survivor,一直这样循环下去。注意的是,Survivor区也会进行垃圾回收,这样就会出现内存碎片化问题。 碎片化会导致堆中可能没有足够大的连续空间存放一个大对象,影响程序性能。如果有两块Survivor就能将剩余对象集中到其中一块Survivor上,避免碎片问题。

如何调整Survivor的比例

  • -XX:SurvivorRatio:设置年轻代中Eden区与Survivor区的大小比值。默认为8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor 区占整个年轻代的1/10。
  • -XX:MaxTenuringThreshold:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概率。

Old Generation为什么不用复制算法

一句话:复制算法在对象存活率较高的场景下要进行大量的复制操作,效率很低。

老年代都是不易被回收的对象,对象存活率高,那么需要有额外的空间进行分配担保,因此一般不能直接选用复制算法。

为什么用分代收集

一句话:JVM的堆分配和对象的生存周期不同,所以不同的堆空间应采用不同的回收算法,因地制宜。

大批对象死去、少量对象存活的(新生代),使用复制算法,复制成本低;对象存活率高、没有额外空间进行分配担保的(老年代),采用标记-清理算法或者标记-整理算法。

CMS的并发标记有什么问题

一句话:无法处理浮动垃圾。

因为在并发清理阶段用户线程还在运行,自然就会产生新的垃圾,而在此次收集中无法收集他们,只能留到下次收集,这部分垃圾为浮动垃圾。 同时,由于用户线程并发执行,所以需要预留一部分老年代空间提供并发收集时程序运行使用。

Old Generation如何处理碎片

因为Old Generation由于采用的「标记-清除」算法,并不会进行压缩和整理,故会产生大量的内存碎片,不利于大对象的分配,可能会提前触发一次Full GC,影响运行效率。 虚拟机提供了

  • -XX:+UseCMSCompactAtFullCollection参数来进行碎片的合并整理过程,这样会使得停顿时间变长。
  • -XX:+CMSFullGCsBeforeCompaction,用于设置执行多少次不压缩的Full GC后会执行一次带压缩的GC。

不过阿里云有 文章 说这些参数已经被Hotspot废弃了,虽然不会影响VM工作:

在使用-XX:+UseCMSCompactAtFullCollection -XX:+CMSFullGCsBeforeCompaction -XX:+UseCMSCollectionPassing等选项时,会打印一个已经弃用的警告,但是VM仍会继续工作。

CMSInitiatingOccupancyFraction,这个参数设置有很大技巧,基本上满足(Xmx-Xmn) (100-CMSInitiatingOccupancyFraction)/100>=Xmn就不会出现promotion failed。在我的应用中Xmx是6000,Xmn是500,那么Xmx-Xmn是5500兆,也就是年老代有5500兆,CMSInitiatingOccupancyFraction=90说明年老代到90%满的时候开始执行对年老代的并发垃圾回收(CMS),这时还剩10%的空间是5500 10%=550兆,所以即使Xmn(也就是年轻代共500兆)里所有对象都搬到年老代里,550兆的空间也足够了,所以只要满足上面的公式,就不会出现垃圾回收时的promotion failed

如何调整线程的个数

-Xss:设置每个线程的堆栈大小。JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。 更具应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。

References

  • https://yq.aliyun.com/articles/236

本文首次发布于ElseF’s Blog, 作者 @stuartlau , 转载请保留原文链接.

  • Previous

    MySQL的MVCC在各种隔离级别中发挥的作用

原文  http://elsef.com/2019/03/23/FAQ-JVM-GC/
正文到此结束
Loading...