☮️ 参数及调优
1. JVM 参数
1) 标准参数
-
开头- 所有的 JVM 实现都必须实现这些参数的功能,而且向后兼容
- 例:
-verbose:gc | class | jni
(输出每次 GC | 类加载 | 本地方法调用 的相关情况)
2) 非标准参数
-X
开头- JVM 默认会实现这些功能,但并不保证所有 JVM 都能实现,且不向后兼容
参数 | 含义 |
---|
-Xms4G or -XX:InitialHeapSize | JVM 最小堆 |
-Xmx4G or -XX:MaxHeapSize=4096m | JVM 最大堆 |
-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:SurvivorRatio | Eden/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:+HeapDumpOnOutOfMemoryError | OOM 自动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 单核
- CPU 多核 & 注重吞吐量
Parallel Scavenge
+ Parallel Old
- CPU 多核 & 关注用户停顿时间
- JDK 1.6 | JDK 1.7
- JDK 1.8 及以上
// 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 空间 [慎重!]
- 增大本机内存
- 优化程序减少内存占用
6) CPU 飚高
- 步骤:
top
在当前进程运行列表中找到 CPU 利用率最高的进程 pid
- default 按照 CPU 利用率由高到低 排序
top -Hp pid
找出当前进程 pid
下面 CPU 消耗最高的线程 tid
printf "0x%x\n" tid
将 tid
十进制转换为 16进制,例 0xab,方便后续快速定位到 当前线程jstack pid > ./pid.log
生成 进程 pid
堆栈快照cat pid.log | grep 0xab
查看 pid
下 0xab 线程 的情况