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 のプロファイリング - V8 プロファイラーとヒープ分析には
nodejs-profilingスキルを使用してください - Python のプロファイリング - cProfile と tracemalloc には
python-profilingスキルを使用してください - アプリケーションレベルの最適化 - これは JVM レベルのプロファイリング用であり、アルゴリズムの最適化用ではありません
- データベースクエリのチューニング - データベース固有のプロファイリングツールを使用してください
- フロントエンドのパフォーマンス - クライアントサイドのプロファイリングにはブラウザーの DevTools を使用してください
詳細な知識:包括的な JFR 設定、GC チューニング、JVM 診断については、テクノロジーに
javaを指定してmcp__documentation__fetch_docsを使用してください。
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 が発生します | 非同期アペンダーを備えた 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 の頻度が高すぎる | Old 世代がいっぱいになっています | ヒープ比率を調整し、オブジェクトの昇格に関する問題を修正します |
| アプリケーションが応答しない | デッドロック、または長い GC | スレッドダンプでデッドロックを特定し、GC ログを確認します |
関連スキル
コメント
Markdown 対応