It taught you how to achieve hot update feature that brings you understand the principles behind Arthas hot update

Source: https://studyidea.cn/java-hotswap

I. Introduction

One afternoon is fishing in troubled waters when the test came little sister for help, saying that the need to change the test environment mock application. But the application of half past one would not find where the source code exists. But little sister live or test must help, Arthas suddenly remembered the heat can update the application code, follow the steps online, decompile application code, coupled with the need to change the logic, last updated hot success. In this regard, little sister very satisfied with the test, and said that next time will be less mention Bug.

Hey, it had previously been very curious about the hot update the principles behind, take this opportunity to look at the principle of heat update.

Two, Arthas hot update

How we first look at Arthas hot update.

Details Reference: Alibaba Arthas practice --jad / mc / redefine one-stop online hot update

Suppose we now have a HelloServiceclass, the following logic, we now use the update code Arthas heat, let the output 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 to decompile the code

First run jadcommand decompile class file to obtain the source code, run the following command:

jad --source-only com.andyxh.HelloService > /tmp/HelloService.java

2.2, modify the code after decompile

After get the source code, using the text editor to edit the like VIM source code, add logic needs to be changed.

2.3 Find ClassLoader

Then use the sccommand to find loads to modify the class ClassLoader, run the following command:

$ sc -d  com.andyxh.HelloService | grep classLoaderHash
 classLoaderHash   4f8e5cde

After running here will be ClassLoaderthe hash value.

2.4, mcmemory compile the source code

Using the mc command to compile the previous step to modify the source code to save to produce a final classdocument.

$ 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 heat update code

Run redefine the command:

$ redefine /tmp/com/andyxh/HelloService.class
redefine success, size: 1

After hot update is successful, the program output results are as follows:

image.png

Under normal circumstances, will be our local source code, the above steps we omit further, we can change the code on his first the IDE, the compiler generates class files. We just need to run the redefinecommand. That in fact play a role only redefine.

Three, Instrumentation and attach mechanism

Arthas hot update function looks amazing, in fact, we can not do without some of the JDK API, respectively instrument API and attach API.

3.1 Instrumentation

Java Instrumentation is an interface provided after JDK5. This set of interfaces, we can get to a running JVM-related information, we use this information to build relevant monitoring program to detect JVM. In addition, the most important thing we can replace and modify the class, thus achieving a hot update.

Instrumentation presence of two ways, one for the pre-mainmode, which requires the virtual machine to specify Instrumentation program parameters, and then will be completed prior to the modification or replacement class program starts. Used as follows:

java -javaagent:jar Instrumentation_jar -jar xxx.jar

Do you feel very familiar with this start-up mode, a closer look at IDEA run output window.

image.png

Also many applications monitoring tools, such as: zipkin, pinpoint, skywalking.

Only in this way before the application starts to take effect, there are some limitations.

JDK6 made to improve this situation and increase agent-mainthe way. After that we can start the application, and then run Instrumentationthe program. Once started, the only connection the corresponding application, we can make the appropriate changes, here we need to use Java to provide attach API.

3.2 Attach API

Attach API located tools.jar package can be used to connect the target JVM. Attach API is very simple, internal only two major classes, VirtualMachineand VirtualMachineDescriptor.

VirtualMachineOn behalf of a JVM instance, use it to provide attacha method, we can connect the target JVM.

 VirtualMachine vm = VirtualMachine.attach(pid);

VirtualMachineDescriptorIt is a container class describing a virtual machine, we can get to the JVM PID (process ID) through the instance that mainly through VirtualMachine#listacquisition method.

        for (VirtualMachineDescriptor descriptor : VirtualMachine.list()){

            System.out.println(descriptor.id());
        }

Introduction to hot update the relevant principles involved, it follows that the use of heat to achieve the above API update feature.

Fourth, to achieve thermal update feature

Here we use Instrumentation agent-mainway.

4.1, to achieve agent-main

First, you need to write a class that contains the following two methods:

public static void agentmain (String agentArgs, Instrumentation inst);          [1]
public static void agentmain (String agentArgs);            [2]

