JVMサービスのJavaエージェント


おそらく、多くの人が-javaagentなどのJVMパラメーターを聞いたり、遭遇したことがあります。JrebelまたはPlumbrを使用すると、このパラメーターを確認できますJAVA_OPTS=-javaagent:[path/to/]jrebel.jarまたは-javaagent:/path-to/plumbr.jar
java1.5にはjavaagentが登場しましたが、多くの開発者はエージェントの機能を使用したことがなく、それが何であるかについて漠然と考えています。
これはどのようなエージェントですか? なぜ必要なのか、独自の記述方法

javaagentとは

上で書いたように、javaagentはJVMパラメーターの1つであり、アプリケーションで起動するエージェントを指定できます。または、アプリケーションの起動前であっても起動します。 エージェント自体は、実行時にバイトコード操作メカニズム( java.lang.instrument )へのアクセスを提供する別個のアプリケーションです。 つまり、要するに。 公式のドキュメントはこちらで読むことができますが、かなり貧弱です。 何もはっきりしていませんか? それでは、正しく理解しましょう。 例を理解することが最善です。

初歩的なエージェントを書きましょう


 package ru.habrahabr.agent; public class Agent007 { public static void premain(String args) { System.out.println("Hello! I`m java agent"); } } 

エージェントは、次のシグネチャでpremainメソッドを実装する必要があることに注意してください
public static void premain(String args);
または
public static void premain(String args, Instrumentation inst);

エージェントクラスはjarにパッケージ化され、必須属性とともにMANIFEST.MFを含む必要があります
PreMain-Class -premainメソッドを持つエージェントクラスを指します。 エージェントには他にも属性がありますが、それらはオプションであり、今は必要ありません。

これが、manifest.mfの外観です。
 Manifest-Version: 1.0 PreMain-Class: ru.habrahabr.agent.Agent007 
ファイルの最後に改行を追加することを忘れないでください

今すぐすべてを瓶に詰める
 jar -cvfm Agent007.jar manifest.mf ru/habrahabr/agent/Agent007.class 

最後に、クラステスター
 package ru.habrahabr.agent; public class AgentTester { public static void main(String[] args) { System.out.println("Hello! I`m agent tester"); } } 

コマンドラインからAgentTesterを起動します
 java -javaagent:Agent007.jar ru.habrahabr.agent.AgentTester Hello! I`m java agent Hello! I`m agent tester 

この例は次のことを示しています。

エージェントから何らかの利益を得ようとします


一般に、エージェントメカニズムはbytecodeを操作するように設計されていますが、この記事でバイトコードを変更することはすぐに言います。そうでなければ、この投稿の範囲をはるかに超えて進むことができます。 気にする人は、javassistを見ることができます。これは、バイトコードを操作するための標準ツールがないためです。

