JVM问题排查及调优

JVM的问题排查及优化方向

报错排查

java.lang.StackOverflowError

  • 栈帧过多:递归调用,栈帧不断扩展,无法申请到更多的内存容纳栈帧,导致栈内存溢出;
  • 栈帧过大;
  • -Xss
    过小;

java.lang.OutOfMemoryError: Java heap space

  • 内存过小,无法容纳程序正常运行所不断创建的对象大小;
  • 超大对象占用过多内存,又无法回收;
  • 内存泄漏,导致无法回收;

java.lang.OutOfMemoryError: GC overhead limit

  • 这种情况通常是由于内存泄漏,GC的回收效率很低,GC占用时间长,但是回收内存很低,就会触发此OOM;

java.lang.OutOfMemoryError: MetaSpace

  • 程序可能动态创建太多的class;

java.lang.OutOfMemoryError: Direct buffer memory

  • Java 允许应用程序通过 Direct ByteBuffer 直接访问堆外内存,当访问的堆外内存大于默认64MB时,触发OOM;
  • 此OOM通常是IO线程引发的,如NIO;
  • 可以通过:
    -XX:MaxDirectMemorySize
    调整堆外内存大小;

内存泄漏

内存泄漏严重,往往会导致OOM; 如果频繁Full GC,并且效果不佳,可能是内存泄漏; 常见原因: 1、连接资源未正常关闭; 2、ThreadLocal未Remove; 3、数据重复;可能是缓存时,缓存的对象未实现

equals
hashcode
导致重复存储;

优化核心

堆区是主要的调优区域: 1、提升对象从年轻代进入老年代的门槛; 2、降低 FullGC 次数;(核心)

常用JVM参数

参数作用
-Xms600m堆的起始内存
-Xmx600m堆的最大内存
-Xmn200m新生代大小
-XX:SurvivorRation=8Eden占新生代的8/10,剩余2/10,From/To 平分
-XX:NewRation=2老年代/新生代比例大小
-XX:+UseAdaptiveSizePolicy动态调整 JVM 各区大小以及 gc 年龄阈值
-XX:MaxTenuringThreshold=15晋升阈值:默认15,超出此阈值,对象移入老年代
-XX:TargetSurvivorRatio=90Survivor 区占用达到 90%,再将对象移入老年代
-XX:PretenureSizeThreshold=3145728如果对象大小超过这个值,将直接分配在老年代
-XX:+PrintGCDetailsGC详情
-XX:+ScavengeBeforeFullGCFullGC前执行一次MinorGC
  1. Heap 大小:
    • -Xms600m
      :堆的起始内存
    • -Xmx600m
      :堆的最大内存
    • (超出触发 MajorGC,无法回收则抛:OutOfMemoryError)
  2. 自适应策略(JDK1.7)
    • -XX:+UseAdaptiveSizePolicy
      :动态调整 JVM 各区大小以及 gc 年龄阈值;
    • 默认初始:老/新 = 2,Eden/Survivor = 8:2,默认年龄阈值 15;
    • 每次 GC 后,动态计算大小 Eden/Survivor 的内存分配;
    • 默认开启,且推荐不要关闭,除非一定要使用特定的 JVM 分区大小;

以下 2 个参数开启前提:关闭自适应策略

  1. 新生代老年代比例:

    • -XX:NewRation=2
      :老年代/新生代比例大小
  2. 新生代内分区比例:

    • -XX:SurvivorRation=8
      :Eden占新生代的比例,剩余 From/To 平分
    • -XX:InitialSurvivorRatio=8 -XX:+UseAdaptiveSizePolocy
      : 初始化比例为8,并动态调整新生代比例
      • 一些垃圾收集器,直接默认动态调整;
  3. 控制对象进入老年代的参数:

    • -XX:MaxTenuringThreshold=15
      :默认 15,超出此阈值,对象移入老年代;
    • -XX:TargetSurvivorRatio=90
      :Survivor 区占用达到 90%,再将对象移入老年代
    • 通常是占用达到 50%,按照年龄排序后,超出 50%的大年龄对象,会在 MinorGC 时移入老年代;
    • -XX:PretenureSizeThreshold=3145728
      :如果对象大小超过这个值,将直接分配在老年代

如何确定参数

  1. 首先要确定线上机器的配置:CPU核数、内存大小;
  2. 根据CPU核数,配置合适的线程池;根据-Xss计算出线程可能占用的内存大小;
    • 比如300线程,默认栈内存1Mb,则需要300Mb
  3. 根据机器内存大小,配置堆区大小,一般配置机器内存的一半;
    • -Xms=4096
      -Xmx=4096
      一般设置一样大;
  4. 元空间一般512Mb,够用了;
    • -XX:MetaspaceSize=512M -XX:MaxMetaspaceSize=512M
  5. 根据机器配置、业务场景,选择合适的垃圾收集器;
    • 内存小于8G,使用CMS:
      -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
    • 大于8G可以考虑G1:
      -XX:+UseG1GC