Java アプリケーションの ガベージコレクション（GC） を New Relic の APM を使ってモニタリング！

　Java の APM を活用して JVM のガベージコレクション（Garbage Collection : GC）の実行回数と実行時間を収集し可視化する方法を紹介します。
　Java アプリケーションのパフォーマンスチューニングにおいて、ガベージコレクションは重要な役割を担います。しかし、その複雑さゆえに、GC の内部動作を理解し、効果的にモニタリングすることは難しいです。この記事では、New Relic の Java APM エージェントを用いて、GC の動作（実行時間・実行回数）を詳細にモニタリングし、パフォーマンス改善に必要な情報を収集・分析する方法を紹介します。

:::note info
最新のアップデートの詳細はこちら
[New Relic アップデート一覧
](https://newrelic.com/jp/blog/nerdlog/new-relic-update  "New Relic アップデート一覧")
:::

:::note info
無料のアカウントで試してみよう！
[New Relic フリープランで始めるオブザーバビリティ！](https://qiita.com/ktst79/items/5ee74b34e6adb8a7a6fa)
:::

# 今回のポイント
この記事で紹介しているポイントは、次の２つです。

**1. APM で収集できる GC 関連のメトリクスとカスタマイズ方法がわかる**
　Java の APM エージェントがデフォルトで収集するメトリクスと JMX のカスタムインストゥルメンテーションを使って不足している情報を収集する方法を紹介します。
 
**2. GC のアルゴリズム毎に収集されるメトリクスを知る**
　3つの GC アルゴリズム毎に収集されるメトリクスとデータを確認するためのクエリを紹介します。

# APM で収集できる GC 関連のメトリクス
　APM の機能を活用すると GC の実行時間と実行回数のメトリクスを収集することが可能です。

## GC の実行時間
　Java の APM エージェントは JVM のメトリクスのうち GC の実行時間をデフォルトで収集しています。
![JVM_GarbageCollection.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3043202/b5c92875-df21-43c6-b557-89990caf9b6e.png)

https://docs.newrelic.com/jp/docs/apm/agents/java-agent/features/jvms-page-java-view-app-server-metrics-jmx/#tab-metrics

GC の実行時間は、 APM の JVM のページで表示されるため、簡単に確認できます。

![GC_CPUTime.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3043202/7c742702-4eee-4829-896e-9a478600f614.png)

:::note info
今回のサンプルは1ホストなのでJVM1つしか出ていませんが、スケールアウト構成の場合、すべて一度に確認できます.
:::

## GC の実行回数
　New Relic の APM エージェントはカスタム JMX YAML を使って JMX から特定のメトリクスを収集できるため、GC の実行回数も収集が可能です。カスタム JMX YAML の概要と細かな設定方法については下記の記事を参考にしてください。

https://docs.newrelic.com/jp/docs/apm/agents/java-agent/custom-instrumentation/custom-jmx-yaml-examples/

### JConsole を使って GC の実行回数の属性を確認
　JMX から値を取得するには **object_name** と **attributes** が必要です。この2つの値を JConsole 使って GC のメトリクスを確認します。JConsole の MBeans のタブを開き、**java.lang -> GarbageCollector** の順に展開していきます。設定している GC のアルゴリズムによって表示される内容は変わりますが、*Copy* 等の配下にある属性を開くと目的の **object_name** と **attributes** の値が表示されます。 
 
![JConsole.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3043202/8d469e06-d12b-44dd-bf76-e296580fe942.png)

### カスタム JMX YAML ファイルの作成
　JConsole で確認した内容をもとにカスタム JMX YAML ファイルを作成します。GC の実行回数は **CollectionCount** で固定ですが、object_name は GC のアルゴリズムによって変わるため、ワイルドカード(**java.lang:type=GarbageCollector,name=\***)を使って GC のどのアルゴリズムでも取り込めるようにします。
　表示されている値は実行された累積の値なので **monotonically_increasing** を使用して増分を取り込むようにしましょう。

```yaml:カスタム JMX YAML
name: JMXCustomGC
version: 1.0
enabled: true
jmx:
  - object_name: java.lang:type=GarbageCollector,name=*
    metrics:
      - attributes: CollectionCount
        type: monotonically_increasing
```
　このファイルを所定の場所に配置してアプリケーションを開始すれば目的の GC の実行回数が取得できるようになります。

### 取得したメトリクスの確認方法
　取得したデータは NRQL を作成して確認する必要があります。JMXのメトリクスは[タイムスライスデータ](https://docs.newrelic.com/jp/docs/data-apis/understand-data/metric-data/query-apm-metric-timeslice-data-nrql/)として保存されていてカスタム JMX YAML で設定した値に合わせて名称が設定されます。今回収集している GC 実行回数のメトリクスの場合は下記のようになり、{Garbage Collector名称}の部分にワイルドカードを設定した Garbage Collector の名前が設定されます。

``JMX/java.lang/GarbageCollector/{Garbage Collector名称}/CollectionCount``
 
　例えば Serial GC を使用した場合に Garbage Collector（Copy, MarkSweepCompact）のそれぞれの実行回数をVM毎に取得するクエリは下記のようになります。
 ```sql
 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
 ```
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3043202/322943e8-2ed5-4c3c-a7a5-a7c51fb0d204.png)

 
　Data Explorer を使って確認することも可能です。**Timeslices** を選択して確認したいエンティティを設定した状態で ``JMX`` というキーワードで検索すると目的のメトリクスが表示されます。
![DataExplorer.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3043202/e8cd208b-f293-459e-b4c2-c892c2623d4b.png)
 
 Data Explorer の使い方についてはこちらの記事を参考にしてください。

https://qiita.com/MarthaS/items/e0967c9f3b66359df2d1

# 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**
```SQL
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
```
![Copy_実行回数.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3043202/261d2100-97d3-4340-bbc1-fc4d39181ef4.png)

* **MarkSweepCompact**
```SQL
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
```
![MarkSweepCompact_実行回数.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3043202/1dfab2a9-1df2-4f7f-8bc8-5050c2befe47.png)

### 実行時間(Serial GC)
ホスト毎にGCの実行時間の平均値を時系列で表示する際の例です。
* **Copy**
```SQL
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
```
![Copy_実行時間.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3043202/b45de9c4-45e3-4979-80ab-355cd2bf528a.png)

* **MarkSweepCompact**
```SQL
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
```
![MarkSweepCompact_実行時間.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3043202/56afc884-1cc5-491a-be66-bb0bb151bb20.png)


## 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**
```SQL
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 Scavenge_実行回数.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3043202/e83b742c-5859-4c98-93b6-8a90bbea8638.png)



* **PS MarkSweep**
```SQL
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
```
![PS MarkSweep_実行回数.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3043202/c3b661a9-7205-435f-a5c3-7d56be822d24.png)


### 実行時間(Parallel GC)
ホスト毎にGCの実行時間の平均値を時系列で表示する際の例です。
* **PS Scavenge**
```SQL
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 Scavenge_実行時間.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3043202/9a4e9852-8219-445a-90c2-4b32f6d74516.png)

* **PS MarkSweep**
```SQL
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
```
![PS MarkSweep_実行時間.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3043202/6f6e3d15-9e4f-4975-8650-208a879c2284.png)

## 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**
```SQL
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 Young Generation_実行回数.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3043202/98a46a79-8564-4a6f-9175-95937f7033cf.png)


* **G1 Concurrent GC**
```SQL
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 Concurrent GC_実行回数.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3043202/ee5ee02d-088e-414e-b818-68d16ab52392.png)


