インストゥルメントAPIの紹介

1.はじめに計装

 JVMTI(JVMツールインターフェイス)は、ネイティブプログラミングインタフェースが提供するJava仮想マシンであるJVMPI(Java仮想マシンプロファイラー・インターフェース)で、JVMDI(Java仮想マシンデバッグインタフェース)が更新されたバージョンです。JVMTIは、接続して、アクセスプロキシJVM、およびJVMTIは、JVMに関連した完全な多くの機能を提供するインタフェースの豊富なプログラミングの利点を活用するために、サードパーティ製のユーティリティをサポートするための「プロキシ」の手続きメカニズムを提供します。

 JVMTIエージェントクライアントそれ、それと同じプロセス上の仮想マシンで実行するJavaプログラムを実行します。これらは通常の状態で提供さJVMTIインターフェースおよび仮想マシンの相互作用は、現在の仮想マシンまたはフォワード制御コマンドを取得し、返却を担当して呼び出すことで、個々のプロセスと現在の仮想マシン間の仲介として機能し、別の独立したプロセスによって制御されています。java.lang.instrumentパッケージを実現し、それはまた、このメカニズムに基づいています。それらを達成するための計装、JVMTIエージェントプログラム、JVMTIそれらを呼び出すことにより、Javaクラスの動的な操作を完了するためのJavaクラスに関連する機能があります。

 インストルメンテーションは、ダイナミックの使用は、Java SE 5の新機能を行うjava.lang.instrumentで、それはJavaコードを経由して、問題を解決することができるようにすることを、その中にネイティブコードからの解放のJavaの楽器の関数です。( - エージェント)計器を使用して、開発者はアプリケーションに依存しないエージェントを構築することができ、監視およびJVM上で実行中のプログラムを支援するため、代替及び修正も、特定のクラスを定義することができます。この機能により、開発者はクラスのJava仮想マシンモニタを実装し、より柔軟な運用を操作することができ、この機能は実際には、開発者がJDKする必要はありませんので、こと、AOPの実装の仮想マシン・レベルのサポートを提供します任意のアップグレードや変更を行うには、我々はAOPの機能の一部を実現することができます。

 JavaのSE6の内部には、最大の変化は、実行可能計装です。Java SEの5では、インストゥルメントは、(ほとんどのJavaクラスライブラリにロードされる前に)初期化時に、プロキシクラスを設定するには、コマンドラインパラメータやシステムパラメータを操作する前に、仮想マシンで実走行を必要と、計装セットアップは、仮想マシンで、立ち上げや荷重条件の特定のタイプを検出し、実際の作業を行うためにコールバック関数を設定されています。しかし、多くの実用的な状況では、我々はそう、実際に楽器の適用を制限、仮想マシンの起動時にそのプロキシを設定する方法がありません。Java SEの6の新機能は、この状況を変えた、JavaのツールAPIが道を添付している、我々は簡単にプロキシクラスは、計測の目的を達成するために、実行時に動的にロードされている設定することができます。

 最大の効果の計測器は、動的クラス定義及び操作を変更することです。Java SE 5以降、通常のJavaプログラムで開発者の間(主な機能を持つJavaクラス)ランタイムJARファイルにプロキシ計測を開始するために、特定のパラメータ-javaagent(含む計装エージェント)によって指定されましたプログラム。

2.変圧器

 スケジューリングファサードバイトコード変換用のトランスバイトコード変換インタフェース、計装管理変圧器、変圧器。計装のときaddTransformer実行、removeTransformer方法は、最終的には、変圧器を管理するために、addTransformer、removeTransformerのTransformerManagerと呼ばれています。

 retransformClasses、通知スケジュールTransformerManagerバイトコード変換のためのredefineClassesの計測。ClassLoader.defineClass1を呼び出すときに加えて、()このメソッドは、ネイティブクラス定義のために使用され、我々は、バイトコードへの変換スケジューリングトランスTransformerManagerを通知します。これら三つの通知タイミングのバイトコードが呼び出されます。

  • 場合ローディングクラス(1)
  • クラスの再定義(2)
  • 重変換クラス(3)

 再変換トランス、非変換重トランス:<B>変圧器は、2つのタイプに分けることができます。再定義されたクラスを変換する際に、トランスいずれかのクラスは、負荷に使用することができます。それは、Transformerを再変換すると、変換が再変換時に実行することができます。すべてのコンバータの登録は、クラスローダ(1)またはクラス(2)の再定義するときに発生した場合、コンバータの性能をトリガします。唯一の変換は重い変圧器変換クラスときにトリガすることができます。</ B>

 ときに複数のコンバータ、変換は、コールチェーンによって変換します。言い換えると、返されたバイト配列を変換するための呼び出しは、次の呼び出しの入力となります。変換は、次の順序で実行しました:

  • コンバータない再変換
  • ローカル(ネイティブ)コンバータを再変換しません
  • 再コンバータコンバータ
  • ローカル(ネイティブ)コンバータを再変換

