Java の APM を活用して JVM のガベージコレクション(Garbage Collection : GC)の実行回数と実行時間を収集し可視化する方法を紹介します。
Java アプリケーションのパフォーマンスチューニングにおいて、ガベージコレクションは重要な役割を担います。しかし、その複雑さゆえに、GC の内部動作を理解し、効果的にモニタリングすることは難しいです。この記事では、New Relic の Java APM エージェントを用いて、GC の動作(実行時間・実行回数)を詳細にモニタリングし、パフォーマンス改善に必要な情報を収集・分析する方法を紹介します。
最新のアップデートの詳細はこちら
New Relic アップデート一覧
無料のアカウントで試してみよう!
New Relic フリープランで始めるオブザーバビリティ!
今回のポイント
この記事で紹介しているポイントは、次の2つです。
1. APM で収集できる GC 関連のメトリクスとカスタマイズ方法がわかる
Java の APM エージェントがデフォルトで収集するメトリクスと JMX のカスタムインストゥルメンテーションを使って不足している情報を収集する方法を紹介します。
2. GC のアルゴリズム毎に収集されるメトリクスを知る
3つの GC アルゴリズム毎に収集されるメトリクスとデータを確認するためのクエリを紹介します。
APM で収集できる GC 関連のメトリクス
APM の機能を活用すると GC の実行時間と実行回数のメトリクスを収集することが可能です。
GC の実行時間
Java の APM エージェントは JVM のメトリクスのうち GC の実行時間をデフォルトで収集しています。
GC の実行時間は、 APM の JVM のページで表示されるため、簡単に確認できます。
今回のサンプルは1ホストなのでJVM1つしか出ていませんが、スケールアウト構成の場合、すべて一度に確認できます.
GC の実行回数
New Relic の APM エージェントはカスタム JMX YAML を使って JMX から特定のメトリクスを収集できるため、GC の実行回数も収集が可能です。カスタム JMX YAML の概要と細かな設定方法については下記の記事を参考にしてください。
JConsole を使って GC の実行回数の属性を確認
JMX から値を取得するには object_name と attributes が必要です。この2つの値を JConsole 使って GC のメトリクスを確認します。JConsole の MBeans のタブを開き、java.lang -> GarbageCollector の順に展開していきます。設定している GC のアルゴリズムによって表示される内容は変わりますが、Copy 等の配下にある属性を開くと目的の object_name と attributes の値が表示されます。
カスタム JMX YAML ファイルの作成
JConsole で確認した内容をもとにカスタム JMX YAML ファイルを作成します。GC の実行回数は CollectionCount で固定ですが、object_name は GC のアルゴリズムによって変わるため、ワイルドカード(java.lang:type=GarbageCollector,name=*)を使って GC のどのアルゴリズムでも取り込めるようにします。
表示されている値は実行された累積の値なので monotonically_increasing を使用して増分を取り込むようにしましょう。
name: JMXCustomGC
version: 1.0
enabled: true
jmx:
- object_name: java.lang:type=GarbageCollector,name=*
metrics:
- attributes: CollectionCount
type: monotonically_increasing
このファイルを所定の場所に配置してアプリケーションを開始すれば目的の GC の実行回数が取得できるようになります。
取得したメトリクスの確認方法
取得したデータは NRQL を作成して確認する必要があります。JMXのメトリクスはタイムスライスデータとして保存されていてカスタム JMX YAML で設定した値に合わせて名称が設定されます。今回収集している GC 実行回数のメトリクスの場合は下記のようになり、{Garbage Collector名称}の部分にワイルドカードを設定した Garbage Collector の名前が設定されます。
JMX/java.lang/GarbageCollector/{Garbage Collector名称}/CollectionCount
例えば Serial GC を使用した場合に Garbage Collector(Copy, MarkSweepCompact)のそれぞれの実行回数をVM毎に取得するクエリは下記のようになります。
FROM Metric
SELECT
filter(sum(newrelic.timeslice.value), where metricTimesliceName = 'JMX/java.lang/GarbageCollector/MarkSweepCompact/CollectionCount') as 'MarkSweepCompact',
filter(sum(newrelic.timeslice.value), where metricTimesliceName = 'JMX/java.lang/GarbageCollector/Copy/CollectionCount') as 'Copy'
WHERE (entityGuid='XXXXXXXXXXX EntityGUID XXXXXXXXXXX')
FACET `host.displayName` OR `instanceName`
LIMIT MAX TIMESERIES
Data Explorer を使って確認することも可能です。Timeslices を選択して確認したいエンティティを設定した状態で JMX
というキーワードで検索すると目的のメトリクスが表示されます。
Data Explorer の使い方についてはこちらの記事を参考にしてください。
GC のアルゴリズム毎に収集されるメトリクス
下記3種類のGCアルゴリズム毎にどのようなメトリクスが取得できるのかを紹介します。
- Serial GC
- Pararel GC
- Garbage First GC(G1GC)
収集された GC の実行回数と実行時間のデータは、タイムスライスメトリクスという形式でNew Relic に保管されています。タイムスライスメトリクスを NRQL や Data Explorer で参照する際には、metricTimesliceName がキーになっています。
ここでは各 GC アルゴリズムが使用している Garbage Collector 毎にどの metricTimesliceName を使用すれば良いのか、どんなクエリ(NRQL) で取得できるのかをメインに紹介します。
Serial GC
単一スレッドで動作しオーバーヘッドが低い Serial GC は、小規模でシングルスレッド向けのアプリケーションやリソースが限られた環境に適しています。
metricTimesliceName(Serial GC)
Garbage Collector | 実行回数 | 実行時間 |
---|---|---|
Copy | JMX/java.lang/GarbageCollector/Copy/CollectionCount | GC/Copy |
MarkSweepCompact | JMX/java.lang/GarbageCollector/MarkSweepCompact/CollectionCount | GC/MarkSweepCompact |
実行回数(Serial GC)
ホスト毎にGCの実行回数の合計値を時系列で表示する際の例です。
- Copy
SELECT sum(newrelic.timeslice.value) AS `Copy Count`
FROM Metric
WHERE metricTimesliceName = 'JMX/java.lang/GarbageCollector/Copy/CollectionCount'
AND `entity.guid` = '{EntityGUID}'
FACET `host.displayName` OR `instanceName`
TIMESERIES
- MarkSweepCompact
SELECT sum(newrelic.timeslice.value) AS `MarkSweepCompact Count`
FROM Metric
WHERE metricTimesliceName = 'JMX/java.lang/GarbageCollector/MarkSweepCompact/CollectionCount'
AND `entity.guid` = '{EntityGUID}'
FACET `host.displayName` OR `instanceName`
TIMESERIES
実行時間(Serial GC)
ホスト毎にGCの実行時間の平均値を時系列で表示する際の例です。
- Copy
SELECT average(newrelic.timeslice.value) AS `Copy CPU Time`
FROM Metric
WHERE metricTimesliceName = 'GC/Copy'
AND `entity.guid` = '{EntityGUID}'
FACET `host.displayName` OR `instanceName`
TIMESERIES
- MarkSweepCompact
SELECT average(newrelic.timeslice.value) AS `MarkSweepCompact CPU Time`
FROM Metric
WHERE metricTimesliceName = 'GC/MarkSweepCompact'
AND `entity.guid` = '{EntityGUID}'
FACET `host.displayName` OR `instanceName`
TIMESERIES
Parallel GC
並列にガベージコレクションを行いスループットを重視する Parallel GC は、マルチプロセッサ環境やスループットを最重視するバックエンドシステムに適しています。
metricTimesliceName(Parallel GC)
Garbage Collector | 実行回数 | 実行時間 |
---|---|---|
PS Scavenge | JMX/java.lang/GarbageCollector/PS Scavenge/CollectionCount | GC/PS Scavenge |
PS MarkSweep | JMX/java.lang/GarbageCollector/PS MarkSweep/CollectionCount | GC/PS MarkSweep |
実行回数(Parallel GC)
ホスト毎にGCの実行回数の合計値を時系列で表示する際の例です。
- PS Scavenge
SELECT sum(newrelic.timeslice.value) AS `PS Scavenge Count`
FROM Metric
WHERE metricTimesliceName = 'JMX/java.lang/GarbageCollector/PS Scavenge/CollectionCount'
AND `entity.guid` = '{EntityGUID}'
FACET `host.displayName` OR `instanceName`
TIMESERIES
- PS MarkSweep
SELECT sum(newrelic.timeslice.value) AS `PS MarkSweep Count`
FROM Metric
WHERE metricTimesliceName = 'JMX/java.lang/GarbageCollector/PS MarkSweep/CollectionCount'
AND `entity.guid` = '{EntityGUID}'
FACET `host.displayName` OR `instanceName`
TIMESERIES
実行時間(Parallel GC)
ホスト毎にGCの実行時間の平均値を時系列で表示する際の例です。
- PS Scavenge
SELECT average(newrelic.timeslice.value) AS `PS Scavenge CPU Time`
FROM Metric
WHERE metricTimesliceName = 'GC/PS Scavenge'
AND `entity.guid` = '{EntityGUID}'
FACET `host.displayName` OR `instanceName`
TIMESERIES
- PS MarkSweep
SELECT average(newrelic.timeslice.value) AS `PS MarkSweep CPU Time`
FROM Metric
WHERE metricTimesliceName = 'GC/PS MarkSweep'
AND `entity.guid` = '{EntityGUID}'
FACET `host.displayName` OR `instanceName`
TIMESERIES
Garbage First GC (G1GC)
リージョンベースでメモリを管理し予測可能なポーズ時間を提供するG1GCは、大規模なヒープを持つアプリケーションやリアルタイムシステムに最適です。
metricTimesliceName(G1GC)
Garbage Collector | 実行回数 | 実行時間 |
---|---|---|
G1 Young Generation | JMX/java.lang/GarbageCollector/G1 Young Generation/CollectionCount | GC/G1 Young Generation |
G1 Concurrent GC | JMX/java.lang/GarbageCollector/G1 Concurrent GC/CollectionCount | GC/G1 Concurrent GC |
G1 Old Generation | JMX/java.lang/GarbageCollector/G1 Old Generation/CollectionCount | GC/G1 Old Generation |
実行回数(G1GC)
ホスト毎にGCの実行回数の合計値を時系列で表示する際の例です。
- G1 Young Generation
SELECT sum(newrelic.timeslice.value) AS `G1 Young Generation Count`
FROM Metric
WHERE metricTimesliceName = 'JMX/java.lang/GarbageCollector/G1 Young Generation/CollectionCount'
AND `entity.guid` = '{EntityGUID}'
FACET `host.displayName` OR `instanceName`
TIMESERIES
- G1 Concurrent GC
SELECT sum(newrelic.timeslice.value) AS `G1 Concurrent GC Count`
FROM Metric
WHERE metricTimesliceName = 'JMX/java.lang/GarbageCollector/G1 Concurrent GC/CollectionCount'
AND `entity.guid` = '{EntityGUID}'
FACET `host.displayName` OR `instanceName`
TIMESERIES
- G1 Old Generation
SELECT sum(newrelic.timeslice.value) AS `G1 Old Generation Count`
FROM Metric
WHERE metricTimesliceName = 'JMX/java.lang/GarbageCollector/G1 Old Generation/CollectionCount'
AND `entity.guid` = '{EntityGUID}'
FACET `host.displayName` OR `instanceName`
TIMESERIES
実行時間(G1GC)
ホスト毎にGCの実行時間の平均値を時系列で表示する際の例です。
- G1 Young Generation
SELECT average(newrelic.timeslice.value) AS `G1 Young Generation CPU Time`
FROM Metric
WHERE metricTimesliceName = 'GC/G1 Young Generation'
AND `entity.guid` = '{EntityGUID}'
FACET `host.displayName` OR `instanceName`
TIMESERIES
- G1 Concurrent GC
SELECT average(newrelic.timeslice.value) AS `G1 Concurrent GC CPU Time`
FROM Metric
WHERE metricTimesliceName = 'GC/G1 Concurrent GC'
AND `entity.guid` = '{EntityGUID}'
FACET `host.displayName` OR `instanceName`
TIMESERIES
- G1 Old Generation
SELECT average(newrelic.timeslice.value) AS `G1 Old Generation CPU Time`
FROM Metric
WHERE metricTimesliceName = 'GC/G1 Old Generation'
AND `entity.guid` = '{EntityGUID}'
FACET `host.displayName` OR `instanceName`
TIMESERIES
Minor GC と Major GC
今回紹介した 3 つの GC アルゴリズムは世代型メモリ管理を行なっています。紹介したメトリクスは下記の表のように young 領域を対象にした Minor GC と old 領域を対象とした Major GC に分けることができます。
Minor GC | Major GC | |
---|---|---|
Serial GC | Copy | MarkSweepCompact |
Palaller GC | PS Scavenge | PS MarkSweep |
G1GC | G1 Young Generation | GC/G1 Old Generation, G1 Concurrent GC |
young/old領域の全てを対象にした Full GC の発生状況を把握することは Java アプリケーションを運用する上で大変重要です。Major GC のメトリクスをモニタリングすることで Full GC の発生状況を確認することが可能になります。
まとめ
Java アプリケーションのパフォーマンスチューニングにおいて、ガベージコレクションは重要です。New Relic の APM を使って GC のメトリクスを収集・確認する方法を紹介しました。New Relic では GC だけではなく、メモリやクラス等の情報も収集しています。GC のメトリクスと関連するデータをもとにしてヒープサイズの調整やパラメータのチューニングを行い、サービスの利用状況の変化に合わせてパフォーマンスを最適化することが可能です。New Relicのテレメトリデータを使ってサービスの改善に取り組んでみてください。
New Relicでは、新しい機能やその活用方法について、QiitaやXで発信しています!
無料でアカウント作成も可能なのでぜひお試しください!
New Relic株式会社のX(旧Twitter) や Qiita OrganizationOrganizationでは、
新機能を含む活用方法を公開していますので、ぜひフォローをお願いします。
無料のアカウントで試してみよう!
New Relic フリープランで始めるオブザーバビリティ!