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 HelloService
class, 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 jad
command 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 sc
command 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 ClassLoader
the hash value.
2.4, mc
memory compile the source code
Using the mc command to compile the previous step to modify the source code to save to produce a final class
document.
$ 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:
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 redefine
command. 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-main
mode, 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.
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-main
the way. After that we can start the application, and then run Instrumentation
the 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, VirtualMachine
and VirtualMachineDescriptor
.
VirtualMachine
On behalf of a JVM instance, use it to provide attach
a method, we can connect the target JVM.
VirtualMachine vm = VirtualMachine.attach(pid);
VirtualMachineDescriptor
It is a container class describing a virtual machine, we can get to the JVM PID (process ID) through the instance that mainly through VirtualMachine#list
acquisition 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-main
way.
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#redefineClasses
this 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-plugin
the 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 HelloService
the code to another engineer, modify hello
the method output hello agent
, recompile to generate a new class file.
Finally, the command line to run the generated jar package.
HelloService
Output results as shown below:
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 HelloService
join 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:
Then in Agent-main
the project to add a remote debugging.
FIG parameters are as follows:
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8001
In the Agent-main
project marked a breakpoint, run remote debugging, HelloService
the program will be started.
Finally, the command line window to run Agent-main
the 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.
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.
Join in operating parameters -Xbootclasspath/a:${java_home}/lib/tools.jar
, complete run the following command:
4.5, there are some restrictions hot update
Not all the changes will be hot updates are successful, the current use Instrumentation#redefineClasses
there 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