同様に、(3)再変換、再利用される前に、変換器と呼ばれるが、変換の結果ではない再変換しません。その他の場合、このメソッドが呼び出されます。このような各グループ通話において、登録のために変換器。

 ClassFileTransformerインタフェースは、唯一の方法があります。

byte[] transform(  ClassLoader         loader,
                String              className,
                Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException; 

フィールドがバイト配列classfileBufferコンテンツクラスがロードされている、それが初期化されないコンテンツバイト配列クラスの結果を返します。そのクラスは、このメソッドによって返されるクラスの動作を変更するように変更の内容を元のコンテンツを変更することができます。あなたが任意の変換を行わない場合は、nullを返却する必要があります。コンバータは例外(キャッチされない例外)をスローした場合、フォローアップコンバータがまだ呼び出されてロードされます、まだ再定義または再変換しようとします。そのため、例外が同じリターンヌル結果とスローされます。

 https://docs.oracle.com/javase/7/docs/api/java/lang/instrument/ClassFileTransformer.htmlを参照してください。

2.1。redefineClasses

 提供再定義が提供するクラスのセットを使用してクラスファイル。

 このメソッドは、単にソースから修理に再コンパイルおよびデバッグを継続する場合のように、既存のクラスファイルバイトを参照することなく、クラスの定義を置き換えるために使用されます。既存のクラスファイルの代わりに使用retransformClassesする必要があります変換されるバイト。

 そのようなB.を再定義することができるクラスAを再定義するように、相互依存の複数のクラスを変更することを可能にしながら、この方法は、クラスのセット上で動作します

 再定義されたメソッドがアクティブなスタックフレームを持っている場合は、それらのアクティブフレームは元のメソッドのバイトコードを実行し続けます。再定義されたメソッドは新しい呼び出しに使用されます。

 任意の初期化が発生することはありません。この方法では、従来のJVMセマンティクスに加えて発生します。言い換えれば、クラスとそれが初期化が実行されてはならない再定義しました。値は、呼び出す前に静的変数をままになります。クラスを再定義例は影響を受けません。

 メソッド本体、定数プールと属性を変更することがあり再定義。再定義は、追加、削除、またはフィールドやメソッドの名前を変更し、メソッドシグネチャ、または変更の継承を変更することはできません。これらの制限は、将来のリリースで解除することができます。クラスファイルバイトがチェックされていない、とインストールの検証は、変換アプリケーションまで、結果はバイトエラーならば、このメソッドは例外をスローします。このメソッドは例外をスローした場合、それは任意のクラスを再定義しません。

 次のようにメソッドが定義されます。

void redefineClasses(ClassDefinition... definitions)  throws ClassNotFoundException,UnmodifiableClassException public ClassDefinition(Class<?> theClass,byte[] theClassFile) { if (theClass == null || theClassFile == null) { throw new NullPointerException(); } mClass = theClass; mClassFile = theClassFile; } 

上述したように、このメソッドは指定されたクラスを交換する必要があるとカスタムクラスのバイトコードファイルのコンテンツを提供するために、https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/を参照してくださいが必要ですInstrumentation.html#redefineClasses-java.lang.instrument.ClassDefinition ...-

2.2。retransformClasses

 クラスのセットを再変換します。

 このメソッドは、クラス、オフロードでの主要な役割を担っています。初期化するかredifineはClassFileTransformerを持つクラスは、再処理されていたにかかわらず、変換前に発生するかどうかの、この関数は、変換処理を再実行します。変換プロセスは、次の手順を実行します。

  • 初期クラスファイルバイトからスタート

  • 各変換器canRetransform負荷に基づいて、偽であるため、または変換器の再利用バイト出力電流、現在アクティブでないに対応する変換器であることがトランスデューサによって復帰時に再定義

  • 真の各コンバータのcanRetransformの設定については、それが現在のコンバータを呼び出します

  • クラスファイルのバイトは、インストールの新しいクラスとして定義変換しました

 そのようなB.を再定義することができるクラスAを再定義するように、相互依存の複数のクラスを変更することを可能にしながら、この方法は、クラスのセット上で動作します

 再定義されたメソッドがアクティブなスタックフレームを持っている場合は、それらのアクティブフレームは元のメソッドのバイトコードを実行し続けます。再定義されたメソッドは新しい呼び出しに使用されます。

 任意の初期化が発生することはありません。この方法では、従来のJVMセマンティクスに加えて発生します。言い換えれば、クラスとそれが初期化が実行されてはならない再定義しました。値は、呼び出す前に静的変数をままになります。再定義された影響を受けたクラスの例

 再変換は、メソッド本体、定数プールと属性を変更することがあります。再送信は、追加、削除、またはフィールドやメソッドの名前を変更し、メソッドシグネチャ、または変更の継承を変更することはできません。これらの制限は、将来のリリースで解除することができます。クラスファイルバイトがチェックされていない、とインストールの検証は、変換アプリケーションまで、結果はバイトエラーならば、このメソッドは例外をスローします。このメソッドは例外をスローした場合、それは任意のクラスを再作成しません。

 次のようにこの方法は、以下のとおりです。

void retransformClasses(Class<?>... classes)  throws UnmodifiableClassException 

この方法は、着信クラスの再変換する必要があり、https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/Instrumentation.html#retransformClasses-java.lang.Classを参照してください。 ..-

 真:必要性がMANIFEST.MFでjavaagentにオープン再定義エージェント機能へのCAN-再定義 - クラスを設定することに留意すべきです。真:私たちは、オープン再変換エージェントにMANIFEST.MFファイルが特徴javaagentにCAN-再変換・クラスを定義する必要があります。

 関連するコンテンツの上に導入され、ここに方法です。

3. JDK5のpremainの道

 Premain的に必要とされる処理ステップを使用して

3.1は、パブリック静的メソッドpremainを提供しています。
//<1>
public static void premain(String agentArgs, Instrumentation inst); //<2> public static void premain(String agentArgs); 

前記<1>から<2>よりも高い優先度であり、優先度が(<1>および<2>存在<2>無視される)実行されます。

 mainメソッドが実行される先行するメソッド名、など。一般に、この方法でプロキシオブジェクトを作成し、addTransformer工大()メソッドのパラメータは、プロキシは、仮想マシンに渡される前に作成されたオブジェクト。「 - javaagent」着信共にagentArgsプログラムパラメーターpremain機能と共に、得られます。違いは、プログラムパラメータが複数ある場合、プログラムは、文字列自体を解析し、主な機能は、このパラメータは文字列ではなく、文字列の配列であることです。

3.2以上ClassFileTransformer実装クラスの

 上述したように、それは上院であるaddTransformer()メソッドにpremainでINST呼び出されるClassFileTransformerオブジェクトです。

 一つの修飾されたバイトコードが導入されているため、様々な方法が存在してもよいです。本明細書で使用される場合、CoreActionImplに基づいて、前のセクションの例では、AOPを達成するように変更しました。コードは以下の通りであります:

public class PreMainProxyAction implements ClassFileTransformer { @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { if (!className.equals("demo/CoreActionImpl")) { return classfileBuffer; } ASMProxyAction proxyAction = new ASMProxyAction(); byte[] bytes = proxyAction.aop(classfileBuffer); //这里可以将bytes写入到文件,输出处理后的calss内容 return bytes; } public static void premain(String agentArgs, Instrumentation inst) throws ClassNotFoundException, UnmodifiableClassException { inst.addTransformer(new PreMainProxyAction()); } } 

次のようにコンテンツがASMの内容例が、再編成コード多重であることを特徴とする請求ASMProxyActionは、コアです。

public byte[] aop(byte[] bytes) {
        ClassReader cr = new ClassReader(bytes);
        return aop(cr); } public byte[] aop(ClassReader cr) { ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); cr.accept(new ClassVisitor(Opcodes.ASM6, cw) { @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); if (!"say".equals(name)) { return mv; } MethodVisitor aopMV = new MethodVisitor(super.api, mv) { @Override public void visitCode() { super.visitCode(); mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); mv.visitLdcInsn("before core action"); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); } @Override public void visitInsn(int opcode) { if (Opcodes.RETURN == opcode) { mv.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); mv.visitLdcInsn("after core action"); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); } super.visitInsn(opcode); } }; return aopMV; } }, ClassReader.SKIP_DEBUG); return cw.toByteArray(); } 
