本番環境で Java メモリリークを特定するには、稼働中の JVM の GC オブジェクト参照チェーンを分析し、本来解放されるべきオブジェクトをどの GC ルートが保持し続けているかを突き止めます——アプリケーションを停止することも、ヒープダンプをエクスポートすることも、プロセスを再起動することもなく行えます。本記事では、実際の注文システムの事例を通じて、OpenResty XRay でその方法をご紹介します。

「全体像から詳細へ」という方針で、3 つのステップで進めます:

  • リークの特定:GC オブジェクト参照分析で、予期せぬメモリ常駐の根本原因を見つけます。
  • パフォーマンス変動の調査:GC オブジェクト割り当て回数分析で、高頻度にオブジェクトを生成するコードパスを明らかにします。
  • メモリ使用量の最適化:GC オブジェクト割り当てサイズ分析で、大オブジェクトの生成元を特定します。

複数回の Full GC 後もヒープが回収されなかった理由:本番環境の Java メモリリーク

あるお客様の注文システムにおける性能リグレッションテスト中に、当社のエンジニアチームは典型的なメモリ異常現象を確認しました。具体的には、アプリケーションのヒープメモリ使用量が継続的に上昇し、複数回の Full GC 後もヒープ領域の占有率が顕著に低下しないという状況です。

これまでの経験から、この現象は通常、以下の 2 つの潜在的な問題を示唆しています。

  1. メモリリーク (Memory Leak) の発生。これは、一部のオブジェクトが参照を失った後も本来回収されるべきであるにもかかわらず、予期せぬ GC ルートからの参照パスが存在するため、メモリ上に常駐し続けてしまう状態を指します。
  2. 特定の業務処理パスにおける高頻度なオブジェクト生成「チャーン」 (Memory Churn) の発生。これは、大量の一時的なオブジェクトが頻繁に生成・破棄されることで Young GC への負荷が過度に増大し、結果として Full GC が頻繁にトリガーされる状況を指します。

従来のヒープダンプ (Heap Dump) 分析手法、例えば jmap を用いて HPROF ファイルをエクスポートし、MAT などのツールでオフライン分析を行う方法は、強力な機能を持つ一方で、本番環境では課題に直面します。数 GB にも及ぶヒープスナップショットのエクスポート自体が、長時間の STW (Stop-the-World) を引き起こし、稼働中のサービスにとっては許容できない影響を与えます。

サービスを中断せず、かつ顕著な性能オーバーヘッドを発生させないという前提で「オンライン」診断を実施するため、エンジニアチームは実行中の JVM を分析する目的で OpenResty XRay を起動しました。

ヒープダンプなしで本番環境の Java メモリリークを診断する方法

「全体像から詳細へ」という診断戦略を採用し、まずメモリリークの有無を確認し、次にアロケーションの頻度とサイズに関する問題を調査しました。

ステップ 1:GC ルート参照分析によるリークの特定

最初の重要なタスクは、メモリリークが存在するかどうかを確認し、その発生源を特定することです。OpenResty XRayGC オブジェクト参照関係アナライザーを使用しました。

分析を実行すると、フレームグラフレポートはメモリ内のオブジェクトを以下の 2 つのカテゴリに明確に分類しました。

  • Dead GC objects:不要となり、ガベージコレクションによる回収を待っているオブジェクト(調査対象外)。
  • GC ルートによって参照されているオブジェクト:生存しており、重点的に調査すべきオブジェクト。

エンジニアチームは後者のオブジェクトに焦点を当てて調査を進めました。GC ルート参照チェーンを階層的に辿っていくと、分析ツールはメモリ使用量が集中している箇所として、OrderProcessingService$OrderService インスタンスを明確に指し示しました。

参照パス図:OrderProcessingService OrderService インスタンスが解放されないリークオブジェクトを保持

さらに深く掘り下げた結果、メモリが主にこのインスタンスの 2 つのフィールドによって保持されていることが判明しました。

  • notificationQueue (並行キュー)
  • paymentRecords (支払い記録配列)

参照パスの分析を通じて、これらのコレクション内のオブジェクトはビジネスプロセス終了後に削除されるべきでしたが、ロジックの欠陥により、想定よりも長く存続してしまっていることが確認され、メモリリークの発生箇所が特定されました。

