私は長い間、コードが主な時間を費やすメソッド、オブジェクト、作成する量を示し、ソースを変更せずにメソッド訪問のトレースをすばやく含めることができるシンプルなユーティリティを書きたいと思っていました。
主な要件は、シンプルさ、テキストモードで実行できること、およびアーキテクチャから独立していることです。
ほとんどのJavaマシンには、
Java Virtual Machine Tool Interface(JVM TI)があります。
しかし、このインターフェースはネイティブモジュールの作成を伴うため、考慮されませんでした。
インストルメンテーション(instrumentation)バイトコードの可能性が残っています。
標準パッケージjava.lang.instrumentを使用すると、バイトコードをダウンロードするプロセスに介入して、その場で変更する可能性があります。
次のようになります。
Javaマシンを起動すると、-javaagentスイッチが示されます:jar-file-name = parameters、
ここで、jar-file-nameはエージェントのjarファイルへの絶対パスで、パラメーターはエージェントに渡されるパラメーターです。
jarファイルのマニフェストでは、メソッドを含むクラスが指定されています
public static void premain(String args, Instrumentation instrumentation)
エージェントがロードされた後、どのjvmが呼び出されます。
ここでこのメカニズムに関する詳細を見つけることができ
ます 。
エージェントのタスクは、ロード可能なクラスのメソッドをそのような方法で変更することです
メソッドを開始および終了すると、
TracerAgentクラスのメソッドが
呼び出されます。
@SuppressWarnings("unused") public static void methodEnter(MethodInfo methodInfo) { methodCallProcessor.methodEnter(methodInfo); } @SuppressWarnings("unused") public static void methodExit() { methodCallProcessor.methodExit(); }
MethodInfoクラスのインスタンスには、メソッドに関する情報が含まれています。
インスツルメントされたメソッドごとにMethodInfoを保存する必要があります。
このため、クラスごとに次の形式のシャドウクラスが作成されます。
public class ShadowClassXXX { public static MethodInfo m1 = agent.TracerAgent.getMethodInfo(1L);
クラスのシャドウフィールドは静的であるため、ロード時に自動的に初期化されます。 コードは、各可変メソッドの先頭に追加されます。
TracerAgent.methodEnter(ShadowClass.mXXX);
最後に
TracerAgent.methodExit();
バイトコードを変更および作成するために、
Javassistライブラリが
使用されました 。 バイトコードに集中せずに、Javaでコード変更を記述できます。
メソッドを開始および終了すると、制御がインターセプトされ、
MethodCallProcessorクラスの対応するメソッドに渡されます。
MethodCallProcessorは、ThreadLocalを使用して各スレッドの呼び出しスタックを保存します。
methodEnterは、呼び出し時間とともにMethodCallMarkerをスタックに保存し、呼び出しカウンターをインクリメントします。
メソッド呼び出しをトレースする必要がある場合、呼び出しスタックが表示されます。
methodExitはMethodCallMarkerスタックから選択し、メソッドで費やした時間をMethodInfoに追加します。
スレッドは
TracerAgentコンストラクターで作成され、shutdownhookに登録されます。 このスレッドは、シャットダウンシーケンスjvmの間に起動され、収集された統計を出力します。 そこで、定期的に統計を表示するためにスレッドが作成および起動されます。
パラメータ:
-javaagent:<エージェントjarファイルへのフルパス> = p:regexp [#t:regexp] [#d:number] [#i:number]
p-インストルメントされたクラス(完全一致-一致)
t-追跡可能なメソッド(部分一致-検索)
d-デバッグレベル
i-秒単位の統計出力の期間(0は定期的な出力を無効にします)
記事のコードの実行例:
java -version java version "1.7.0_21" Java(TM) SE Runtime Environment (build 1.7.0_21-b11) Java HotSpot(TM) 64-Bit Server VM (build 23.21-b01, mixed mode) java -XX:-UseSplitVerifier -javaagent:D:\traceragent\target\traceragent-1.0-SNAPSHOT-complete.jar=p:examples.*
javassistは、まだ新しいバージョン51クラスのバイトコード形式に対応していません。
スイッチ
-XX:-UseSplitVerifierは、JVM7で導入された新しいバイトコード検証、
詳細を無効にします。
結果 [エージェント+] ***パフォーマンスレポート***
時間のかかる上位5つの方法(マイクロ秒):
965050 examples.data.CashAccountRow $ masks.access $ 100(examples.data.CashAccountRow $ masks)
779944 examples.data.CashAccountRow.setAge(int)
766023 examples.data.CashAccountRow.setGender(int)
764292 examples.data.CashAccountRow.setHeight(int)
763387 examples.data.CashAccountRow.setAmount(int)
呼び出された上位5つのメソッド:
22805261 examples.data.CashAccountRow $ masks.access $ 100(examples.data.CashAccountRow $ masks)
9122242 examples.data.CashAccountRow $ shifts.access $ 000(examples.data.CashAccountRow $ shifts)
2280592 examples.data.CashAccountRow.setAge(int)
2280623 examples.data.CashAccountRow.setBitStorage(long)
2280653 examples.data.CashAccountRow.setGender(int)
構築された上位5つのオブジェクト:
1 examples.data.CashAccountStore
1 examples.data.CashAccountRow
[エージェント] ***パフォーマンスレポートの終了***
---スキップ---
一致したレコードの数:38
経過時間:7707ms
使用メモリ:91MB
[エージェント+] ***パフォーマンスレポート***
時間のかかる上位10のメソッド(マイクロ秒):
36081913 examples.App.main(java.lang.String [])
20606259 examples.data.CashAccountStore()
15428502 examples.data.CashAccountStore.find2(examples.data.CashAccountStore $ CashAccountFinder)
12439241 examples.data.GenMatcherAMOUNTHEIGHTGENDER.c(examples.data.CashAccountRow)
9469502 examples.data.CashAccountRow.getAmount()
5928018 examples.data.CashAccountRow $ masks.access $ 100(examples.data.CashAccountRow $ masks)
3392146 examples.data.CashAccountRow $ shifts.access $ 000(examples.data.CashAccountRow $ shifts)
3279163 examples.data.CashAccountRow.setAge(int)
3271959 examples.data.CashAccountRow.setHeight(int)
3267890 examples.data.CashAccountRow.setAmount(int)
呼び出されたメソッドのトップ10:
140079808 examples.data.CashAccountRow $ masks.access $ 100(examples.data.CashAccountRow $ masks)
80079812 examples.data.CashAccountRow $ shifts.access $ 000(examples.data.CashAccountRow $ shifts)
40000000 examples.data.CashAccountRow.getAmount()
30000000 examples.data.CashAccountRow.setBitStorage(long)
20,000,000のexamples.data.GenMatcherAMOUNTHEIGHTGENDER.c(examples.data.CashAccountRow)
10000000 examples.data.CashAccountRow.setGender(int)
10000000 examples.data.CashAccountRow.setAge(int)
10000000 examples.data.CashAccountRow.setAmount(int)
10000000 examples.data.CashAccountRow.setHeight(int)
10000000 examples.data.CashAccountRow.getBitStorage()
構築された上位10個のオブジェクト:
3 examples.data.CashAccountStore $ CashAccountFinder $ FieldGetter
3 examples.data.CashAccountStore $ CashAccountFinder $ PredicateHolder
3 examples.data.CashAccountRow
1 examples.data.GenMatcherAMOUNTHEIGHTGENDER
1 examples.data.GenMatcherBase
1 examples.data.CashAccountStore $ CashAccountFinder $ HeightFieldGetter
1 examples.data.CashAccountStore $ CashAccountFinder
1 examples.data.CashAccountStore
1 examples.data.CashAccountStore $ CashAccountFinder $ AmountFieldGetter
1 examples.data.CashAccountStore $ CashAccountFinder $ GenderFieldGetter
[エージェント] ***パフォーマンスレポートの終了***
当然、エージェントメソッドの呼び出しはパフォーマンスに影響を与えるだけでした。
ご覧のとおり、稼働時間はほぼ500倍に増加しています。
しかし、これはほとんど空のボディを持つメソッドが呼び出される極端なケースであり、通常の生活ではオーバーヘッドを我慢できます。
目標は、正確な時間プロファイリングではなく、正確なコール数です。
作成されたオブジェクトとトレース機能。 コードは
GitHubで入手でき
ます 。
PS。 結果のコードを既製の商用レベルのプロファイラーと見なさないでください。 これは、JVMの別のメカニズムを明らかにする実験に過ぎません。