手取りはあなたに1つの方法の時間の統計のjava agentを実現することを教えます
5855 ワード
前述の2つの敷敷布団博文は、博文「200303-javaでコードブロックの消費時間をどのように優雅に統計するか」で、最後にjava agentを利用して統計方法の消費時間を統計する博文「200316-IDEA+mavenゼロ基礎に基づいてjava agentプロジェクトを構築する」に言及した.では、java agent開発テストプロジェクトを構築する全過程を詳しく説明します.このブログはjava agentの実戦に入り、統計方法を実現するのに時間がかかるjava agentを教えます.基本姿勢点上の2節では、手を取ってhello world版agentを実現しましたが、実際にはjava agentに対して依然として茫然としているので、まず基礎知識を補充しなければなりません.まずagentの2つの方法のパラメータInstrumentationを見てみましょう.まず、インタフェースの定義/** を見てみましょう.はTransformerを登録し、その後のクラスロードはTransformerによってブロックされます. Transformerはクラスのバイトコードbyte[]を直接修正することができる*/void addTransformer(ClassFileTransformer transformer);
/**は、JVMにロードされたクラスに対してクラスロードを再起動する.上に登録したTransformerを使用しています. retransformationではメソッドボディを変更できますが、メソッド署名の変更、メソッド/クラスのメンバー属性の追加、削除はできません*/void retransformClass(Class>...classes)throws UnmodifiableClassException;
/**オブジェクトのサイズ*/long getObject Size(Object objectToSize)を取得します.
/** bootstrap classloaderのclasspathにjarを追加*/void appendToBootstrapClassLoaderSearch(JarFile jarfile);
/**現在JVMにロードされているすべてのクラスオブジェクト*/Class[]getAllLoadedClass()を取得します.前の2つのメソッドが重要で、addTransformerメソッドが構成されると、後続のクラスロードがTransformerによってブロックされます.ロードされたクラスの場合、retransformClassesを実行して、このTransformerのブロックを再トリガーできます.クラスにロードされたバイトコードは、再びretransformされない限り、変更されません.以上の説明から分かるように、 Transformerでクラスクラスのロードを変更できる場合、Transformerブロッキングがトリガーされます実装では、メソッドを統計するのに時間がかかるので、メソッドの実行前に時間を記録し、実行後に時間差を統計すると、バイトコードを直接変更するのは面倒なので、神器javaassistを利用してバイトコードを修正してカスタムClassFileTransformerを実現します.コードは以下のpublic class CostTransformer implements ClassFileTransformer{@Override public byte[]transform(ClassLoader loader,String className,Class>classBeingRedefined,ProtectionDomain protectionDomain,byte[]classfileBuffer){//ここで制限するのは、ターゲットパッケージの下についてのみ時間統計if(!className.startsWith((「com/git/hui/java/」){return classfileBuffer;} を少し変更します. Created by @author yihui in 16:39 20/3/15. */public class SimpleAgent {/** jvmパラメータ形式で起動し、このメソッド を実行します.
manifestプロパティPremain-Class を構成する必要があります
@param agentArgs @param inst */public static void premain(String agentArgs, Instrumentation inst) { System.out.println(“premain”); customLogic(inst); }
/**動的attach方式が起動し、この方法 が実行される.
manifestプロパティAgent-Class を構成する必要があります.
@param agentArgs @param inst */public static void agentmain(String agentArgs, Instrumentation inst) { System.out.println(“agentmain”); customLogic(inst); }
/**統計方法消費時間
@param inst*/private static void customLogic(Instrumentation inst){inst.addTransformer(new CostTransformer()、true)}}このagentが完了し、パッケージ化は上記の手順と同じで、次にテストの一環に入ってDemoClzを作成し、その中の2つの方法public class DemoClz{ public int print(int i) { System.out.println("i: "+ i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } return i + 2; } public int count(int i) { System.out.println("cnt: "+ i); try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } return i + 1; } }次に対応するmainメソッドは、public class BaseMain{public static void main(String[]args)throws InterruptedException{DemoClz demoClz=new DemoClz();int cnt=0;for(int i=0;i<20;i+){if(++cnt%2==0){i=demoClz.print(i);el{se i=demoClz.cont(i)}}jvmパラメータを選択してagentの実行方法を指定する(具体的な動作は上記と同様)、出力は以下のである.
我々のアプリケーションにはメソッドの時間消費統計はありませんが、最終的な出力は各メソッドの呼び出し時間消費を完璧に印刷し、侵入のない時間消費統計機能を実現しました.ここではjava agentのブラインドスキャン+実戦(メソッドの時間消費統計を開発する)すでに完成していて、まとめができることを宣言しているかどうかは、そうではありません.上のdemoを実現する過程で出会った問題3.Exception in thread「main」を紹介します.JAva.lang.VerifyError:Expecting a stack map frameプレゼンテーション方法に時間がかかるagentの例では、最初のテスト用例ではなく、DemoClzを新規作成したものですが、なぜこのように選択したのでしょうか.第2節のテスト用例をそのまま使うとどうなるのでしょうか.public class BaseMain{public int print(int i){System.out.println("i:"+i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } return i + 2; }
}依然としてjvmパラメータでagentを指定する方式で、上のコードを実行すると、投げ異常が見つかり、正常に実行できなくなります
runメソッドでは,バイトコードの誤りが存在することを指摘し,メソッドの開始前と終了後にそれぞれ1行のコードを追加し,runメソッドに直接補足すると,次のコードに相当する時間のかかるエージェントを統計した.
上のヒントは明らかに教えて、最後の行の文は永遠に達成することができなくて、コンパイルは異常が存在します;それでは問題が来て、java agentの提供者として、私は使用者がこのような死の循環の方法を書くかどうかを知っていて、もし応用の中でこのような死の循環の任務が存在するならば、私のagentを載せて、応用を起こすことができなくて、これは鍋は誰のですか???以下に解決策を提供しますが、簡単です.jvmパラメータに-noverify(異なるjdkバージョンに注意してください.パラメータは異なるかもしれませんが、私の地元はjdk 8です.このパラメータを使用します.jdk 7であれば-XX:-UseSplitVerifierを試してみてください)IDEA開発環境で、以下のように構成すればいいです.
再稼働、正常小結本編は実戦プロジェクトであり、まず方法パラメータInstrumentationのインタフェース定義を明確にし、それによってjavaバイトコードの修正を実現する.我々はカスタムClassFileTransformerを実現することによって、javassistを利用してバイトコードを修正し、各方法の最初の行と最後の行に時間統計のコードを注入し、それによって方法の時間統計が最後に残ることを実現する.一つの小さな問題は、上の実装では、方法の内部に異常が投げ出された場合、私たちが注入した最後の行の統計が予定通りに出力されるかどうか、できない場合は、どのように修正すべきか、皆さんのメッセージを歓迎して解決策 を指摘します.
/**
/**
/**
/**
CtClass cl = null;
try {
ClassPool classPool = ClassPool.getDefault();
cl = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));
for (CtMethod method : cl.getDeclaredMethods()) {
// , ; , `addLocalVariable`
method.addLocalVariable("start", CtClass.longType);
method.insertBefore("start = System.currentTimeMillis();");
String methodName = method.getLongName();
method.insertAfter("System.out.println(\"" + methodName + " cost: \" + (System" +
".currentTimeMillis() - start));");
}
byte[] transformed = cl.toBytecode();
return transformed;
} catch (Exception e) {
e.printStackTrace();
}
return classfileBuffer;
}そしてagent/**/**
/**
我々のアプリケーションにはメソッドの時間消費統計はありませんが、最終的な出力は各メソッドの呼び出し時間消費を完璧に印刷し、侵入のない時間消費統計機能を実現しました.ここではjava agentのブラインドスキャン+実戦(メソッドの時間消費統計を開発する)すでに完成していて、まとめができることを宣言しているかどうかは、そうではありません.上のdemoを実現する過程で出会った問題3.Exception in thread「main」を紹介します.JAva.lang.VerifyError:Expecting a stack map frameプレゼンテーション方法に時間がかかるagentの例では、最初のテスト用例ではなく、DemoClzを新規作成したものですが、なぜこのように選択したのでしょうか.第2節のテスト用例をそのまま使うとどうなるのでしょうか.public class BaseMain{public int print(int i){System.out.println("i:"+i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } return i + 2; }
public void run() {
int i = 1;
while (true) {
i = print(i);
}
}
public static void main(String[] args) {
BaseMain main = new BaseMain();
main.run();
}依然としてjvmパラメータでagentを指定する方式で、上のコードを実行すると、投げ異常が見つかり、正常に実行できなくなります
runメソッドでは,バイトコードの誤りが存在することを指摘し,メソッドの開始前と終了後にそれぞれ1行のコードを追加し,runメソッドに直接補足すると,次のコードに相当する時間のかかるエージェントを統計した.
上のヒントは明らかに教えて、最後の行の文は永遠に達成することができなくて、コンパイルは異常が存在します;それでは問題が来て、java agentの提供者として、私は使用者がこのような死の循環の方法を書くかどうかを知っていて、もし応用の中でこのような死の循環の任務が存在するならば、私のagentを載せて、応用を起こすことができなくて、これは鍋は誰のですか???以下に解決策を提供しますが、簡単です.jvmパラメータに-noverify(異なるjdkバージョンに注意してください.パラメータは異なるかもしれませんが、私の地元はjdk 8です.このパラメータを使用します.jdk 7であれば-XX:-UseSplitVerifierを試してみてください)IDEA開発環境で、以下のように構成すればいいです.
再稼働、正常