3.3。Jarファイルのパッケージ

 このJavaクラスは3.1のJavaクラスの中premainで書かれていることのステップを指定するには、「Premainクラス」を追加これらのプロパティをjarファイルにパッケージ化し、マニフェストされます。

3.4。ファイル名を指定して実行

 計装の次のような方法でJavaプログラムを実行すると:

java -javaagent:jar 文件的位置 [= 传入 premain 的参数 ]

 次のようにコメントの内容に応じて上記のサンプルコードは、バイトコード出力処理の後です。

public class demo/CoreActionImpl implements demo/Action  {


  // access flags 0x1
  public <init>()V ALOAD 0 INVOKESPECIAL java/lang/Object.<init> ()V RETURN MAXSTACK = 1 MAXLOCALS = 1 // access flags 0x1 public say()V GETSTATIC java/lang/System.out : Ljava/io/PrintStream; LDC "before core action" INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V GETSTATIC java/lang/System.out : Ljava/io/PrintStream; LDC "hello world" INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V GETSTATIC java/lang/System.out : Ljava/io/PrintStream; LDC "after core action" INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V RETURN MAXSTACK = 2 MAXLOCALS = 1 } 

実際には、お祭りは、ASM処理、AOP後の結果です。この方法は、premainすなわち楽器を提供し、前メイン方法、元のクラスのコンテンツ、追加のカスタムロジックを実行するエントリを提供します。

