ClassLoader是Java的核心组件,所有的Class都是由ClassLoader进行加载的。ClassLoader通过各种方式,将CLass信息的二进制流读入系统,然后交给JVM进行连接、初始化等操作。
比较两个类是否“相等”
比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义。这里所指的相等,包括equals()、isAssignableFrom()、isInstance()、instanceof等。如下所示:
运行结果:
产生上述的运行结果的原因是:obj是用户自定义类加载器加载的,instanceof后面的com.moguhu.***.ClassLoaderTest是系统应用程序类加载器加载的。
ClassLoader类
如果我们需要自定义ClassLoader,那么JDK中提供了java.lang.ClassLoader抽象类,我们可以通过继承来实现自定义ClassLoader。其主要方法如下:
双亲委派模式
双亲委派模式的过程如下:如果一个类加载器收到了类加载请求,他首先不会自己去加载,而是把请求为拍给父类加载器去完成。一层一层的循环下去,直到顶层类加载器。当父类返回无法完成时,子类加载器才会尝试自己加载。如下图所示:
下面介绍一下JDK中提供的几个系统类加载器:
启动类加载器(Bootstrap ClassLoader):它负责加载放在/lib目录中的(按照文件名识别,如果lib下面存放了其他jar文件,则不予加载),或者被-Xbootclasspath参数指定的路径。
拓展类加载器(Extension ClassLoader):它由sun.misc.Launcher$ExtClassLoader实现,负责加载/lib/ext目录中,或者被java.ext.dirs系统变量指定的路径中的类库,开发者可以直接使用。
应用程序类加载器(Application ClassLoader):它由sun.misc.Launcher$AppClassLoader实现,它负责加载用户ClassPath上指定的类库。如果应用程序中没有自定义的类加载器,一般情况下它就是默认的加载器,还有另外一个名字叫:系统类加载器。
突破双亲模式
双亲委派模式是JVM的默认行为,事实上可以通过自定义ClassLoader来改变其行为。比如Tomcat和OSGI都有各自独特的类加载顺序。下面看一个自定义ClassLoader的Demo:
继承ClassLoader
从上面代码可以看出,classPath目录下的class文件使用自定义的ClassLoader加载,其他的类还是使用父类加载器去加载。
继承URLClassLoader
URLClassLoader可以设定自定义的URL来加载Class文件,如下所示:
热替换实现思路
热替换是指在程序的运行过程中,不停止服务,只通过替换程序文件来修改程序的行为。大部分的脚本语言都是天生支持热替换的,如:PHP。而对于Java来说,实现热替换的思路如下所示:
在实现时,首先需要自定义ClassLoader(如下代码所示),他可以在给定的目录下查找目标类,主要的实现思路是重载findClass()方法。
自定义ClassLoader
需要热替换的类
测试类
上述代码运行时,会不断的打出“OldDemoA”。当我们更改DemoA的打印内容(如:“NewDemoA”),并且重新编译并替换原先.class文件。程序将会打印“NewDemoA”。
Class对象在JVM中只有一份,理论上可以直接替换,然后更新Java栈中所有对原对象的引用关系。看起来被替换了,但是仍然不可行,因为其违反了JVM的设计原则。对象引用关系只有创建者持有和使用,JVM不可以柑橘对象的引用关系。
造成不能动态替换类对象的关键是:对象的状态被保存了,并且被其他对象引用了。一个简单的方法就是不保存对象的状态,对象创建使用后就被释放掉,当修改后就是新的了。这种思想比较典型的例子就是JSP,其他解释型语言亦是如此。
参考:《深入理解Java虚拟机》、《实战Java虚拟机》、《深入分析Java Web》