java高*之类热加载(2)

时间:2022-10-30 17:19:18

java高*之类热加载(2)

之前写过一篇文章,讨论使用自定义类加载器实现类的热加载。文章地址如下:

http://blog.csdn.net/maosijunzi/article/details/45696589

这篇文章将要讨论另一种方法实现热加载,这种方法理论上说可以对应用中所有的类

进行热加载,但是在实际应用中,我们也不可能对所有的类进行热加载,也都是指定

相关的算法类进行热加载。

本节所使用的是lang 包下的Instrumentation。下面这段话摘自百度百科。

“java.lang.instrument”包的具体实现,依赖于 JVMTI。JVMTI(Java Virtual Machine Tool Interface)是一套由 Java 虚拟机提供的,为 JVM 相关的工具提供的本地编程接口集合。

JVMTI 提供了一套”代理”程序机制,可以支持第三方工具程序以代理的方式连接和访问 JVM,并利用 JVMTI 提供的丰富的编程接口,完成很多跟 JVM 相关的功能。事实上,java.lang.instrument

包的实现,也就是基于这种机制的:在 Instrumentation 的实现当中,存在一个 JVMTI 的代理程序,通过调用 JVMTI 当中 Java 类相关的函数来完成 Java 类的动态操作。除开 Instrumentation 功能外,JVMTI

还在虚拟机内存管理,线程控制,方法和变量操作等等方面提供了大量有价值的函数。

也就是jvm实际上是提供了直接替换类的功能的。只不过需要我们告诉jvm什么时候,那些类需要重新加载。然后我们把改变后的字节码发给jvm,它就会帮我们完成替换。

下面我们直接看代码:

package com.instrument;

import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;

/**
* @author chuer
* @Description: 代理类:这个类需要在mainfest.mf文件中指定
* @date 2015年5月22日 下午3:30:57
* @version V1.0
*/

public class AgentMain {

/**
* 这个类需要在mainfest.mf文件中指定,而且此方法名以及参数都是固定的,不能随意指定。
* 当我们告诉JVM需要热加载类的时候,JVM会自动调用此方法。
* @param agentArgs
* @param inst
* @throws ClassNotFoundException
* @throws UnmodifiableClassException
* @throws InterruptedException
*/

public static void agentmain(String agentArgs, Instrumentation inst)
throws ClassNotFoundException, UnmodifiableClassException,
InterruptedException {
inst.addTransformer(new Transformer(), true);
inst.retransformClasses(TransClass.class);
System.out.println("Agent Main Done");
}
}
package com.instrument;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

/**
* @author chuer
* @Description: 类转换
* @date 2015年5月22日 下午3:32:56
* @version V1.0
*/

public class Transformer implements ClassFileTransformer {


/**
* 此方法获得新类的字节数据,发送给JVM,JVM会自动替换旧类的字节码数据
*/

public byte[] transform(ClassLoader l, String className, Class<?> c,
ProtectionDomain pd, byte[] b) throws IllegalClassFormatException {

className = className.replace('/', '.');
// 重新载入类文件(完整路径)
String classNameSimple = className.substring(className.lastIndexOf('.') + 1);
if (!classNameSimple.equals("TransClass")) {
return null;
}

String newClassFile = "D:/com/instrument/" + classNameSimple + ".class";

System.out.println(newClassFile);
return getBytesFromFile(newClassFile);
}
/**
* 获得class文件的字节数据
* @param fileName
* @return
*/

public static byte[] getBytesFromFile(String fileName) {
File file = new File(fileName);
long length = file.length();
try (InputStream is = new FileInputStream(file)) {
byte[] bytes = new byte[(int) length];

int offset = 0;
int numRead = 0;
while (offset < bytes.length
&& (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) {
offset += numRead;
}

if (offset < bytes.length) {
throw new Exception("Could not completely read file "
+ file.getName());
}
is.close();
return bytes;
} catch (Exception e) {
System.out.println("error occurs in _ClassTransformer!"
+ e.getClass().getName());
return null;
}
}
}
package com.instrument;


/**
* @author chuer
* @Description: 需要进行热加载的类
* @date 2015年5月22日 下午3:32:37
* @version V1.0
*/

public class TransClass {
public int getNumber() {
return 2222;
}
}
package com.instrument;

public class Main {
public static void main(String[] args) throws InterruptedException {

while (true) {
int number = new TransClass().getNumber();
System.out.println(number);
Thread.sleep(1000);
}
}
}

manifest.mf文件内容如下:

Manifest-Version: 1.0
Agent-Class: com.instrument.AgentMain
Can-Retransform-Classes: true

使用eclipse的export功能打包,包名为chutest.jar,使用下面命令运行包:

java -cp chutest.jar com.instrument.TestMainInJar

运行如下:

D:\>java -cp chutest.jar com.instrument.TestMainInJar
2222
2222
2222
2222
2222
2222
2222
...

另打开一个cmd命令窗口,使用jps命令查看进程号:

C:\Users\Administrator>jps
5840 TestMainInJar
2196 Jps
4252

我们看到TestMainInJar的进程号为 5840.下面的程序会用到此进程号。

我们看一下,通知JVM进行类加载的程序:

package com.instrument;
import java.util.List;

import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;

public class AttachThread extends Thread {
private final String jar;

AttachThread(String attachJar, List<VirtualMachineDescriptor> vms) {
jar = attachJar;
}

public void run() {
VirtualMachine vm = null;
List<VirtualMachineDescriptor> listAfter = null;
try {
while (true) {
//如果被修改了则重新加载
listAfter = VirtualMachine.list();
for (VirtualMachineDescriptor vmd : listAfter) {
if ("5840".equals(vmd.id())) {//5840 是进程号
vm = VirtualMachine.attach(vmd);//连接JVM
break;
}
}
//通知JVM需要进行类的热加载,然后JVM会调用代理类的指定方法。
vm.loadAgent(jar);
vm.detach();
break;
}
} catch (Exception e) {
}
}

public static void main(String[] args) throws InterruptedException {
new AttachThread("D:/chutest.jar", VirtualMachine.list()).start();
}
}

然后修改TransClass类如下:

public class TransClass {
public int getNumber() {
return 3333;
}
}

编译后把,新的class文件放到D:/com/instrument目录下:

这时候,运行AttachThread类,然后我们进入命令窗口就会看到结果,如下:

2222
2222
2222
2222
D:/com/instrument/TransClass.class
Agent Main Done
3333
3333
3333
3333
3333
3333
3333
....

说明类已经被重新加载了。