上一章我们了解到了双亲委派机制到底是个什么样的东西,但为什么要这样设计呢?今天主要在这方面进行探究。
类加载器,虽然只用于实现类的加载动作,但它在Java程序中起到的作用却远远不限于类加载阶段。对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器都拥有一个独立的类名称空间,通俗的来讲就是比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载他们的类加载器不用,那么这两个类就必定不相等,这里的“相等”,包括代表类的Class对象的equals方法,isAssignableFrom()方法,isInstance方法的返回结果,也包括使用instanceof 关键字做对象所属关系判定等情况。
我们可以通过下面的例子做个实验:
package fun.lain.jvm;
import java.io.IOException;
import java.io.InputStream;
public class ClassLoadTest {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
ClassLoader myLoader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try {
String fileName = name.substring(name.lastIndexOf(".") +1)
+".class";
InputStream inputStream = getClass().getResourceAsStream(fileName);
if(inputStream == null){
return super.loadClass(name);
}
byte[] b = new byte[inputStream.available()];
inputStream.read(b);
return defineClass(name,b,0,b.length);
}catch (IOException e){
throw new ClassNotFoundException(name);
}
}
};
Object ob = myLoader.loadClass("fun.lain.jvm.ClassLoadTest").newInstance();
Object ob2 = fun.lain.jvm.ClassLoadTest.class.newInstance();
System.out.println(ob);
System.out.println(ob2);
//比较两者类型
System.out.println(ob instanceof fun.lain.jvm.ClassLoadTest);
//比较两者的类加载器
System.out.println(ob.getClass().getClassLoader());
System.out.println(ob2.getClass().getClassLoader());
}
}
输出结果为:
fun.lain.jvm.ClassLoadTest@2ff4f00f
fun.lain.jvm.ClassLoadTest@c818063
false
fun.lain.jvm.ClassLoadTest$1@7cf10a6f
sun.misc.Launcher$AppClassLoader@18b4aac2
代码中我们构造了一个简单的类加载器,功能就是加载与自己同目录的类。这里我们用它加载了测试类自身。我们发现,虽然他们都是由fun.lain.jvm.ClassLoadTest实例化出来的对象,但是在类型比较的时候却返回了false。而当我们输出他们的类加载器的时候,可以看到一个来自我们自定义的类加载器(测试类的匿名内部类),另一个是我们的系统应用程序类加载器加载的。因此虚拟机中存在两个同名的ClassLoadTest类,他们虽然加载自同一个class文件,但依旧是两个独立的类。
让我们回到双亲委派模型上来,我们知道,在双亲委派模型中,无论你用哪个类加载器加载类,都会先一路提交到启动类加载器去执行加载,这样我们的类随着他们的加载器一起具备了一种带有优先级的层次关系,比如java.lang.Object,Java中所有的类都继承自这个类,它放在rt.jar中,只有启动类加载器才能加载到他,因此不管你用什么类加载器去加载Object类,最终只会由启动类加载器加载,这样就保证了Object类在虚拟机中的唯一性。相反,如果没有双亲委派模型,用户自己写了个java.lang.Object的类放到classpath里并加载它,那java类型体系就直接崩盘了。
我们可以自己写一个Object类试试:
package java.lang;
public class Object {
public static void main(String[] args) {
System.out.println("Lain");
}
}
这个类可以正常被编译,但是永远不会被加载,直接执行main方法会发现这个main方法根本找不到,因为虚拟机里加载的是rt.jar里的Object,就算我们自己定义一个加载器,强行用defineClass方法去加载它,也只会收到一个来自虚拟机的java.lang.SecurityException: Prohibited package name: java.lang 异常。