Java基础之ClassLoader

与那些在编译时需要进行连接工作的语言不同,在Java语言里面,类型的加载,连接和初始化过程都是在程序运行期间完成的。
这种策略虽然会令类加载时稍微增加一些性能开销,但是会为Java应用程序提供高度的灵活性。
Java里天生可以动态扩展的语言特性就是依赖运行期动态加载和动态链接这个特点实现的。

1. 概述

ClassLoader顾名思义就是类加载器,负责将Class加载到JVM中。其三个作用作用:
1. 将Class加载到JVM中
2. 审查每个类应该由谁加载,它是一种父优先的等级加载机制。
3. 将Class字节码重新解析成JVM统一要求的对象格式。

2. 三层ClassLoader

2.1 Bootstrap ClassLoader

  1. 服务对象是系统属性System.getProperty("sun.boot.class.path") 目录下。
  2. Bootstrap ClassLoader并不属于JVM的类等级层次,因为Bootstrap ClassLoader没有遵守ClassLoader的加载规则。另外Bootstrap ClassLoader并没有子类。
  3. 其使用C++语言编写,负责装载JRE的核心类库,例如JRE目标下的rt.jar, charset.jar等。

2.2 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)

  1. 服务对象是系统属性System.getProperty("java.ext.dirs") 目录下。
  2. ExtClassLoader的父类也不是Bootstrap ClassLoaderExtClassLoader并没有父类,我们在应用中能够提取到的顶级父类就是ExtClassLoader
  3. 负责装载JRE扩展目录ext中的JAR类包。

2.3 AppClassLoader(sun.misc.Launcher$AppClassLoader)

  1. 服务对象是系统属性System.getProperty("java.class.path")目录下。即classpath路径下的类包。

2.4 sun.misc.Launcher

  1. ExtClassLoaderAppClassLoader都是sun.misc.Launcher的内部类,且都继承自URLClassLoader
    继承链

  2. 在创建Launcher对象时,

    1. 首先会创建ExtClassLoader
    2. 然后将ExtClassLoader对象作为父加载器创建AppClassLoader对象。
    3. 通过Launcher.getClassLoader获取到的ClassLoader就是AppClassLoader对象,所以如果在Java应用中没有定义其他ClassLoader,那么除了System.getProperty("java.ext.dirs") 目录下的类是由ExtClassLoader加载外,其他类都是由AppClassLoader来加载。

3. 双亲委派

一般以类加载器需要加载一个class或者资源文件的时候,他会先委托给他的parent类加载器,让parent类加载器先来加载,如果没有,才再在自己的路径上加载。这就是人们常说的双亲委托,即把类加载的请求委托给parent。

4. 思考——隐式加载时的ClassLoader

在写这篇文章前,有个问题困扰了我很长时间,那就是:两种加载Class方法中,显式加载自不必说,问题是隐式加载某个Class时,JVM是如何选择进行加载Class的ClassLoader的?

参见如下样例代码:

public class CustomCls2 {
    public void say() {
        System.out.println(this.getClass().getClassLoader());
    }
}

public class CustomCls1 {
    public void say() {
        System.out.println("加载CustomCls1的ClassLoader: ");
        System.out.println(this.getClass().getClassLoader());

        System.out.println("加载CustomCls2的ClassLoader: ");
        new CustomCls2().say();
    }
}

public class CustomClassLoader extends URLClassLoader {

    private static final String PATH = "E:\\gitRepository\\_javaDir\\javaCode\\_BasicLearing\\bin\\";

    public CustomClassLoader() {
        super(new URL[] {});
    }

    /**
     * @param urls
     */
    public CustomClassLoader(URL[] urls) {

        super(urls);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] loadClassData = loadClassData(name);
        return defineClass(name, loadClassData, 0, loadClassData.length);
    }

    private byte[] loadClassData(String name) {
        name = name.replace(".", "/");
        try {
            FileInputStream iStream = new FileInputStream(new File(PATH + name + ".class"));
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int b;
            while ((b = iStream.read()) != -1) {
                baos.write(b);
            }

            return baos.toByteArray();
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        return null;
    }
}

// 单元测试
@Test
public void customClassLoader() throws Exception {
    CustomClassLoader loader = new CustomClassLoader();
    Class<?> loadClass = loader.loadClass("__java.classloader_.CustomCls1");
    CustomCls1 newInstance = (CustomCls1)loadClass.newInstance();
    newInstance.say();
    /* 输出结果
        加载CustomCls1的ClassLoader: 
        sun.misc.Launcher$AppClassLoader@1fe9999
        加载CustomCls2的ClassLoader: 
        sun.misc.Launcher$AppClassLoader@1fe9999
    */
}

由上面的示例代码可以得出:
1. 隐式加载的Class,其对应的ClassLoader正常情况下是隐式引入该Class的那个Class对应的ClassLoader。即加载调用者的那个ClassLoader。在上例中就是 加载 CustomCls2 的ClassLoader就是加载 CustomCls1的那个ClassLoader
2. 其实以上这段逻辑在Java显式加载Class中已有暗示。具体代码就是Class.forName("xxx"),在其源码中可以看到,虽然getClassLoader0()是个native方法。但是我们大概可以肯定上一条逻辑。

    @CallerSensitive
    public static Class<?> forName(String className)
                throws ClassNotFoundException {
        // 获取调用本方法的那个类
        Class<?> caller = Reflection.getCallerClass();
        return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
    }
  1. 《深入分析Java Web技术内幕(修订版)》 P156
  2. 《深入理解Java虚拟机》 P209
  3. 《How Tomcat Works》 P157
  4. 类加载器ClassLoader(二):外传
  5. (Class.forName(“name”)和ClassLoader.loadClass(“name”)的区别)

猜你喜欢

转载自blog.csdn.net/lqzkcx3/article/details/79840572