JVM GC调优整理

JVM GC调优整理,第1张

JVM GC调优整理

文章目录
  • GC调优
    • 1、预备知识
    • 2、GC收集器的选择
    • 3、最快的GC是不发生GC
    • 4、新生代调优
    • 5、老年代的内存调优
    • 6、案例
      • 6.1、案例1
      • 6.2、案例2
      • 6.3、案例3

GC调优 1、预备知识

调优需要掌握GC相关的VM参数,而具体参数可以查看官方文档

另外,我们还可以通过命令的方式在本地查看虚拟机运行参数:

"D:jdk1.8.0_121binjava" -XX:+PrintFlagsFinal -version | findstr "GC"

调优跟应用、环境有关,没有放之四海而皆准的法则

2、GC收集器的选择

想要选择适合自己的GC收集器,首先要明白自己的应用程序是干什么的,是做科学运算,还是互联网项目,如果是科学运算,则追求的应该是高吞吐量,对于延长一点点响应时间,对我来说无关紧要,如果我们做的是互联网项目,则我们追求的是低响应时间,如果每次垃圾回收的时间太长,导致应用程序暂停时间太长,则会给用户带来不好的体验。

确定了自己的目标,我们才能去选择适合自己的GC收集器

  • 对于高吞吐量而言,我们没有太多的选择,也就是ParallelGC
  • 对于低延迟,也就是响应时间优先,可以选择的就有很多了,比如
    • CMS:目前比较主流,但是JDK9已经不推荐了
    • G1:JDK9推荐使用,在超大堆内存下有着很明显的优势,相当于是CMS和ParallelGC的结合,既可以做到低延迟,也可以像ParallelGC一样去确定一个吞吐量目标(单位时间控制暂停时间阈值)
    • ZGC:JDK12引入
3、最快的GC是不发生GC

对于我们GC调优,我们最终目的是控制STW的时间,想要将GC调至最优,当然便是尽可能少的GC,如果你的应用程序经常发生GC,那么你就应该考虑如下几个问题:

  • 数据是不是太多?是不是加载了太多不必要的数据?
  • 数据表示是否太臃肿?
    • 对象
      • 查询一个对象的时候,我们把它所有相关的数据都查出来了,但我们实际上只需要这个对象的一部分数据
    • 对象大小
      • 比如我们Java里面,最小的一个Object对象,都要占用16字节,我们在程序中经常使用一个包装器类型,如Integer,它的一个对象头就16字节,然后还有它真实表示的int类型的整数值4个字节,然后再做一个对齐,这就占用了24字节,我们如果只使用基本类型,那么它只占用Integer的六分之一大小
  • 是否存在内存泄露?
    • 比如我们声明一个static Map对象,我们不停的往里面放对象,但不移除,早晚有一天,我们内存就会内存溢出,我们对于这种长期存活的对象,可以使用软弱引用去处理,或者对于这种缓存的数据,我们不适用Java去处理,而是采用第三方的其他缓存实现去处理,比如Redis等
4、新生代调优

在上面,我们排除了自身代码的问题后,就可以开始对我们的内存进行调优,而内存调优,肯定是需要先从新生代调优走起

新生代的特点:

  • 所有对象创建的内存分配效率非常高
    • 对于每一个Java线程都会再伊甸园中分配一块属于自己的一块私有的区域,这个区域名为TLAB即thread-local allocation buffer,顾名思义也就是线程自己的分配缓冲区,线程在创建对象的时候,首先会去查看TLAB中是否有空余内存,如果有,则优先会在这里创建
    • 为什么要在这里呢?因为对象创建也需要考虑线程安全的问题,多个线程同时在伊甸园中创建对象,需要考虑这块内存是否被其他线程指定,而JVM也提供了响应的保护措施,但是为了效率,我们要尽可能减少这种内存区域的保护 *** 作,所以对象创建才会首先去TLAB
  • 死亡对象的回收代价为零
    • 目前介绍的所有GC收集器,在这里都是采用的复制算法,即我们在GC的时候,会把所有伊甸园和幸存区from的存活对象复制到幸存区to中去,复制过去以后,伊甸园和幸存区from的内存就被完全释放出来了
  • 大部分对象用过即死,也就是“朝生夕灭”
  • 正是因为大部分对象存活时间很短,幸存对象很少,有因为新生代GC的时候采用的复制算法,所以Minor GC的时间远远低于Full GC

如何对新生代调优?直接通过参数-Xmn将新生代的内存加大就可以了吗?当然不行,我们看看官方文档如何介绍

  • 翻译一下意思就是:如果新生代的内存太小,则很容易触发大量的Minor GC,从而增加STW的次数和时间。如果内存分配太大,相对而言老年代的内存空间就变小了,因为存活时间很长的对象会转移到老年代中,而老年代的内存空间很小,当老年代内存满了,则会直接触发Full GC,Full GC触发的次数太多,就需要更长时间的STW。Oracle建议将新生代的大小保持在总体堆大小的**25%至50%**以下。

