44

JVM垃圾回收的Tips

 5 years ago
source link: http://elsef.com/2019/03/23/FAQ-JVM-GC/?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

整理了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 , 转载请保留原文链接.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK