はじめに:メモリリークとは?
Javaは「ガーベジコレクション(GC)」によって使われなくなったオブジェクトを自動的に回収してくれます。
そのため、「C言語みたいにメモリ解放をしなくていい」と思われがちです。
しかし実際には、Javaでもメモリリークは起こります。
メモリリークとは、
「もう使わないはずのオブジェクトが、参照を持たれ続けていてGCが回収できない状態」
のことです。
時間が経つにつれてメモリ使用量が増え続け、最終的にはOutOfMemoryError やパフォーマンス低下を引き起こします。
メモリリークが起きる主な原因
1. 静的フィールド(static変数)に参照を残す
static フィールドは、クラスがロードされている限りメモリ上に残り続けます。
その中に大きなオブジェクトを保持してしまうと、
GCはそれを「まだ使われている」と判断して回収しません。
|
1 2 3 4 5 6 7 8 |
public class LeakExample { private static List<String> cache = new ArrayList<>(); public static void addData(String data) { cache.add(data); } } |
このように cache にデータを追加し続けると、アプリ終了までメモリが増え続けます。
使い終わったデータは cache.clear() などで削除しましょう。
2. コレクション(List、Mapなど)の使いすぎ
List や Map に追加したオブジェクトを削除し忘れるのも典型的なリーク原因です。
特にキャッシュ用途で使う場合は注意が必要です。
対策:
- 期限切れデータを自動削除するようにする
WeakHashMapやSoftReferenceを使って、GCが自動的に回収できるようにする
3. イベントリスナーやコールバックの解除忘れ
GUIやサーバーアプリなどでイベントリスナーを登録した後、
解除(removeListener()など)をしないと、
そのリスナーがオブジェクトへの参照を持ち続けてしまいます。
対策:
- リスナーやオブザーバーを使ったら、必ず削除処理を入れる
- ライフサイクルに合わせて明示的に解除する
4. 外部リソース(ファイル、DB、ソケットなど)のクローズ漏れ
ファイルやデータベース接続を開いたままにすると、
ヒープだけでなくネイティブメモリまで使い続けてしまうことがあります。
対策:
try-with-resources構文を使って自動的に閉じるようにする
|
1 2 3 4 |
try (FileInputStream in = new FileInputStream("data.txt")) { // ファイル読み込み処理 } // 自動でclose()される |
5. ThreadLocalの使い方を誤る
ThreadLocal もメモリリークを起こしやすい場所です。
スレッドプールで再利用されるスレッドが、古いデータを参照し続けることがあります。
対策:
- スレッド処理の終了時に
remove()を呼び出す - 特にWebサーバー上で使う場合は注意
6. クラスローダーリーク(アプリサーバー系)
Webアプリでは、アプリを再デプロイするとクラスローダーが新しくなります。
古いクラスローダーに参照が残っていると、
そのクラス群全体がGCで回収されません。
対策:
- 静的変数にクラスやリスナーを残さない
- 外部ライブラリのクラスローディングにも注意
メモリリークが発生しているサイン
次のような現象が見られたら、メモリリークの可能性があります。
- 時間とともにヒープ使用量が減らない
- GCが頻繁に発生しているのに、メモリが解放されない
- アプリが長時間動作後に急に遅くなる
OutOfMemoryError: Java heap spaceが発生する
これらは、オブジェクトが参照を保持し続けているサインです。
メモリリークの防止・解決法まとめ
| 原因 | 対策 |
|---|---|
| static変数に参照を残す | clear()またはnullを代入して参照を切る |
| コレクションの削除漏れ | 弱参照(WeakReference, WeakHashMap)を使う |
| イベントリスナー解除忘れ | 明示的にremoveListenerを呼ぶ |
| 外部リソースのクローズ忘れ | try-with-resources構文を使う |
| ThreadLocalのremove忘れ | スレッド終了時にremove()を呼ぶ |
| クラスローダーリーク | 静的参照やリスナーを残さない |
メモリリークを検出するには?
実際のアプリで「どこでリークしているか」を調べるには、
JDK標準ツールを使うのがおすすめです。
- VisualVM:リアルタイムでヒープ使用量を可視化できる
- jconsole:メモリ、スレッド、GCの動きを確認できる
- ヒープダンプ分析:
jmapコマンドでヒープをダンプして、不要オブジェクトを特定
どんなに経験を積んでも、「自分のコードが完璧」と思わないことが大切です。
ツールを活用して、客観的にメモリ挙動を確認しましょう。
コード例:典型的なメモリリーク
|
1 2 3 4 5 6 7 8 9 10 11 |
public class MemoryLeakSample { private static List<byte[]> cache = new ArrayList<>(); public static void main(String[] args) { while (true) { byte[] data = new byte[1024 * 1024]; // 1MB確保 cache.add(data); // 毎回追加、削除しない } } } |
このプログラムはGCが回収できず、数秒で OutOfMemoryError が発生します。
対策はシンプルで、使い終わったデータを削除する こと。
Javaプログラマーとしての考え方
「メモリリークを起こさないコードを書く」ことは、
安定して長時間動くシステムを作るための第一歩です。
特にサーバー開発や業務システムでは、
小さなメモリリークが数週間後に大きな障害を引き起こすこともあります。
コードを書くときは常に、
「このオブジェクトはいつ不要になるのか?」
「参照はどこで切れるのか?」
を意識しましょう。
これからJavaを極めたい人へ
今回紹介したメモリリークは、Javaの基礎を理解していれば防げるものばかりです。
まずは 絶対にJavaプログラマーになりたい人へ。 で
Javaのメモリの仕組みを体系的に学んでみてください。
さらに実際のコードレビューを受けながら、
「自分のコードのどこが危ないのか」をプロに見てもらいたい人は、
サイゼントアカデミー の講座がおすすめです。
自己学習で理論を固め、
サイゼントアカデミーで実践力を磨く。
この2ステップが、あなたを現場で信頼されるJavaエンジニアへ導きます。
まとめ
- Javaでもメモリリークは起こる
- 主な原因は「参照が残り続ける」こと
static、コレクション、リスナー、ThreadLocal、リソース漏れに注意- 対策は「参照を切る」「自動クローズ」「弱参照を使う」
- ツールで定期的にメモリ使用状況を監視する
次回は、「ヒープダンプを使ったメモリリーク調査の実践」をわかりやすく紹介します。
あなたのJavaコードを、より効率的で安全なものにしていきましょう。
-120x68.jpg)

コメント