类加载器与双亲委派模型

1. 双亲委派模型

1.1 什么是双亲委派模型

首先,先要知道什么是类的加载器。简单说,类加载器就是根据指定全限定名称将class文件加载到JVM内存,装维Class对象。如果站在JVM的角度来说,只存在两种加载器:

  • 启动类加载器(Bootstrap ClassLoader):由C++语言实现(针对HotSpot),负责将存放在\lib目录或-Xbootclasspath参数指定路径中的类库加载到内存中。
  • 其他类型加载器:由Java语言实现,继承自抽象类ClassLoader。如:
    • 扩展类加载器(Extension ClassLoader):负责加载\lib\ext目录或java.ext.dirs系统便令指定的路径中的所有类库。
    • 应用程序类加载器(Application ClassLoader)。负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。

双亲委派模型工作过程:如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载中这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在及其的搜索范围内找不到指定的类时(即ClassNotFoundException),子加载器才会尝试自己去加载。

1.2 为什么需要双亲委派模型?

为什么需要双亲委派模型呢?假设没有双亲委派模型,试想一个场景:

黑客自定义一个java.lang.String类,该String类具有系统的String类一样的功能,只是在某个函数稍作修改。比如equals函数,这个函数经常使用,如果在这个函数中,黑客加入一些“病毒代码”。并通过自定义类加载器加入到JVM中。此时,如果没有双亲委派模型,那么JVM就可能误以为黑客自定义的java.lang.String类是系统的String类,导致“病毒代码”被执行。

而有了双亲委派模型,黑客自定义的java.lang.String永远不会被加载到内存中。因为首先是最顶端的类加载器加载系统的java.lang.String类,最终自定义的类加载器无法加载java.lang.String类。

或许你会想,我在定义的类加载器里面强制加载自定义的java.lang.String类,不去通过调用父加载器不就好了吗?的确,这样是可行的。但是,在JVM中,判断一个对象是否是某个类型时,如果该对象的实际类型与带比较类型的类加载器不同,那么会返回false。

举个栗子:

ClassLoader1、ClassLoader2都加载java.lang.String类,对应Class1,、Class2对象。那么Class1对象不属于ClassLoader2对象加载的java.lang.String类型。

2. 自定义类加载器

2.1 几个重要函数

loadClass默认实现如下:

public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
}

再看看loadClass(String name, boolean resolve) 函数:

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();
                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;
    }
}

从上面代码可以明显看出,loadClass(String,boolean)函数即实现了双亲委派模型,整个大致过程如下:

  1. 首先,检查下制定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回。
  2. 如果此类没有加载过,那么,再判断下是否有父加载器;如果有父加载器,则由父加载器加载(即调用parent.ladClass(name,false);)或者是调用bootstrap类加载器来加载。
  3. 如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的findClass方法来完成类加载。

换句话说,如果自定义类加载器,就必须重写findClass方法!

find Class

findClass的默认实现如下

protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
}

可以看出,抽象类ClassLoader的findClass函数默认是抛出异常的。而前面我们知道,loadClass在父加载器无法加载类的时候,就会降低用我们自定义的类加载器中的findClass函数,因此我们必须在loadClass这个函数里面实现将一个指定类名称转换为class对象。

如果是读取一个指定的名称的类为字节数组的话,这个很好办。但是如何将字节数组转为class对象呢?很简单,Java提供了defineClass方法,通过这个方法,就可以把一个字节数组转为Class对象了。

defineClass

defineClass主要的功能是:

将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组。如,假设class文件时加密过的,则需要解密后作为形参传入defineClass函数。

defineClass默认实现如下:

protected final Class<?> defineClass(String name, byte[] b, int off, int len)
        throws ClassFormatError  {
        return defineClass(name, b, off, len, null);
}

2.2 函数调用过程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值