本篇文章实际是<Dynamic class loading int the java virtual machine>的摘译,具体来讲是摘译了其中的第三部分<Applications of class loaders>。这个章节的题目翻译成中文的话应该是: class loaders的应用案例,主要是讲class loaders的两个重要应用场景。
第一、重新装载类
如果需要升级、更新服务器的某个组件,那么最好可以在不终止服务器、不重启服务器的前提下完成更新。在Java平台上,升级组件其实就是将已经装载虚拟机的一些类卸载,然后重新装载这系列的类。下面我将这系列的类简称为“目标类”。这个问题其实是比较难的,可以想象的出至少存在下面的难点:java虚拟机中很可能仍然有存活的目标类对象,如何将这些旧目标类对象变成新目标类对象呢?具体来说,是下面的三个子问题:
- 如果新目标类中增加、删除、修改了某些实例成员变量,那么如何建立旧目标类的实例变量与新目标类的实例变量之间的映射关系?
- 如果新目标类中增加、删除、修改了某些类成员变量(static),那么如何建立旧目标类的类变量与新目标类的类变量之间的映射关系?
- java虚拟机引擎仍然在运行旧目标类的某个方法,如何做迁移呢?
虽然此处并不打算研究这个问题,但是却可以介绍借助class loaders实现重新装载类的方法。如下图所示,将服务器端的系统分为三个部分:server, old service, new service。使用三个不同的class loader对象去装载这三个部分。
首先定义一个MyClassLoader如下,
class MyClassLoader extends ClassLoader{ private String directory; public MyClassLoader(String dir){ directory = dir; } public synchronized Class loadClass(String name){ Class c = findLoadedClass(name); if (c != null){ return c; } try{ c = findSystemClass(name); return c; }catch(ClassNotFoundException e){ //keep looking } try{ byte[] data = getClassData(directory, name); return defineClass(name, data, 0, data.length()); }catch(IOException e){ throw new ClassNotFoundException(); } } byte[] getClassData(...){...} }
Server的代码如下,
class Server{ private Object service; public void updateService(String location){ MyClassLoader cl = new MyClassLoader(location); Class c = cl.loadClass("Service"); service = c.newInstance(); } public void processRequest(...){ Class c = service.getClass(); Method m = c.getMethod("run", ...); m.invoke(service, ...); } }
Server.processRequest方法使用反射机制来调用Service.run方法。另外,Server.updateService方法使用class loader从指定目录动态装载Service类,这样就可以动态的更新服务了。执行Server.updateService方法之后,再次调用Server.processRequest方法,就是有新Service来响应请求了。等到旧Service处理完所有的请求后,旧Service的class loader以及旧Service都会被垃圾回收期回收。
第二、在class文件中添加代码
如果自定义class loader,那么就可以在执行defineClass之前,在原class文件中添加一些代码。将上节的MyClassLoader修改为如下的类,
class MyClassLoader extends ClassLoader{ public synchronized Class loadClass(String name){ .... byte[] data = getClassData(directory, name); byte newData = instrumentClassFile(data); return defineClass(name, newData, 0, newData.length()); } } 当然instrumentClassFile方法生成的新class文件必须符合Java虚拟机规范。在符合java虚拟机规范的前提下,程序员可以在任意修改class文件。可以增加、修改、删除方法,可以增加、修改、删除实例变量,可以增加、修改、删除类变量。另外需要注意,常量池之后的第3、4字节描述了类名,instrumentClassFile不能更改类名。因为,java虚拟机会检测类文件中的类名与loadClass的参数是否相同。如果不同,那么会抛出异常的。
这个方法很有用,如下的场景都需要用到这种的方法。
- 检测某个函数的执行效率,在目标函数中添加统计代码。
- 检测某个函数的资源使用情况。
- 自动生成模板类。