☮️ 参数及调优

吞佛童子2022年6月9日
  • Java
  • JVM
大约 7 分钟

☮️ 参数及调优

1. JVM 参数

1) 标准参数

  • - 开头
  • 所有的 JVM 实现都必须实现这些参数的功能,而且向后兼容
  • 例:
    • -verbose:gc | class | jni(输出每次 GC | 类加载 | 本地方法调用 的相关情况)

2) 非标准参数

  • -X 开头
  • JVM 默认会实现这些功能,但并不保证所有 JVM 都能实现,且不向后兼容
参数含义
-Xms4G or -XX:InitialHeapSizeJVM 最小堆
-Xmx4G or -XX:MaxHeapSize=4096mJVM 最大堆
-Xmn2G or -XX:NewSize=2G & -XX:MaxNewSize = 2G or -XX:NewRatio=1[老年代/新生代]新生代大小
-Xss1024k每个线程大小

3) 非Stable参数

  • -XX 开头
  • 此类参数各个 jvm 实现会有所不同,将来可能会随时取消,需要慎重使用
参数含义
行为参数
-XX:-DisableExplicitGC禁止调用System.gc();但jvm的gc仍然有效
-XX:+DoEscapeAnalysis是否开启逃逸分析,JDK 1.7 默认开启
性能调优
-XX:NewRatio老年代/新生代 比例,default = 2
-XX:SurvivorRatioEden/Survivor 比例,default = 8
-XX:MetaspaceSize元空间大小
-XX:MaxGCPauseMillis设置 GC 停顿时间
-XX:MaxTenuringThreshold对象晋升老年代阈值, default = 15, CMS = 6
-XX:TargetSurvivorRatio动态年龄判定超过 Survivor 区的百分比则晋升老年代,default = 50%
调优参数
-XX:+PrintGC or -verbose:gc每次GC时打印相关信息,默认关闭
-XX:+PrintGCDetails每次GC时打印详细信息,默认关闭
-XX:+PrintGCTimeStamps or -XX:+PrintGCTimeStamps分析 CC 之间的时间间隔,默认关闭
-Xloggc:filename指定将 GC 日志输出到具体文件,默认为 标准输出
-XX:+HeapDumpOnOutOfMemoryErrorOOM 自动jump,线上环境代替 jump

2. 常用工具

1) jps + jmap + jhat

  • jps: 虚拟机进程状态工具
    • 列出正在运行的虚拟机进程,并显示执行虚拟机主类及这些进程的本地虚拟机唯一 ID
  • jmap: Java 内存映像工具
    • 生成堆转储快照 dump 文件
    • jmap -dump:format=b,file=heapdump.phrof vmid
  • jhat: 虚拟机堆转储快照分析工具
    • 功能比较简陋,生产环境可用 VisualVM 等代替

2) jstat

  • 虚拟机统计信息监视工具
  • 监视虚拟机各种运行状态信息
  • jstat -gc | -class vmid 查看某个JVM 进程 id 下的 GC | Class 情况

3) jps + jstack

  • jps: 虚拟机进程状态工具
  • jstack: Java 堆栈跟踪工具
    • 生成 JVM 当前时刻某个线程的堆栈快照
    • 可用于定位线程出现长时间停顿的原因
    • jstack vmid

4) VisualVM

  • 可实现:
    • jps + jinfo
    • jstat + jstack
    • jmap + jhat 的综合功能

3. JVM 参数设置

1) 选择垃圾收集器

  • CPU 单核
    • Serial
  • CPU 多核 & 注重吞吐量
    • Parallel Scavenge + Parallel Old
  • CPU 多核 & 关注用户停顿时间
    • JDK 1.6 | JDK 1.7
      • CMS
    • JDK 1.8 及以上
      • G1
// 1. 使用 Serial 
-XX:+UseSerialGC

// 2. 使用 Parallel Scavenge + Parallel Old
-XX:+UseParallelOldGG

// 3. 使用 CMS
-XX:+UseConcMarkSweepGC

// 4. 使用 G1
-XX:+UseG1GC

2) 设置内存

  • 若 堆内存 设置的过小,则会导致频繁的 垃圾收集,影响性能
  • default 堆空闲空间 < 40% 时,JVM 增大堆,直到达到 -Xmx
  • default 堆空闲空间 > 70% 时,JVM 减少堆,直到达到 -Xms
  • 因此,一般设置 -Xmx == -Xms,从而避免在每次 CG 后调整堆的大小
// 1. 设置 堆最小值
-Xms4g
-XX:InitialHeapSize=4096m

// 2. 设置 堆最大值
-Xmx4g
-XX:MaxHeapSize=4096m

// 3. 设置 新生代内存
-Xmn2g
-XX:MaxNewSize=2048m

