I.はじめに
テストは必要がテスト環境モックアプリケーションを変更することを言って、助けのための妹が来たとき、ある日の午後は、荒れた海で釣りをしています。ソースコードが存在する場合でも、一時半のアプリケーションが見つかりませんでした。しかし、妹ライブまたはテストする必要がありますヘルプ、Arthasは突然熱が、アプリケーションのコードを更新ロジックを変更する必要性と相まって、アプリケーション・コード、最後の更新ホット成功コンパイル、オンラインの手順に従うことができ思い出しました。この点で、妹は非常にテストに満足し、次の時間があまり言及バグになるという。
ねえ、それは以前にホット・アップデートの原理について非常に好奇心旺盛だった、熱アップデートの原則を見て、この機会を利用。
二、Arthasはホット更新
私たちは、最初にArthasはホット・アップデートを見てどのように。
我々が今持っていると仮定しHelloService
、私たちは今、出力をさせ、更新コードArthasは熱を使用して、クラス、次のロジックをhello arthas
。
public class HelloService {
public static void main(String[] args) throws InterruptedException {
while (true){
TimeUnit.SECONDS.sleep(1);
hello();
}
}
public static void hello(){
System.out.println("hello world");
}
}
2.1は、コードをコンパイルするためにJAD
最初の実行jad
ソースコードを取得するために、クラスファイルを逆コンパイルコマンドは、次のコマンドを実行します。
jad --source-only com.andyxh.HelloService > /tmp/HelloService.java
2.2、デコンパイル後のコードを変更
後にVIMのソースコードのように編集するテキストエディタを使用して、ソースコードを取得し、追加のロジックを変更する必要があります。
2.3検索 ClassLoader
次に、使用sc
するクラスを変更するために負荷を見つけるために、コマンドをClassLoader
、次のコマンドを実行します。
$ sc -d com.andyxh.HelloService | grep classLoaderHash
classLoaderHash 4f8e5cde
ここで実行した後になりますClassLoader
ハッシュ値。
2.4、mc
メモリは、ソースコードをコンパイル
最終的に生産するために保存するためにソースコードを変更する前のステップをコンパイルするMCコマンドを使用してclass
文書を。
$ mc -c 4f8e5cde /tmp/HelloService.java -d /tmp
Memory compiler output:
/tmp/com/andyxh/HelloService.class
Affect(row-cnt:1) cost in 463 ms.
2.5、再定義熱更新コード
コマンドを再定義し実行します。
$ redefine /tmp/com/andyxh/HelloService.class
redefine success, size: 1
ホット更新が成功したら、次のように、プログラムの出力結果は、次のとおりです。
通常の状況下で、我々はさらに省略上記の手順は、我々は彼の最初のIDEでコードを変更することができ、私たちの地元のソースコードになり、コンパイラはクラスファイルを生成します。私達はちょうど実行する必要があるredefine
コマンドを。それは実際には唯一の役割を果たしていますredefine
。
三、計装とはメカニズムを添付します
ホット更新機能は素晴らしい見えますArthasは、実際には、我々はそれぞれ、JDKのAPIの一部せずに、機器のAPIを実行し、APIを添付することはできません。
3.1計装
Javaの計測はJDK5の後に提供されるインタフェースです。インターフェースのこのセットは、我々はJVMを検出するために、関連する監視プログラムを構築するために、この情報を使用して、実行中のJVM関連の情報を取得することができます。また、最も重要なことは、私たちがすることができます置き換えると修正ので、ホット・アップデートを実現、クラスを。
二つの方法、のための1つの計装存在pre-main
その後、計装プログラムパラメータを指定するには、仮想マシンを必要とし、モードは、修正または交換クラスのプログラムが起動する前に完了します。次のように使用されます:
java -javaagent:jar Instrumentation_jar -jar xxx.jar
あなたは、この起動モード、IDEAの実行出力ウィンドウをよく見に非常に精通感じます。
また、多くのようなツールは、監視アプリケーション:zipkinを、ピンポイント、skywalking。
この方法でのみアプリケーションを有効にするために開始する前に、いくつかの制限があります。
JDK6は、この状況を改善し、向上させるために作られたagent-main
道を。その後、我々は、アプリケーションを起動し、実行することができInstrumentation
、プログラムを。開始後は、接続のみに対応するアプリケーションは、我々は適切な変更を加えることができ、ここではAPIを添付提供するためにJavaを使用する必要があります。
3.2 APIを添付
tools.jarのパッケージは、ターゲットJVMを接続するために使用することができる位置APIを取り付けます。APIを取り付けのみ2つの主要なクラスの内部、非常に簡単です、VirtualMachine
とVirtualMachineDescriptor
。
VirtualMachine
JVMインスタンスを代表して、提供するために、それを使用するattach
方法を、私たちは、ターゲットJVMを接続することができます。
VirtualMachine vm = VirtualMachine.attach(pid);
VirtualMachineDescriptor
これは主に通じていることインスタンスを通じて、我々はJVM PID(プロセスID)を取得することができ、仮想マシンを記述したコンテナクラスですVirtualMachine#list
取得方法。
for (VirtualMachineDescriptor descriptor : VirtualMachine.list()){
System.out.println(descriptor.id());
}
関連する原則が関与ホット・アップデートの概要は、熱の使用は、上記のAPIのアップデート機能を達成することになります。
第四に、熱アップデート機能を達成するために
ここでは、計測機器の使用agent-main
方法を。
エージェント・メインを達成するために4.1、
まず、次の2つのメソッドを含むクラスを記述する必要があります。
public static void agentmain (String agentArgs, Instrumentation inst); [1]
public static void agentmain (String agentArgs); [2]
上記の方法は、唯一の缶を実装する必要があります。両方が達成された場合は、[1]優先順位[2]よりも大きい場合、優先度が実行されます。
続いて、入ってくる外部クラスファイルを読み込む呼び出しInstrumentation#redefineClasses
、このメソッドは、クラスが、現在新しいクラスで実行されているので、私たちはクラスを変更しなければならなかった置き換えられます。
public class AgentMain {
/**
*
* @param agentArgs 外部传入的参数,类似于 main 函数 args
* @param inst
*/
public static void agentmain(String agentArgs, Instrumentation inst) {
// 从 agentArgs 获取外部参数
System.out.println("开始热更新代码");
// 这里将会传入 class 文件路径
String path = agentArgs;
try {
// 读取 class 文件字节码
RandomAccessFile f = new RandomAccessFile(path, "r");
final byte[] bytes = new byte[(int) f.length()];
f.readFully(bytes);
// 使用 asm 框架获取类名
final String clazzName = readClassName(bytes);
// inst.getAllLoadedClasses 方法将会获取所有已加载的 class
for (Class clazz : inst.getAllLoadedClasses()) {
// 匹配需要替换 class
if (clazz.getName().equals(clazzName)) {
ClassDefinition definition = new ClassDefinition(clazz, bytes);
// 使用指定的 class 替换当前系统正在使用 class
inst.redefineClasses(definition);
}
}
} catch (UnmodifiableClassException | IOException | ClassNotFoundException e) {
System.out.println("热更新数据失败");
}
}
/**
* 使用 asm 读取类名
*
* @param bytes
* @return
*/
private static String readClassName(final byte[] bytes) {
return new ClassReader(bytes).getClassName().replace("/", ".");
}
}
コードを完了した後、我々はまた、JARパッケージのマニフェストに次の属性を記述する必要があります。
## 指定 agent-main 全名
Agent-Class: com.andyxh.AgentMain
## 设置权限,默认为 false,没有权限替换 class
Can-Redefine-Classes: true
私たちは、使用しmaven-assembly-plugin
たファイルに書き込まれた上記の属性を。
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<!--指定最后产生 jar 名字-->
<finalName>hotswap-jdk</finalName>
<appendAssemblyId>false</appendAssemblyId>
<descriptorRefs>
<!--将工程依赖 jar 一块打包-->
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifestEntries>
<!--指定 class 名字-->
<Agent-Class>
com.andyxh.AgentMain
</Agent-Class>
<Can-Redefine-Classes>
true
</Can-Redefine-Classes>
</manifestEntries>
<manifest>
<!--指定 mian 类名字,下面将会使用到-->
<mainClass>com.andyxh.JvmAttachMain</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id> <!-- this is used for inheritance merges -->
<phase>package</phase> <!-- bind to the packaging phase -->
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
ここでは、アタッチAPIを使用し、その後、主熱の更新プログラムをターゲット仮想マシンへの接続を、コードを完了して、ホットコードの更新をトリガします。
public class JvmAttachMain {
public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
// 输入参数,第一个参数为需要 Attach jvm pid 第二参数为 class 路径
if(args==null||args.length<2){
System.out.println("请输入必要参数,第一个参数为 pid,第二参数为 class 绝对路径");
return;
}
String pid=args[0];
String classPath=args[1];
System.out.println("当前需要热更新 jvm pid 为 "+pid);
System.out.println("更换 class 绝对路径为 "+classPath);
// 获取当前 jar 路径
URL jarUrl=JvmAttachMain.class.getProtectionDomain().getCodeSource().getLocation();
String jarPath=jarUrl.getPath();
System.out.println("当前热更新工具 jar 路径为 "+jarPath);
VirtualMachine vm = VirtualMachine.attach(pid);//7997是待绑定的jvm进程的pid号
// 运行最终 AgentMain 中方法
vm.loadAgent(jarPath, classPath);
}
}
この起動クラスでは、我々は呼び出す結局VirtualMachine#loadAgent
、JVMは、着信クラスファイルを使用してAgentMain実行しているクラスを置き換えるために、上記の方法を使用します。
4.2、実行しています
ここでは、先頭を使用し続けますが、JVM実行中のプロセスIDを取得する方法が追加例です。
public class HelloService {
public static void main(String[] args) throws InterruptedException {
System.out.println(getPid());
while (true){
TimeUnit.SECONDS.sleep(1);
hello();
}
}
public static void hello(){
System.out.println("hello world");
}
/**
* 获取当前运行 JVM PID
* @return
*/
private static String getPid() {
// get name representing the running Java virtual machine.
String name = ManagementFactory.getRuntimeMXBean().getName();
System.out.println(name);
// get pid
return name.split("@")[0];
}
}
最初の実行はHelloService
、その後、コピーし、現在のPIDを取得するにはHelloService
、別のエンジニアにコードを変更hello
する方法出力hello agent
新しいクラスファイルを生成し、再コンパイルを。
最後に、生成されたjarファイルのパッケージを実行するためのコマンドライン。
HelloService
以下のように出力結果:
ソースアドレスします。https://github.com/9526xu/hotswap-example
4.3、デバッグ技術
一般的なアプリケーションでは、我々は、デバッグモードデバッガーIDE内で直接使用することができますが、上記のプログラムは、直接デバッグを使用することはできません。このプログラムは、単に、多くの問題が発生した走り始め絶望、唯一のエラーログを印刷し、最も原始的な方法を選択することができました。その後、ビューは、上記の記事は、IDEAリモートデバッグモードデバッガの使用が記載されていることが判明し、文書をArthasは。
まず、我々はする必要がHelloService
JVM引数に次のパラメータに参加します:
-Xrunjdwp:transport=dt_socket,server=y,address=8001
このとき、プログラムは次のように出力され、リモートデバッガの接続ポート8001までブロックされます。
その後でAgent-main
リモートデバッグを追加するプロジェクト。
次のように図のパラメータは次のとおりです。
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8001
でAgent-main
リモートデバッグを実行し、ブレークポイントをマークしたプロジェクト、HelloService
プログラムが開始されます。
最後に、実行するコマンドラインウィンドウAgent-main
プログラムは、リモートそしてちょうどもはや物語として通常のデバッグデバッグモードのように、ブレークポイントをデバッグ適切に中断されます。
4.4関連の質問
APIは、tools.jarの中に位置しており、コンパイラと実行プロセスがエラーになり、jarファイルパッケージを見つけることができませんのでJDK8 tools.jarのJDKのjarパッケージの前に、私たちと共通で、同じ位置にないされて取り付けますので。
コンパイルと使用JDK9後のMaven実行すると、以下の質問を心配しないでください。
Mavenのコンパイルの問題
次のエラーがコンパイルMavenの中に発生する可能性があります。
ポンポンに参加するのtools.jarソリューション。
<dependency>
<groupId>jdk.tools</groupId>
<artifactId>jdk.tools</artifactId>
<scope>system</scope>
<version>1.6</version>
<systemPath>${java.home}/../lib/tools.jar</systemPath>
</dependency>
または、次の依存を使用。
<dependency>
<groupId>com.github.olivergondza</groupId>
<artifactId>maven-jdk-tools-wrapper</artifactId>
<version>0.1</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
プロセスのtools.jarを実行している見つけることができません
プログラムを実行するためにスローされた場合にはjava.lang.NoClassDefFoundError
、主な理由は、tools.jarをシステムが原因を発見していません。
動作パラメータに参加し-Xbootclasspath/a:${java_home}/lib/tools.jar
、完全に次のコマンドを実行します。
4.5は、いくつかの制限ホットアップデートがあります
いないすべての変更は、ホット更新が成功している現在の使用になりますInstrumentation#redefineClasses
いくつかの制限があります。私たちは、deleteメソッドやフィールドを追加することはできません、などの内部ロジックメソッド、プロパティ値を変更することができ、または継承の署名方法を変更することはできません。
ファイブ卵
ホットは、コードの更新が完了修復する電子メールのヒントXXXバグシステムを受け取ります。まあ、それの良いバグはほとんどの言及を言うO(╥﹏╥)O。
第六に、ヘルプへ
1.深さ探査のJavaホットデプロイ
2.Instrumentation新機能
インタプリタプログラム、および取得毎日ドライプッシュ:私は公共の数の関心を歓迎します。:あなたは私のトピックの内容に興味があるなら、あなたは私のブログに焦点を当てることができstudyidea.cn