それはあなたがArthasはホット・アップデートの背後にある原理を理解もたらしホットアップデート機能を達成するためにどのように教え

出典:https://studyidea.cn/java-hotswap

I.はじめに

テストは必要がテスト環境モックアプリケーションを変更することを言って、助けのための妹が来たとき、ある日の午後は、荒れた海で釣りをしています。ソースコードが存在する場合でも、一時半のアプリケーションが見つかりませんでした。しかし、妹ライブまたはテストする必要がありますヘルプ、Arthasは突然熱が、アプリケーションのコードを更新ロジックを変更する必要性と相まって、アプリケーション・コード、最後の更新ホット成功コンパイル、オンラインの手順に従うことができ思い出しました。この点で、妹は非常にテストに満足し、次の時間があまり言及バグになるという。

ねえ、それは以前にホット・アップデートの原理について非常に好奇心旺盛だった、熱アップデートの原則を見て、この機会を利用。

二、Arthasはホット更新

私たちは、最初にArthasはホット・アップデートを見てどのように。

詳細参考:アリババArthasは練習--jad / MC /再定義ワンストップオンラインホットアップデート

我々が今持っていると仮定し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

ホット更新が成功したら、次のように、プログラムの出力結果は、次のとおりです。

image.png

通常の状況下で、我々はさらに省略上記の手順は、我々は彼の最初の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の実行出力ウィンドウをよく見に非常に精通感じます。

image.png

また、多くのようなツールは、監視アプリケーション:zipkinを、ピンポイント、skywalking。

この方法でのみアプリケーションを有効にするために開始する前に、いくつかの制限があります。

JDK6は、この状況を改善し、向上させるために作られたagent-main道を。その後、我々は、アプリケーションを起動し、実行することができInstrumentation、プログラムを。開始後は、接続のみに対応するアプリケーションは、我々は適切な変更を加えることができ、ここではAPIを添付提供するためにJavaを使用する必要があります。

3.2 APIを添付

tools.jarのパッケージは、ターゲットJVMを接続するために使用することができる位置APIを取り付けます。APIを取り付けのみ2つの主要なクラスの内部、非常に簡単です、VirtualMachineVirtualMachineDescriptor

VirtualMachineJVMインスタンスを代表して、提供するために、それを使用する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ファイルのパッケージを実行するためのコマンドライン。

image.png

HelloService 以下のように出力結果:

image.png

ソースアドレスします。https://github.com/9526xu/hotswap-example

4.3、デバッグ技術

一般的なアプリケーションでは、我々は、デバッグモードデバッガーIDE内で直接使用することができますが、上記のプログラムは、直接デバッグを使用することはできません。このプログラムは、単に、多くの問題が発生した走り始め絶望、唯一のエラーログを印刷し、最も原始的な方法を選択することができました。その後、ビューは、上記の記事は、IDEAリモートデバッグモードデバッガの使用が記載されていることが判明し、文書をArthasは。

まず、我々はする必要がHelloServiceJVM引数に次のパラメータに参加します:

-Xrunjdwp:transport=dt_socket,server=y,address=8001  

このとき、プログラムは次のように出力され、リモートデバッガの接続ポート8001までブロックされます。

image.png

その後でAgent-mainリモートデバッグを追加するプロジェクト。

image.png

image.png

次のように図のパラメータは次のとおりです。

-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の中に発生する可能性があります。

image.png

ポンポンに参加するの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をシステムが原因を発見していません。

image.png

動作パラメータに参加し-Xbootclasspath/a:${java_home}/lib/tools.jar、完全に次のコマンドを実行します。

image.png

4.5は、いくつかの制限ホットアップデートがあります

いないすべての変更は、ホット更新が成功している現在の使用になりますInstrumentation#redefineClassesいくつかの制限があります。私たちは、deleteメソッドやフィールドを追加することはできません、などの内部ロジックメソッド、プロパティ値を変更することができ、または継承の署名方法を変更することはできません。

ファイブ卵

ホットは、コードの更新が完了修復する電子メールのヒントXXXバグシステムを受け取ります。まあ、それの良いバグはほとんどの言及を言うO(╥﹏╥)O。

第六に、ヘルプへ

1.深さ探査のJavaホットデプロイ
2.Instrumentation新機能

インタプリタプログラム、および取得毎日ドライプッシュ:私は公共の数の関心を歓迎します。:あなたは私のトピックの内容に興味があるなら、あなたは私のブログに焦点を当てることができstudyidea.cn

その他のプラットフォーム.PNG

おすすめ

転載: www.cnblogs.com/goodAndyxublog/p/11880314.html