下面我们总结一下常用的工具及使用方法:
前提条件:
- 确保已安装 JDK (Java Development Kit) 并且
JAVA_HOME
环境变量已正确配置。 - 假设我们有一个正在运行的 Java 应用程序。 为了演示,我创建一个简单的 Java 程序,模拟一些特殊情况。
示例 Java 程序 (SimpleApp.java):
java
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
public class SimpleApp {
private static final int LIST_SIZE = 100000;
private static final int SLEEP_TIME = 100; // Milliseconds
public static void main(String[] args) throws InterruptedException {
List<Integer> data = new ArrayList<>();
Random random = new Random();
while (true) {
// Simulate memory allocation
for (int i = 0; i < 100; i++) {
data.add(random.nextInt(1000));
}
// Simulate some CPU intensive work
int sum = 0;
for (Integer num : data) {
sum += num;
}
// Simulate some IO wait
Thread.sleep(SLEEP_TIME);
// Periodically clear some memory
if (data.size() > LIST_SIZE) {
data.subList(0, LIST_SIZE / 2).clear();
System.out.println("List size: " + data.size());
}
}
}
}
-
编译和运行 SimpleApp.java:
bashjavac SimpleApp.java java SimpleApp
让程序在后台运行。 可以使用
nohup java SimpleApp &
以确保程序在终端关闭后可以继续运行。
监控步骤及输出示例:
1. 使用 jps
找到进程 ID (PID):
bash
jps -l
输出:
bash
12345 SimpleApp
67890 sun.tools.jps.Jps // jps 进程本身
我们一定要记住 SimpleApp
的进程 ID (在这个例子中是 12345
)。 之后所有的命令都需要用到这个 ID。
2. 使用 jstat
监控 JVM 统计信息:
- 监控 GC 汇总信息:
bash
jstat -gc 12345 1000 // 每秒刷新一次
输出
bash
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
15360.0 15360.0 0.0 1024.0 256000.0 131072.0 524288.0 262144.0 11264.0 10496.0 1280.0 1152.0 23 0.256 2 0.123 0.379
15360.0 15360.0 0.0 1024.0 256000.0 131072.0 524288.0 262144.0 11264.0 10496.0 1280.0 1152.0 23 0.256 2 0.123 0.379
15360.0 15360.0 0.0 1024.0 256000.0 131072.0 524288.0 262144.0 11264.0 10496.0 1280.0 1152.0 23 0.256 2 0.123 0.379
解释
-
S0C
,S1C
: Survivor 0/1 的容量 (Capacity)。S0U
,S1U
: Survivor 0/1 的使用量 (Utilization)。EC
: Eden 区的容量。EU
: Eden 区的使用量。OC
: 老年代的容量。OU
: 老年代的使用量。MC
: Metaspace 的容量。MU
: Metaspace 的使用量。CCSC
: 压缩类空间 (Compressed Class Space) 的容量。CCSU
: 压缩类空间的使用量。YGC
: Young GC (Minor GC) 的次数。YGCT
: Young GC 的总时间。FGC
: Full GC 的次数。FGCT
: Full GC 的总时间。GCT
: 总 GC 时间。
-
监控 GC 容量:
bash
jstat -gccapacity 12345
输出示例:
bash
NGCMN NGCMX NGC S0C S1C EC OGCMN OGCMX OGC MC CCSC YGC FGC
21504.0 699392.0 28672.0 15360.0 15360.0 28672.0 532480.0 1398784.0 532480.0 11264.0 1280.0 23 2
解释:
-
NGCMN
: 新生代最小容量。 -
NGCMX
: 新生代最大容量。 -
NGC
: 新生代当前容量。 -
S0C
,S1C
,EC
: 和上面相同。 -
OGCMN
: 老年代最小容量。 -
OGCMX
: 老年代最大容量。 -
OGC
: 老年代当前容量。 -
MC
: Metaspace 容量。 -
CCSC
: 压缩类空间容量。 -
YGC
,FGC
: Young GC 和 Full GC 次数。 -
监控 GC 利用率:
bashjstat -gcutil 12345
输出示例:
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT 0.00 6.67 52.25 50.00 93.20 89.84 23 0.256 2 0.123 0.379
解释:
S0
,S1
: Survivor 0/1 使用率。E
: Eden 区使用率。O
: 老年代使用率。M
: Metaspace 使用率。CCS
: 压缩类空间使用率。YGC
,YGCT
,FGC
,FGCT
,GCT
: 和上面相同。
3. 使用 jinfo
查看 JVM 配置:
bash
jinfo -flags 12345
输出示例:
bash
Attaching to process ID 12345, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 21.0.2+13-LTS-58
Non-default VM flags:
-XX:CICompilerCount=3
-XX:InitialHeapSize=268435456
-XX:MaxHeapSize=4294967296
-XX:MetaspaceSize=21807104
-XX:+UseCompressedClassPointers
-XX:+UseCompressedOops
-XX:+UseSerialGC
Command line:
-java.compiler=NONE
解释: 我们可以看到堆大小 (InitialHeapSize, MaxHeapSize),GC 算法 (UseSerialGC) 等信息。
4. 使用 jstack
查看线程堆栈信息:
bash
jstack 12345
输出示例 (节选):
bash
Full thread dump OpenJDK 64-Bit Server VM (21.0.2+13-LTS-58, mixed mode, sharing):
"main" #1 prio=5 os_prio=0 cpu=5.23ms elapsed=44.25s tid=0x00000176d88845d0 nid=0x307c runnable [0x0000005f4d2ffd80]
java.lang.Thread.State: RUNNABLE
at SimpleApp.main(SimpleApp.java:23)
"Reference Handler" #2 daemon prio=10 os_prio=2 cpu=0.00ms elapsed=44.25s tid=0x00000176d8897330 nid=0x3114 waiting on condition [0x0000005f4d3ff000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait([email protected]/Native Method)
- waiting on <0x00000000c0000540> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait([email protected]/Object.java:339)
at java.lang.ref.Reference.waitForReferencePendingList([email protected]/Reference.java:241)
- locked <0x00000000c0000540> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference.processPendingReferences([email protected]/Reference.java:213)
at java.lang.ref.Reference$ReferenceHandler.run([email protected]/Reference.java:174)
"Finalizer" #3 daemon prio=8 os_prio=1 cpu=0.00ms elapsed=44.25s tid=0x00000176d8897e90 nid=0x30f8 in Object.wait() [0x0000005f4d4ff000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait([email protected]/Native Method)
- waiting on <0x00000000c0000630> (a java.lang.ref.Finalizer$Lock)
at java.lang.ref.Finalizer$FinalizerThread.run([email protected]/Finalizer.java:172)
解释: 显示了每个线程的堆栈跟踪。 main
线程正在执行 SimpleApp.main
方法,并且处于 RUNNABLE
状态。 其他线程 (如 Reference Handler
, Finalizer
) 是 JVM 内部线程。
5. 使用 jmap
生成 Heap Dump 并分析:
bash
jmap -dump:format=b,file=heapdump.bin 12345
这将生成一个名为 heapdump.bin
的文件。 你可以使用诸如 Eclipse Memory Analyzer Tool (MAT) 或 VisualVM 之类的工具来分析这个文件。
-
使用
jmap -histo
(谨慎使用):bashjmap -histo 12345 | head -n 20 # 只显示前20行
输出示例:
num #instances #bytes className ---------------------------------------------- 1: 325316 37106776 java.lang.Integer 2: 99991 2399784 java.util.ArrayList 3: 32328 775872 java.lang.String 4: 2779 239824 [B 5: 1501 126424 java.util.HashMap$Node 6: 403 49568 java.lang.Class 7: 829 33160 [C 8: 446 28544 java.lang.reflect.Method 9: 457 21936 java.lang.invoke.MethodHandleNatives$Lookup 10: 373 14920 java.util.concurrent.ConcurrentHashMap$Node 11: 372 11904 java.lang.invoke.MethodHandle$PolymorphicInvoker 12: 361 11552 java.util.LinkedHashMap$Entry 13: 278 8896 [Ljava.lang.Object; 14: 141 8472 java.util.HashMap 15: 92 7360 java.lang.invoke.MethodHandle 16: 150 6000 java.io.FileDescriptor 17: 185 5920 java.util.LinkedHashMap 18: 135 5400 sun.nio.cs.UTF_8$Encoder 19: 65 5200 java.util.WeakHashMap$Entry
解释: 显示了堆中各个类别的实例数量和总字节数。 可以看到
java.lang.Integer
占用了大量的内存,这符合我们的程序逻辑。
警告 :jmap -histo
可能导致 JVM 停顿一段时间,尤其是堆很大的时候。 尽量使用Heap Dump文件分析代替它.
6. 使用 jcmd
(推荐):
-
查看所有可用的命令:
bashjcmd 12345 help
-
获取 GC 统计信息:
bashjcmd 12345 GC.stats
输出示例: (类似
jstat -gc
的输出,但更详细)bash12345: Timestamp: 2024-01-26T14:30:00.123+0800 Full GC count: 2 Full GC elapsed time: 0.123 seconds Young GC count: 23 Young GC elapsed time: 0.256 seconds Heap memory usage: Committed: 819200 KB Used: 524288 KB Eden space: Capacity: 262144 KB Used: 131072 KB
-
打印线程堆栈信息 (包含锁信息):
bashjcmd 12345 Thread.print -l
输出示例: (类似
jstack -l
的输出) -
请求 Full GC (谨慎使用):
bashjcmd 12345 GC.run_fullgc
警告: 手动触发 Full GC 可能会导致应用程序停顿。 只在必要时使用.
-
创建 Heap Dump:
bashjcmd 12345 GC.heap_dump filename=heapdump.hprof
实践:
jps
是基础: 始终先用jps
找到 PID。jstat
监控: 使用jstat
监控一段时间,观察 GC 频率,内存使用情况。 如果 Young GC 太频繁,说明新生代太小。 如果 Full GC 频繁,说明老年代或者 Metaspace 内存不够。jinfo
查看配置: 使用jinfo
确认 JVM 参数是否符合预期。jstack
查线程: 当发现 CPU 占用率高,或者程序卡顿的时候, 使用jstack
查看线程状态,找出问题线程。jmap
分析堆: 当怀疑内存泄漏时, 使用jmap
生成 Heap Dump, 使用 MAT 或 VisualVM 等工具进行分析。jcmd
是首选: 尽可能使用jcmd
,因为它更强大和灵活。- 监控频率: 合理设置监控频率。 频繁的监控会增加 JVM 的负担, 太低的频率可能无法及时发现问题。
案例分析:
假设在使用 jstat
监控一段时间后,发现 FGC
(Full GC) 的次数在不断增加,而且 FGCT
(Full GC 时间) 也很长。 这说明程序可能存在内存泄漏,或者老年代内存不足。
- 使用
jmap -histo:live <PID>
: 观察存活对象,找出占用内存最多的对象,初步判断可能的内存泄漏点。 - 使用
jmap -dump:format=b,file=heapdump.bin <PID>
: 生成 Heap Dump 文件。 - 使用 Eclipse MAT 或 VisualVM 打开
heapdump.bin
: 分析对象引用链,找出是谁持有了这些对象,导致它们无法被回收。
总结:
通过这些命令行工具,我们可以了解 JVM 的运行状态,及时发现和解决性能问题。 jcmd
是一个非常强大的工具,建议深入学习和使用。