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

前三种算法的Heap堆结构: Heap堆结构 Heap堆结构

  • 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种:

  1. Java heap space
  2. GC overhead limit exceeded
  3. Requested array size exceeded VM limit
  4. Permgen space
  5. 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

参考资源

官方文档:

其他: