ガベージコレクション(GC)のある言語を使っていると、メモリのことを忘れられますよね。

でも本番では、GC言語なのにメモリリークし、ヒープが空いているのにOutOfMemoryが出ます。今回は、「GCがあるからメモリは気にしなくていい」への反例を8つ集めました。1つでも知らなかったら儲けものです。

① GC言語でも、メモリリークは起きます

GCが回収するのは、「どこからも参照されていない」オブジェクトだけです。

つまり、使い終わったのに参照が残っていれば、GCは「まだ使っている」と判断して回収しません。定番の犯人がこれです。

static Map<String, Data> cache = new HashMap<>();
// 入れるだけ入れて、消すコードがない

staticなコレクションに溜め続けるパターン。リスナーの解除忘れも同類です。

GCは「不要」を検知するのではなく、「参照ゼロ」を検知する。この一文が、GC言語のメモリリークの全てなんです。

② GCの裏で、世界は止まっています

GCが動く瞬間、アプリケーションの全スレッドが一時停止します。Stop the Worldと呼ばれる時間です。

現代のGCはこの停止を劇的に短くしました。JavaのZGCのように、停止を1ミリ秒未満に抑えることを目指すGCもあります。それでも、ゼロにはなっていません。

「レスポンスタイムのグラフに、数十ミリ秒〜の棘が周期的に立つ」「タイムアウトがたまに起きるが再現しない」——この症状の容疑者リストに、GC停止は常に入れておくべきなんですよね。

アプリのログに何も出ないのに遅い。そういうときは、アプリの下の階を見るんです。

③ ヒープが空いていても、OutOfMemoryは出ます

OutOfMemoryError=ヒープ不足、とは限りません。

メモリにはヒープ以外の領域があります。

  • クラス情報を置くメタスペース
  • スレッドごとのスタック
  • ヒープ外に直接確保されるバッファ

それぞれが別々に枯渇し、それぞれのOOMを出します。たとえばスレッドを無限に生成すれば、ヒープがガラガラでも「新しいスレッドが作れない」というOOMで死にます。

エラーメッセージのどの領域が枯れたのかを読む。そこから調査が始まるんです。

④ 参照には、「強さ」があります

参照は全部同じではありません。強さの段階があります。

参照の種類GCとの関係
強参照(普段書いている参照)繋がっている限り、絶対に回収されない
ソフト参照(SoftReference)メモリが苦しくなったら、回収してよい
弱参照(WeakReference)他に強参照がなければ、次のGCで回収してよい

①のようなキャッシュ用途に効くのが、この使い分けです。「あれば使うが、メモリが苦しいなら捨てていい」ならソフト参照。「キーの持ち主がいなくなったら一緒に消えてほしい」なら弱参照(WeakHashMapなど)。

「絶対に持つ」と「あれば嬉しい」を区別して書ける——知っていると設計の語彙が増える機能なんです。

⑤ finalizeに、後始末を任せてはいけません

「オブジェクトが回収されるときに呼ばれるメソッドがあるなら、そこでファイルを閉じればいい」——危険な発想です。

finalizeは、いつ呼ばれるか分からず、呼ばれない可能性すらあります。GCが走らなければ永遠に来ませんし、実行順序も保証されません。Javaでは非推奨になり、将来の削除が予告されています。

リソースの後始末は、try-with-resourcesのような「スコープで確実に閉じる」仕組みで書く。

「いつか誰かがやってくれる」は、メモリ管理では設計になりません。

⑥ メモリ使用量が減らない=リーク、ではありません

監視グラフでメモリが高止まりしている。リークでしょうか?

そうとは限りません。多くのランタイムは、一度OSから確保したメモリを、使い終わってもすぐには返しません。また借りるのは高くつくので、手元にキープするんです。

つまり「プロセスのメモリ使用量」は、いま使っている量ではなく、これまでの最大需要の名残だったりします。

リークかどうかは、使用量の絶対値ではなく、GC後のヒープ使用量が右肩上がりを続けるかで判断する。見るグラフが違うんです。

⑦ 「ほとんどのオブジェクトは、すぐ死ぬ」が設計の土台です

GCの内部には、美しい観察があります。

生成されたオブジェクトの大多数は、生まれてすぐ不要になる。一時変数、途中結果、リクエストの間だけ生きるデータ。長生きするのは、ごく一部です。

だから現代のGCは、メモリを「若い世代」と「古い世代」に分け、若い世代だけを高頻度・高速に掃除します。世代別GCです。

大量の短命オブジェクトはむしろ得意分野。逆に、中途半端に長生きするオブジェクトを量産する設計が、GCを一番苦しめるんです。

⑧ リーク調査の武器は、ヒープダンプです

最後は、実務の話です。

メモリリークの調査は、勘ではなくヒープダンプ(メモリの中身のスナップショット)で行います。見方の基本は2つ。

  • ヒストグラム:どのクラスのインスタンスが何個・何MBあるかで、犯人の当たりを付ける
  • 参照経路:そのオブジェクトを誰が参照しているかで、①のような「握っている場所」を特定する

時間を空けて2回取り、増え続けているものを探す。これだけで、リーク調査の8割は戦えるんです。

番外編:知っておくと得する小ネタ

System.gc() は、お願いにすぎません
「GCを実行せよ」ではなく「そろそろどうですか」という提案です。無視されることもあります。これに頼るコードは、だいたい設計を見直すサインです。

殺したのはアプリではなく、OSかもしれません
Linuxには、メモリが枯渇したときにOSがプロセスを強制終了する仕組み(OOM Killer)があります。アプリのログに何も残さず消えていたら、OS側のログを見るんですよね。

-Xmxは「上限」であって「予約」ではありません
ヒープの最大値の指定は、そこまで育ち得るという枠です。コンテナのメモリ制限との関係を間違えると、③やOOM Killerの餌食になります。

RJCでも、メモリと向き合っています

RJCが携わる金融や決済のシステムは、長時間稼働し続けることが前提の世界です。じわじわ育つリークは、まさに⑧の手順で追い詰める対象なんですよね。

社内のJava探求グループでも、こういう仕組みの話を掘り下げています。Techチームで共有しながら開発しています。

よくいただくご質問

Q. まずどれから押さえるべきですか?
A. ①かなと思います。「GCは参照ゼロしか回収しない」の一文だけで、リークの原因の大半が説明できるんですよね。

Q. GCのチューニングは必要ですか?
A. まず計測が先です。②のような停止時間が実害になっているかをGCログで確認してから。多くの場合、①のリーク退治と⑦を意識した設計の方が効きます。

Q. 監視では何を見ればいいですか?
A. ⑥のとおり「GC後のヒープ使用量の推移」です。これが右肩上がりならリーク、平らなら健康。1本のグラフで判断できます。

Q. 未経験からでも、分かるようになりますか?
A. なります。GCは「片付けロボットは、名札の付いた物を捨てられない」という比喩で全部説明できる世界なんです。

自動化されたのは「解放」だけです

今日の8つに共通していたのは、これでした。

GCが自動化してくれたのは、メモリの解放だけ。「何を、いつまで持つか」という判断は、いまも人間の設計の仕事です。

参照を握った責任は、GCには渡せない。そこを引き受けている人のコードが、長時間稼働に耐えるんですよね。

みなさんが踏んだメモリの事故、よかったら教えてください。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?