下面我们总结一下常用的工具及使用方法:
前提条件:
JAVA_HOME
环境变量已正确配置。示例 Java 程序 (SimpleApp.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:
javac SimpleApp.java
java SimpleApp
让程序在后台运行。 可以使用 nohup java SimpleApp &
以确保程序在终端关闭后可以继续运行。
监控步骤及输出示例:
1. 使用 jps
找到进程 ID (PID):
jps -l
输出:
12345 SimpleApp
67890 sun.tools.jps.Jps // jps 进程本身
我们一定要记住 SimpleApp
的进程 ID (在这个例子中是 12345
)。 之后所有的命令都需要用到这个 ID。
2. 使用 jstat
监控 JVM 统计信息:
jstat -gc 12345 1000 // 每秒刷新一次
输出
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 容量:
jstat -gccapacity 12345
输出示例:
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 利用率:
jstat -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 配置:
jinfo -flags 12345
输出示例:
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
查看线程堆栈信息:
jstack 12345
输出示例 (节选):
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 并分析:
jmap -dump:format=b,file=heapdump.bin 12345
这将生成一个名为 heapdump.bin
的文件。 你可以使用诸如 Eclipse Memory Analyzer Tool (MAT) 或 VisualVM 之类的工具来分析这个文件。
使用 jmap -histo
(谨慎使用):
jmap -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
(推荐):
查看所有可用的命令:
jcmd 12345 help
获取 GC 统计信息:
jcmd 12345 GC.stats
输出示例: (类似 jstat -gc
的输出,但更详细)
12345:
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
打印线程堆栈信息 (包含锁信息):
jcmd 12345 Thread.print -l
输出示例: (类似 jstack -l
的输出)
请求 Full GC (谨慎使用):
jcmd 12345 GC.run_fullgc
警告: 手动触发 Full GC 可能会导致应用程序停顿。 只在必要时使用.
创建 Heap Dump:
jcmd 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
,因为它更强大和灵活。案例分析:
假设在使用 jstat
监控一段时间后,发现 FGC
(Full GC) 的次数在不断增加,而且 FGCT
(Full GC 时间) 也很长。 这说明程序可能存在内存泄漏,或者老年代内存不足。
jmap -histo:live
: 观察存活对象,找出占用内存最多的对象,初步判断可能的内存泄漏点。jmap -dump:format=b,file=heapdump.bin
: 生成 Heap Dump 文件。heapdump.bin
: 分析对象引用链,找出是谁持有了这些对象,导致它们无法被回收。总结:
通过这些命令行工具,我们可以了解 JVM 的运行状态,及时发现和解决性能问题。 jcmd
是一个非常强大的工具,建议深入学习和使用。