JVM参数调优教程


一提到 JVM 参数调优,脑海中第一眼浮现就是面试八股文,但在日常的开发中,有时间一个不起眼的参数总能给你带来意想不到的效果。

正所谓技多不压身,今天让我们一起来揭开 JVM 调优的神秘面纱。

一、参数配置

1. 配置方式

在虚拟机中 -X-D 都可用于指定 JVM 参数,但它们在使用方式和作用范围上有一些区别。

(1) -X 方式

-X 用于指定非标准的 JVM 参数,这些参数通常是供具体的 JVM 实现或特定的虚拟机选项使用的。

例如 -X<parameter>,其中 <parameter> 是具体的 JVM 参数名称。

(2) -D 方式

-D 用于设置系统属性,提供了一种在应用程序中传递配置信息的机制,可以在应用程序中通过 System.getProperty() 方法获取。

例如 -D<property>=<value>,其中 <property> 是属性名称,<value> 是属性值。

(3) 基本格式

JVM 配置参数通常由 -XX: 开头,基本格式如下:

  • 若用于配置是否启用,由 + - 分别控制开启与关闭。
  • 若用于配置大小或数值变量,遵循 <option>=<value> 格式。
配置 作用
-XX:+option 表示开启 option 选项。
-XX:-option 表示关闭 option 选项。
-XX:option=value 表示将 option 值赋为 value。

2. 优先级

Java 命令行运行 JAR 包时,JVM 参数可以在 -jar 选项之前或之后指定,这两种方式会影响参数的解析和执行顺序。

  • 参数在 -jar 之前,则会在 JVM 启动前解析和应用。
  • 参数在 -jar 之后,则会在解析 JAR 包时被应用。
  • JVM 参数与 JAR 包的命令行参数存在冲突,JVM 参数的优先级更高。

如下示例中方式一的 -Xmx512m 优先级高于方式二中的 -Xmx1024m

# 方式一
java -Xmx512m -jar myapp.jar

# 方式二
java -jar myapp.jar -Xmx1024m

二、常用配置

1. 内存参数

JVM 中内存常见配置参数如下,其中 -Xss 在相同物理内存下,减小这个值能生成更多的线程,但是操作系统对一个进程内的线程数还是有限制并不能无限生成,经验值在 3000~5000 左右。

对于 -Xmn 用于调整年轻代内存空间,对于当下使用对象广泛的 Appel 式回收而言对系统性能影响较大,官方推荐配置为整个堆的 3/8

参数 作用
-Xms1G 堆内存初始值。
-Xmx2G 堆内存最大值。
-Xss512k 每个线程的堆栈大小,默认 1MB。
-Xmn1024m 设置年轻代大小,增大年轻代后,将会减小年老代大小。

同时在默认的 JVM 服务启动中,如果没有手动指定堆内存值,则默认的 -Xms-Xmx 取值方式如下:

  • 最小值(-Xms): 默认为物理内存大小的 1/64,但不超过 1GB
  • 最大值(-Xmx): 默认为物理内存大小的 1/4,但不超过 32GB

2. JVM参数

针对于 JVM 的内存堆 (Heap) 以及垃圾回收,常见的配置参数如下:

参数 作用
-XX:ConcGCThreads=4 CMS 垃圾回收器并行线程线,推荐值为 CPU 核心数。
-XX:ParallelGCThreads=8 新生代并行收集器的线程数。

在基于标记复制的 Appel 式回收方式中较为关键的 JVM 参数为下述三类:

参数 描述
-XX:SurvivorRatio=8 设置新生代中 Eden 与 Survivor 的比例,默认为 8:2。
-XX:MaxTenuringThreshold=15 设置 To Space 最大复制次数,取值 0~15,默认为 15。
-XX:PretenureSizeThreshold=1m 设置忽略 MaxTenuringThreshold 直接晋升到老年代的对象大小。

3. 系统参数

常见的系统参数配置参数如下:

# 防止虚拟机阻塞
-Djava.security.egd=file:/dev/./urandom

4. GC参数

常见的虚拟机垃圾回收(GC)参数配置参数如下:

开启日志后生成的 GC 文件推荐 gceasy 在线解析网站,可以生成详细的分析报告。

参数 作用
-XX:+PrintGCDateStamps 打印 gc 发生的时间戳。
-XX:+PrintTenuringDistribution 打印 gc 发生时的分代信息。
-XX:+PrintGCApplicationStoppedTime 打印 gc 停顿时长。
-XX:+PrintGCApplicationConcurrentTime 打印 gc 间隔的服务运行时长。
-XX:+PrintGCDetails 打印 gc 详情,包括 gc 前/内存等。
-Xloggc:./gclogs/gc.log 指定 gc log 的路径,支持相对路径与绝对路径。

三、服务模式

1. 启动模式

Java 9 开始 JDK 默认使用的垃圾收集器取决于所选择的 JVM 启动模式,有两种启动模式可供选择:

服务器模式不同 JDK 版本默认垃圾收集器如下:

  • Server 模式,这是 JDK 默认的启动模式,适用于长时间运行的服务器应用程序。
  • Java 8 及之前的版本,默认使用 Parallel (基于标记复制) 垃圾收集器。
  • Java 9 及之后默认使用的垃圾收集器是 G1 垃圾收集器。

客户端模式不同 JDK 版本默认垃圾收集器如下:

  • Client 模式,这是面向桌面应用程序和短期运行的应用程序的启动模式。
  • Java 8 及之前的版本,默认使用 Serial (基于标记清除)垃圾收集器。
  • Java 9 及之后的版本,默认使用 Serial/Serial Old 垃圾收集器组合。

2. 模式查看

那如何查看当前 JVM 的启动方式呢?方法其实很简单直接通过 java -version 命令即可。

可以看到上图的最后一行中有 64-Bit Server VM 字样,即代表当前 JVM 默认的启动模式为服务器模式,简单的讲即当前通过 java -jar 命令运行的程序其 JVM 模式为服务器模式。

而针对正在运行中的程序,则可以通过 jcmd 命令进行查看,命令格式如下:

jcmd <pid> VM.version

执行后可以看到返回的信息和上述 java -version 结果类似,同样为 Server VM

如果你不想以默认的模式启动,则可以在执行运行命令时通过 -client-server 指定模式。

# 以客户端模式启动
java -client -jar test.jar

# 以服务器模式启动
java -server -jar test.jar

3. 应用选择

我们知道 JVM 有着一系列垃圾回收器例如 CMSG1ZGC 等,那在实际中 JVM 又是如何选择呢?

其实很简单,JVM 使用的垃圾回收器跟 JDK 版本息息相关,对应的版本参照下表:

JDK 版本 默认回收器 可选回收器
JDK 8 Parallel GC Serial GC、CMS
JDK 9 G1 GC Parallel GC、CMS
JDK 11 G1 GC Serial GC、Parallel GC
JDK 17 G1 GC ZGC、Shenandoah GC

可以看到在 JDK 8 之后 G1 GC 成为了默认 JVM 垃圾回收器,同时从 JDK 11 开始 CMS 已被标记为弃用。而 ZGCJDK 11 中首次引入,经过多个版本的迭代,在 JDK 17 中已经发展完善拥有客观的性能。

如果想要更换默认生效的垃圾回收器,可以通过 -XX:+Use 命令进行指定。

例如在 JDK 17JVM 默认生效的垃圾回收器为 G1 GC,那么在启动程序时,可通过下述命令将默认生效的垃圾回收器替换为 ZGC

# 使用 ZGC 
java -XX:+UseZGC -jar app.jar   

四、JDK工具

安装 JDK 环境之后,在其 bin 目录下提供了丰富工具可用于查看程序运行状况,下面介绍几类常用的命令。

1. 版本

在不同的 JDK 版本中使用方式略有差异,尤其在 JDK 11 之后工具进行了重新整合,后面介绍的几种方式都是基于 JDK 8 版本而言。

例如 jinfo 命令在 JDK 8 之前执行方式如下:

jinfo <options> <pid>

而在 JDK 11 之后的版本,需要通过下述方式执行,可以看到需通过 jhsdb 执行,同时通过 --pid 参数用于指定进程号。

jhsdb jinfo <option> --pid <pid>

同时 <option> 的取值也略有不同,需要将 - 替换为 --,例如下述示例中 JDK 11 之后需要将 -flags 替换为 --flags

# JDK 8
jinfo -flags 12345


# JDK 11 以上
jhsdb jinfo --flags --pid 12345

2. Jinfo

jinfoJDK中的一个命令行工具,用于实时查看和调整 JVM 的配置信息。

通过 jinfo 可以获取和修改正在运行的 Java 进程的虚拟机配置参数,对于调优和诊断 Java 应用程序是很有帮助的。

# 查看进程的虚拟机参数信息
jinfo <options> <pid>

其中 options 可选参数参考下表:

方法 作用
-flag 显示所有可用的虚拟机参数及其当前值。
-flag (name) 显示虚拟机指定参数的值。
-flag [+|-] (name) 启用或禁用指定的标志。
-sysprops 显示 Java 系统属性的值。

例如下图中即显示进程 22788 进程所有虚拟机参数。

同时可以通过下述方式动态调整进程的执行虚拟机参数。

# 开启配置
jinfo -flag +<name> <pid>

# 关闭配置
jinfo -flag -<name> <pid>

3. JMap

jmapJDK 中的一个命令行工具,用于生成 Java 进程的内存转储快照。

这个快照通常称为 heap dump,它是 Java 堆内存的详细信息,包括对象的数量、类型、分布等,对于分析内存泄漏和性能问题非常有用。

jmap [options] <pid>

其中 options 可选参数参考下表:

