java高分局之类热加载(2)

来源:互联网 发布:淘宝网的电子商务模式 编辑:程序博客网 时间:2024/04/29 00:26

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.0Agent-Class: com.instrument.AgentMainCan-Retransform-Classes: true

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

java -cp chutest.jar com.instrument.TestMainInJar

运行如下:

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

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

C:\Users\Administrator>jps5840 TestMainInJar2196 Jps4252

我们看到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类,然后我们进入命令窗口就会看到结果,如下:

2222222222222222D:/com/instrument/TransClass.classAgent Main Done3333333333333333333333333333....

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

0 0
原创粉丝点击