类加载器的作用
类加载器(class loader)是用来加载 Java 类到 Java 虚拟机中的。它使得 Java 类可以被动态加载到 Java 虚拟机中并执行。
类加载器的分类
类加载器有两个分类,一种是用于加载JAVA核心组件的启动类加载器(bootstrap class loader),另一种是用户自定义的类加载器,定义这样的加载器需要继承java.lang.ClassLoader
。
但是如果没有什么特别需求,也无需要自定义类加载器,JAVA已经默认提供了一些自定义的类加载器,一般够用了。
启动类加载器到底是加载什么类呢?
public class ClassLoaderTest {
public static void main(String[] args) {
String property = System.getProperty("sun.boot.class.path");
for(String e : property.split(";")){
System.out.println(e);
}
}
}
上面代码在我的Window机器上的执行结果如下:
C:\Program Files\Java\jdk1.7.0_25\jre\lib\resources.jar
C:\Program Files\Java\jdk1.7.0_25\jre\lib\rt.jar
C:\Program Files\Java\jdk1.7.0_25\jre\lib\sunrsasign.jar
C:\Program Files\Java\jdk1.7.0_25\jre\lib\jsse.jar
C:\Program Files\Java\jdk1.7.0_25\jre\lib\jce.jar
C:\Program Files\Java\jdk1.7.0_25\jre\lib\charsets.jar
C:\Program Files\Java\jdk1.7.0_25\jre\lib\jfr.jar
C:\Program Files\Java\jdk1.7.0_25\jre\classes
可见启动类加载器加载的是jre目录下lib目录中的类。
而JAVA提供的自定义类加载器ExtClassLoader加载的类如下:
ExtClassLoader
public class ClassLoaderTest {
public static void main(String[] args) {
String property = System.getProperty("java.ext.dirs");
for(String e : property.split(";")){
System.out.println(e);
}
}
}
结果:
C:\Program Files\Java\jdk1.7.0_25\jre\lib\ext
C:\Windows\Sun\Java\lib\ext
至于AppClassLoader就是用于加载classpath下的类的。
AppClassLoader和ExtClassLoader是自定义的类加载器,并且是继承自java.lang.ClassLoader
,有两种办法来证明这个事情,一种是去看openjdk的源代码,另一种是直接用JAVA代码输出,例如:
public class TempTestClass {
public void test()
{
}
}
public class ClassLoaderTest {
public static void main(String[] args) {
TempTestClass hello = new TempTestClass();
hello.test();
System.out.println("看看TempTestClass是由哪个类加载器加载的---begin");
ClassLoader classLoaderOfTemp = TempTestClass.class.getClassLoader();
System.out.println(classLoaderOfTemp);
System.out.println("看看TempTestClass是由哪个类加载器加载的---end");
System.out.println("");
System.out.println("");
System.out.println("看看加载TempTestClass的加载器的类继承关系---begin");
@SuppressWarnings("rawtypes")
Class appClass = classLoaderOfTemp.getClass();
while (appClass != null) {
System.out.println(appClass);
appClass = appClass.getSuperclass();
}
System.out.println("看看加载TempTestClass的加载器的类继承关系---end");
System.out.println("");
System.out.println("");
System.out.println("看看加载TempTestClass的加载器的父类加载器的类继承关系---begin");
@SuppressWarnings("rawtypes")
Class extClass = classLoaderOfTemp.getParent().getClass();
while (extClass != null) {
System.out.println(extClass);
extClass = extClass.getSuperclass();
}
System.out.println("看看加载TempTestClass的加载器的父类加载器的类继承关系---end");
}
}
结果如下:
看看TempTestClass是由哪个类加载器加载的---begin
sun.misc.Launcher$AppClassLoader@65f4ba51
看看TempTestClass是由哪个类加载器加载的---end
看看加载TempTestClass的加载器的类继承关系---begin
class sun.misc.Launcher$AppClassLoader
class java.net.URLClassLoader
class java.security.SecureClassLoader
class java.lang.ClassLoader
class java.lang.Object
看看加载TempTestClass的加载器的类继承关系---end
看看加载TempTestClass的加载器的父类加载器的类继承关系---begin
class sun.misc.Launcher$ExtClassLoader
class java.net.URLClassLoader
class java.security.SecureClassLoader
class java.lang.ClassLoader
class java.lang.Object
看看加载TempTestClass的加载器的父类加载器的类继承关系---end
有上面的输出结果便可知道结论了。
类加载器的委托机制
当一个类加载器加载类时,会把这个加载请求委托给父亲加载器,一直递归的委托上去。如果父亲没法加载,才会尝试自己加载,如下图:
之所以使用这种委托机制,是想保护JAVA核心库的类型安全,因为不同的加载器加载同一份字节码的时候,对于JVM来说,就是不同的类了,而且这些类还没法进行类型转换。设想一下,两个类加载器都加载了java.lang.Object到JVM中,那JVM中就有两份不同的java.lang.Object,而且它们之间是没法做类型转换的。
由此可以看到,虽然某个加载器触发了加载的动作,但是真正加载这个类是它的父类加载器。这里就有个说法,触发加载动作的类加载器叫初始类加载器,真正加载类的加载器叫做定义加载器。JVM判断一个类是否是同一个,会从两个方面来考虑,第一个是包名+类名是否相同,第二是加载这个类的类加载器是否相同,其中的类加载器指的就是定义加载器。
参考的文章