 このように変換(トランスクラス)メソッド、addTransformerがどのクラスの変換方法を指定していないことに留意され、プログラムは、現在、上記の例のように、変換する独自のクラスのニーズを必要とするかどうかを判断します。

4. JDK6のagentmain方式

 JDK5はクラスのみのために提供premain方法は、アプリケーション・プロセスを開始する前に行って、JDK6は、開発者は、main関数の後に始まり、その後、独自のハンドラを起動することができ、これに基づいて改善されました。

 Agentmainように、必要に応じて処理の手順を用いて

4.1は、パブリック静的メソッドagentmainを提供しています。
//<1>
public static void agentmain (String agentArgs, Instrumentation inst); //<2> public static void agentmain (String agentArgs); 

前記<1>から<2>よりも高い優先度であり、優先度が(<1>および<2>存在<2>無視される)実行されます。

4.2以上ClassFileTransformer実装クラスの

 この方法は、3.2と一致しています。違いはつまり、agentmainの方法が原因は、再ターゲットクラスへの必要性が、上記の説明に従って処理され、あなたがクラスの再処理のretransformClassesメソッドを呼び出すことができ、この時間は、ターゲットクラスがあまりにもロードされている可能性があり、仮想マシンが起動した後に処理されています。

4.3。Jarファイルのパッケージ

 このJavaクラスが書かれagentmain工程のうち4.1でJavaクラスを指定するには、「エージェント・クラス」を追加これらのプロパティをjarファイルにパッケージ化し、マニフェストされます。

4.4。ロードのjarパッケージ

 これは、アクセス外部アプリケーションの表示トリガーにagentmain必要premainと矛盾しています。Attach APIを提供するJava SE 6、ターゲットJVMへのユーティリティのためのエージェントが添付します。APIを取り付け、ノートには、Javaの標準APIはありませんが、拡張API日会社のセット。

 アタッチAPIは、com.sun.tools.attachパッケージされているだけで2つの主なクラス、非常に簡単です:VirtualMachineに、プログラムのターゲット仮想マシンを監視する必要があるJava仮想マシン、の代わりに、JVMは添付して、アクション、列挙を提供します切り離し操作(逆の挙動が運転を取り付け、薬剤は上部JVMから放出される)などが、このクラスは、様々な機能を実行するのVirtualMachineとVirtualMachineDescriptorがは、仮想マシンコンテナクラスに記載されています。

 アップ実行中の瓶の仮想マシンにパッケージを取り付けて、以下の方法でご利用可能:

public void start(String processId,String agentArgs, String agentJarPath) throws Exception { VirtualMachine virtualMachine = null; try { virtualMachine = VirtualMachine.attach(processId); virtualMachine.loadAgent(agentJarPath,agentArgs); } finally { if (virtualMachine != null) { virtualMachine.detach(); } } } 

必要なパラメータは、どの:

  • PROCESSID:ターゲットアプリケーションのPID
  • agentArgs:agentmainパラメータを渡します
  • agentJarPath:ジャーパッケージをロードします

出典:香港Xianxiaoウェブサイトの最適化

おすすめ

転載: www.cnblogs.com/1994jinnan/p/12078802.html