MENU

Javaのガベージコレクションを徹底理解!メモリリーク対策まで解説

こんにちは!バックエンドエンジニアのキョンです。今回は、開発現場で悩まされがちな「ガベージコレクション(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」って言葉を聞いて混乱しましたよね。実はシンプルな考え方なんです:

  1. Young世代(新生代)
    • 新しいオブジェクトが配置される
    • GCが頻繁に発生
    • Minor GCとも呼ばれる
  2. 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. 監視とトラブルシューティング

メモリリークの検出

実際の現場で使用しているツール群:

  1. 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は「黒魔術」なんて言われることもありますが、基本を押さえれば怖くありません。

私の経験から、以下のポイントを意識すると良いでしょう:

  1. メモリリークを防ぐための適切なコーディング
  2. 定期的なメモリ使用状況の監視
  3. 適切なGCログの設定と分析
  4. 必要に応じたGCチューニング

さらなる学習のために

  • Java Performance: The Definitive Guide
  • Java SE Documentation
  • GC Tuning Guide

次回は「Javaのスレッド管理について」解説する予定です。お楽しみに!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次