Java虚拟机-类加载器和类加载过程

时间:2022-12-27 12:52:20

类加载器

java.lang.ClassLoader类及其子类可以让java代码动态地加载到JVM中。每一个类都有加载它的ClassLoader的引用。每一个类加载器类都有一个加载它的父类加载器,类加载器的顶端称为启动类加载器(Bootstrap ClassLoader),启动类加载器由c++实现。

逻辑上结构是:

Bootstrap ClassLoader
|
ExtClassLoader extends URLClassLoader extends SecureClassLoader extends ClassLoader
|
AppClassLoader extends URLClassLoader extends SecureClassLoader extends ClassLoader
|
自定义类加载器

实际上它们是通过组合方式访问父加载器。

  • Bootstrap ClassLoader : 启动类加载器。负责加载$JAVA_HOME/lib 目录下的类。
  • ExtClassLoader :扩展类加载器。负责加载$JAVA_HOME/lib/ext目录下的类。 它的parent加载器为空,表示为Bootstrap ClassLoader。
  • AppClassLoader :应用类加载器。负责加载应用类,即classpath指定目录下的类。它的parent加载器为ExtClassLoader。Launcher在启动时会调用Thread.currentThread().setContextClassLoader()方法,将当前线程的类加载器设置为AppClassLoader。

  • URLClassLoader : 从指定jar文件或目录加载类

  • SecureClassLoader :确保安全并正确地将类装入到 Java 运行时中。 主要作用:

    1. 确保以正确的顺序完成对类的搜索。当 JVM 需要一个类时,SecureClassLoader 首先查看由 JVM 的类路径所引用的文件是否可用。类路径中的文件特指完全可信的类,是 Java 运行时的一部分。例如,随 JVM 一起提供的所有代码包含在类路径中,因此被认为是可信的代码。如果在类路径中没有,可以搜索应用程序定义的位置(例如,URL 请求中的 Web 服务器)。最后,代码可以是“Java 标准扩展”(Java Standard Extension)的一部分,是在主机文件系统中可用的一组类,但不是 JVM 类路径的一部分。“标准扩展”中的类通常位于主机系统(例如,工作站或个人计算机)的磁盘驱动器上,但这些类不是 JVM 的完全可信的运行时类的一部分。

    2. 给装入到 JVM 中的类创建并设置 ProtectionDomain 信息。当 SecureClassLoader 将一个类装入到 JVM 中时,用来签发类文件的 codebase URL 和数字证书(如果存在)用来创建 CodeSource。CodeSource 用来定位(或实例化)类的 ProtectionDomain。ProtectionDomain 包含授予类的 Pemission。一旦类文件被装入到 JVM 中,SecureClassLoader 就将恰当的 ProtectionDomain 分配给类。这个 ProtectionDomain 信息以及特别是 ProtectionDomain 中的 Pemission 用来确定运行时期间的访问控制。

创建ExtClassLoader和AppClassLoader 源码:

sun.misc.Launcher#Launcher() 方法:


// Create the extension class loader
ClassLoader extcl;
try {
extcl = ExtClassLoader.getExtClassLoader();
} catch (IOException e) {
throw new InternalError(
"Could not create extension class loader");
}

// Now create the class loader to use to launch the application
try {
loader = AppClassLoader.getAppClassLoader(extcl);
} catch (IOException e) {
throw new InternalError(
"Could not create application class loader");
}

// Also set the context class loader for the primordial thread.
Thread.currentThread().setContextClassLoader(loader);

类加载过程

  1. 启动虚拟机,创建一个Bootsrap ClassLoader;
  2. Bootstrap ClassLoader 创建ExtClassLoader,父加载器为null(表示为Bootsrap ClassLoader);
  3. Bootstrap ClassLoader 创建 AppClassLoader,父加载器为ExtClassLoader;
  4. 当需要加载某个类时,通过调用AppClassLoader.loadClass()方法进行加载。这个方法的处理过程为:

    protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
    {
    synchronized (getClassLoadingLock(name)) {
    // 查看类是否已加载过
    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); //如果没有找到类,则抛出ClassNotFoundException异常

    // 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;
    }
    }

总的原则

类在加载时会逐级向上委托其父加载器进行加载,如果它们都加载不了,则由它自已加载。