Javaパフォーマンス診断ツールの代表格「JProfiler」の基本原理とベストプラクティス

この記事では、いくつかの一般的な**Java**パフォーマンス診断ツールについて説明し、**JProfiler**の基本的な原則とベストプラクティスを強調しています。

*本ブログは英語版からの翻訳です。オリジナルは[こちら](https://www.alibabacloud.com/blog/jprofiler-best-practices-powerful-performance-diagnostic-tool_594958)からご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです。*

#背景
パフォーマンス診断は、ソフトウェアエンジニアが日々の業務の中で頻繁に直面し、解決しなければならない問題です。アプリケーションのパフォーマンスを向上させることで大きなメリットを得ることができます。Javaは最も人気のあるプログラミング言語の一つです。そのパフォーマンス診断は、以前から業界全体で注目を集めています。Javaアプリケーションでは、多くの要因がパフォーマンスの問題を引き起こす可能性があります。そのような要因には、スレッド制御、ディスクI/O、データベースアクセス、ネットワークI/O、ガベージコレクション（GC）などがあります。これらの問題を見つけるためには、優れた性能診断ツールが必要です。この記事では、いくつかの一般的なJavaパフォーマンス診断ツールについて説明し、これらのツールの代表格である[JProfiler](https://www.ej-technologies.com/products/jprofiler/overview.html?spm=a2c65.11461447.0.0.4e294d65AIiUSB)の基本的な原理とベストプラクティスを紹介します。この記事で取り上げた JProfiler のバージョンは `JProfiler10.1.4` です。

#Javaパフォーマンス診断ツールの簡単な紹介
Javaの世界には、jmapやjstatなどのシンプルなコマンドラインツールから、JVisualvmやJProfilerなどの包括的なグラフィカルな診断ツールまで、さまざまなパフォーマンス診断ツールが存在します。以下のセクションでは、これらのツールのそれぞれについて簡単に説明します。

#シンプルなコマンドラインツール
Java開発キット(JDK)は、多くの組み込みコマンドラインツールを提供しています。これらのツールは、ターゲットJava仮想マシン(JVM)の情報をさまざまな側面や異なるレイヤーから取得するのに役立ちます。

- jinfo - ターゲットJVMの様々なパラメータをリアルタイムで表示したり、調整したりすることができます。
- jstack - ターゲットJavaプロセスのスレッドスタック情報を取得し、デッドロックを検出し、無限ループを見つけることができます。
- jmap - ターゲットJavaプロセスのメモリ関連情報を取得します。これには、異なるJavaヒープの使用状況、Javaヒープ内のオブジェクトの統計情報、ロードされたクラスなどが含まれます。
- jstat - 軽量で汎用性の高いモニタリングツールです。ターゲットJavaプロセスのロードされたクラス、ジャストインタイム(JIT)コンパイル、ガベージコレクション、メモリ使用量に関する様々な情報を得ることができます。
- jcmd - jstatよりも包括的です。ターゲットJavaプロセスのパフォーマンス統計、Java Flight Recorder (JFR)、メモリ使用量、ガベージコレクション、スレッドスタッキング、JVMランタイムに関する様々な情報を取得することができます。

#包括的なグラフィカル診断ツール
ターゲットJavaアプリケーションに関する基本的なパフォーマンス情報を得るために、上記のコマンドライン・ツールの任意の、または任意の組み合わせを使用することができます。しかし、これらのツールには次のような欠点があります。

1、異なるメソッド間の呼び出し関係や、メソッドが呼び出される頻度や期間など、メソッドレベルの分析データを取得することはできません。これらは、アプリケーションのパフォーマンスのボトルネックを特定するために非常に重要です。
2、これらを使用するには、ターゲットJavaアプリケーションのホストマシンにログオンする必要がありますが、これはあまり便利ではありません。
3、分析データは端末で生成され、結果は表示されません。

以下に、いくつかの包括的なグラフィカルなパフォーマンス診断ツールを紹介します。

###JVisualvm
[JVisualvm](https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jvisualvm.html?spm=a2c65.11461447.0.0.4e294d65AIiUSB) は、JDK が提供するビルトインのビジュアル性能診断ツールです。JMX、jstatd、Attach APIなどの各種メソッドを利用して、CPU使用率、メモリ使用率、スレッド数、ヒープ数、スタック数など、対象となるJVMの分析データを取得します。また、Javaヒープ内の各オブジェクトの量やサイズ、Javaメソッドの呼び出し回数、Javaメソッドの実行時間などを表示することができます。

###JProfiler
[JProfiler](https://www.ej-technologies.com/products/jprofiler/overview.html?spm=a2c65.11461447.0.0.4e294d65AIiUSB)は、ej-technologies社が開発したJavaアプリケーションのパフォーマンス診断ツールです。4つの重要なトピックに焦点を当てています。

1、メソッドコール - メソッドコールの分析は、アプリケーションが何をしているかを理解し、パフォーマンスを向上させる方法を見つけるのに役立ちます。
2、割り当て - ヒープ上のオブジェクト、参照チェーン、ガベージコレクションの分析を通して、この機能はメモリリークを修正し、メモリ使用量を最適化することを可能にします。
3、スレッドとロック - JProfilerは、スレッドとロックに関する複数の解析ビューを提供し、マルチスレッドの問題を発見するのに役立ちます。
4、高レベルサブシステム - 多くのパフォーマンス問題は、より高いセマンティックレベルで発生します。たとえば、Java Database Connectivity (JDBC) の呼び出しでは、どの SQL 文が最も遅いかを調べたいと思うでしょう。JProfilerは、これらのサブシステムの統合的な解析をサポートしています。

#分散型アプリケーション性能診断
スタンドアロンJavaアプリケーションのパフォーマンスのボトルネックを診断するだけであれば、上記の診断ツールで十分にニーズを満たすことができます。しかし、スタンドアロン型の最新システムが徐々に分散システムやマイクロサービスへと進化していくと、上記のツールでは要件を満たすことができなくなります。そこで、[Jaeger](https://github.com/jaegertracing/jaeger?spm=a2c65.11461447.0.0.4e294d65AIiUSB)や[ARMS](https://cn.aliyun.com/product/arms?spm=a2c65.11461447.0.0.4e294d65AIiUSB)、[SkyWalking](https://github.com/apache/skywalking?spm=a2c65.11461447.0.0.4e294d65AIiUSB)などの分散トレースシステムのエンドツーエンドのトレース機能を利用する必要があります。様々な分散トレースシステムが市販されていますが、実装の仕組みは似ています。コードトラッキングによってトレース情報を記録し、記録されたデータをSDKやエージェントを介して中央処理システムに送信し、結果を表示・分析するためのクエリインターフェースを提供します。分散トレースシステムの原理については、JaegerのOpenTracing実装というタイトルの記事を参照してください。

#JProfilerの紹介
###コアコンポーネント
JProfilerは、対象となるJVMから分析データを収集するためのJProfilerエージェント、データを視覚的に分析するためのJProfiler UI、各種機能を提供するコマンドラインユーティリティで構成されています。これらの間の重要な相互作用の全体像を以下に示します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/18d8f287-7173-3681-0d0b-3e7555ab7783.png)


###JProfilerエージェント
JProfilerエージェントはネイティブ・ライブラリとして実装されています。パラメータ-agentpath:<path to native library>を使用してJVMの起動時にロードするか、[JVM Attachメカニズム](http://lovestblog.cn/blog/2014/06/18/jvm-attach/?spm=a2c65.11461447.0.0.4e294d65AIiUSB)を使用してアプリケーションの実行中にロードすることができます。JProfilerエージェントがロードされた後、JVMツール・インターフェース(JVMTI)環境を設定し、スレッドの作成やクラスのロードなど、JVMによって生成されるあらゆる種類のイベントを監視します。例えば、クラス・ローディング・イベントを検出すると、JProfilerエージェントは、これらのクラスに独自のバイトコードを挿入して測定を実行します。

###JProfiler UI
JProfiler UIは個別に起動され、ソケットを介してプロファイリングエージェントに接続します。つまり、プロファイリングされたJVMがローカルマシン上で実行されているか、リモートマシン上で実行されているかは関係ありません - プロファイリングエージェントとJProfiler UI間の通信メカニズムは常に同じです。

JProfiler UIから、エージェントにデータの記録、UIでのプロファイリングデータの表示、ディスクへのスナップショットの保存を指示することができます。

###コマンドラインツール
JProfilerは、さまざまな機能を実装するための一連のコマンドラインツールを提供します。

- jpcontroller - エージェントがデータを収集する方法を制御するために使用します。エージェントによって登録された JProfiler MBean を介してエージェントに命令を送信します。
- jpenable - 実行中のJVMにエージェントをロードするために使用します。
- jpdump - 実行中のJVMのヒープスナップショットをキャプチャするために使用します。
- jpexport & jpcompare - 以前に保存されたスナップショットからデータを抽出し、HTMLレポートを作成するために使用します。


#インストール
JProfilerは、ローカルとリモートの両方のJavaアプリケーションのパフォーマンス診断をサポートします。リモートJVMの分析データをリアルタイムで収集して表示する必要がある場合は、以下の手順を実行します。

1、ローカルに JProfiler UI をインストールします。
2、リモートホストマシンに JProfiler エージェントをインストールし、ターゲット JVM にロードします。
3、JProfiler UI をエージェントに接続します。

インストール手順の詳細については、「[JProfiler のインストール](https://www.ej-technologies.com/resources/jprofiler/help/doc/main/introduction.html#jprofiler.installing)」と 「[JVM のプロファイリング](https://www.ej-technologies.com/resources/jprofiler/help/doc/main/introduction.html#jprofiler.profiling)」を参照してください。

#ベストプラクティス
ここでは、LogHubクラスライブラリである[Alibaba Cloud LOG Java Producer](https://github.com/aliyun/aliyun-log-java-producer?spm=a2c65.11461447.0.0.4e294d65AIiUSB)（以下、Producer）のパフォーマンスをJProfilerを使って診断する方法を紹介します。お使いのアプリケーションや Producer を使用した際にパフォーマンスに問題が発生した場合は、同様の対策を行うことで根本原因を探ることができます。Producerをご存じない方は、まずこの記事をお読みになることをお勧めします。Alibaba Cloud LOG Java Producer - ログをクラウドに移行するための強力なツールです。

ここで使用したサンプルコードは[SamplePerformance.java](https://github.com/aliyun/aliyun-log-producer-sample/blob/master/src/main/java/com/aliyun/openservices/aliyun/log/producer/sample/SamplePerformance.java?spm=a2c65.11461447.0.0.4e294d65AIiUSB&file=SamplePerformance.java)を参照してください。

#JProfilerの設定
###データ収集モード
JProfilerには、サンプリングと計測の2つのデータ収集方法があります。

- サンプリング - 高いデータ収集精度を必要としないシナリオに適しています。この方法の利点は、システム・パフォーマンスへの影響が少ないことです。欠点は、メソッドレベルの統計などの一部の機能がサポートされていないことです。
- インストルメンテーション - 高精度をサポートする完全なデータ収集モードです。欠点は、多くのクラスを分析しなければならないことと、アプリケーションのパフォーマンスへの影響が比較的重いことです。影響を軽減するには、フィルタと一緒に使用するとよいでしょう。
この例では、メソッドレベルの統計情報を取得する必要があるため、インストルメンテーション方式を選択します。フィルタは、エージェントがjavaパッケージの下で`com.aliyun.openservices.aliyun.log.producer`と`com.aliyun.openservices.log.Client`の2つのクラスのみのCPUデータを記録するように構成されています。

###アプリケーションの起動モード
JProfilerエージェントにさまざまなパラメータを指定して、アプリケーションの起動モードを制御することができます。

- JProfiler GUIからの接続を待つ - JProfiler GUIがプロファイリングエージェントとの接続を確立し、プロファイリング設定が完了した場合にのみ、アプリケーションが起動します。このオプションを使用すると、アプリケーションの起動フェーズをプロファイリングすることができます。このオプションを有効にするために使用できるコマンド: `-agentpath:<path to native library>=port=8849`

- すぐに起動して、後で JProfiler GUI で接続 - JProfiler GUI は、必要なときにプロファイリングエージェントとの接続を確立し、プロファイリング設定を送信します。このオプションは柔軟性がありますが、アプリケーションの起動フェーズをプロファイリングすることはできません。このオプションを有効にするために使用できるコマンド: `-agentpath:<path to native library>=port=8849,nowait.`

- プロファイルがオフライン、JProfiler が接続できない場合 - データを記録し、後で JProfiler GUI で開くことができるスナップショットを保存するトリガーを設定する必要があります。このオプションを有効にするために使用できるコマンドは、`-agentpath:<path to native library>=offline,id=xxx,config=/config.xml.`

テスト環境では、起動フェーズでアプリケーションのパフォーマンスを判断する必要があります。そこで、ここではデフォルトのWAITオプションを使用します。

#JProfilerを使用してアプリケーションのパフォーマンスを診断
プロファイリングの設定が完了すると、プロデューサーのパフォーマンス診断に進むことができます。

###概要
概要ページでは、メモリ、GC活動、クラス、スレッド、CPU負荷など、様々なメトリクスに関するグラフ（テレメトリ）を明確に見ることができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/57db1715-6bf1-03a4-5e7f-bd0401196775.png)


このテレメトリに基づいて、以下のような前提を立てることができます。

1、アプリケーションの実行中に大量のオブジェクトが生成されます。これらのオブジェクトのライフサイクルは非常に短く、ほとんどのオブジェクトはガベージコレクタによって速やかにリサイクルされます。これらのオブジェクトは、メモリ使用量が継続的に増加する原因にはなりません。
2、予想通り、ロードされるクラスの数は起動期間中に急速に増加し、その後は安定します。
3、アプリケーションの実行中は、多くのスレッドがブロックされます。この問題には特に注意が必要です。
4、アプリケーションの起動時には、CPUの使用率が高くなります。その原因を探る必要があります。


###CPUビュー
アプリケーション内の各メソッドの実行回数、実行時間、呼び出し関係は、CPU ビューで表示されます。これらは、アプリケーションのパフォーマンスに最も大きな影響を与えるメソッドを見つけるのに役立ちます。

###コールツリー
コールツリーは、ツリーグラフを使って、異なるメソッド間のコール関係を階層的に表示します。さらに、JProfiler はサブメソッドを総実行時間でソートするので、キーとなるメソッドを素早く見つけることができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/94d5d263-3a53-0cea-ca42-5b242bd51a3b.png)


Producerの場合、`SendProducerBatchTask.run()`というメソッドの実行にほとんどの時間がかかっています。下を見続けると、ほとんどの時間が `Client.PutLogs() `メソッドの実行にかかっていることがわかります。

###ホットスポット
多くのアプリケーション・メソッドがあり、サブ・メソッドの多くが短い間隔で実行されている場合、ホット・スポット・ビューを使用すると、パフォーマンスの問題を素早く見つけることができます。このビューでは、個別の実行時間、合計実行時間、平均実行時間、呼び出し回数などのさまざまな要因に基づいてメソッドを並べ替えることができます。個々の実行時間は、メソッドの合計実行時間からすべてのサブメソッドの合計実行時間を差し引いたものです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/87e484fa-a1a0-93e4-ac72-ddd4a316c99c.png)

このビューでは、次の3つのメソッドが最も実行に時間がかかっていることがわかります。`Client.PutLogs()`、`LogGroup.toByteArray()`、`SamplePerformance$1.run()`の3つのメソッドが個別に実行されるのに最も時間がかかることがわかります。

###コールグラフ
キーメソッドを見つけた後、コールグラフビューでは、これらのキーメソッドに直接関連するすべてのメソッドを表示することができます。これは、問題の解決策を見つけ、最適なパフォーマンス最適化ポリシーを開発するのに役立ちます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/1805b11f-6e9a-1646-92d3-fed658e545bd.png)

ここでは、メソッド Client.PutLogs() の実行時間のほとんどがオブジェクトのシリアライズに費やされていることがわかります。したがって、パフォーマンスを最適化する鍵は、より効率的なシリアライズメソッドを提供することです。

###ライブメモリ
ライブメモリビューでは、メモリの詳細な割り当てと使用状況を知ることができ、メモリリークがあるかどうかを判断するのに役立ちます。

###All Objects
All Objects ビューには、現在のヒープ内の様々なオブジェクトの数と合計サイズが表示されます。次の図からわかるように、アプリケーションの実行中に多数の LogContent オブジェクトが作成されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/8732bf0e-5e2d-4f6c-c8d6-2d84a5766f98.png)

###Allocation Call Tree
Allocation Call Tree ビューには、各メソッドに割り当てられたメモリ量がツリー図の形で表示されます。ご覧のように、`SamplePerformance$1.run()`と`SendProducerBatchTask.run()`は大量のメモリを消費しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/b31a8f61-c977-c77e-4970-45db55d98c27.png)


###Allocation Hot Spots
多くのメソッドがある場合は、Allocation Hot Spots ビューで、どのメソッドに最も多くのオブジェクトが割り当てられているかをすぐに確認できます。

###Thread History
Thread Historyビューでは、異なる時点での各スレッドの状態が表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/76b179d7-748e-77eb-1dfa-4d948342f1fa.png)

異なるスレッドによって実行されるタスクは、異なる特徴を持っています。

- スレッド `Pool-1-thread-<M>` は定期的に `Producer.send() `メソッドを呼び出して非同期にデータを送信しています。これらはアプリケーションの起動中は実行を続けていましたが、その後はほとんどがブロックされていました。この現象の原因は、データ生成速度よりも Producer のデータ送信速度が遅いことと、各 Producer インスタンスのキャッシュサイズが制限されていることにあります。アプリケーション起動後、Producerには送信待ちのデータをキャッシュするための十分なメモリがあったため、`pool-1-thread-<M>`はしばらく起動したままでした。これは、アプリケーションの起動時にCPU使用率が高かった理由を説明するものです。`pool-1-thread-<M>`はプロデューサーが十分なスペースを解放するまで待たなければなりません。そのため、大量のスレッドがブロックされています。

- `aliyun-log-producer-0-mover`は、期限切れのバッチを検出し、iothreadPoolに送信します。データ蓄積速度が速く、キャッシュされたデータサイズが上限に達した直後に、`pool-1-thread-<M>`によってプロデューサバッチがIOThreadPoolに送信されます。そのため、ムーバースレッドはほとんどの時間アイドル状態のままでした。

- `aliyun-log-producer-0-io-thread-<N>`は、IOThreadPoolから指定したログストアにデータを送信し、ネットワークのI/O状態にほとんどの時間をかけています。
- `aliyun-log-producer-0-success-batch-handler`は、ログストアに正常に送信されたバッチを処理します。コールバックはシンプルで、実行にかかる時間は非常に短いです。そのため、SuccessBatchHandlerはほとんどの時間アイドル状態のままでした。
- `aliyun-log-producer-0-failure-batch-handler`は、ログストアへの送信に失敗したバッチを処理します。私たちの場合、送信に失敗したデータはありません。それはずっとアイドルのままでした。
私たちの分析によると、これらのスレッドのステータスは私たちの予想の範囲内です。

###検出されたオーバーヘッドホットスポット
アプリケーションの実行が終了すると、JProfilerは、実行時間が非常に短い頻繁に呼び出されるメソッドを表示するダイアログボックスを表示します。次回は、これらのメソッドを無視するようにJProfilerエージェントを設定することで、JProfilerによるアプリケーションのパフォーマンスへの影響を軽減することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/98aea172-ef00-eaf0-619d-854f387d05f1.png)

#概要
JProfilerの診断に基づき、アプリケーションには大きなパフォーマンスの問題やメモリリークはありません。次の最適化のステップは、オブジェクトのシリアライズ効率の向上です。

#参考文献
- [Jprofiler の紹介](https://www.ej-technologies.com/resources/jprofiler/help/doc/main/introduction.html#jprofiler.introduction)

*アリババクラウドは日本に２つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
[アリババクラウドジャパン公式ページ](https://www.alibabacloud.com/ja)*