JVM和GC调优
曾多次接触Java的GC参数调优和日志分析,零散的记录在各处,时间一长也忘的差不多了,汇总于此。
Hotspot JVM
这里的JVM特指Oracle的 Hotspot JVM.
由 class loader, the runtime data areas(含heap), 和execution engine(含GC和JIT)三部分组成。
Heap主要用于对象数据的存放。
性能优化主要涉及heap大小和适合的GC算法的调优。
优化着重于两个目标:
- 响应能力:长时间的停顿不可接受
- 吞吐率:较长一段时间的处理量,快速响应不是必须的。
注:Java程序实际占用内存大于Xmx
RSS(Resident Set Size) = Heap size + MetaSpace + OffHeap size
OffHeap = thread stacks + direct buffers + mapped files (libraries and jars) + JVM code itself
查看当前JVM设置
Spring Boot应用默认使用系统JVM内存设置。
# 当前环境的常用默认值
java -XX:+PrintFlagsFinal -version | grep -iE 'HeapSize|PermSize|ThreadStackSize'
查看运行JVM进程
# 使用jps查看进程id
jps -v
# 使用jmap
jmap -heap <pid>
常用设置
# 查看常用命令参数
java -X
# 设置最大heap size
java -Xmx4G
Spring Boot2 设置
java -Xmx4g -jar my-springboot-app.jar
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<jvmArguments>-Xmx4G</jvmArguments>
</configuration>
</plugin>
GC主要算法
Serial
-XX:+UseSerialGC
Parallel
JDK8默认值。
-XX:+UseParallelGC
-XX:+UseParallelOldGC
CMS
新生代使用Parallel New + 老年代使用CMS(Concurrent Mark-Sweep Collector)。
-XX:+UseParNewGC -XX:+UseConcMarkSweepGC
- Young Generation = Eden + Survivor Space(S0+S1)
- Old Generation == Tenured
- Permanent Generation == Permanent
G1
代表Garbage First,适用于多核和超大内存的情况,需要JDK7u4+。
是CMS的替代方案,被划分为相同大小的区域,由eden,survivor和old三部分组成。
使用场景:Heap 6G+ 或要求停顿时间小于0.5s
-XX:+UseG1GC
其他参数:
- -XX:MaxGCPauseMillis=200 (默认200ms)
- -XX:InitiatingHeapOccupancyPercent=45 (默认45%,开始并发GC Cycle)
- -XX:G1ReservePercent=10(默认10%,提高以避免 to-space overflow错误)
通常2000个区域,每个区域1~32Mb。
注:不要设置新生代大小(-Xmn)
调优
建议:老年代占用率至少大于新生代活跃数据的1.5倍,新生代空间大小至少为堆大小的10%.
主要触发事件:
新生代用完会触发Minor GC,时间通常很快;
老年代(tenure+perm)用完会触发Full GC,时间通常较长。
原始是更频繁的Minor GC和更少的Full GC。
Heap Size = Young Gen + Old Gen + Meta space
主要参数
- -Xms Heap初始大小
- -Xmx Heap最大值
- -Xmn 新生代Heap大小(推荐不设置,使用则该值不能太大)
XMX和XMS设置一样大,减轻伸缩堆带来的压力 JVM占用内存会超过Xmx大致1/3, 因此Xmx的值不应超过总内存的60%~70%. -X 是设置JVM参数的新命令,不带-X 的是为了兼容之前版本.
打印默认参数
java -XX:+PrintFlagsFinal -version
Thread Stack Size
Java 8 64bit 默认1024k.
每个线程启动时JVM为该线程创建一个新的Java Stack,在discrete frames存放线程状态,执行两类操作push和pop frames.
使用递归算法或使用第三方框架会增加该大小.
如果应用创建很多线程,在申请更多线程栈内存会报StackOverFlowError错误.
可以用设置最大线程栈内存: -Xss256k 或 -XX:ThreadStackSize=256
也可以压缩操作符和类指针:
-XX:+UseCompressedClassPointers
-XX:+UseCompressedOops
原则:
- 一般没必要设置该值,除非64bit JVM的物理内存较小产生OutOfMemory错误
- 设置为较小的值可以有更大的Heap size
- 一般来说,64bit JVM使用256k足够用
OOM问题
- 如果JVM Crash, 通常会生成类似于hs_err_pidNNN.log的文件.
- 或者查看/var/log/messages崩溃时段的响应日志
- 或使用命令dmesg | grep java查看
- 查看Tomcat进程: ps -ef | grep tomcat
针对Tomcat
# 检索gc日志
grep 'java.lang.OutOfMemoryError: GC overhead limit exceeded' catalina.2016-10-25.log
# 记录heap dump,setenv.sh
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/tomcat/logs/
GC日志
参数设置
一些gc日志参数,经常使用的:
-verbosegc
-Xloggc:/var/log/gc.log
设置更详细日志
-XX:+PrintGC
-XX:+PrintGCDetails
显示时间
-XX:+PrintGCTimeStamps
-XX:+PrintGCDateStamps
日志分析
Allocation Failure:运行GC(一般是新生代回收即minor GC)的原因,意味着新生代已没有空间.
一共存在8种OutOfMemoryErrors , GC日志可以记录5种:
- Java heap space
- GC overhead limit exceeded
- Requested array size exceeded VM limit
- Permgen space
- Metaspace
名称术语
- 栈内存/Stack : Java中存放函数主体和变量名 (栈: 后进先出)
- 堆内存/Heap: Java中存放实例 (GC操作)
- JRE: Java Runtime Environment
- JDK: Java Development Kit
- JVM: Java Virtual Machine
- GC: Garbage Collection
- JMX: Java Management Extentions
- JPA: Java Persistence API
工具
GC日志分析
Heap Dump分析
jhat heap-dump.hprof
获取heap dump
- jmap -dump : 运行时获得(会停止java进程)
- JConsole使用 HotSpotDiagnosticMXBean
- 使用JVM参数 -XX:+HeapDumpOnOutOfMemoryError
- 使用hprof命令: java -agentlib:hprof=help
参考资源
官方文档:
- Getting Started with the G1 Garbage Collector
- HotSpot Virtual Machine Garbage Collection Tuning Guide
- Java Virtual Machine Troubleshooting
其他:
- GC Handbook : GC手册
- JVM Anatomy Park :深入讲解JVM
- How to Tune Java Garbage Collection