一提到 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
有着一系列垃圾回收器例如 CMS
、G1
与 ZGC
等,那在实际中 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
已被标记为弃用。而 ZGC
在 JDK 11
中首次引入,经过多个版本的迭代,在 JDK 17
中已经发展完善拥有客观的性能。
如果想要更换默认生效的垃圾回收器,可以通过 -XX:+Use
命令进行指定。
例如在 JDK 17
中 JVM
默认生效的垃圾回收器为 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
jinfo
是 JDK
中的一个命令行工具,用于实时查看和调整 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
jmap
是 JDK
中的一个命令行工具,用于生成 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
jstack
是 JDK
中的一个命令行工具,用于生成 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
jstat
是 JDK
中的一个命令行工具,用于监视 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 | 最大持有次数限制。 |
参考文档