使用arthas修改代码并热交换

在运维过程中,有时候需要临时修改下服务器上已运行项目的代码。此时可使用arthas修改代码并热交换,达到不停机修改的目的。

测试案例

假设服务器上已经运行一段代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class Demo1Application {

public static void main(String[] args) {
SpringApplication.run(Demo1Application.class, args);
}


@GetMapping("/test")
public String test() {
return "aaa";
}

}

此时放问接口返回aaa,需要修改返回结果为bbb

过程

安装

1
2
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar

输出结果如下:

1
2
3
4
5
6
7
8
[INFO] arthas-boot version: 3.5.4
[INFO] Process 28783 already using port 3658
[INFO] Process 28783 already using port 8563
[INFO] Found existing java process, please choose one and input the serial number of the process, eg : 1. Then hit ENTER.
* [1]: 28783 target/demo1-0.0.1-SNAPSHOT.jar
[2]: 33008 org.jetbrains.jps.cmdline.Launcher
[3]: 33009 org.jetbrains.jps.cmdline.Launcher
[4]: 75256

输入1选择需要调试的进程

搜索

搜索需要修改的类:

1
sc *Demo1*
1
com.example.demo.Demo1Application

“Search-Method” 的简写,这个命令能搜索出所有已经加载了 Class 信息的方法信息。

反编译

将class反编译为java文件到当前目录的Demo1Application.java

1
jad --source-only --lineNumber=false com.example.demo.Demo1Application > ./Demo1Application.java

jad 命令将 JVM 中实际运行的 class 的 byte code 反编译成 java 代码,便于你理解业务逻辑。

修改代码

其他终端使用vim或其他编辑器修改生成在当前目录下的Demo1Application.java为如下:

1
2
3
4
@GetMapping("/test")
public String test() {
return "bbb";
}

查看classloader

输入命令

1
classloader

如下:

1
2
3
4
5
6
7
8
9
10
classloader
name numberOfInstances loadedCountTotal
org.springframework.boot.loader.LaunchedURLClassLoader 1 4208
BootstrapClassLoader 1 3922
com.taobao.arthas.agent.ArthasClassloader 1 2238
jdk.internal.loader.ClassLoaders$AppClassLoader 1 1198
jdk.internal.loader.ClassLoaders$PlatformClassLoader 1 157
jdk.internal.reflect.DelegatingClassLoader 47 47
Affect(row-cnt:6) cost in 7 ms.

classloader 命令将 JVM 中所有的classloader的信息统计出来,并可以展示继承树,urls等。

重新编译

选择SpringBoot的classLoader重新编译。

1
mc --classLoaderClass=org.springframework.boot.loader.LaunchedURLClassLoader ./Demo1Application.java
1
2
3
4
5
6
7
Memory compiler output:
/Users/yhan219/IdeaProjects/demo1/com/example/demo/Demo1Application.class
Affect(row-cnt:1) cost in 280 ms.
[arthas@28783]$ retransform com/example/demo/Demo1Application.class
retransform success, size: 1, classes:
com.example.demo.Demo1Application

生成class文件到当前目录的com/example/demo/目录下。

Memory Compiler/内存编译器,编译.java文件生成.class

交换

1
retransform com/example/demo/Demo1Application.class
1
2
retransform success, size: 1, classes:
com.example.demo.Demo1Application

结果

此时再次访问该接口地址即返回了bbb

使用arthas修改代码并热交换

https://blog.yhan219.com/arthas-hot-swap/

作者

yhan219

发布于

2021-09-22

更新于

2021-09-22

许可协议