Java 类加载器深入理解

Java 的执行过程

  1. 首先根据java后的运行模式配置项或/jre/lib/i386/jvm.cfg来决定是以client还是server模式运行JVM,然后加载/jre/bin/client或server/jvm.dll,并开始启动JVM;

  2. 在启动JVM的同时将加载Bootstrap ClassLoader(启动类加载器,使用C/C++编写,属于JVM的一部分);

  3. 通过Bootstrap ClassLoader加载sun.misc.Launcher类(ExtClassLoader和AppClassLoader是它的内部类);

  4. sun.misc.Launcher类在执行初始化阶段时,会创建一个自己的实例,在创建过程中会创建一个ExtClassLoader(扩展类加载器)实例、一个AppClassLoader(系统类加载器)实例,并将AppClassLoader实例设置为主线程的ThreadContextClassLoader(线程上下文类加载器)。

  5. 然后AppClassLoader实例就开始加载Main.class及其所依赖的类库了

四种类加载器

注意:Bootstrap ClassLoader只会加载特定名称的类库,如rt.jar等。假如我们自己定义一个jar类库丢进<JAVA_HOME>/jre/lib目录下也不会被加载的!

    @Test
    public void testGetBootstrapClassPath(){
        URL[] urls = Launcher.getBootstrapClassPath().getURLs();
        for (URL url : urls)
            System.out.println(url.toExternalForm());
    }
    /*输出结果
     file:/C:/Java/jdk1.7.0_79/jre/lib/resources.jar
     file:/C:/Java/jdk1.7.0_79/jre/lib/rt.jar
     file:/C:/Java/jdk1.7.0_79/jre/lib/sunrsasign.jar
     file:/C:/Java/jdk1.7.0_79/jre/lib/jsse.jar
     file:/C:/Java/jdk1.7.0_79/jre/lib/jce.jar
     file:/C:/Java/jdk1.7.0_79/jre/lib/charsets.jar
     file:/C:/Java/jdk1.7.0_79/jre/lib/jfr.jar
     file:/C:/Java/jdk1.7.0_79/jre/classes*/

双亲委派模型

当一个类加载器收到类加载的请求,首先会将请求委派给父类加载器,这样一层一层委派到Bootstrap ClassLoader。然后加载器根据请求尝试搜索和加载类,若搜索失败则向子类加载器反馈信息(抛出ClassNotFoundException),然后子类加载器才尝试自己去加载。JAVA中采用组合的方式实现双亲委派模型,而不是继承的方式。

类加载机制

手动加载类

  1. 利用现有的类加载器
    // 通过当前类的类加载器加载(会执行初始化)
    Class.forName("二进制名称");
    Class.forName("二进制名称", true, this.getClass().getClassLoader());
    
    // 通过当前类的类加载器加载(不会执行初始化)
    Class.forName("二进制名称", false, this.getClass().getClassLoader());
    this.getClass().loadClass("二进制名称");
    
    // 通过系统类加载器加载(不会执行初始化)
    ClassLoader.getSystemClassLoader().loadClass("二进制名称");
    
    // 通过线程上下文类加载器加载(不会执行初始化)
    Thread.currentThread().getContextClassLoader().loadClass("二进制名称");
  1. 利用URLClassLoader
    URL[] baseUrls = {new URL("file:/d:/testLib/")};
    URLClassLoader loader = new URLClassLoader(baseUrl, ClassLoader.getContextClassLoader());
    Class clazz = loader.loadClass("com.fsjohnhuang.HelloWorld");

自定义类加载器

    public class MyClassLoader extends ClassLoader{
      private String dir;
    
      public MyClassLoader(String dir, ClassLoader parent){
        super(parent);
        this.dir = dir;
      }
      
      @Override
      protect Class<?> findClass(String binaryName) throws ClassNotFoundException{
        String pathSegmentSeperator = System.getProperty("file.separator");
        String path = binaryName.replace(".", pathSegmentSeperator ).concat(".class");
    
        FileInputStream fis = new FileInputStream(dir + pathSegmentSeperator  + path);
        byte[] b = new byte[fis.available()];
        fis.read(b, 0, b.length);
        fis.close();
        return defineClass(binaryName, b, 0, b.length);
      }
    }

加载图片、视频等非类资源

ClassLoader除了用于加载类外,还可以用于加载图片、视频等非类资源。同样是采用双亲委派模型将加载资源的请求传递到顶层的Bootstrap ClassLoader,在其管辖的目录下搜索资源,若失败才逐层返回逐层搜索。

    URL getResource(String name)
    InputStream getResourceAsStream(String name)
    Enumeration<URL> getResources(String name)