ロードされたクラスの名前を表示し、ロードされたクラスの数をカウントするAgentCounterを作成します。 したがって、classloader`aの動作を観察できます。

 package ru.habrahabr.agent; import java.lang.instrument.Instrumentation; public class AgentCounter { public static void premain(String agentArgument, Instrumentation instrumentation) { System.out.println("Agent Counter"); instrumentation.addTransformer(new ClassTransformer()); } } 

今、私はpremainメソッドの異なるシグネチャを使用していることに注意してください。 インストルメンテーションオブジェクトでは、すべての作業を行うClassTransformerを渡します。 ClassTransformerは、クラスがロードされるたびに起動します。 ClassTransformerを使用する場合は、 java.lang.instrument.ClassFileTransformerインターフェースを実装し、 Instrumentation.addTransformerメソッドを介してオブジェクトを追加する必要があります。

 package ru.habrahabr.agent; import java.lang.instrument.ClassFileTransformer; import java.security.ProtectionDomain; public class ClassTransformer implements ClassFileTransformer { private static int count = 0; @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { System.out.println("load class: " + className.replaceAll("/", ".")); System.out.println(String.format("loaded %s classes", ++count)); return classfileBuffer; } } 

classfileBuffer-これは、バイトの配列として表される現在のクラスのバイトコードです。オーバーライドするには、トランスフォーマーは新しいバイト配列を返す必要があります。この例では、クラスの内容を変更しないため、単純に同じ配列を返します。

エージェントとトランスフォーマーを新しいjarにパックします
 jar -cvfm agentCounter.jar manifest.mf ru/habrahabr/agent/AgentCounter.class ru/habrahabr/agent/ClassTransformer.class 

テスタークラスをわずかに変更します
 package ru.habrahabr.agent; public class AgentTester { public static void main(String[] args) { A a = new A(); B b = new B(); C c = null; } } class A {}; class B {}; class C {}; 

新しいエージェントでAgentTesterを起動します
 java -javaagent:agentCounter.jar ru.habrahabr.agent.AgentTester Agent Counter load class: sun.launcher.LauncherHelper loaded 1 classes load class: ru.habrahabr.agent.AgentTester loaded 2 classes load class: ru.habrahabr.agent.A loaded 3 classes load class: ru.habrahabr.agent.B loaded 4 classes 
Javaのバージョンによって、結果は異なる場合があります

このようなエージェントを使用してエンタープライズアプリケーションを実行すると、非常に興味深い結果を得ることができます。たとえば、開始後のプロジェクトの1つから次のことがわかりました。
 sun.reflect.GeneratedMethodAccessor230 loaded 33597 classes java.rmi.server.Unreferenced loaded 33598 classes 

Javaオブジェクトのサイズの測定


エージェントを使用する別の例を考えてみましょう。 Javaオブジェクトのサイズを返すクラスを作成し、javaagentが重要な役割を果たします。 JVMが作成されたオブジェクトの実際のサイズをどのように知ることができるかに関係なくInstrumentationインターフェースには、 long getObjectSize(Object objectToSize)のサイズを返す素晴らしいメソッドlong getObjectSize(Object objectToSize)あります。 しかし、アプリケーションからエージェントにアクセスする方法は? また、特別な操作を行う必要はありません。javaagentが自動的にクラスパスに追加されます。Instrumentationインストルメンテーションタイプのフィールドをエージェントに追加し、premainメソッドで初期化するだけです。

 package ru.habrahabr.agent; import java.lang.instrument.Instrumentation; public class AgentMemoryCounter { private static Instrumentation instrumentation; public static void premain(String args, Instrumentation instrumentation) { AgentMemoryCounter.instrumentation = instrumentation; } public static long getSize(Object obj) { if (instrumentation == null) { throw new IllegalStateException("Agent not initialised"); } return instrumentation.getObjectSize(obj); } } 

アプリケーションクラスからAgentMemoryCounter.getSize(obj)メソッドにアクセスします。
 package ru.habrahabr.agent; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Calendar; public class AgentTester { public static void main(String[] args) { printObjectSize(new Object()); printObjectSize(new A()); printObjectSize(1); printObjectSize("string"); printObjectSize(Calendar.getInstance()); printObjectSize(new BigDecimal("999999999999999.999")); printObjectSize(new ArrayList<String>()); printObjectSize(new Integer[100]); } public static void printObjectSize(Object obj) { System.out.println(String.format("%s, size=%s", obj.getClass() .getSimpleName(), AgentMemoryCounter.getSize(obj))); } } class A { Integer id; String name; } 

アプリケーションの結果は次のようになります
 java -javaagent:agentMemoryCounter.jar ru.habrahabr.agent.AgentTester Agent Counter Object, size=8 A, size=16 Integer, size=16 String, size=24 GregorianCalendar, size=112 BigDecimal, size=32 ArrayList, size=24 Integer[], size=416 

getObjectSize()メソッドはネストされたオブジェクトのサイズを考慮しないことに注意してください。つまり、オブジェクトリンクに費やされたメモリのみが考慮されます。

おわりに


この投稿がjavaagentの目的を理解したことを願っています。また、javaagentの代わりの使用法(バイトコードの変換用ではない)のデモも試みました。 そして、なぜプロジェクトでエージェントを使用しているのですか? コメントに書いて、それは非常に興味深いでしょう。

Source: https://habr.com/ru/post/J230239/


All Articles