Java类加载器

来源:互联网 发布:数据工程师是干嘛的 编辑:程序博客网 时间:2024/06/05 19:01

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开始引入的,为了解决“顶层类”需要调用“底层类”却识别不了的问题。

0 0