まず、javaagentは何ですか
javaagentは、「プラグイン」JVM、特別に細工された.jarファイルである、それは利点計装API JVMを提供して取ることができます。
1.1、概要
Javaのエージェントは、3つの部分から構成:プロキシクラス、メタ情報およびプロキシクラスローディング機構との.jar JVM剤、コンテンツ全体を次のように:
1.2、礎石のjavaagent
java.lang.instrument
JVMサービス上で実行されているjavaagent方式のバイトコードプログラムの方法を変更することによって。JARパッケージとして展開javaagentは、エージェントを起動するためのプロキシクラスのJARマニフェストファイルをロードする属性を指定します。
次の方法を開始しjavaagent:
-
コマンドラインでパラメータを指定して起動します。
-
スタートJVMが開始しました。例えば、既に実行中のアプリケーションに取り付けることができるツールを提供し、アプリケーションにロードエージェントが実行されている可能にします。
-
アプリケーションで、実行可能ファイルとしてパッケージ化。
1.3、スタートjavaagent
1.3.1、開始するには、コマンドライン
次のパラメータを開始するには、コマンドライン:
-javaagent:<jarpath>[=<options>]
<jarpath>
:Javaagentパス、例えば/opt/var/Agent-1.0.0.jar
。
<options>
:Javaagentパラメータ、分析パラメータ責任javaagent。
javaagent JARマニフェストファイルが含まれている必要がありますPremain-Class
属性を、エージェントクラス属性値のフルパス名(パッケージ名+クラス名)。Agentクラスが実装しなければならないpremain
メソッド、premain
メソッド、およびmain
メソッドは、エントリポイントとアプリケーションプロキシと同じです。最初は、JVMの初期化が完了した後、エージェントpremain関数を呼び出すと、premainメソッドは、起動するプロセスを返さなければなりませんした後、その後、アプリケーションのメイン関数を呼び出します。
premain
メソッドシグネチャを次のように
public static void premain(String agentArgs, Instrumentation inst)
public static void premain(String agentArgs)
エージェントクラスは、メソッドシグネチャを実装していない場合、プロキシにメソッドシグネチャを呼び出すJVM最初の試みは、方法2を呼び出す署名JVMの試み:
演技のクラスが持つことができるagentmain
機能を、関数呼び出しは、JVMの完了後に開始されます。あなたは、エージェントを開始するには、コマンドラインを使用している場合、agentmain
方法は呼び出されません。
すべてのパラメータは、文字列でエージェントとして扱われるagentArgs
パラメータ文字列を解析するためのエージェントが担当し、可変変速機。
エージェントクラスをロードできないため、エージェントは、実装していないエージェントクラス場合はpremain
メソッドが例外またはキャッチされないをスローし、JVMは終了します。
javaagentスタートの実装は、コマンドラインからの達成支援を開始した場合、実装は指定して、コマンドラインをサポートしている必要があり、特定のコマンドラインを提供するために必要とされていない-javaagent
起動パラメータを。-javaagent
複数のエージェントを起動し、コマンドラインで複数回使用することができます。premain
コマンドラインで指定された順序で配列し、関数を呼び出すことは一貫して、薬剤の同じ複数を使用することができます<jarpath>
。
定義しない厳格なモデルpremain
の仕事関数の範囲を、任意のmain
機能は、このようなスレッドの作成など、仕事をすることができ、premain
機能は有効です。
1.3.2、JVM起動時のブート
実装は、JVMの起動後にエージェントを再起動するための機構を提供することができます。エージェント実装固有の詳細を開始するにはどのように、通常はアプリケーションが起動され、そのmain
メソッドが呼び出されています。実装がサポートするブート・エージェントJVMが起動している場合、エージェントは次の基準を満たす必要があります。
-
マニフェストファイルが含まれている
Agent-Class
属性は、プロキシクラスのフルネームで、属性を。 -
Agentクラスが実装しなければならない
public static agentmain
方法を。
agentmain署名方法は、以下の2つの機能を有します。
public static void agentmain(String agentArgs, Instrumentation inst)
public static void agentmain(String agentArgs)
メソッドシグネチャを呼び出すJVM最初の試みエージェントクラスは、このメソッドを実装していない場合、メソッドシグネチャ2を起動するJVMの試み1を有しています。
演技のクラスが同時に達成することができますpremain
し、agentmain
エージェントがJVM呼び出し、コマンドラインを起動したときに、二つの方法premain
スタートJVM後に開始したエージェントは、JVMが呼び出したときに、機能をagentmain
機能を、そしてJVMが呼び出されないpremain
機能を。
agentmain
また、伝達関数パラメータによってagentArgs
、すべてのパラメータは1つの文字列、解像度パラメータを担当するエージェントに統合されています。
agentmain
機能は、起動が完了したときに、必要なすべてのブートエージェントの初期化操作を完了しなければならないagentmain
関数が返す必要があります。エージェントが起動またはキャッチされない例外をスローすることができない場合は、JVMは終了します。
実行可能ファイルとしてパッケージ1.3.3、
エージェントが実行可能JARファイルにパッケージ化されている場合は、実行可能なJARファイルのリストが含まれている必要がありますLauncher-Agent-Class
主な機能の開始がクラスを呼び出す前に、アプリケーションでプロキシを指定する属性を。JVMプロキシのメソッドを呼び出そう。
public static void agentmain(String agentArgs, Instrumentation inst)
エージェントクラスは、上記の方法を実装していない場合は、JVM以下のメソッドが呼び出されます。
public static void agentmain(String agentArgs)
agentArgs
パラメータは、空の文字列でなければなりません。
agentmain
初期化機能は、エージェントが起動し、起動後に返却しなければならないすべてのアクションを完了する必要があります。エージェントが起動に失敗したり、キャッチされない例外をスローした場合、JVMは終了します。
1.3.4、ローディングモジュールエージェントクラスと利用可能なプロキシクラス/クラス
システムクラスローダは、すべてのクラスのエージェントJARファイルをロードするための責任がある、と無名のモジュールシステムクラスローダの一員となりました。このシステムはまた、一般的に定義されたクラスローダアプリケーションが含まれているmain
クラスメソッドを。プロキシクラスのすべてのクラスが見えるシステムクラスローダに表示され、次の最小要件を満たしている必要があります。
-
スタート層モジュールは、パッケージ内のクラスを派生しました。層を起動すると、プラットフォームモジュールまたはアプリケーションモジュールに応じて、すべての最初の起動モードが含まれています。
-
クラスは、システムクラスローダーとして定義することができます。
-
すべてのプロキシクラスは、クラスローダモジュールは、そのメンバーは、名前の定義され始めます。
プロキシクラス層のプラットフォーム(または他の)起動しないモジュールクラスにリンクする必要がある場合は、これらのモジュールは、アプリケーションを起動する方法を開始する層に配置されていることを確認する必要があります。例えば、JDKの実装では、--add-modules
コマンドラインオプションは、開始濃縮ルートモジュールで解決されるモジュールを追加するために使用することができます。
スタートクラスローダは、プロキシサポートクラス(で読み込むことができるappendToBootstrapClassLoaderSearch
唯一のカスタムブートクラスローダクラスにリンクしなければならないか、指定されたブート-Class-Path属性を)。私たちは、ブートクラスローダは、すべてのプラットフォーム上で動作することを保証することはできません。
(によって構成されるカスタム・システム・クラス・ローダ場合はgetSystemClassLoader
指定されたメソッドのシステムプロパティjava.system.class.loader
)、定義されなければならないappendToSystemClassLoaderSearch
指定にappendToClassPathForInstrumentation
方法。つまり、カスタムクラスローダシステムは、検索範囲内のシステムクラスローダのメカニズムへのエージェントのJARファイルの追加をサポートしている必要があります。
1.4、javaagent一覧のプロパティ
プロパティ | 説明 | 必要とされます | デフォルト値 |
---|---|---|---|
Premainクラス | Premainクラスメソッド備えます | スタート依存的 | ノー |
エージェントクラス | クラスのagentmainを含む方法 | スタート依存的 | ノー |
ブートクラスパス | スタートクラスローダの検索パス | ノー | ノー |
CAN-再定義-Classis | 私は必要なプロキシクラスを再定義することができます | ノー | 偽 |
CAN-再変換-Classis | あなたは、このエージェントが必要とするクラスを再変換することができます | ノー | 偽 |
CAN-セットネイティブメソッドのプレフィックス | このエージェントは、必要に応じて、ネイティブメソッドの接頭辞を提供することができます | ノー | 偽 |
第二に、Javaのエージェントを書きます
上記の説明に基づいて、我々は、すべての非システムクラスがダウンロードjavaagent JVMを達成します。
開発プロセス全体を通じて、以下の3つのステップを含みます。
-
1)プロキシクラスの定義、実装クラスのダウンロード。
-
2)配置パッケージ。
-
3)コマンドラインはテストを開始します。
2.1、プロキシクラスが実装
実装premain
の機能を
package io.ct.java.agent;
import java.lang.instrument.Instrumentation;
public class AgentApplication {
public static void premain(String arg, Instrumentation instrumentation) {
System.err.println("agent startup , args is " + arg);
// 注册我们的文件下载函数
instrumentation.addTransformer(new DumpClassesService());
}
}
ダウンロードクラスが実装ClassFileTransformer
クラスは、クラスのバイトコードロードダウンロードされるインタフェース:
package io.ct.java.agent;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import java.util.Arrays;
import java.util.List;
/**
* Copyright (C), 2018-2018, open source
* FileName: DumpClassesService
*
* @author : 大哥
* Date: 2018/12/8 21:01
*/
public class DumpClassesService implements ClassFileTransformer {
private static final List<String> SYSTEM_CLASS_PREFIX = Arrays.asList("java", "sum", "jdk");
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if (!isSystemClass(className)) {
System.out.println("load class " + className);
FileOutputStream fos = null;
try {
// 将类名统一命名为classNamedump.class格式
fos = new FileOutputStream(className + "dump.class");
fos.write(classfileBuffer);
fos.flush();
} catch (IOException ioe) {
ioe.printStackTrace();
} finally {
// 关闭文件输出流
if (null != fos) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
return classfileBuffer;
}
/**
* 判断一个类是否为系统类
*
* @param className 类名
* @return System Class then return true,else return false
*/
private boolean isSystemClass(String className) {
// 假设系统类的类名不为NULL而且不为空
if (null == className || className.isEmpty()) {
return false;
}
for (String prefix : SYSTEM_CLASS_PREFIX) {
if (className.startsWith(prefix)) {
return true;
}
}
return false;
}
}
2.2、コンフィギュレーションMANIFEST.MF
MANIFEST.MF
二つの方法で生成されたファイル:だけする必要があり、手動と自動構成生成、手動設定resources
次のファイルを作成するMETA-INF/MENIFEST.MF
ファイルを。手動外形を除去して自動的に次のような構成のプラグインのmaven、ステージパッキングMavenのプラグを使用して生成することができます。
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifestEntries>
<Premain-Class>io.ct.java.agent.AgentApplication</Premain-Class>
<Agent-Class>io.ct.java.agent.AgentApplication</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
</configuration>
</plugin>
ジャー次の形式の生成:
MANIFEST.MFファイルは、以下の特徴(異なる構成のコンテンツファイルを生成することは正確ではありません)。
Manifest-Version: 1.0
Implementation-Title: agent
Premain-Class: io.ct.java.agent.AgentApplication
Implementation-Version: 0.0.1-SNAPSHOT
Built-By: chentong
Agent-Class: io.ct.java.agent.AgentApplication
Can-Redefine-Classes: true
Implementation-Vendor-Id: io.ct.java
Can-Retransform-Classes: true
Created-By: Apache Maven 3.5.4
Build-Jdk: 1.8.0_171
Implementation-URL: https://projects.spring.io/spring-boot/#/spring-bo
ot-starter-parent/agent
2.3、Javaのエージェントを開始するには、コマンドライン
こんにちは、すでにコンパイルされたクラスを実行するには、次のコマンドを実行し、同じディレクトリにHellodump.classという名前のファイルを生成することができます。
java -javaagent:agent-0.0.1-SNAPSHOT.jar Hello