* **G1 Old Generation**
```SQL
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
```
![G1 Old Generation_実行回数.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3043202/7829c196-4a32-491d-962b-4450e75cda99.png)


### 実行時間(G1GC)
ホスト毎にGCの実行時間の平均値を時系列で表示する際の例です。
* **G1 Young Generation**
```SQL
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 Young Generation_実行時間.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3043202/1cab8302-96e8-46a5-9638-aa49f46ee494.png)



* **G1 Concurrent GC**
```SQL
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 Concurrent GC_実行時間.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3043202/eb98c3da-d749-40d2-af11-66bae9e0634b.png)



* **G1 Old Generation**
```SQL
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
```
![G1 Old Generation_実行時間.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3043202/5da41982-d119-4486-950e-2f9b0f393818.png)

## 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で発信しています！
**無料でアカウント作成**も可能なのでぜひお試しください！

:::note info
New Relic株式会社の[X(旧Twitter)](https://x.com/NewRelicJapan) や [Qiita OrganizationOrganization](https://qiita.com/organizations/newrelic)では、
新機能を含む活用方法を公開していますので、ぜひフォローをお願いします。
:::

:::note info
無料のアカウントで試してみよう！
[New Relic フリープランで始めるオブザーバビリティ！](https://qiita.com/ktst79/items/5ee74b34e6adb8a7a6fa)
:::

[![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535230/66d6a010-e4fe-4004-a09b-c01eaad5a614.png)](https://newrelic.com/jp/contact-sales)