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 | 
- 
GARBAGE-COLLECTORSGC 相关
- 
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 | 
ognl : 执行 ognl 表达式
| 1 | # 调用静态函数 | 
vmoption : 查看,更新 VM 诊断相关的参数
| 1 | # 查看所有的 option | 
vmtool : 实现查询内存对象,强制 GC 等功能
| 1 | # 获取对象,--action getInstances: 获取对象实例,--className: 指定类名,--limit: 指定获取数量 | 
 
 
 
 
 
 
 
 
 
