前几篇文章主要是去理解JVM类加载的原理和应用,这一回讲一个可以自己动手的例子,希望能从头到尾的理解类加载以及执行的整个过程。
这个例子是从周志明的著作《深入理解Java虚拟机》第9章里抄来的。原作者因为有丰富的经验,可以站在一个很高的高度去描述整个过程。而我只能以现有的水平,简单的理解这个例子。
深入理解Java虚拟机:JVM高级特性与最佳实践 第2版 高清PDF+源码
如果读者感觉不错,那都是原作者的智慧;如果觉得不过尔尔,那就是我水平有限。
先说说日志。原先,我特别不喜欢在自己的程序里输出日志。写的时候那叫一个爽,可是一旦运行出错,那就麻烦了。因为不知道具体执行到哪一步出的错,所以就要调试一大片代码。尤其是大的项目,是要经常去分析日志的。所以,我们都尽量在代码里输出详细的日志。
但是,我们不可能把所有的情况考虑到。也就是说,当程序在服务器上跑的时候,我们想查看某个运行时的状态和数据,如果没有日志输出,就无能为力。
当然,并不是真的无能为力。这篇文章就是教你一些思考,以及解决这个问题的一个思路。
说白了,要是服务器能够临时去执行一段代码,输出日志,问题迎刃而解。有了前面类加载的知识,我们应该会想到:我们自己写一个类,然后动态加载到服务器的JVM进程的方法区,最后反射调用输出日志的那个方法。
但是,仔细想想,需要考虑的事情还有许多:
1)这个类可能会经常的被修改,经常的被加载,所以,执行完之后,要能够从方法区卸载。而能够被卸载的条件之一,就是它的类加载器被回收。之前已经加载了多个类的类加载器,是不可能那么快被回收的。所以,这里要自定义一个类加载器去加载待执行的类。
2)待执行的类要能够访问原来项目里的类,比如说WEB-INF下面的那些类。那怎么办呢?就要用到双亲委派模型了,将自定义类加载器的父类加载器设置为加载这个类加载器的类加载器。听起来有点绕,没关系,直接上代码
/**
* 为了多次载入执行类而加入的加载器<br>
* 把defineClass方法开放出来,只有外部显式调用的时候才会使用到loadByte方法
* 由虚拟机调用时,仍然按照原有的双亲委派规则使用loadClass方法进行类加载
*
* @author zzm
*/
public class HotSwapClassLoader extends ClassLoader {
public HotSwapClassLoader() {
// 设置父类加载器,用以访问JVM进程中的原来的类
super(HotSwapClassLoader.class.getClassLoader());
}
/**
* 加载待执行的类
*/
public Class loadByte(byte[] classByte) {
return defineClass(null, classByte, 0, classByte.length);
}
}
3)待执行类的方法里面的日志输出到哪里?你可能脱口而出,System.out.println()。但是System.out是标准输出,是整个JVM进程的资源,也不利于查看。也许,你会想通过System.setOut()指定一个文件作为输出。可是,一旦设定,那以后整个JVM进程的输出都会写到这个文件里面,这样就影响了原来的程序,这不是我们想要的。所以,我们必须写一个类来代替System类的作用。
/**
* 为JavaClass劫持java.lang.System提供支持
* 除了out和err外,其余的都直接转发给System处理
*
* @author zzm
*/
public class HackSystem {
public final static InputStream in = System.in;
private static ByteArrayOutputStream buffer = new ByteArrayOutputStream();
public final static PrintStream out = new PrintStream(buffer);
public final static PrintStream err = out;
public static String getBufferString() {
return buffer.toString();
}
public static void clearBuffer() {
buffer.reset();
}
public static void setSecurityManager(final SecurityManager s) {
System.setSecurityManager(s);
}
public static SecurityManager getSecurityManager() {
return System.getSecurityManager();
}
public static long currentTimeMillis() {
return System.currentTimeMillis();
}