java热修复实例

本文介绍了Java热修复的实例,通过动态加载class文件实现Bug的热修复。内容包括被替换类的准备、热修复的准备,以及运行过程中的观察和遇到的一些问题。在热修复的准备中,涉及了修复类的编写、代理类的设计和attach到已有进程的操作。实验结果显示,静态方法在main中直接调用时无法被热修复。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

闲来无事试一下java的热修复。主要的原理是通过动态加载class文件的方式,参考文章为在 Java 中运用动态挂载实现 Bug 的热修复动态替换目标进程的Java类

两篇文章分别在原理和实践上给出了详细的说明,这里做一下实例的整理并对遇到的问题和两篇文章中一些没有提到的注意事项进行一下说明

 

 

被替换的类的准备

先写一个要被替换掉的类libUtil,里面只有一个静态方法printString,打印一句话

 

public class libUtil {
   public static void printString(String s){
      System.out.println("this is a bug string : "+s);
   }
}

 

然后是测试类,需要完成以下工作

1. 打印进程号,方便热修复时新建的虚拟机attach到这个进程上

2.      不停的调用要被替换掉的类的方法,以便我们可以清晰的看到热修复的效果

 

测试类如下:

 

import java.lang.management.ManagementFactory;
 
public class testHotSwap {
   public static void main(String[] args) {
      testStatic t=(newtestHotSwap()).new testStatic();
      while(true){
         try{
            Thread.sleep(5000);
            String name = ManagementFactory.getRuntimeMXBean().getName();
            System.out.println(name);
            // get pid  
            String pid = name.split("@")[0];
            System.out.println("Pid is:" + pid);
            t.print();
         }
         catch(InterruptedException e){}
      }
   }
   class testStatic{
      public void print(){
         libUtil.printString("did u miss me ?");
      }
   }
}

 

 

 

 

 

热修复的准备

接下来我们需要完成三件事:

1.      写一个进程去启动一个虚拟机并attach到已上线的进程上去

2.      写一个修复类去覆盖有问题的类

3.      写一个代理类,通过代理类的接口去实现热修复,其中这个代理类是要打包成jar文件的形式

 

首先来写一个修复类,这个修复类需要与原来的类在包名,类名,修饰符上完全一致,否则在classRedefine过程中会产生classname don’t match 的异常

代码如下:

 

public class libUtil {
   public static void printString(String s){
      System.out.println("this is a fixed string : "+s);
   }
}

 

然后来写代理类,其中需要特别注意的有以下几点:

1.      必须含有static的代理main方法

2.      打成jar包后的Manifest.mf文件中必须含有以下两行:

l agent class: Agent-Class:testHotSwapDebug.debugJar

l Can-Redefine-Classes: true

Agent class指定了代理类的包名类名

Can redefineclass属性使得这个包可以对已载入的class进行redefine

 

 

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;
 
public class debugJar {
   public static void agentmain(String arg, Instrumentation inst)
            throws Exception {
        // only if header utility is on the classpath; otherwise,
        // a class can be found within any classloader by iterating
        // over the return value ofInstrumentation::getAllLoadedClasses
      // 需要打成jar包并修改manifest.mf 添加一行指定agent class : Agent-Class:testHotSwapDebug.debugJar 
      //还需要加一行 :Can-Redefine-Classes: true
        Class<?> libUtil = Class.forName("processWithBug.libUtil");
 
        // copy the contents of typo.fix into abyte array
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        InputStream input=null;
        try  {
           input =new FileInputStream("C:\\Users\\Administrator\\workspace\\testHotSwapDebug\\bin\\processWithBug\\libUtil.class");//包名类名都要与原来的一致   
            byte[] buffer = new byte[1024];
            int length;
            while ((length = input.read(buffer)) != -1) {
                output.write(buffer, 0, length);
            }
        }catch(Exception e){
          if(input==null)
             System.out.println("class input is null");
          else
             e.printStackTrace();
        }
 
        // Apply the redefinition
        inst.redefineClasses(
                new ClassDefinition(libUtil, output.toByteArray()));
    }
 
}

接下来是用来attach到已有进程的类,注意事项有以下几点:

1. 需要在工程中引入tools.jar,这个jar包在jdklib文件夹下,但一般ide的运行环境为jre,所以需要特别添加进环境变量里面

2. 需要将attach.dll进行动态加载,同样attach.dlljdk的一个bin目录下,需要拷贝到运行目录下,忘记具体在哪了,可以在jdk目录下进行搜索,找到以后放到jre/bin下就可以了

 

import java.io.File;
import com.sun.tools.attach.VirtualMachine;

public classtestFixBug {
  static
  {
     System.loadLibrary("attach");
     //需要将attach.dll放到java.library.path下  
  }

  public static void main(String[] args) {
     String processId = "17244";//需要修改为要被修复进程的pid
     String jarFileName ="fixBug.jar";//有agentmain修复类的jar包名
     File f=new File(jarFileName);
     System.out.println(f.getAbsolutePath());
     VirtualMachine virtualMachine;
     try {
        virtualMachine =VirtualMachine.attach(processId);
        virtualMachine.loadAgent(jarFileName, "World!");
        virtualMachine.detach();
     } catch(Exception e){
        e.printStackTrace();
     }
  }
}

 

 

 

 

 

 

运行结果

先运行要被替换的类的测试进程

11368@SKY-20150127GHW
Pid is:11368
this is a bugstring : did u miss me ? 

 

 

 

 

 

运行attach的进程:

11368@SKY-20150127GHW
Pid is:11368
thisis a bug string : did u miss me ?
11368@SKY-20150127GHW
Pid is:11368
thisis a bug string : did u miss me ?
11368@SKY-20150127GHW
Pid is:11368
thisis a fixed string : did u miss me ?
11368@SKY-20150127GHW
Pid is:11368
this is a fixedstring : did u miss me ?

 

 

 

 

 

 

一些奇怪的小现象

当static方法直接在main中调用时不能被热修复,准确的说redefine成功了却没有效果。猜测是static块中调用static方法在编译时就已经固定了无法用动态重定义的方法产生影响

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值