ステップ 2:GC 割り当て回数分析でメモリチャーンを検出する

メモリリーク箇所を特定した後、次に問題となるのは、不必要なメモリの頻繁な確保・解放(メモリチャーン)が発生していないかという点です。高頻度なメモリ割り当ては GC(ガベージコレクション)負荷を増大させ、メモリリークがない場合でもシステムのスループットを低下させる原因となります。

OpenResty XRayGC オブジェクト割り当て回数アナライザー を使用することで、コードパス別に分類されたオブジェクト作成頻度フレームグラフを取得しました。

レポートによると、createOrder メソッド (@OrderProcessingService.java:118) におけるオブジェクト作成回数(フレームグラフの幅)が、他の業務ロジックのパスと比較して著しく高いことが示されています。

高頻度なオブジェクト作成、特に Old 世代への昇格は、Young GC の頻繁な発生を招き、Old 世代のメモリ領域の枯渇を加速させる可能性があります。これは STW(Stop-The-World)時間の増加を引き起こし、結果として P99 応答遅延の悪化につながります。

ステップ 3:GC 割り当てサイズ分析で大オブジェクトを特定する

最後に、「大オブジェクト」の問題、つまり一部の処理パスでサイズの大きなオブジェクトが頻繁に生成されていないかを確認しました。

そこで、GC オブジェクトアロケーションサイズアナライザー に切り替えました。GC オブジェクトアロケーションフレームグラフ は、サンプリング期間中に生成された GC オブジェクトの総サイズに基づいて統計を表示します。生成されたオブジェクトの総サイズが大きいほど、グラフ上での割合が高くなります。

分析レポートによると、generateOrderId メソッド (@OrderProcessingService.java:155) およびその呼び出しチェーンは、実行回数は多くないものの、メモリ使用量の面で非常に目立っていました。主な原因は、サイズの大きな文字列オブジェクトが頻繁に生成されていたためです。

評価の結果、この ID 生成ロジックは同一入力に対する再計算が多く、キャッシュの導入が適切であると判断しました。文字列生成ロジックにキャッシュ機構を導入する改修を行うことで、このパスにおけるヒープ使用量のピークを大幅に削減することができました。

成果:より低い GC オーバーヘッドと安定した P99 レイテンシ

OpenResty XRay の 3 つのアナライザーを活用することで、当社の技術チームは短時間で現象から根本原因までの特定を完遂しました。

  • 診断効率の向上:従来、ヒープダンプツールに頼っていたため、繰り返し比較したり、参照チェーンを手動で追跡したりする必要がありました。しかし、XRay のフレームグラフ(Flame Graph)を使用することで、わずか数分で問題が集中している領域を直感的に把握できるようになり、トラブルシューティングのプロセスを大幅に短縮できました。
  • システム性能の改善:問題を修正した結果、GC 時間の占有率が明らかに低下し、アプリケーションは同じ負荷下でより安定した応答を示すようになりました。これにより、全体の遅延曲線も安定傾向にあります。

さらに重要な点として、今回の実践は方法論に変化をもたらしました。OpenResty XRay の可視化分析により、チームメンバーはメモリ内のオブジェクトのライフサイクルと参照関係を直接理解できるようになりました。チューニングに関する議論は「経験による推測」から「証拠に基づく意思決定」へと転換し、性能最適化の確実性を高めています。

Java メモリリークを特定する方法:再利用可能な 3 ステップ法

上記の事例は、再利用可能な方法へと一般化できます。どのようなツールを使う場合でも、Java メモリリークは「常駐から割り当てへ」という順序で 3 つの問いに答えることで最も効率的に切り分けられます:

  1. どのオブジェクトが、何によって保持されているのか? GC オブジェクト参照チェーンを GC ルートまで辿ります。リークは、本来解放されるべきなのに予期せぬ参照によって到達可能なまま残るオブジェクトとして現れます——本事例では、業務プロセス終了後に削除されるはずが削除されなかった notificationQueuepaymentRecords の 2 つのコレクションです。
  2. どのコードパスが最も頻繁に割り当てているのか? 高頻度なオブジェクト生成(メモリチャーン)は、リークがなくても Young GC 負荷を高め、Full GC を頻繁にトリガーする可能性があります。割り当て回数分析はホットパスを浮かび上がらせます——本事例では createOrder メソッドです。
  3. どのコードパスが最も多くのメモリを割り当てているのか? 生成する数は少なくても、非常に大きなオブジェクトを作るパスもあります。割り当てサイズ分析はそれらを特定します——本事例では generateOrderId で生成される大きな文字列オブジェクトです。

