Java中由类字节码流转化为JVM运行时类数据必须使用类加载器进行加载,Java中提供了三个类加载器:根类加载器,扩展类加载器,应用程序类加载器,使用的机制可以概括为“全盘负责双亲委托”机制。
注意图中的关系是委派关系,不是继承关系!源码中使用组合实现,即ClassLoader类的parent成员变量,而最上面的那个类加载器(根类加载器)的parent为null。
双亲委托机制在代码中具体体现在ClassLoader类的loadClass方法(下面是JDK1.8代码):
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
//如果存在父类加载器则委托父类加载器去加载
if (parent != null) {
c = parent.loadClass(name, false);
//没有父类加载器,尝试委托根类加载器加载
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
//上面流程委托加载失败,则使用当前加载器加载
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
//由类全限定名得到Class对象,自定义类加载器(仅)需要重写该方法
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
自定义类加载器
public class MyClassLoader extends ClassLoader {
public MyClassLoader() {
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
//这里类字节流来源为class文件
String path = "C:\\Users\\mao\\Desktop\\" + name + ".class";
//读取class文件,转化为字节流
byte[] b = IoUtils.getBytes(path);
return defineClass(name, b, 0, b == null ? 0 : b.length);
}
}
这样就定义了一个自己的ClassLoader,接下来为测试代码,在C:\Users\mao\Desktop\路径下放一个String.class文件,然后使用该ClassLoader加载的一个class文件:
public class Test {
public static void main(String[] args) throws Exception {
//没有指定父类加载器,则会被默认设置为应用程序类加载器为父类加载器
ClassLoader cl = new MyClassLoader();
Class<?> clazz = Class.forName("String", true, cl);
Object obj = clazz.newInstance();
System.out.println(obj);
}
}
代码打印:
I am a custom String class
因为我们放在C:\Users\mao\Desktop\路径下的String类的toString方法被重写了:
public class String {
@Override
public java.lang.String toString() {
return "I am a custom String class";
}
}
注意:这个String类是我们自己定义的,而JDK内部也有一个String类,运行时这两个类当然不是等价的,因此至少类加载器不同(当然上面的例子类全限定名也不同),而我们是无法做到把JDK的String类替换成我们自己定义的String类的(像上面的例子根类加载器会拒绝加载),这也说明了双亲委派机制的一个优点就是安全性高。
另外,Thread类有一个成员变量contextClassLoader,表示线程上下文类加载器,可以通过setContextClassLoader和getContextClassLoader方法设置和获取该变量的值,默认情况下contextClassLoader的值为父线程的contextClassLoader的值,而Java中最顶层的线程的contextClassLoader为应用程序类加载器。这个是在JDK1.2开始引入的,为了解决“顶层类”需要调用“底层类”却识别不了的问题。
本博客已停止更新,转移到微信公众号上写文章,欢迎关注:Android进阶驿站