在Java虚拟机中,Java类可以被动态装载到 Java 虚拟机中并执行。加载(Loading)指寻找一个具有特定名称的类或者接口类型的二进制形式,并且用这个二进制形式构造一个代表该类或者接口的Class对象的进程。
由类ClassLoader和它的子类实现的类装载器负责加载进程,读取Java 字节代码,并转换成java.lang.Class类的一个实例。
Java中系统提供的类加载器主要有下面三个:
1. 引导类加载器(bootstrap classloader):用来加载 Java 的核心库,是用原生代码来实现的,并不继承自java.lang.ClassLoader。
2. 扩展类加载器(extensions classloader):用来加载 Java 的扩展库。该类加载器在扩展库目录(ext)里面查找并加载 Java 类。
3. 系统类加载器(system classloader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过ClassLoader.getSystemClassLoader() 来获取它。
下面的示例展示了这三种类加载器各自的作用:
public class LoaderSample {
public static void main(String[] args) {
Class<?> c;
ClassLoader cl;
try {
c = Class.forName("java.lang.Object");
cl = c.getClassLoader();// 启动类装载器 (bootstrap)
System.out.println(" java.lang.Object's loader is " + cl);
c = Class.forName("sun.net.spi.nameservice.dns.DNSNameService");
cl = c.getClassLoader();// 扩展类装载器
System.out.println(" sun.net.spi.nameservice.dns.DNSNameService's loader is "+ cl);
c = Class.forName("LoaderSample");
cl = c.getClassLoader();// 系统类装载器
System.out.println(" LoaderSample's loader is " + cl);
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
java.lang.Object's loader is null
sun.net.spi.nameservice.dns.DNSNameService's loader is sun.misc.Launcher$ExtClassLoader@42e816
LoaderSample's loader is sun.misc.Launcher$AppClassLoader@addbf1
类加载器的继承关系:
类加载器树状组织结构:
类加载器在加载某个类时,会首先代理给其父类加载器,由父类加载器先去尝试加载这个类,依次类推。
ClassLoader类中的loadClass(String name, boolean resolve)实现:
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
可以看出,loadClass()方法会首先调用findLoadedClass() 方法来检查该类是否已经被加载过;如果没有加载过的话,会按照代理模式调用父类加载器的loadClass() 方法来尝试加载该类。如果两种方式都未成功加载的话,则调用 findClass() 方法来查找该类。
当自定义继承自ClassLoader的类加载器时,只需重写findClass(String name)即可,保留loadClass()的代理模式的实现。
由于代理模式,启动加载过程的类加载器和完成类的加载工作的类加载器可能不是同一个。启动加载过程的类加载器通过调用loadClass实现,被称为初始类加载器;完成类的加载工作的类加载器最终调用defineClass将字节码转换成Java类,被称为定义类加载器。一个类的定义类加载器是它所引用的其他类的初始类加载器(这也不难理解,一个类最终由定义类加载器载入虚拟机,此时需要载入它的引用类,所用的类加载器则使用该定义类加载器)。
在 Java 虚拟机判断两个类是否相同的时候,使用的是类的定义加载器。
public class Sample {
private Sample instance;
public void setSample(Object instance) {
this.instance = (Sample) instance;
}
}
public void testClassIdentity() {
String classDataRootPath = "E:\\workspace\\Classloader ";
FileSystemClassLoader fscl1 = new FileSystemClassLoader(classDataRootPath);
FileSystemClassLoader fscl2 = new FileSystemClassLoader(classDataRootPath);
String className = " Sample";
try {
Class<?> class1 = fscl1.loadClass(className);
Object obj1 = class1.newInstance();
System.out.println(fscl1);
System.out.println("class1 loaded!");
System.out.println(class1.getClassLoader());
Class<?> class2 = fscl2.loadClass(className);
Object obj2 = class2.newInstance();
Class<?> class2 = fscl2.loadClass(className);
System.out.println(fscl2);
System.out.println("class2 loaded!");
System.out.println(class2.getClassLoader());
Method setSampleMethod = class1.getMethod("setSample", java.lang.Object.class);
setSampleMethod.invoke(obj1, obj2);
} catch (Exception e) {
e.printStackTrace();
}
}
运行结果:
FileSystemClassLoader@61de33
class1 loaded!
sun.misc.Launcher$AppClassLoader@addbf1
FileSystemClassLoader@14318bb
class2 loaded!
sun.misc.Launcher$AppClassLoader@addbf1
可以看出两个Class的初始类加载器虽然不同,定义类加载器却同是系统类加载器的一个实例,因此两个Class完全相同。由此可以推断,继承自ClassLoader的自定义类加载器,由于无法重写defineClass方法(final),同一个类即使由不同的自定义类加载器实例加载,虚拟机依然认为是相同的类。
参考:
http://www.ibm.com/developerworks/cn/java/j-lo-classloader/index.html#fig1