参数 作用
-heap 打印堆内存的概要信息,包括堆的使用情况和配置。
-histo 打印堆内存中对象的直方图,显示每个类的实例数量和占用的内存大小。
-F 当进程无响应时,强制执行。
-dump 生成堆内存信息文件。

通过 -heap 得到堆内存信息如下图所示,其中参数参考下表:

参数 描述
MinHeapFreeRatio 设置堆中最小空闲空间的比例。
MaxHeapFreeRatio 设置堆中最大空闲空间的比例。
MaxHeapSize JVM 堆的最大大小,即堆内存的最大限制。
NewSize 年轻代的初始大小。
MaxNewSize 年轻代的最大值。
OldSize 老年代的初始大小。
NewRatio 年轻代与老年代的大小比例,默认 1:2。
SurvivorRatio Eden 与 Survivor 的比例,默认 8:2。

针对 -dump 生成文件,这里推送一个在线文件分析网站:heaphero

jmap -dump:file=<export_path> <pid>


# 生成堆栈信息日志
jmap -dump:format=b,file=./dump.bin 1234

4. Jstack

jstackJDK中的一个命令行工具,用于生成 Java 进程的线程转储(thread dump)。

线程转储是一个描述 Java 虚拟机中所有线程当前状态的快照,包括线程的堆栈信息。注意这里的 pid 需要为十六进制形式,在 Linux 中可通过 printf '%x\n' <pid> 命令将对应 pid 转为十六进制。

# 查询进程堆栈信息
jstack [options] <pid>


# <line num>: 限制显示行数
# <pid_16>: 进程转十六进制
jstack <pid> | grep -A <line num> <pid_16_hex>

其中 options 可选参数参考下表:

参数 作用
-l 除了线程堆栈外,还显示关于锁的附加信息,这将显示每个锁的拥有者和等待者。
-e 打印线程的锁信息。
-F 当进程无响应时,强制生成线程转储,在进程卡死或无响应时非常有用。
-m 打印 Java 和本地 C/C++ 帧的混合堆栈。

5. Jstat

jstatJDK 中的一个命令行工具,用于监视 Java 虚拟机 (JVM) 的各种统计信息。

它提供了对堆内存、垃圾回收、类装载、JIT 编译等方面的实时性能数据,帮助开发人员和系统管理员诊断和分析 Java 应用程序的性能问题。

# <pid>: jps process id
# <interval>: time interval, ms
jstat -gc <pid> <interval>


# Simple info
jstat -gcutil <pid> <interval>

详细输出

描述
S0C 年轻代中第一个 Survivor 的容量 (KB)。
S1C 年轻代中第二个 Survivor 的容量 (KB)。
S0U 年轻代中第一个 Survivor 目前已使用空间 (KB)。
S1U 年轻代中第二个 Survivor 目前已使用空间 (KB)。
EC 年轻代中 Eden 的容量 (KB)。
EU 年轻代中 Eden 目前已使用空间 (KB)。
OC 老年代的容量 (KB)。
OU 老年代目前已使用空间 (KB)。
MC 元数据区的容量 (KB)。
MU 元数据区目前已使用空间 (KB)。

其中 GC 垃圾回收相关列描述如下:

描述
YGC 从应用程序启动到采样时年轻代中 GC 次数。
YGCT 从应用程序启动到采样时年轻代中 GC 所用时间(秒)。
FGC 从应用程序启动到采样时老年代 Full GC 次数。
FGCT 从应用程序启动到采样时老年代 Full GC 所用时间(秒)。
GCT 从应用程序启动到采样时 GC 用的总时间(秒)。

简要输出

描述
S0 年轻代中第一个 Survivor 已使用的占当前容量百分比。
S1 年轻代中第二个 Survivor 已使用的占当前容量百分比。
S0CMX 年轻代中第一个 Survivor 的最大容量 (KB)。
S1CMX 年轻代中第二个 Survivor 的最大容量 (KB)。
E 年轻代中 Eden 已使用的占当前容量百分比。
ECMX 年轻代中 Eden 的最大容量 (KB)。
OGCMN 老年代中初始化(最小)的大小 (KB)。
OGCMX 老年代的最大容量 (KB)。
OGC 老年代当前新生成的容量 (KB)。
O 老年代已使用的占当前容量百分比。
NGCMN 年轻代中初始化(最小)的大小 (KB)。
NGCMX 年轻代的最大容量 (KB)。
NGC 年轻代中当前的容量 (KB)。
DSS 当前需要 Survivor 的容量 (KB)( Eden 区已满)。
TT 持有次数限制。
MTT 最大持有次数限制。

参考文档

  1. JVM常用调优参数 ——JVM篇
  2. 一次简单的 JVM 调优,拿去写到简历里
  3. jstat命令查看jvm内存情况及GC内存变化

文章作者: 烽火戏诸诸诸侯
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 烽火戏诸诸诸侯 !
  目录