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
....
说明类已经被重新加载了。