Java类加载器机制

什么是类加载器

Java 需要把源代码编译成 .class 文件,程序才能运行,那个类加载器就是把 .class 文件加载到 JVM 运行的。

ClassLoader介绍

  1. AppClassLoader
    应用类加载器,又称系统类加载器。它负责在 JVM 启动时,加载来自在命令 java 中的 - classpath 或者 java.class.path 系统属性或者 CLASSPATH 操作系统属性所指定的 JAR 类包和类路径。

调用 ClassLoader.getSystemClassLoader() 可以获取该类加载器。

默认情况下,用户自定义的 ClassLoader 的父类都是 AppClassLoader

下面看一下代码:

import com.sun.java.accessibility.AccessBridge;


public class TestClassLoader {

   public static void main(String[] args) {
       // app classloader
       System.out.println(TestClassLoader.class.getClassLoader());
       System.out.println(TestClassLoader.class.getClassLoader().getParent());
       System.out.println(ClassLoader.getSystemClassLoader());
       System.out.println(OwnClassLoader.class.getClassLoader().getParent());
       System.out.println("path ->" + System.getProperty("java.class.path"));
       System.out.println("ext ->" + System.getProperty("java.ext.dirs"));

       // ext classloader
       System.out.println(AccessBridge.class.getClassLoader());
       System.out.println(AccessBridge.class.getClassLoader().getParent());

       // bootstrap classloader
       System.out.println(String.class.getClassLoader());



   }

   class OwnClassLoader extends ClassLoader {

   }

}


运行程序可以看到结果当前类的 ClassLoader 是 AppClassLoader,
AppClassLoader 的父级 ClassLoader 是 ExtClassLoader。

  1. ExtClassLoader
    示例中的 AccessBridge 类是属于 jdk 包里的 ext 目录下的类,ExtClassLoader 默认加载 JAVA_HOME/jre/lib/ext/ 目录下的类,ExtClassLoader 的父类是 null。

  2. BootstrapClassLoader
    BootstrapClassLoader 主要加载 jdk 包里的核心类,例如 rt.jar,resource.jar 里面的类,严格来说 BootstrapClassLoader 不属于 Java 类加载器,因为这个类是由 C 语言实现的,对 Java 不可见,示例中的 String 类就是属于由 BootstrapClassLoader 加载的类,运行代码可以看到打印的是 null,因为对 Java 不可见。

三种类加载器关系

Java类加载器机制_第1张图片
image

AppClassloader 的父加载器是 ExtClassloader。

ExtClassloader 的父加载器为 null,但是要注意的是 ExtClassloader 的父加载器并不是 BootstrapClassloader。

类加载器原理

Java 的类加载使用了委托机制。每次加载类之前都先交由父加载器来加载。那 String 类作为例子,假设我们自定义了一个包名类名都和
jdk 包里的 String 类一样类,如果还是使用 AppClassLoader 来加载的话就会和 BootstrapClassLoader 加载的 String 类冲突了。所以每次先交由父类加载可以避免这种情况的发生。

首先从 JVM 缓存查找该类,如果该类之前被加载过,则直接从 JVM 缓存返回该类。

如果 JVM 缓存不存在该类,则看当前类加载器是否有父加载器,如果有的话则委托父类加载器进行加载,否者委托 BootStrapClassloader 进行加载,如果还是没有找到,则调用当前 Classloader 的 findclass 方法进行查找。

ContextClassLoader

Java 规定,类依赖的类也由同一个 ClassLoader 加载,结合双亲委派的加载模式,有些场景则要违反这一规则来达成扩展性。例如 jdk 核心包里的 SPI (Service Provider Interface 服务提供接口)机制 ServiceLoader。按双亲委派的原则 ServiceLoader 方法引用的类也需要由 BootstrapClassLoader 来加载,但事实上确并非如此。这其中,主要是通过线程 Thread 的 ContextClassLoader 来实现的。这里以 MySql 的 JDBC 驱动为例,Driver 是 JDK 提供的接口,mysql 提供的驱动对应的是服务供应商。提供者只需在 JDBC 实现的 Jar 的 META-INF/services/java.sql.Driver 文件里指定实现类的方式暴露驱动提供者。


Java类加载器机制_第2张图片
image.png

示例代码:

import com.sun.java.accessibility.AccessBridge;
import sun.misc.Launcher;

import java.sql.Driver;
import java.util.Iterator;
import java.util.ServiceLoader;

public class TestServiceLoader {

    public static void main(String[] args) {
        //Thread.currentThread().setContextClassLoader(TestServiceLoader.class.getClassLoader().getParent());
        ServiceLoader drivers = ServiceLoader.load(Driver.class);
        for (Driver driver : drivers) {
            System.out.println("driver class:" + driver.getClass().getName() +" || loader:" + driver.getClass().getClassLoader());
        }
        System.out.println(TestClassLoader.class.getClassLoader());
        System.out.println(Thread.currentThread().getContextClassLoader());
        System.out.println(ServiceLoader.class.getClassLoader());
    }
}

运行程序可以看到,ServiceLoader 的类加载器为 null 也就是 BootstrapClassLoader。Thread.currentThread().getContextClassLoader() 代指当前线程的上下文类加载器,默认的就是和当前类一样的 AppClassLoader。for 循环里面打印的也是 AppClassLoader。这里说明使用 BootstrapClassLoader 类加载器加载的 ServiceLoader 类加载了使用 AppClassLoader 类加载器的用户类。

    public static  ServiceLoader load(Class service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    }

查看源码可以发现 load 方法使用了线程上下文的 ClassLoader 来加载用户传进来的 Class 对象。

大家可以试一下把第一行的代码给注释掉,显示指定当前上下文的 ClassLoader 为 ExtClassLoader 的话,则不会加载服务实现类,因为 MySql 驱动不在 ext 目录下。

总结

总结下,默认的类加载器有3种:AppClassLoader,ExtClassLoader,BootstrapClassLoader。Java 加载类使用双亲委派机制。当父类加载器需要加载子类加载器中的资源时,可以通过设置和获取线程上下文类加载器来实现。

你可能感兴趣的:(Java类加载器机制)