The above method only need to implement a can. If both are achieved, [1] is greater than the priority [2], the priority will be executed.

Then read incoming external class file, call Instrumentation#redefineClassesthis method will replace the class is currently running with the new class, so we've had to modify the class.

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("/", ".");
    }
}

After completing the code, we also need to write the following attributes to the jar package manifest.

## 指定 agent-main 全名
Agent-Class: com.andyxh.AgentMain
## 设置权限,默认为 false,没有权限替换 class
Can-Redefine-Classes: true

We use maven-assembly-pluginthe above attributes written to the file.

<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>

Here we have completed the main heat update the code, then use the Attach API, connection to the target virtual machine, triggers hot code updates.

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);
    }
}

In this startup class, we end up calling VirtualMachine#loadAgent, JVM will use the above method to replace AgentMain running class using the incoming class file.

4.2, running

Here's an example we continue to use the beginning, but added there a method to get the JVM running process 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];
    }

}

First run HelloService, to get the current PID, then copy HelloServicethe code to another engineer, modify hellothe method output hello agent, recompile to generate a new class file.

Finally, the command line to run the generated jar package.

image.png

HelloService Output results as shown below:

image.png

Source address: https: //github.com/9526xu/hotswap-example

4.3, debugging techniques

Common application we can use directly in Debug mode debugger IDE, but the above program can not directly use Debug. Program has just started running encountered a lot of problems, desperation, can only choose the most primitive way, print the error log. Later View arthas documents, found that the above article describes the use of IDEA Remote Debug mode debugger.

First, we need to HelloServicejoin JVM arguments the following parameters:

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

At this time, the program will be blocked until the remote debugger connection port 8001, output is as follows:

image.png

Then in Agent-mainthe project to add a remote debugging.

image.png

image.png

FIG parameters are as follows:

-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8001

In the Agent-mainproject marked a breakpoint, run remote debugging, HelloServicethe program will be started.

Finally, the command line window to run Agent-mainthe program, will be suspended to the appropriate remote debugging breakpoint, then just like ordinary Debug debugging mode as no longer narrative.

4.4 Related Questions

Since Attach API is located in tools.jar, and in common with us before JDK8 tools.jar JDK jar package is not in the same position, so the compiler and run process can not find the jar package, resulting in an error.

If you compile and run maven after use JDK9, do not worry about the following questions.

maven compile problem

The following error may occur during compilation maven.

image.png

Tools.jar solutions to join in pom.

        <dependency>
            <groupId>jdk.tools</groupId>
            <artifactId>jdk.tools</artifactId>
            <scope>system</scope>
            <version>1.6</version>
            <systemPath>${java.home}/../lib/tools.jar</systemPath>
        </dependency>

Or using the following dependent.

        <dependency>
            <groupId>com.github.olivergondza</groupId>
            <artifactId>maven-jdk-tools-wrapper</artifactId>
            <version>0.1</version>
            <scope>provided</scope>
            <optional>true</optional>
        </dependency>

Can not find running process tools.jar

When thrown to run the program java.lang.NoClassDefFoundError, the main reason is tools.jar system not found cause.

image.png

Join in operating parameters -Xbootclasspath/a:${java_home}/lib/tools.jar, complete run the following command:

image.png

4.5, there are some restrictions hot update

Not all the changes will be hot updates are successful, the current use Instrumentation#redefineClassesthere are some restrictions. We can only modify the internal logic method, property values, etc., can not add, delete method or field, or can not change the signature method of inheritance.

Five eggs

Hot finished updating the code, receive an e-mail tips xxx bug system to be repaired. Well, say good Bug little mention of it o (╥﹏╥) o.

Sixth, to help

1. depth exploration Java hot deploy
2.Instrumentation new features

I welcome the attention of the public number: Interpreter program, and getting daily dry push. If you are interested in my topic content, you can focus on my blog: studyidea.cn

Other platforms .png

Guess you like

Origin www.cnblogs.com/goodAndyxublog/p/11880314.html