package geym.zbase.ch10.clshot; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.nio.channels.FileChannel; import java.nio.channels.WritableByteChannel; public class MyClassLoader extends ClassLoader { private String fileName; public MyClassLoader(String fileName) { this.fileName = fileName; } protected Class<?> findClass(String className) throws ClassNotFoundException { Class clazz = this.findLoadedClass(className); if (null == clazz) { try { String classFile = getClassFile(className); FileInputStream fis = new FileInputStream(classFile); FileChannel fileC = fis.getChannel(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); WritableByteChannel outC = Channels.newChannel(baos); ByteBuffer buffer = ByteBuffer.allocateDirect(1024); while (true) { int i = fileC.read(buffer); if (i == 0 || i == -1) { break; } buffer.flip(); outC.write(buffer); buffer.clear(); } fis.close(); byte[] bytes = baos.toByteArray(); clazz = defineClass(className, bytes, 0, bytes.length); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } return clazz; } private byte[] loadClassBytes(String className) throws ClassNotFoundException { try { String classFile = getClassFile(className); FileInputStream fis = new FileInputStream(classFile); FileChannel fileC = fis.getChannel(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); WritableByteChannel outC = Channels.newChannel(baos); ByteBuffer buffer = ByteBuffer.allocateDirect(1024); while (true) { int i = fileC.read(buffer); if (i == 0 || i == -1) { break; } buffer.flip(); outC.write(buffer); buffer.clear(); } fis.close(); return baos.toByteArray(); } catch (IOException fnfe) { throw new ClassNotFoundException(className); } } private String getClassFile(String name) { StringBuffer sb = new StringBuffer(fileName); name = name.replace('.', File.separatorChar) + ".class"; sb.append(File.separator + name); return sb.toString(); } }
package geym.zbase.ch10.clshot; import java.lang.reflect.Method; public class DoopRun { public static void main(String args[]) { while(true){ try{ MyClassLoader loader = new MyClassLoader("D:/tmp/clz"); Class cls = loader.loadClass("geym.zbase.ch10.clshot.DemoA"); Object demo = cls.newInstance(); Method m = demo.getClass().getMethod("hot", new Class[] {}); m.invoke(demo, new Object[] {}); Thread.sleep(10000); }catch(Exception e){ System.out.println("not find"); try { Thread.sleep(10000); } catch (InterruptedException e1) { } } } } }
package geym.zbase.ch10.clshot; public class DemoA { public void hot(){ System.out.println("NewDemoA"); } }
以上程序复制于(《实战JAVA虚拟机》)
MyClassLoader是自定义加载器。
DoopRun用于执行热加载文件中的hot方法。
DemoA用于提供hot方法,不同DemoA中的hot方法可以打印不同的内容。
将DemoA.class文件放到D:/tmp/clz下面,需保持DemoA的全限定名的目录结构。另外Java程序的启动参数需要设置为-Xbootclasspath/a:/tmp/clz 。这样替换DemoA.java 中的hot方法,可以打印不同的内容,不需要重启Java程序。
MyClassLoader loader = new MyClassLoader("D:/tmp/clz"); 另外这一句非常重要,这一句要放到while中,放到while外的话,即使替换Demo.java也不会打印新的内容。原因是findClass方法中的Class clazz = this.findLoadedClass(className); 会判断class文件是否已经加载。如果已经加载,则使用之前加载过的class文件,而不会重新加载。而放到while中,相当于每次重新new一个MyClassLoader,生成了不同的加载器,不同的加载器对应的是不同的class文件,此时this.findLoadedClass(className)每次返回的都是null,都会去重新读取DemoA.class文件,重新加载,从而实现了热加载。在JVM虚拟机中,加载器和类的全限定名结合起来才能唯一确定一个类。