关于 java-profiling
java-profiling 是面向工程师的 JVM 性能诊断参考技能,涵盖 JFR、jcmd、垃圾回收、内存、CPU 和 JIT 分析,并提供命令与故障排查建议。
主要功能
- 01JFR 录制与分析
- 02jcmd 进程和内存诊断
- 03GC 选择、日志与调优
- 04CPU、分配和锁分析
- 05JIT 与内存故障排查
适用场景
- 01排查 CPU 使用率过高或线程争用
- 02诊断堆或元空间耗尽
- 03检查垃圾回收暂停
- 04分析内存分配和锁行为
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ claude-dev-suite/ claude-dev-suite --skill java-profiling安装后在 Claude Code 中输入/java-profiling确认已加载。
详细介绍
Java/JVM 性能分析
不适用场景
- Node.js/JavaScript 性能分析 - 使用
nodejs-profiling技能分析 V8 性能分析器和堆 - Python 性能分析 - 使用
python-profiling技能分析 cProfile 和 tracemalloc - 应用层优化 - 本技能用于 JVM 层级的性能分析,而非算法优化
- 数据库查询调优 - 使用数据库专用的性能分析工具
- 前端性能 - 使用浏览器 DevTools 进行客户端性能分析
深入了解:使用技术为
java的mcp__documentation__fetch_docs,全面了解 JFR 配置、GC 调优和 JVM 诊断。
Java Flight Recorder(JFR)
启动 JFR
# Start recording with application
java -XX:+FlightRecorder \
-XX:StartFlightRecording=duration=60s,filename=recording.jfr \
-jar app.jar
# Start recording on running JVM
jcmd <pid> JFR.start duration=60s filename=recording.jfr
# Continuous recording (always-on)
java -XX:+FlightRecorder \
-XX:FlightRecorderOptions=stackdepth=256 \
-XX:StartFlightRecording=disk=true,maxsize=500m,maxage=1d \
-jar app.jar
# Dump current recording
jcmd <pid> JFR.dump filename=dump.jfr
JFR 配置
# custom.jfc
<?xml version="1.0" encoding="UTF-8"?>
<configuration version="2.0">
<event name="jdk.CPULoad">
<setting name="enabled">true</setting>
<setting name="period">1 s</setting>
</event>
<event name="jdk.GCHeapSummary">
<setting name="enabled">true</setting>
</event>
<event name="jdk.ObjectAllocationInNewTLAB">
<setting name="enabled">true</setting>
<setting name="stackTrace">true</setting>
</event>
</configuration>
分析 JFR 文件
# Print JFR summary
jfr summary recording.jfr
# Print specific events
jfr print --events jdk.CPULoad recording.jfr
jfr print --events jdk.ExecutionSample --json recording.jfr
# Export to JSON
jfr print --json recording.jfr > recording.json
jcmd 诊断
进程信息
# List all Java processes
jcmd
# VM info
jcmd <pid> VM.version
jcmd <pid> VM.flags
jcmd <pid> VM.system_properties
jcmd <pid> VM.command_line
# Thread dump
jcmd <pid> Thread.print
# Heap info
jcmd <pid> GC.heap_info
# Class histogram
jcmd <pid> GC.class_histogram
内存分析
# Native memory tracking (requires -XX:NativeMemoryTracking=summary)
jcmd <pid> VM.native_memory summary
# Heap dump
jcmd <pid> GC.heap_dump /path/to/dump.hprof
# Force GC
jcmd <pid> GC.run
GC 调优
GC 选择
# G1GC (default in JDK 9+, recommended for heap > 4GB)
java -XX:+UseG1GC -jar app.jar
# ZGC (low latency, JDK 15+)
java -XX:+UseZGC -jar app.jar
# Shenandoah (low latency, OpenJDK)
java -XX:+UseShenandoahGC -jar app.jar
# Parallel GC (throughput)
java -XX:+UseParallelGC -jar app.jar
GC 日志
# JDK 9+ unified logging
java -Xlog:gc*:file=gc.log:time,uptime,level,tags:filecount=5,filesize=10m \
-jar app.jar
# Common GC flags
java -XX:+PrintGCDetails \
-XX:+PrintGCDateStamps \
-XX:+PrintTenuringDistribution \
-Xloggc:gc.log \
-jar app.jar
G1GC 调优
java -XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \ # Target pause time
-XX:G1HeapRegionSize=16m \ # Region size
-XX:InitiatingHeapOccupancyPercent=45 \ # Start marking at 45%
-XX:G1ReservePercent=10 \ # Reserve for promotions
-XX:ConcGCThreads=4 \ # Concurrent GC threads
-XX:ParallelGCThreads=8 \ # Parallel GC threads
-jar app.jar
内存优化
堆大小设置
# Set heap size
java -Xms4g -Xmx4g -jar app.jar # Fixed heap (recommended for production)
# Metaspace sizing
java -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -jar app.jar
# Direct memory
java -XX:MaxDirectMemorySize=256m -jar app.jar
内存泄漏检测
// Common leak patterns
// ❌ Bad: Static collections that grow
private static List<Object> cache = new ArrayList<>();
public void process(Object obj) {
cache.add(obj); // Never removed
}
// ✅ Good: Bounded cache with eviction
private static final Cache<String, Object> cache = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(Duration.ofMinutes(10))
.build();
// ❌ Bad: Unclosed resources
public void readFile(String path) {
InputStream is = new FileInputStream(path);
// Missing is.close()
}
// ✅ Good: Try-with-resources
public void readFile(String path) {
try (InputStream is = new FileInputStream(path)) {
// Process
}
}
// ❌ Bad: ThreadLocal not cleaned
private static ThreadLocal<Connection> connHolder = new ThreadLocal<>();
public void process() {
connHolder.set(getConnection());
// Missing connHolder.remove()
}
// ✅ Good: Always clean ThreadLocal
public void process() {
try {
connHolder.set(getConnection());
// Process
} finally {
connHolder.remove();
}
}
CPU 性能分析
async-profiler(推荐)
# Profile CPU
./profiler.sh -d 30 -f profile.html <pid>
# Profile allocations
./profiler.sh -d 30 -e alloc -f alloc.html <pid>
# Profile locks
./profiler.sh -d 30 -e lock -f lock.html <pid>
# Flame graph output
./profiler.sh -d 30 -f flamegraph.html -o flamegraph <pid>
JMC(Java Mission Control)
# Open JFR recording in JMC
jmc recording.jfr
常见瓶颈模式
同步问题
// ❌ Bad: Coarse-grained locking
public synchronized void process(String key, Object value) {
cache.put(key, value);
compute(value);
}
// ✅ Good: Fine-grained locking
private final ConcurrentHashMap<String, Object> cache = new ConcurrentHashMap<>();
public void process(String key, Object value) {
cache.put(key, value); // Lock-free for different keys
compute(value);
}
// ✅ Good: Read-write lock
private final ReadWriteLock lock = new ReentrantReadWriteLock();
public Object get(String key) {
lock.readLock().lock();
try { return cache.get(key); }
finally { lock.readLock().unlock(); }
}
字符串操作
// ❌ Bad: String concatenation in loop
String result = "";
for (String s : list) {
result += s; // Creates new String each iteration
}
// ✅ Good: StringBuilder
StringBuilder sb = new StringBuilder();
for (String s : list) {
sb.append(s);
}
String result = sb.toString();
// ✅ Good: String.join for simple cases
String result = String.join("", list);
集合优化
// ❌ Bad: ArrayList when size known
List<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
list.add(getData(i)); // Multiple resizes
}
// ✅ Good: Pre-size collections
List<String> list = new ArrayList<>(10000);
// ✅ Good: Use primitive collections for performance
// Use Eclipse Collections, Trove, or fastutil
IntList list = new IntArrayList(10000);
装箱/拆箱
// ❌ Bad: Autoboxing in hot path
public long sum(List<Long> numbers) {
long sum = 0;
for (Long n : numbers) {
sum += n; // Unboxing each iteration
}
return sum;
}
// ✅ Good: Use primitive streams
public long sum(List<Long> numbers) {
return numbers.stream().mapToLong(Long::longValue).sum();
}
// ✅ Good: Primitive arrays when possible
public long sum(long[] numbers) {
return Arrays.stream(numbers).sum();
}
JIT 优化
预热
// Warm-up critical paths before measuring
public static void main(String[] args) {
// Warm-up phase
for (int i = 0; i < 10_000; i++) {
criticalMethod(i);
}
// Measurement phase
long start = System.nanoTime();
for (int i = 0; i < 100_000; i++) {
criticalMethod(i);
}
long duration = System.nanoTime() - start;
}
JIT 日志
# Print JIT compilation
java -XX:+PrintCompilation -jar app.jar
# Print inlining decisions
java -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining -jar app.jar
# Disable specific optimizations for debugging
java -XX:+UnlockDiagnosticVMOptions -XX:DisableIntrinsic=_hashCode -jar app.jar
性能分析检查清单
| 检查项 | 工具 | 命令 |
|---|---|---|
| CPU 热点 | JFR | jcmd <pid> JFR.start |
| 内存使用情况 | jcmd | jcmd <pid> GC.heap_info |
| GC 行为 | GC 日志 | -Xlog:gc* |
| 线程争用 | JFR | jdk.JavaMonitorWait 事件 |
| 内存泄漏 | 堆转储 | jcmd <pid> GC.heap_dump |
| 类加载 | jcmd | jcmd <pid> VM.classloaders |
生产环境标志
java \
-server \
-Xms4g -Xmx4g \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:+UseStringDeduplication \
-XX:+AlwaysPreTouch \
-XX:+DisableExplicitGC \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/var/log/heap.hprof \
-Xlog:gc*:file=/var/log/gc.log:time,uptime:filecount=5,filesize=10m \
-jar app.jar
反模式
| 反模式 | 错误原因 | 正确做法 |
|---|---|---|
热路径中使用 System.out.println() | 极慢的同步 I/O | 使用带异步 appender 的 SLF4J |
| 创建大量短生命周期对象 | 增加 GC 压力和分配开销 | 复用对象,使用对象池 |
热路径中使用 synchronized | 线程争用,可扩展性差 | 使用 ConcurrentHashMap、ReentrantLock 或无锁结构 |
在循环中使用 + 拼接字符串 | 会创建大量中间字符串 | 使用 StringBuilder |
| 在循环中自动装箱 | 会创建包装对象 | 使用原始类型 |
| 未设置集合大小 | 频繁调整大小,造成内存波动 | 使用 new ArrayList<>(expectedSize) 预设大小 |
使用 finalize() 清理资源 | 不可预测且已弃用 | 使用 try-with-resources 或显式清理 |
| 忽略 GC 日志 | 会错过性能下降问题 | 始终在生产环境启用 GC 日志 |
| 对所有情况使用相同的堆配置 | GC 暂停时间与工作负载不匹配 | 根据应用行为调整堆配置 |
| 不使用连接池 | 创建连接会产生开销 | 使用 HikariCP 或类似工具 |
快速故障排查
| 问题 | 诊断 | 解决方案 |
|---|---|---|
| GC 暂停时间过长 | 堆过大或 GC 配置不当 | 使用 ZGC/Shenandoah,或调整 G1GC 暂停目标 |
OutOfMemoryError: Java heap space | 内存泄漏或堆过小 | 分析堆转储;必要时增大 -Xmx |
OutOfMemoryError: Metaspace | 加载的类过多 | 增大 -XX:MaxMetaspaceSize,或修复类加载器泄漏 |
| CPU 使用率过高 | 热点循环、算法效率低 | 使用 JFR 或 async-profiler 进行 CPU 性能分析 |
| 线程竞争 | 锁竞争 | 分析线程转储,缩小锁的作用范围 |
| 启动缓慢 | 类加载、初始化 | 使用 AppCDS、延迟初始化 |
| 内存泄漏 | 资源未关闭、静态集合 | 对比堆转储,找出持续增长的对象 |
| GC 开销超过限制 | GC 耗时超过 98% | 修复内存泄漏或增大堆 |
| Full GC 过于频繁 | 老年代不断被填满 | 调整堆比例,修复对象晋升问题 |
| 应用无响应 | 死锁或 GC 耗时过长 | 通过线程转储查找死锁,检查 GC 日志 |
相关技能
评论
支持 Markdown