常駐(ステップ 1)から割り当ての頻度とサイズ(ステップ 2・3)へと進めることで、調査範囲を「メモリはリークしているか?」から「正確にどのコードが原因か」へと絞り込めます。

稼働中の JVM で Java メモリリークを特定するのに OpenResty XRay を使う理由

従来の Java メモリ分析は、ヒープダンプに大きく依存しています。この「オフラインでの事後分析」型のアプローチには、固有の限界があります。

従来のヒープダンプツール主な課題
プロセスの停止または再起動が必要1. ヒープスナップショット(jmap)のエクスポートは通常 STW(Stop-The-World)をトリガーし、本番サービスを長時間停止させる可能性があります。
分析コストが高い2. ヒープファイルは数 GB にも及ぶ膨大なサイズになるため、転送とロードに時間がかかります。
データのリアルタイム性が低い3. その時点のスナップショットしか分析できず、システムのリアルタイムな変化を観察できません。

OpenResty XRay は、異なるアプローチを提供します。それは**非侵入型の「オンライン分析」**です。OpenResty XRay は、その設計により従来のツールの課題を回避しています。

  1. 本番環境での安全性 OpenResty XRay は、Java プロセスの再起動、コードの変更、または JDK の置換を必要としません。安全なアタッチメカニズムにより、実行中の JVM に直接接続し、業務に透過的に機能します。

  2. 極めて低いオーバーヘッドと非侵入型設計 これは OpenResty XRay の中核となる技術的優位性です。JVM のセーフポイント (Safepoint) メカニズムに依存しないサンプリング方式を採用しているため、STW (Stop-the-World) を一切発生させることなく、JVM のメモリ、CPU、I/O などを分析できます。これにより、リアルタイムの本番トラフィック下で詳細な診断を実行できる、数少ないツールの一つとなっています。

  3. 直感的な可視化分析 提供される GC オブジェクト参照フレームグラフやメモリ参照パスグラフは、「誰が誰を参照しているか」「メモリがどこで使われているか」を直感的に可視化し、メモリ問題の分析ハードルを大幅に低減します。

  4. 多角的なパフォーマンスプロファイル

OpenResty XRay の機能はメモリ分析に限定されません。今回のケースでは GC に焦点を当てましたが、必要に応じて CPU フレームグラフや I/O 分析などに切り替えることで、システム全体のパフォーマンスプロファイルを多角的に構築できます。

この設計により、OpenResty XRay は本番環境で Java メモリ問題をリアルタイムに分析できる、真に数少ないツールの一つです。上記の注文システム事例では、チームが「リアルタイム診断 + 迅速な検証」というクローズドループを実現し、システム停止を伴う分析のリスクを回避するのに貢献しました。

要点:再起動もダウンタイムもなく Java メモリリークを診断する

今回の注文システムにおけるメモリ問題の調査では、OpenResty XRay が体系的な診断プロセスを構築する上で大いに役立ちました。具体的には、「メモリリークの有無」から「発生頻度」、そして「オブジェクトサイズ」へ。さらに、「参照関係」から「割り当て回数」、そして「割り当てサイズ」へと段階的に掘り下げることで、調査範囲を効率的に絞り込むことができました。

このリアルタイムサンプリングと可視化に基づく分析アプローチは、従来のヒープダンプとオフライン分析による手法を効果的に代替します。これにより診断効率が向上するだけでなく、さらに重要な点として、同時接続数が多く高可用性が求められる Java サービスの本番環境におけるパフォーマンスチューニングに対し、より安全で効率的な問題解決のアプローチを提供します。

よくある質問

ヒープダンプなしで Java メモリリークを特定できますか?