那具体设置为多大合适呢?对于应用程序的一个理想情况是,新生代能容纳所有【并发量*单位请求响应过程所需要的内存大小】的数据,比如现在一次响应我们创建的对象大概占用了1M的内存,而并发量大概是1000,这个时候新生代需要的内存比较理想的就是1000M大小,因为对于新生代中创建的对象,绝大部分都是朝生夕灭,所以这个应用程序最终可以较好的避免或者是减少GC的次数

单独考虑一下幸存区,它也需要足够大,以便能够保留【当前活跃对象+需要晋升对象】,也就是说,其中的对象可以分为两类,一种是生存周期较短,可能几次GC就会被清理掉了,另一种就是长时间存活对象,肯定会被晋升到老年代,但是它的年龄暂时还不够,还只能存在于幸存区中,如果幸存区比较小,那么JVM会动态的调整晋升阈值,容易使本来存活期不那么长的对象,提前晋升到了老年代,导致本来早就应该被回收的对象,长期存活于老年代中,占用内存

合理设置晋升阈值,可以减少长时间存活的对象在新生代中复制的次数,毕竟对于新生代的标记复制算法,主要耗费的时间就在复制对象上

  • -XX:+PrintTenuringDistribution:可以在每次Minor GC的时候,将幸存区中存活的对象详细信息打印出来
Desired survivor size 48286924 bytes, new threshold 10 (max 10)
- age 1: 28992024 bytes, 28992024 total
- age 2: 1366864 bytes, 30358888 total #1+2
- age 3: 1425912 bytes, 31784800 total #1+2+3
...
  • -XX:MaxTenuringThreshold=threshold:指定晋升阈值
5、老年代的内存调优
  • 以CMS为例,因为它是工作于老年代的一款GC处理器,GC线程可以和用户线程并发执行,这样会带来一定的问题,比如浮动垃圾,当浮动垃圾过多,内存又不足了,就会导致并发失败的问题,CMS便会退化为串行的Serial Old,它会直接暂停用户线程,因为Serial Old的效率很低,这时候程序STW的时间将会很长,所以对于老年代的内存规划,最好是能给大一些,预留更多空间避免浮动垃圾过多导致内存不足的问题

  • 一般情况下,可以先尝试不做老年代调优,因为如果程序运行一段时间,没有触发Full GC,也就是说,不会因为老年代空间不足导致的垃圾回收,这就已经能说明老年代的内存很充裕,就算是触发了Full GC,我们也应该先尝试调优新生代

  • 如果新生代调优已经做过了,还是容易内存不足,再回头来适当调整老年代内存大小,调大1/4~1/3

  • -XX:CMSInitiatingOccupancyFraction=percent:设置老年代空间占用比达到多少开始使用CMS开始垃圾回收,越低,表示CMS垃圾回收的时机越早

6、案例 6.1、案例1

Full GC和Minor GC频繁

  • 那么可以推断,我们的堆内存空间比较紧张,具体是哪一部分内存紧张呢,如果是新生代的空间紧张,当业务高峰期来了,大量对象被创建,很快就把新生代占满了,因为幸存区的晋升阈值会动态调整,导致很多生存周期很短的对象直接被晋升到了老年代,大量的垃圾存在于老年代,导致老年代空间也紧张了,所以这时候可以尝试先加大新生代的内存大小
6.2、案例2

请求高峰期发生Full GC,单次暂停时间特比长,由于业务要求低延迟,所以选择了CMS垃圾收集器

  • 查看CMS的收集日志,可以判断是哪一个阶段耗时较长,对于四个阶段:初始标记,并发标记,重新标记,并发清理,最耗时的便是重新标记,这一步会扫描所有堆内存,如果此时新生代中对象太多了,那么这一步耗费的时间也就更长,又因为新生代中绝大部分对象都会直接被GC清理了,并不会存活太长时间,所以此时会耗费额外的时间去扫描这些对象,那么我们可以尝试在标记新生代对象的时候,先做一次Minor GC,这样可以减少很多不必要的标记,我们可以使用-XX:+CMSScavengeBeforeRemark参数来指定
6.3、案例3

老年代内存充裕的情况发生Full GC,采用的仍然是CMS,开发环境为1.7

  • CMS可能是因为空间不足或者内存碎片太多导致并发失败,触发Full GC,但是这里老年代内存是充裕的,这时候可以考虑考虑JDK1.7以前方法区的实现是永久代,对于JDK1.7以前,如果永久代的空间不足,也会导致一次Full GC,而JDK1.8中,方法去实现改为了元空间,它直接使用了 *** 作系统的空间,所以可以尝试调整永久代的内存大小

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5691999.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存