3) 设置符合预期的停顿时间

  • 若未设置确切的停顿时间,垃圾收集器会以 吞吐量 为主,那么垃圾收集时间会变得不稳定,从而导致程序间接性的卡顿
  • 停顿时间也不可设置过短,因为容易引起频繁的 GC 才能保证每次 GC 时间过短
// 设置 GC 停顿时间,垃圾收集器会尝试用各种手段达到这个时间
-XX:MaxGCPauseMillis

4) 设置内存区域比率

// 1. 新生代:老年代 = 1:2
-XX:NewRatio=2
// 2. 新生代中 Eden:Survivor From:Survivor To = 8:1:1
-XX:SurvivorRatio=8

5) 调整对象升老年代的年龄

  • 若升代年龄设置的过大,则对象要存活很多代才能进入老年代,可能导致新生代对象过多,YGC 频繁
  • 若升代年龄设置的过小,则对象很快进入老年代,导致老年代对象增多,FGC 频繁
// 设置升入老年代的最小 GC 年龄
-XX:InitialTenuringThreshold=7

6) 调整大对象的标准

  • 大对象直接分配到老年代
  • 若大对象的标准设置的过小,很多对象直接进入老年代,会导致老年代对象增多,FCG 频繁
  • 若大对象的标准设置的过大,会默认进入新生代,可能导致新生代很快被填满,YGC 频繁
// 设置大对象的标准,超过这个值会直接进入老年代,0 - 无限制
-XX:PretenureSizeThreshold=10000

7) 调整 GC 的触发时机

  • CMS & G1 由于 GC 过程是并发收集的,因此需要预留部分内存空间,用于并发标记中用户线程新生成的对象存放
// 1. CMS 内存空间达到多少比例后进行 CMS 收集
-XX:CMSInitiatingOccupancyFraction=68
// 2. G1 内存空间达到多少比例后进行 G1 收集
-XX:G1MaxedGCLiveThresholdPercent=65

4. 常见异常 & 调优思路

1) StackOverflowError

  • 描述:
    • 单个线程请求栈深度大于虚拟机所允许的最大深度
    • HotSpot 虚拟机上区分虚拟机栈和本地方法栈
  • 原因:
    • 在单线程下,当栈桢太大 | 虚拟机容量太小导致内存无法分配
  • 举例:
    • 递归调用层级过深
    • 单个线程定义了大量的本地变量,导致方法帧中本地变量表长度过大

2) OutOfMemoryError:Java heap space

  • 描述:
    • 堆溢出
  • 原因:
    • 大对象的分配,最有可能的是大数组分配
    • 内存泄漏
      • 理论上对象不再使用就会被 JVM 回收,若无法回收,则发生内存泄漏
      • 此时,只增大堆的大小无法根本解决问题,只能延缓 OOM 的发生

3) OutOfMemoryError:GC overhead limit exceeded

  • 描述:
    • 超过98%的时间用来做 GC 并且回收了不到2%的堆内存时会抛出此异常
  • 原因:
    • 分配大内存数组
    • 内存泄漏
  • 后果:
    • 经过几个 GC 后只回收了不到 2% 的内存,堆很快又会被填满,然后又频繁发生 GC
    • 导致 CPU 负载很快就达到 100%
    • 同时,GC 过程中的 STW 阻塞工作线程,从而导致严重的性能问题

4) OutOfMemoryError:Permgen space

  • 描述:
    • Java 8 的永久代空间不足
    • 一些主流框架,如Spring、Hibernate,对于类进行增强的时候都会使用到 CGLib 这类字节码技术,增强的类越多,就需要越大的方法区来保证动态生成Class可以加载入内存,可能会造成方法区的OOM异常
  • 原因:
    • 错误地频繁地使用 String.intern()
    • 运行期间生成了大量的代理类
  • 解决方案:
    • 适当调大 永久代大小
    • 检查代码是否有大量的反射操作

5) OutOfMemoryError: Out of swap space

  • 描述:
    • JVM 要求的总内存空间大于可用的本机内存,则操作系统会将内存中的部分数据交换到硬盘上,此时无法交换
  • 原因:
    • swap 分区大小不足
    • 其他进程耗尽了本机的内存
  • 解决方案:
    • 增大 swap 空间 [慎重!]
    • 增大本机内存
    • 优化程序减少内存占用

6) CPU 飚高

  • 步骤:
    • top 在当前进程运行列表中找到 CPU 利用率最高的进程 pid
      • default 按照 CPU 利用率由高到低 排序
    • top -Hp pid 找出当前进程 pid 下面 CPU 消耗最高的线程 tid
      • -H 表示以线程的维度展示,默认以进程维度展示
    • printf "0x%x\n" tidtid 十进制转换为 16进制,例 0xab,方便后续快速定位到 当前线程
    • jstack pid > ./pid.log 生成 进程 pid 堆栈快照
    • cat pid.log | grep 0xab 查看 pid 下 0xab 线程 的情况
上次编辑于: 2022/10/10 下午8:43:48
贡献者: liuxianzhishou