こんにちは!バックエンドエンジニアのキョンです。今回は、開発現場で悩まされがちな「ガベージコレクション(GC)」について、実務での経験を交えながら詳しく解説していきます。
目次
1. ガベージコレクションとは
GCの基本
入社当時、私はよく「なんでJavaってGCがあるの?」って思っていました。C++とかだと手動でメモリ管理するのに…
実は、GCには重要な役割があります:
- 不要になったメモリの自動解放
- メモリリークの防止
- 開発者の生産性向上
メモリ領域の基本
public class MemoryExample {
// ヒープ領域に確保される
private List<String> heapData = new ArrayList<>();
public void method() {
// スタック領域に確保される
int localVar = 10;
// ヒープ領域に確保される
String str = new String("こんにちは");
}
}
2. GCのメカニズム
世代別GC
新人エンジニアの頃、「Young GC」とか「Old GC」って言葉を聞いて混乱しましたよね。実はシンプルな考え方なんです:
- Young世代(新生代)
- 新しいオブジェクトが配置される
- GCが頻繁に発生
- Minor GCとも呼ばれる
- Old世代(老年代)
- 長生きしたオブジェクトが昇格する
- GCは比較的少ない
- Major GCとも呼ばれる
GCアルゴリズムの種類
// JVMオプションでGCの種類を指定
// 例:G1GCを使用する場合
java -XX:+UseG1GC MyApplication
// 例:Parallel GCを使用する場合
java -XX:+UseParallelGC MyApplication
3. 実際のメモリリーク事例と対策
よくあるメモリリークパターン
実際のプロジェクトで遭遇した例です:
public class CacheManager {
// 悪い例:際限なく増えるキャッシュ
private static Map<String, Object> cache = new HashMap<>();
public void addToCache(String key, Object value) {
cache.put(key, value); // キャッシュの肥大化!
}
}
// 改善例:サイズ制限付きキャッシュ
public class ImprovedCacheManager {
private static final int MAX_ENTRIES = 1000;
private static Map<String, Object> cache =
new LinkedHashMap<String, Object>(MAX_ENTRIES, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<String, Object> eldest) {
return size() > MAX_ENTRIES;
}
};
}
コレクションのメモリリーク
// 悪い例:リスナーの解除忘れ
public class EventManager {
private List<EventListener> listeners = new ArrayList<>();
public void addListener(EventListener listener) {
listeners.add(listener);
}
// removeListenerメソッドがない!
}
// 良い例:適切なリスナー管理
public class ImprovedEventManager {
private List<WeakReference<EventListener>> listeners = new ArrayList<>();
public void addListener(EventListener listener) {
listeners.add(new WeakReference<>(listener));
}
public void removeListener(EventListener listener) {
listeners.removeIf(ref -> ref.get() == null || ref.get() == listener);
}
}
4. GCチューニングの実践
メモリ設定の最適化
実運用で使用している設定例:
# 本番環境でよく使用する設定例
java -Xms2g -Xmx2g # 初期ヒープ2GB、最大ヒープ2GB
-XX:NewRatio=2 # New:Old = 1:2
-XX:+UseG1GC # G1GCを使用
MyApplication
GCログの設定
# GCログを有効化する設定
java -Xms2g -Xmx2g
-Xloggc:/var/log/gc.log
-XX:+UseGCLogFileRotation
-XX:NumberOfGCLogFiles=10
-XX:GCLogFileSize=10M
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
MyApplication
5. 監視とトラブルシューティング
メモリリークの検出
実際の現場で使用しているツール群:
- JVisualVM
# JVisualVMの起動
jvisualvm
2.jmapによる解析
# ヒープダンプの取得
jmap -dump:format=b,file=heap.bin <pid>
# ヒープ統計の表示
jmap -heap <pid>
GC問題の特定と解決
// メモリリークの可能性がある処理のプロファイリング
public class MemoryProfiler {
public static void profileMemoryUsage(Runnable task) {
Runtime rt = Runtime.getRuntime();
long beforeUsed = rt.totalMemory() - rt.freeMemory();
task.run();
long afterUsed = rt.totalMemory() - rt.freeMemory();
System.out.println("メモリ使用量: " + (afterUsed - beforeUsed) + " bytes");
}
}
まとめ
GCは「黒魔術」なんて言われることもありますが、基本を押さえれば怖くありません。
私の経験から、以下のポイントを意識すると良いでしょう:
- メモリリークを防ぐための適切なコーディング
- 定期的なメモリ使用状況の監視
- 適切なGCログの設定と分析
- 必要に応じたGCチューニング
さらなる学習のために
- Java Performance: The Definitive Guide
- Java SE Documentation
- GC Tuning Guide
次回は「Javaのスレッド管理について」解説する予定です。お楽しみに!
コメント