はい。jmap で HPROF ヒープスナップショットをエクスポートしてオフライン分析する代わりに、OpenResty XRay は安全なアタッチメカニズムで稼働中の JVM に接続し、GC オブジェクト参照チェーンをリアルタイムに分析します。これにより、従来のヒープダンプツールが必要とする数 GB のヒープファイルや「オフラインの静的スナップショット」方式を回避できます。

本番環境で Java メモリを分析すると Stop-the-World 停止が発生しますか?

jmap でヒープスナップショットをエクスポートすると通常 Stop-the-World (STW) が発生し、本番サービスが長時間停止する可能性があります。OpenResty XRay はサンプリングに JVM セーフポイント (Safepoint) メカニズムを使用しないため、STW を発生させずに JVM メモリを分析でき、リアルタイムの本番トラフィック下で利用できます。

メモリリークとメモリチャーンはどう見分けますか?

メモリリークとは、本来回収されるべきオブジェクトが予期せぬ GC ルート参照チェーンによって生存し続ける状態です。メモリチャーンとは、短命なオブジェクトの高頻度な生成・破棄であり、Young GC 負荷を高め Full GC を頻繁にトリガーし得ます。本事例では、参照関係アナライザーがリークを確認し、割り当てアナライザーが createOrder パス上のチャーンを明らかにしました。

OpenResty XRay で Java メモリ問題を診断する 3 つのステップとは?

本事例は「全体像から詳細へ」という流れで、3 つのアナライザーを使用します:(1) GC オブジェクト参照関係アナライザー で GC ルート参照チェーンからリークを特定;(2) GC オブジェクト割り当て回数アナライザー で高頻度なオブジェクト生成(メモリチャーン)を発見;(3) GC オブジェクト割り当てサイズアナライザー で大オブジェクトを生成するコードパスを特定します。

なぜ jmap と MAT を使うだけではだめなのですか?

本記事で述べたとおり、従来のヒープダンプツールには 3 つの中核的な限界があります:jmap でのスナップショットのエクスポートは通常 Stop-the-World を引き起こしダウンタイムを招くこと、ヒープファイルは数 GB に及び転送とロードに時間がかかること、そして静的なスナップショットしか分析できずシステムのリアルタイムな変化を観察できないことです。

OpenResty XRay について

OpenResty XRay動的トレーシング製品であり、実行中のアプリケーションを自動的に分析して、パフォーマンスの問題、挙動の問題、セキュリティの脆弱性を診断し、実行可能な提案を提供します。内部実装では、OpenResty XRay は弊社の Y 言語によって駆動され、Stap+、eBPF+、GDB、ODB など、多様なコンテキストで複数のランタイムをサポートしています。

著者について

章亦春(Zhang Yichun)は、オープンソースの OpenResty® プロジェクトの創始者であり、OpenResty Inc. の CEO および創業者です。

章亦春(GitHub ID: agentzh)は中国江蘇省生まれで、現在は米国ベイエリアに在住しています。中国における初期のオープンソース技術と文化の提唱者およびリーダーの一人であり、Cloudflare、Yahoo!、Alibaba など、国際的に有名なハイテク企業に勤務した経験を持ちます。「エッジコンピューティング」、「動的トレーシング」、「マシンコーディング」の先駆者であり、22 年以上のプログラミング経験と 16 年以上のオープンソース経験を持っています。世界で 4000 万以上のドメインに採用されているオープンソースプロジェクトのリーダーとして、OpenResty® オープンソースプロジェクトをベースに、米国シリコンバレーの中心部にハイテク企業 OpenResty Inc. を設立しました。同社の主力製品である OpenResty XRay動的トレーシング技術を利用した非侵入型の障害分析・診断ツール)と OpenResty Edge(マイクロサービスおよび分散トラフィックに最適化された多機能ゲートウェイソフトウェア)は、世界中の多くの上場企業および大企業から高い評価を得ています。OpenResty 以外にも、章亦春は Linux カーネル、Nginx、LuaJITGDBSystemTapLLVM、Perl など、複数のオープンソースプロジェクトに累計 100 万行以上のコードを寄与し、60 以上のオープンソースソフトウェアライブラリを執筆しています。

翻訳

英語版の原文と日本語訳版(本文)をご用意しています。読者の皆様による他の言語への翻訳版も歓迎します。全文翻訳で省略がなければ、採用を検討します。ご協力ありがとうございます。