Java ClassLoader

415 阅读3分钟

ClassLoader的概念

Java虚拟机设计团队把类加载阶段种的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类,实现这个动作的代码模块称为“类加载器”。 类加载器是Java语言的一项重大创新,在类层次划分、热部署、代码加密等领域都有应用。

ClassLoader的类型

对Jvm虚拟机而言,ClassLoader就分为两类:

  • Bootstrap ClassLoader(启动类加载器) C/C++编写,是虚拟机的一部分
  • 其他类加载器 在虚拟机外实现

从我们开发人员角度看,ClassLoader还可以划分为以下几类:

  • Bootstrap ClassLoader(启动类加载器)

    C/C++编写,是虚拟机的一部分,用于加载指定的JDK核心类库,如:java.langjava.util等。它会加载$JAVA_HOME/jre/lib目录和-Xbootclasspath参数指定的目录中的类库(必须是虚拟机识别的,但仅按文件名识别)。

  • Extentions ClassLoader(扩展类加载器)

    负责加载$JAVA_HOME/jre/ext目录和系统变量java.ext.dir目录中指定的类库. 在代码中由Launcher$ExtClassLoader实现.通过它内部方法可以看出它的加载范围:

    private static File[] getExtDirs() {
            String var0 = System.getProperty("java.ext.dirs");
            File[] var1;
            if (var0 != null) {
                StringTokenizer var2 = new StringTokenizer(var0, File.pathSeparator);
                int var3 = var2.countTokens();
                var1 = new File[var3];
    
                for(int var4 = 0; var4 < var3; ++var4) {
                    var1[var4] = new File(var2.nextToken());
                }
            } else {
                var1 = new File[0];
            }
    
            return var1;
        }
    
  • Application ClassLoader(应用程序类加载器)

    负责加载当前程序的classPath目录和系统属性java.class.path目录下的类库. 在代码中由Launcher$AppClassLoader实现,可通过ClassLoader.getSystemClassLoader获取,一般情况下它就是系统中默认的类加载器.

  • Custom ClassLoader(自定义类加载器)

    当系统提供的类加载器不满足我们的需求时,我们可以通过继承ClassLoader来自定义类加载器

加载机制——双亲委派模式

ClassLoader查找类时采用的时双亲委派模式,简单来说有以下几步基本流程:

  1. 子加载器判断该class是否已经加载
  2. 如果没有则委托父加载器查找,找到则返回,没有则依次递归直到顶层bootstrap classLoader
  3. bootstrap classLoader中查找是否已加载该class,有则返回,没有则依次向下查找,如果没找到则最后会交给最初委托的ClassLoader去查找.

双亲委派的实现

看看ClassLoaderloadClass()中的实现会很容易理解:

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) {
                try {
                    // 这里的parent不是父类,而是父加载器,未指定的情况下则为SystemClassLoader
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                    // 因为BootStrap ClassLoader为C/C++实现,在Java中无法直接获取,所以parent为null
                        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.
                    c = findClass(name);
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

类加载器之间的关系(不是继承关系)

我们自定义ClassLoader时只需要继承java.lang.ClassLoader,复写findClass(),在该方法中获取字节码数组,再通过defineClass()把字节码数组转换为Class类实例.

为什么要使用双亲委派

  1. 避免类重复加载

  2. 安全性.像ObjectString之类的系统类,在虚拟机启动时就会被加载,避免了自定义的String无法替换系统String类.

     两个类名完全一致,且被同一个类加载器加载的类,虚拟机才会认为它们是同一个类.