JVM 之 命令行工具
摘要
-
本文介绍JVM的命令行工具
JVM 的参数(三类)
-
标准参数: 以
-
开头,所有 HotSpot 都⽀持。例如java -version
。这类参数可以使⽤java -help
或者java -?
全部打印出来 -
⾮标准参数: 以
-X
开头,是特定 HotSpot版本⽀持的指令。例如java -Xms200M -Xmx200M
。这类指令可以⽤java -X
全部打印出来。 -
不稳定参数: 这也是 JVM调优的噩梦。以
-XX
开头,这些参数是跟特定HotSpot版本对应的,很有可能换个版本就没有了。详细的⽂档资料也特别少。JDK8 中的以下⼏个指令可以帮助开发者了解 JDK8 中的这⼀类不稳定参数。
1 | java -XX:+PrintFlagsInitial -version # 所有参数的默认值 |
运行 java -XX:+UnlockExperimentalVMOptions -XX:+PrintFlagsFinal -version
返回值说明
1 | [Global flags] |
-
flag类型:
jdk8
标识符 | 含义 | 描述说明 |
---|---|---|
intx |
有符号整数(int extended) | 用于表示带符号整数型参数 |
uintx |
无符号整数(unsigned int extended) | 表示非负整数型参数 |
uint64_t |
无符号 64 位整数 | 适用于需要更大整数值的参数,比如内存大小、纳秒时间戳等 |
bool |
布尔型 | true 或 false ,表示开关型参数。+ 表示开启,- 表示关闭。 如 -XX:+UseG1GC ,表示开启 G1 回收器功能。 |
double |
双精度浮点型 | 用于表示浮点数值类型的 JVM 参数 |
ccstr |
常量 C 字符串指针 | 表示字符串参数(不可变) |
ccstrlist |
C 字符串列表 | 表示以逗号分隔的字符串列表 |
jdk11后出现更多的类型
标识符 | 含义 | 描述说明 |
---|---|---|
int |
有符号整数,通常是 32 位 | JVM 内部使用的普通 int 参数 |
uint |
无符号整数,通常是 32 位 | 少量用于特殊场景的参数 |
size_t |
内存大小 | 表示内存大小参数(单位一般为字节),jdk11后出现,原来是 uintx |
-
flag来源与作用域修饰符
jdk8
标记 | 含义 |
---|---|
{product} |
正式产品参数(用户可用,可通过 -XX: 进行设置) |
{pd product} |
平台相关的正式参数(platform-dependent),在某些操作系统/CPU 上可用 |
{C1 product} |
仅在使用 C1 编译器(优化编译器) 时有效的参数 |
{C2 product} |
仅在使用 C2 编译器(优化编译器) 时有效的参数 |
{manageable} |
可通过 JMX 动态管理的参数(运行时可调整) |
{ARCH product} |
与 CPU 架构相关的产品参数(如 x86、ARM 等) |
{lp64_product} |
仅在 64 位 JVM 上才有效的产品参数 |
{experimental} |
实验性的 参数,可能在将来的版本中删除或更改。需要开启 -XX:+UnlockExperimentalVMOptions |
jdk11后出现更多的类型
标记 | 含义 |
---|---|
{C1 pd product} |
平台相关的正式参数,仅在使用 C1 编译器(优化编译器) 时有效的参数 |
{C2 pd product} |
平台相关的正式参数,仅在使用 C2 编译器(优化编译器) 时有效的参数 |
{JVMCI product} |
此参数适用于 JVMCI 或 Graal 编译器相关功能,jdk21添加 |
另外,jdk11+ 中,最后还会多出一列,其作用是说明该参数的设置来源
来源标识 | 含义说明 |
---|---|
{default} |
使用的是该参数的默认值(没有被用户或系统设置) |
{command line} |
用户通过命令行显式设置的参数(如 -XX:+UseG1GC ) |
{ergonomic} |
JVM 根据系统环境自动选择的值(自适应设置) |
jps: 查看当前jvm中的进程
1 | # 查看java进程ID |
jinfo: 查看JVM参数
-
jinfo 是 JDK 自带的命令行工具之一,用于 查看和修改正在运行中的 Java 进程的配置信息,主要包括 JVM 参数、系统属性等信息。
-
在 Java 9+ 之后被标记为 deprecated,建议改用 jcmd 替代
1 | # 查看正在运行的 JVM 参数 |
jstat: 查看指定JAVA进程的运行状态
1 | # 查看运行状态 |
-
内存区统计(单位:KB)
列名 | 含义说明 |
---|---|
S0C | Survivor 0 区的容量(KB) |
S1C | Survivor 1 区的容量(KB) |
S0U | Survivor 0 区已使用内存(KB) |
S1U | Survivor 1 区已使用内存(KB) |
EC | Eden 区的容量(KB) |
EU | Eden 区已使用内存(KB) |
OC | Old Generation(老年代)的容量(KB) |
OU | Old Generation 已使用内存(KB) |
MC | Metaspace(元空间)的容量(KB) |
MU | Metaspace 已使用内存(KB) |
CCSC | Compressed Class Space 的容量(KB) |
CCSU | Compressed Class Space 已使用内存(KB) |
-
GC 次数与耗时(从应用程序启动到采样时)
列名 | 含义说明 |
---|---|
YGC | Young GC(Minor GC) 的累计次数 |
YGCT | Young GC 累计耗时(单位:秒) |
FGC | Full GC 的累计次数 |
FGCT | Full GC 累计耗时(单位:秒) |
GCT | GC 总耗时(YGCT + FGCT,总和) |
jstack: 查看指定JAVA进程中各线程的调用堆栈
1 | # 打印线程的标准栈信息(栈帧 + 线程状态) |
-
jstack输出内容含义
字段 | 含义 |
---|---|
"idle-connection-reaper" |
线程名称。这个线程是 AWS SDK 中用于清理空闲连接的后台线程。 |
#68 |
线程 ID(Java 层分配的编号)。 |
daemon |
表示这是一个 守护线程。JVM 退出时不会等待守护线程运行结束。 |
prio=5 |
Java 层的线程优先级(默认 1–10,5 是默认)。 |
os_prio=0 |
操作系统层线程优先级(取决于操作系统和 JVM 的实现)。 |
tid=0x00007f38b6ae2800 |
线程 ID(Java 层使用的地址标识)。 |
nid=0x62e9 |
Native ID,操作系统分配的线程 ID(十六进制表示),可以通过命令printf "%d\n" 0x62e9 转换为10进制。ps -Lp <PID> 和 top -Hp <PID> 展示的线程id是10进制 |
waiting on condition |
线程状态说明:正在等待某种条件,一般指 sleep() 或 wait() 等。 |
[0x00007f385b0f5000] |
栈帧的内存地址。 |
java.lang.Thread.State: TIMED_WAITING (sleeping) |
线程状态。 |
at java.lang.Thread.sleep(Native Method) ………………………… |
线程调用栈信息,包括调用方法、调用参数、调用栈帧。 |
-
另外还有如下信息,表示此线程持有的可拥有同步器(例如 ReentrantLock),具体内容如下:
1 | Locked ownable synchronizers: |
内容 | 含义 |
---|---|
<0x00000000c561cfd8> |
持有锁的对象地址。可以在其他线程中查找这个地址,以判断死锁等问题。 |
(a ThreadPoolExecutor$Worker) |
表示该锁属于 ThreadPoolExecutor 的某个 Worker 线程(这是线程池的工作线程)。 |
-
线程状态
状态 | 含义 | 常见原因 / 示例方法 | 示例 jstack 输出 |
---|---|---|---|
RUNNABLE |
线程正在运行或准备运行,等待 CPU 时间片。 | 线程活跃运行中 | "Thread-0" #1 prio=5 os_prio=0 tid=0x00007f8d9c001000 nid=0x1a runnable [0x00007f8dbf7fe000] |
BLOCKED (on object monitor) |
等待获取对象的监视器锁(同步锁),即等待进入同步块或方法。 | synchronized 同步块或方法 |
"Thread-1" #2 prio=5 os_prio=0 tid=0x00007f8d9c002000 nid=0x1b waiting for monitor entry [0x00007f8dbeaff000] |
WAITING (on object monitor) |
无限期等待其他线程执行某操作。 | Object.wait() Thread.join() LockSupport.park() Condition.await() |
"Thread-2" #3 prio=5 os_prio=0 tid=0x00007f8d9c003000 nid=0x1c waiting on condition [0x00007f8dbebff000] |
TIMED_WAITING (on object monitor) |
等待固定时间,直到条件满足或超时。 | Object.wait(long) Thread.sleep(long) Condition.awaitNanos(long) DelayQueue.take() |
"Thread-3" #4 prio=5 os_prio=0 tid=0x00007f8d9c004000 nid=0x1d timed_waiting [0x00007f8dbebff000] |
TERMINATED |
线程已运行完毕并退出(通常不在 jstack 中显示)。 | - | - |
使用jstack检查死锁
-
死锁是指多个线程互相等待对方释放锁,导致无法继续运行的情况。
直接查找 Found one Java-level deadlock
提示
1 | Found one Java-level deadlock: |
-
表示两个线程互相等待对方释放锁,造成死锁。
-
目前只支持找出
synchronized
关键字阻塞住的线程,如果是java.util.concurrent.Lock
目前还不支持。
手动判断死锁的特征(如果没有上面的提示)
-
如果 JVM 没有显式提示,你可以通过以下死锁特征进行手动识别:
特征 | 描述 |
---|---|
线程处于 BLOCKED 状态 |
线程被锁阻塞在 monitor 上。 |
waiting to lock 和 locked 出现交叉 |
一个线程正在等待另一个线程持有的锁,而另一个线程也在等待当前线程的锁。 |
没有能继续执行的线程 | 多个线程都处于 BLOCKED 状态,且相互依赖。 |
在 Java 8 之后支持获取 JVM 内部线程
- VM 内部线程包括下面几种:
线程类型 | 示例名称 |
---|---|
JIT(Just-In-Time)编译线程 | C1 CompilerThread0, C2 CompilerThread0 |
GC 线程 | GC Thread0, G1 Young RemSet Sampling |
其它内部线程 | VM Periodic Task Thread, VM Thread, Service Thread |
-
自 JDK 7 及以上版本开始,HotSpot JVM 默认启用了:分层编译(Tiered Compilation),即同时使用 C1 和 C2 编译器。
编译器 | 特性 | 用途 |
---|---|---|
C1(Client Compiler) | 编译速度快,优化少 | 程序启动时使用,提升启动速度 |
C2(Server Compiler) | 编译速度慢,优化强 | 热点代码达到一定门槛后使用,提升长期运行性能 |
JVM 启动后,先解释执行字节码;热点代码首先由 C1 编译器处理;如果进一步变热,交由 C2 编译器优化;整个过程由 JVM 自动管理,不需要手动干预。
-
是否可以指定只用某个编译器?是的,你可以用 JVM 参数控制,但真没必要。
参数 | 含义 |
---|---|
-client |
使用 C1 编译器(适合启动快) |
-server |
使用 C2 编译器(适合长时间运行的服务) |
-XX:-TieredCompilation |
禁用分层编译,仅使用 -client 或 -server 指定的编译器 |
jmap: 分析运行中 Java 进程的内存使用情况
-
jmap 比较消耗内存,所以不推荐生产环境使用。
jmap -histo <pid>
: 查看类实例数量及占用内存
1 | $ jmap -histo 25238 |
jmap -heap <pid>
: 查看堆使用情况
1 | $ jmap -heap 25238 |
jmap -dump:live,format=b,file=<file> <pid>
: 创建一个快照,并保存到文件中
1 | # 创建一个快照,并保存到当前目录下 |
jcmd: 可以替代jmap
命令,jdk1.8+
推荐使用
-
jcmd
是JDK
的一个命令行工具,用于管理JVM
,jcmd
可以查看JVM
的运行状态、查看堆内存信息、触发 GC、查看线程信息、查看类信息等等。 -
生产环境不建议使用
jmap
,因其会占用大量内存,推荐使用jcmd
。
jcmd <pid> GC.heap_info
: 查看堆使用情况
-
类似
jmap -heap <pid>
1 | $ jcmd 25238 GC.heap_info |
jcmd <pid> GC.class_histogram
: 查看类加载情况
-
类似
jmap -histo <pid>
1 | $ jcmd 25238 GC.class_histogram |
jcmd <pid> GC.heap_dump /path/to/heap.hprof
: 导出 heap dump
-
替代
jmap -dump:format=b,file=heap.hprof <pid>
,但仍会有停顿(触发FullGC),dump 过程慢,生产环境慎重使用
1 | $ jcmd 25238 GC.heap_dump ./heap.hprof |
-
因为生产环境不推荐使用
jmap
或jcmd
导出 heap dump,所以我们需要在生产环境中配置如下jvm参数
1 | -XX:+HeadDumpOnOutOfMemoryError # 内存溢出(OOM)时会自动保存堆内存快照文件 |
jhat: 分析 Java Heap Dump 文件(.hprof)的工具
-
jhat(Java Heap Analysis Tool)是 JDK 附带的一个用于**分析 Java Heap Dump 文件(.hprof)**的工具。它可以将堆快照作为 HTTP 服务加载,并允许你通过浏览器交互式地查看和查询对象信息。
-
不过需要注意的是:
🔺 从 JDK 9 起,jhat 已被官方废弃,推荐使用更强大的工具如VisualVM
、Eclipse MAT
或jcmd + 外部工具
。
1 | # 默认端口7000 |
jvisualvm: 图形化的 JVM 监控与分析工具
-
jvisualvm(全称:Java VisualVM)是 Java 官方提供的一款 图形化的 JVM 监控与分析工具,用于观察、分析和调试正在运行的 Java 程序。它非常适合开发和测试环境中进行内存分析、线程分析、GC 行为观察等任务。
-
功能概览
功能 | 描述 |
---|---|
进程监控 | 查看本地或远程 JVM 进程的内存、CPU、线程等运行状态 |
内存使用分析 | 查看堆内存使用情况、GC 次数和时间、类实例分布等 |
线程分析 | 查看线程状态、线程栈、是否存在死锁 |
GC 分析 | 图形化展示 GC 活动、频率与耗时 |
堆转储分析 | 导入 .hprof 文件进行对象实例、类、引用关系分析 |
CPU 分析 | 分析方法调用路径、耗时、热点代码(需手动启用 CPU profiler) |
插件支持 | 可以通过插件安装更多功能(如 Visual GC) |
-
启动 jvisualvm
1 | # 不要在生产环境使用,如果是分析服务端 .hprof 文件,可以导出到本地后在本地启动 |
-
在 jvisualvm 中导入 .hprof 文件
1 | $ jvisualvm --openfile <heap dump file> |
-
⚠️ 注意事项
注意点 | 说明 |
---|---|
不适用于大规模生产环境监控 | 因为其分析过程可能会对 JVM 有轻微影响 |
.hprof 文件过大时加载缓慢 |
可配合 Eclipse MAT 使用 |
CPU/Memory Profiler 会增加系统负担 | 使用时谨慎,建议只在测试环境启用 |
Eclipse MAT: Java Heap Dump 分析工具
-
Eclipse MAT(Eclipse Memory Analyzer Tool)是一个用于分析 Java 堆快照的工具,它提供了许多功能来帮助开发人员理解 Java 应用程序中的内存问题。
MaxOS系统安装Eclipse MAT后,启动报错,报错信息为:The JVM shared library “/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin/…/lib/server/libjvm.dylib” does not contain the JNI_CreateJavaVM symbol.
- 解决方法:
1.在应用列表,找到MemoryAnalyzer.app
,然后右键单击后,选择显示包内容
,进入Contents目录,找到Info.plist
文件
2.打开Info.plist
文件后,可以看到注释<string>-vm</string>
配置项,我们需要做的就是打开这个配置项,并且将其设置为我们系统的Java路径,最新版需要jdk17+
1 | <string>-vm</string> |
Arthas : 推荐
-
Arthas 是阿里巴巴开源的一款 Java 诊断工具,专为线上诊断而设计。它可以帮助开发者在不重启、不修改代码的情况下,排查生产环境中 Java 应用的问题。
-
Arthas 支持 JDK 6+,支持 Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的 Tab 自动补全功能,进一步方便进行问题的定位和诊断。
-
这里要注意一点,就是Arthas从
4.x
版本开始,需要依赖JDK8+
,如果是JDK6/7
,可以下载最后一个3.x版本 3.7.2。 -
为了方便我们学习,Arthas还为我们提供了一个练习场,Arthas Playground。
Arthas 核心特点
-
无需重启、侵入性低
Arthas 可以 attach 到正在运行的 JVM 上,不需要重启服务或修改源代码。 -
命令式交互体验
类似 Linux shell 的操作方式,支持 tab 补全、上下键历史命令等,非常直观。 -
实时查看和监控
可查看方法参数、返回值、调用栈、执行耗时、JVM 线程、内存等实时数据。 -
多种连接方式
支持命令行终端、本地 shell、Web 页面、telnet 等多种连接方式。 -
支持多种 JVM 版本
支持 Java 6+,包括 OpenJDK、Oracle JDK、Alibaba Dragonwell 等。
Arthas 常用命令
启动
1 | java -jar arthas-boot.jar |
-
arthas 命令有很多,当常用的也就几个,另外如果记不住某个命令怎么用了可以通过如下命令查询
1 | <command> -h 或者 help <command> |
dashboard : 实时查看 JVM 运行状态
1 | # 查看 JVM 运行状态, 默认每隔 5 秒刷新 CPU、内存、线程、GC 等信息 |
thread : 查看当前 JVM 线程信息
1 | # 查看当前 JVM 线程信息,按 CPU 使用率倒序 |
sc : 查看 JVM 已加载的类信息
-
Search-Class
的简写,这个命令能搜索出所有已经加载到 JVM 中的 Class 信息
1 | # 通配符匹配,打印搜索到的类全名 |
sm : 查看已加载类的方法信息
-
Search-Method
的简写,这个命令能搜索出所有已经加载到 JVM 中的 Method 信息
1 | # 打印指定类中的方法 |
jad : Java源码反编译工具
-
验证生产环境下的代码是否与提交的代码一致
1 | # 打印出类的源码,类名 |
1 | # 反编译后的源码会有类加载器的信息,如果只希望打印源码,可以加上 --source-only |
-
当有多个 ClassLoader 都加载了这个类时,jad 命令会输出对应 ClassLoader 实例的 hashcode,然后你只需要重新执行 jad 命令,并使用参数 -c
就可以反编译指定 ClassLoader 加载的那个类了;
1 | $ jad org.apache.log4j.Logger |
mc : Memory Compiler/内存编译器,编译.java
文件生成.class
1 | # 编译,这里一定要指定类加载器,否则会报错,因为你编译的类中可能import其它类,不指定就会提示找不到对应的类。 |
-
mc 命令有可能失败。如果编译失败可以在本地编译好.class文件,再上传到服务器。
retransform : 重新加载类(热修改代码)
1 | retransform /tmp/com/example/demo/arthas/user/UserController.class |
sysprop : 查看当前 JVM 的系统属性
1 | # 打印系统属性 |
sysenv : 查看当前 JVM 的环境变量
1 | # 列出所有环境变量 |
jvm : 查看当前 JVM 的信息
1 | jvm |
-
THREAD
相关
1 | COUNT: JVM 当前活跃的线程数 |
-
FILE-DESCRIPTOR
文件描述符相关
1 | MAX-FILE-DESCRIPTOR-COUNT:JVM 进程最大可以打开的文件描述符数 |
watch : 函数执行数据观测
-
让你能方便的观察到指定函数的调用情况。能观察到的范围为:返回值、抛出异常、入参,通过编写 OGNL 表达式进行对应变量的查看。
1 | # 观察指定类中某个方法的调用情况,className + methodName + 返回值表达式 |
-
watch 命令表达式支持变量说明表
名称 | 类型/说明 | 作用/含义 |
---|---|---|
loader |
ClassLoader |
方法所在类的类加载器对象 |
clazz |
Class<?> |
方法所属的类(Class 对象) |
method |
java.lang.reflect.Method |
被调用的方法的反射对象 |
target |
Object |
方法的调用目标对象(即 this 对象),静态方法中为 null |
params |
Object[] |
方法的入参数组 |
returnObj |
Object |
方法的返回值 |
throwExp |
Throwable |
方法抛出的异常 |
isBefore |
boolean |
当前执行位置是否是方法调用之前(BEFORE 阶段) |
isThrow |
boolean |
当前方法是否以异常结束(THROWS 阶段) |
isReturn |
boolean |
当前方法是否正常返回(RETURN 阶段) |
jobs : 查看运行中的任务
1 | # 如下命令会转到后台运行 ,与linux命令类似,命令最后加上 `&` 即可 |
logger : 查看 logger 信息,更新 logger level
1 | # 查看 logger 信息 |
classloader : 查看 classloader 的继承树,urls,类加载信息
1 | # 查看 classloader 的信息,加载class的数量,hashcode,classloader 的 parent |