Java中的类加载器

类加载是工作和面试过程中常涉及的问题,类加载器顾名思义是用来加载类的一种工具。除了常见的诸如“双亲委派原则”问题,还有其他热部署,字节码增强等高级特性也与类加载器有关。本文从JDK和JVM两个层面来梳理一下Java中的类加载器及其实现。


加载主类

在前面分析JVM的启动过程时,讲到JVM在启动过程中会加载主类并执行其中的main方法。本文以加载主类作为切入点来分析类加载器的工作原理。

c
jdk/src/share/bin/java.c
static jclass
LoadMainClass(JNIEnv *env, int mode, char *name)
{
    jmethodID mid;
    jstring str;
    jobject result;
    jlong start, end;
    // 加载sun.launcher.LauncherHelper类
    jclass cls = GetLauncherHelperClass(env);
    NULL_CHECK0(cls);
    if (JLI_IsTraceLauncher()) {
        start = CounterGet();
    }
    /*
     * 获取sun.launcher.LauncherHelper类中定义的checkAndLoadMain()方法的指针
     */
    NULL_CHECK0(mid = (*env)->GetStaticMethodID(env, cls,
                "checkAndLoadMain", // 方法名称
                "(ZILjava/lang/String;)Ljava/lang/Class;")); // 方法签名

    str = NewPlatformString(env, name);
    /*
     * 调用sun.launcher.LauncherHelper类中的checkAndLoadMain()方法
     * 参考jni.cpp中的jni_invoke_static()函数
     */
    result = (*env)->CallStaticObjectMethod(env, cls, mid, USE_STDERR, mode, str);

    if (JLI_IsTraceLauncher()) {
        end   = CounterGet();
        printf("%ld micro seconds to load main class\n",
               (long)(jint)Counter2Micros(end-start));
        printf("----%s----\n", JLDEBUG_ENV_ENTRY);
    }

    return (jclass)result;
}

上面函数主要逻辑为加载sun.launcher.LauncherHelper类,并调用了其中的checkAndLoadMain方法。这里暂时不分析是怎么加载LauncherHelper类的,只需要知道是通过启动类加载器来加载的就行了,下面会分析到启动类的类加载过程。

LauncherHelper

先来看看LauncherHelper类的checkAndLoadMain方法的逻辑:

java
jdk/src/share/classes/sun/launcher/LauncherHelper.java
// 获取系统类加载器,也就是AppClassLoader
private static final ClassLoader scloader = ClassLoader.getSystemClassLoader();
/*
 * 检查和加载主类
 */
public static Class<?> checkAndLoadMain(boolean printToStderr,
                                        int mode,
                                        String what) {
    // 初始化输出
    initOutput(printToStderr);
    // get the class name
    // 获取类名称
    String cn = null;
    // 根据启动模式按照不同形式获取主类
    switch (mode) {
        case LM_CLASS:
            cn = what;
            break;
        case LM_JAR:
            // 从jar包中获取主类
            cn = getMainClassFromJar(what);
            break;
        default:
            // should never happen
            throw new InternalError("" + mode + ": Unknown launch mode");
    }
    // 将类文件路径中的"/"替换为“.”,将其转为包名的形式
    cn = cn.replace('/', '.');
    Class<?> mainClass = null;
    try {
        /*
         * 根据主类名称加载主类,这里的scloader是指系统类加载器。
         * 一般情况下,这里的scloader也就是应用类加载器。
         */
        mainClass = scloader.loadClass(cn);
    } catch (NoClassDefFoundError | ClassNotFoundException cnfe) {
        if (System.getProperty("os.name", "").contains("OS X")
            && Normalizer.isNormalized(cn, Normalizer.Form.NFD)) {
            try {
                // On Mac OS X since all names with diacretic symbols are given as decomposed it
                // is possible that main class name comes incorrectly from the command line
                // and we have to re-compose it
                mainClass = scloader.loadClass(Normalizer.normalize(cn, Normalizer.Form.NFC));
            } catch (NoClassDefFoundError | ClassNotFoundException cnfe1) {
                abort(cnfe, "java.launcher.cls.error1", cn);
            }
        } else {
            abort(cnfe, "java.launcher.cls.error1", cn);
        }
    }
    // set to mainClass
    appClass = mainClass;

    /*
     * Check if FXHelper can launch it using the FX launcher. In an FX app,
     * the main class may or may not have a main method, so do this before
     * validating the main class.
     */
    if (mainClass.equals(FXHelper.class) ||
            FXHelper.doesExtendFXApplication(mainClass)) {
        // Will abort() if there are problems with the FX runtime
        FXHelper.setFXLaunchParameters(what, mode);
        return FXHelper.class;
    }

    // 验证主类
    validateMainClass(mainClass);
    return mainClass;
}

关键点就在于调用了scloader属性的loadClass方法来加载类,这里要搞清楚两个问题:

  • scloader是什么?从上面给出的代码中可以看出,是通过ClassLoader类的静态方法getSystemClassLoader来获取系统类加载器。
  • loadClass方法的过程是怎样的?该方法是类加载的入口方法,后面单独写文章来分析类加载的过程。

ClassLoader

getSystemClassLoader
initSystemClassLoader
<
>
java
jdk/src/share/classes/java/lang/ClassLoader.java
/*
 * 获取系统类加载器
 */
@CallerSensitive
public static ClassLoader getSystemClassLoader() {
    // 初始化系统类加载器
    initSystemClassLoader();
    if (scl == null) {
        return null;
    }
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkClassLoaderPermission(scl, Reflection.getCallerClass());
    }
    return scl;
}
java
jdk/src/share/classes/java/lang/ClassLoader.java
private static ClassLoader scl;
private static synchronized void initSystemClassLoader() {
    if (!sclSet) {
        if (scl != null)
            throw new IllegalStateException("recursive invocation");
        /*
         * 这里会触发Launcher类的加载
         */
        sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
        if (l != null) {
            Throwable oops = null;
            /*
             * 通过Launcher来获取类加载器
             * 这里返回的是Launcher类的loader属性,在Launcher的构造方法中会将该属性设置为AppClassLoader
             * ,即应用类及载器。 
             */
            scl = l.getClassLoader();
            try {
                scl = AccessController.doPrivileged(
                    new SystemClassLoaderAction(scl));
            } catch (PrivilegedActionException pae) {
                oops = pae.getCause();
                if (oops instanceof InvocationTargetException) {
                    oops = oops.getCause();
                }
            }
            if (oops != null) {
                if (oops instanceof Error) {
                    throw (Error) oops;
                } else {
                    // wrap the exception
                    throw new Error(oops);
                }
            }
        }
        sclSet = true;
    }
}

这里在获取sun.misc.Launcher类的实例,并通过该实例来获取类加载器作为系统类加载器。

Launcher

java
jdk/src/share/classes/sun/misc/Launcher.java
private static URLStreamHandlerFactory factory = new Factory();
/*
 * 创建Launcher类实例,会创建扩展类加载器和应用类加载器,并设置线程上下文类加载器。
 * 但是在什么地方加载的Launcher类?
 * 在JVM初始化的时候,会调用ClassLoader的getSystemClassLoader方法,在该方法中会先调用initSystemClassLoader方法,
 * 在该初始化方法中,会调用下面的getLauncher方法,从而加载了Launcher类。
 */
private static Launcher launcher = new Launcher();
private static String bootClassPath =
    System.getProperty("sun.boot.class.path");

public static Launcher getLauncher() {
    return launcher;
}

private ClassLoader loader;
public Launcher() {
    // Create the extension class loader
    ClassLoader extcl;
    try {
        // 创建扩展类加载器
        extcl = ExtClassLoader.getExtClassLoader();
    } catch (IOException e) {
        throw new InternalError(
            "Could not create extension class loader", e);
    }

    // Now create the class loader to use to launch the application
    try {
        /*
         * 获取应用类加载器,并传入扩展类加载器将其作为父类加载器
         * 这里将应用类加载器设置为loader属性,
         * Hotspot VM在启动的时候会调用ClassLoader的静态方法getSystemClassLoader,获取的就是该属性。
         */
        loader = AppClassLoader.getAppClassLoader(extcl);
    } catch (IOException e) {
        throw new InternalError(
            "Could not create application class loader", e);
    }

    // Also set the context class loader for the primordial thread.
    /*
     * 设置线程上下文类加载器为应用类加载器
     */
    Thread.currentThread().setContextClassLoader(loader);

}

在该方法中,可以看到先是创建了扩展类加载器,然后将其作为父类加载器创建应用类加载器,最后将应用类加载器设置为线程上下文类加载器。

ExtClassLoaderAppClassLoader都是Launcher类的内部类,都继承自URLClassLoader

创建扩展类加载器

java
jdk/src/share/classes/sun/misc/Launcher.java
public static ExtClassLoader getExtClassLoader() throws IOException
{
    // 获取加载类的加载路径
    final File[] dirs = getExtDirs();

    try {
        // Prior implementations of this doPrivileged() block supplied
        // aa synthesized ACC via a call to the private method
        // ExtClassLoader.getContext().

        return AccessController.doPrivileged(
            new PrivilegedExceptionAction<ExtClassLoader>() {
                public ExtClassLoader run() throws IOException {
                    int len = dirs.length;
                    for (int i = 0; i < len; i++) {
                        MetaIndex.registerDirectory(dirs[i]);
                    }
                    // 实例化扩展类加载器
                    return new ExtClassLoader(dirs);
                }
            });
    } catch (java.security.PrivilegedActionException e) {
        throw (IOException) e.getException();
    }
}

这里先是获取类扩展类加载器应该加载的类路径。

java
jdk/src/share/classes/sun/misc/Launcher.java
private static File[] getExtDirs() {
    // 获取系统属性
    String s = System.getProperty("java.ext.dirs");
    File[] dirs;
    if (s != null) {
        StringTokenizer st =
            new StringTokenizer(s, File.pathSeparator);
        int count = st.countTokens();
        dirs = new File[count];
        for (int i = 0; i < count; i++) {
            dirs[i] = new File(st.nextToken());
        }
    } else {
        dirs = new File[0];
    }
    return dirs;
}

这里就不分析是怎么解析路径的了,只知道主类获取java.ext.dirs属性作为扩展类加载器应该加载的类路径。可以在Java代码中代码获取一下该系统属性,在我的电脑上结果如下:

txt
/home/harrisonlee/Documents/projects/openjdk/openjdk8/build/linux-x86_64-normal-server-slowdebug/jdk/lib/ext:/usr/java/packages/lib/ext

可以发现默认有两个:

  • $JAVA_HOME/lib/ext目录;
  • /usr/java/packages/lib/ext目录; 扩展类加载器默认情况下会尝试从这两个目录下加载类。

创建应用类加载器

java
jdk/src/share/classes/sun/misc/Launcher.java
public static ClassLoader getAppClassLoader(final ClassLoader extcl)
    throws IOException
{
    // 获取系统属性
    final String s = System.getProperty("java.class.path");
    // 创建路径数组
    final File[] path = (s == null) ? new File[0] : getClassPath(s);

    // Note: on bugid 4256530
    // Prior implementations of this doPrivileged() block supplied
    // a rather restrictive ACC via a call to the private method
    // AppClassLoader.getContext(). This proved overly restrictive
    // when loading  classes. Specifically it prevent
    // accessClassInPackage.sun.* grants from being honored.
    //
    return AccessController.doPrivileged(
        new PrivilegedAction<AppClassLoader>() {
            public AppClassLoader run() {
            URL[] urls =
                (s == null) ? new URL[0] : pathToURLs(path);
            // 实例化应用类加载器
            return new AppClassLoader(urls, extcl);
        }
    });
}

这里以java.class.path系统属性作为应用类加载器的类加载路径。

设置线程上下文类加载器

所谓的线程上下文类加载器并不是和上面扩展类加载器和应用类加载器一样的具体形式的类加载器,而只是保存在线程中的类加载器,用于在同一个线程中共享同一个类加载器,方便在线程不同执行时机需要使用同一类加载来加载类的场景。

我以前看过一本讲Java的书,其中就讲到线程上下文类加载器,但始终不理解其是什么意思,到底是一种具体的类加载器还是什么?很多其他资料也是泛泛而谈,其实翻开源码,很容易就能解决该疑惑点。所以要敢于看源码,很多时候源码也并没有想象中的那么复杂,而且源码才是最详细的资料。 另外,系统类加载器也不是一种具体的类加载器,而就是上面提到的应用类加载器。

java
jdk/src/share/classes/java/lang/Thread.java
/* The context ClassLoader for this thread */
private ClassLoader contextClassLoader;
public void setContextClassLoader(ClassLoader cl) {
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        sm.checkPermission(new RuntimePermission("setContextClassLoader"));
    }
    contextClassLoader = cl;
}
@CallerSensitive
public ClassLoader getContextClassLoader() {
    if (contextClassLoader == null)
        return null;
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        ClassLoader.checkClassLoaderPermission(contextClassLoader,
                                               Reflection.getCallerClass());
    }
    return contextClassLoader;
}

在线程运行过程中,可以很方便地使用Thread类中的contextClassLoader属性来在线程不同执行点共享类加载器。

ClassLoader

值得一提的是,从Java类继承体系而言,扩展类加载器和应用类加载器并无父子类关系,都继承自URLClassLoader。而所谓双亲委派模型则是通过组合来实现的,即在ClassLoader抽象类中定义了一个叫做parent的属性来保存父级类加载器。

JDK中定义了几个与类加载器相关的类,其继承关系如下:

其中URLClassLoader抽象了从哪里加载类,其不仅会从磁盘加载,还可以从网络中加载类,只不过后者应用范围较窄,我个人暂时没遇到过这种场景,所以不过多介绍。

启动类加载器

很多资料都会说启动类加载器是由C++实现的,但没说是怎么实现的,这里就来分析一下具体的实现过程。在Hotspot VM的实现中,专门定义了一个叫做ClassLoader的类来表示启动类加载器。该类是静态的,其中的所有属性和函数都是静态的,这类似一种单例实现方式。

c
hotspot/src/share/vm/classfile/classLoader.hpp
/*
 * 引导类加载器由该类来实现,该类中的所有字段和函数都是static的,说明应该并不会创建创建该类的实例,而是一种单例的实现形式。
 */
class ClassLoader: AllStatic {
 public:
  enum SomeConstants {
    // 包hash表的长度,也就是31个哈希桶
    package_hash_table_size = 31  // Number of buckets
  };
 private:
  friend class LazyClassPathEntry;

  // Performance counters
  // 一些性能计数器字段
  static PerfCounter* _perf_accumulated_time;
  static PerfCounter* _perf_classes_inited;
  static PerfCounter* _perf_class_init_time;
  static PerfCounter* _perf_class_init_selftime;
  static PerfCounter* _perf_classes_verified;
  static PerfCounter* _perf_class_verify_time;
  static PerfCounter* _perf_class_verify_selftime;
  static PerfCounter* _perf_classes_linked;
  static PerfCounter* _perf_class_link_time;
  static PerfCounter* _perf_class_link_selftime;
  static PerfCounter* _perf_class_parse_time;
  static PerfCounter* _perf_class_parse_selftime;
  static PerfCounter* _perf_sys_class_lookup_time;
  static PerfCounter* _perf_shared_classload_time;
  static PerfCounter* _perf_sys_classload_time;
  static PerfCounter* _perf_app_classload_time;
  static PerfCounter* _perf_app_classload_selftime;
  static PerfCounter* _perf_app_classload_count;
  static PerfCounter* _perf_define_appclasses;
  static PerfCounter* _perf_define_appclass_time;
  static PerfCounter* _perf_define_appclass_selftime;
  static PerfCounter* _perf_app_classfile_bytes_read;
  static PerfCounter* _perf_sys_classfile_bytes_read;

  static PerfCounter* _sync_systemLoaderLockContentionRate;
  static PerfCounter* _sync_nonSystemLoaderLockContentionRate;
  static PerfCounter* _sync_JVMFindLoadedClassLockFreeCounter;
  static PerfCounter* _sync_JVMDefineClassLockFreeCounter;
  static PerfCounter* _sync_JNIDefineClassLockFreeCounter;

  static PerfCounter* _unsafe_defineClassCallCounter;
  static PerfCounter* _isUnsyncloadClass;
  static PerfCounter* _load_instance_class_failCounter;

  /*
   * 多个ClassPathEntry组成一个单向链表结构
   */

  // First entry in linked list of ClassPathEntry instances
  // 链表头结点
  static ClassPathEntry* _first_entry;
  // Last entry in linked list of ClassPathEntry instances
  // 链表尾节点
  static ClassPathEntry* _last_entry;
  // Hash table used to keep track of loaded packages
  static PackageHashtable* _package_hash_table;
  static const char* _shared_archive;

  // Hash function
  static unsigned int hash(const char *s, int n);
  // Returns the package file name corresponding to the specified package
  // or class name, or null if not found.
  static PackageInfo* lookup_package(const char *pkgname);
  // Adds a new package entry for the specified class or package name and
  // corresponding directory or jar file name.
  static bool add_package(const char *pkgname, int classpath_index, TRAPS);

  // Initialization
  static void setup_meta_index();
  static void setup_bootstrap_search_path();
  static void load_zip_library();
  static ClassPathEntry* create_class_path_entry(char *path, const struct stat* st,
                                                 bool lazy, TRAPS);

  // Canonicalizes path names, so strcmp will work properly. This is mainly
  // to avoid confusing the zip library
  static bool get_canonical_path(char* orig, char* out, int len);
 public:
  // Used by the kernel jvm.
  static void update_class_path_entry_list(char *path,
                                           bool check_for_duplicates);
  static void print_bootclasspath();

  // Timing
  static PerfCounter* perf_accumulated_time()         { return _perf_accumulated_time; }
  static PerfCounter* perf_classes_inited()           { return _perf_classes_inited; }
  static PerfCounter* perf_class_init_time()          { return _perf_class_init_time; }
  static PerfCounter* perf_class_init_selftime()      { return _perf_class_init_selftime; }
  static PerfCounter* perf_classes_verified()         { return _perf_classes_verified; }
  static PerfCounter* perf_class_verify_time()        { return _perf_class_verify_time; }
  static PerfCounter* perf_class_verify_selftime()    { return _perf_class_verify_selftime; }
  static PerfCounter* perf_classes_linked()           { return _perf_classes_linked; }
  static PerfCounter* perf_class_link_time()          { return _perf_class_link_time; }
  static PerfCounter* perf_class_link_selftime()      { return _perf_class_link_selftime; }
  static PerfCounter* perf_class_parse_time()         { return _perf_class_parse_time; }
  static PerfCounter* perf_class_parse_selftime()     { return _perf_class_parse_selftime; }
  static PerfCounter* perf_sys_class_lookup_time()    { return _perf_sys_class_lookup_time; }
  static PerfCounter* perf_shared_classload_time()    { return _perf_shared_classload_time; }
  static PerfCounter* perf_sys_classload_time()       { return _perf_sys_classload_time; }
  static PerfCounter* perf_app_classload_time()       { return _perf_app_classload_time; }
  static PerfCounter* perf_app_classload_selftime()   { return _perf_app_classload_selftime; }
  static PerfCounter* perf_app_classload_count()      { return _perf_app_classload_count; }
  static PerfCounter* perf_define_appclasses()        { return _perf_define_appclasses; }
  static PerfCounter* perf_define_appclass_time()     { return _perf_define_appclass_time; }
  static PerfCounter* perf_define_appclass_selftime() { return _perf_define_appclass_selftime; }
  static PerfCounter* perf_app_classfile_bytes_read() { return _perf_app_classfile_bytes_read; }
  static PerfCounter* perf_sys_classfile_bytes_read() { return _perf_sys_classfile_bytes_read; }

  // Record how often system loader lock object is contended
  static PerfCounter* sync_systemLoaderLockContentionRate() {
    return _sync_systemLoaderLockContentionRate;
  }

  // Record how often non system loader lock object is contended
  static PerfCounter* sync_nonSystemLoaderLockContentionRate() {
    return _sync_nonSystemLoaderLockContentionRate;
  }

  // Record how many calls to JVM_FindLoadedClass w/o holding a lock
  static PerfCounter* sync_JVMFindLoadedClassLockFreeCounter() {
    return _sync_JVMFindLoadedClassLockFreeCounter;
  }

  // Record how many calls to JVM_DefineClass w/o holding a lock
  static PerfCounter* sync_JVMDefineClassLockFreeCounter() {
    return _sync_JVMDefineClassLockFreeCounter;
  }

  // Record how many calls to jni_DefineClass w/o holding a lock
  static PerfCounter* sync_JNIDefineClassLockFreeCounter() {
    return _sync_JNIDefineClassLockFreeCounter;
  }

  // Record how many calls to Unsafe_DefineClass
  static PerfCounter* unsafe_defineClassCallCounter() {
    return _unsafe_defineClassCallCounter;
  }

  // Record how many times SystemDictionary::load_instance_class call
  // fails with linkageError when Unsyncloadclass flag is set.
  static PerfCounter* load_instance_class_failCounter() {
    return _load_instance_class_failCounter;
  }

  // Load individual .class file
  // 加载类文件
  static instanceKlassHandle load_classfile(Symbol* h_name, TRAPS);

  // If the specified package has been loaded by the system, then returns
  // the name of the directory or ZIP file that the package was loaded from.
  // Returns null if the package was not loaded.
  // Note: The specified name can either be the name of a class or package.
  // If a package name is specified, then it must be "/"-separator and also
  // end with a trailing "/".
  static oop get_system_package(const char* name, TRAPS);

  // Returns an array of Java strings representing all of the currently
  // loaded system packages.
  // Note: The package names returned are "/"-separated and end with a
  // trailing "/".
  static objArrayOop get_system_packages(TRAPS);

  // Initialization
  static void initialize();
  static void create_package_info_table();
  static void create_package_info_table(HashtableBucket<mtClass> *t, int length,
                                        int number_of_entries);
  static int compute_Object_vtable();

  static ClassPathEntry* classpath_entry(int n) {
    ClassPathEntry* e = ClassLoader::_first_entry;
    while (--n >= 0) {
      assert(e != NULL, "Not that many classpath entries.");
      e = e->next();
    }
    return e;
  }

  // Sharing dump and restore
  static void copy_package_info_buckets(char** top, char* end);
  static void copy_package_info_table(char** top, char* end);

  // VM monitoring and management support
  static jlong classloader_time_ms();
  static jlong class_method_total_size();
  static jlong class_init_count();
  static jlong class_init_time_ms();
  static jlong class_verify_time_ms();
  static jlong class_link_count();
  static jlong class_link_time_ms();

  // indicates if class path already contains a entry (exact match by name)
  static bool contains_entry(ClassPathEntry* entry);

  // adds a class path list
  static void add_to_list(ClassPathEntry* new_entry);

  // creates a class path zip entry (returns NULL if JAR file cannot be opened)
  static ClassPathZipEntry* create_class_path_zip_entry(const char *apath);

  // Debugging
  static void verify()              PRODUCT_RETURN;

  // Force compilation of all methods in all classes in bootstrap class path (stress test)
#ifndef PRODUCT
 private:
  static int _compile_the_world_class_counter;
  static int _compile_the_world_method_counter;
 public:
  static void compile_the_world();
  static void compile_the_world_in(char* name, Handle loader, TRAPS);
  static int  compile_the_world_counter() { return _compile_the_world_class_counter; }
#endif //PRODUCT
};

ClassPathEntry

ClassLoader使用ClassPathEntry来表示一个类加载时的搜索路径,因为可以存在多个,所以在ClassLoader中以链表来管理这些路径对象。

c
hotspot/src/share/vm/classfile/classLoader.hpp
class ClassPathEntry: public CHeapObj<mtClass> {
 private:
  // 指向单向链表中的下一个节点
  ClassPathEntry* _next;
 public:
  // Next entry in class path
  ClassPathEntry* next()              { return _next; }
  void set_next(ClassPathEntry* next) {
    // may have unlocked readers, so write atomically.
    OrderAccess::release_store_ptr(&_next, next);
  }
  virtual bool is_jar_file() = 0;
  virtual const char* name() = 0;
  virtual bool is_lazy();
  // Constructor
  ClassPathEntry();
  // Attempt to locate file_name through this class path entry.
  // Returns a class file parsing stream if successfull.
  // 获取或创建表示类文件的流
  virtual ClassFileStream* open_stream(const char* name, TRAPS) = 0;
  // Debugging
  NOT_PRODUCT(virtual void compile_the_world(Handle loader, TRAPS) = 0;)
  NOT_PRODUCT(virtual bool is_rt_jar() = 0;)
};

在启动类加载器加载类时,会遍历ClassPathEntry链表中的各个对象,并调用其open_stream函数,如果返回结果不为NULL,则说明加载到了类。 ClassPathEntry有几个子类型,ClassPathDirEntry是指从文件目录中加载,ClassPathZipEntry是从zip文件中加载,另外还有负责懒加载的LazyClassPathEntry。其中ClassPathZipEntry用于表示jar包。

ClassPathDirEntry
ClassPathZipEntry
LazyClassPathEntry
<
>
c
hotspot/src/share/vm/classfile/classLoader.hpp
class ClassPathDirEntry: public ClassPathEntry {
 private:
  char* _dir;           // Name of directory
 public:
  bool is_jar_file()  { return false;  }
  const char* name()  { return _dir; }
  ClassPathDirEntry(char* dir);
  ClassFileStream* open_stream(const char* name, TRAPS);
  // Debugging
  NOT_PRODUCT(void compile_the_world(Handle loader, TRAPS);)
  NOT_PRODUCT(bool is_rt_jar();)
};
c
hotspot/src/share/vm/classfile/classLoader.hpp
class ClassPathZipEntry: public ClassPathEntry {
 private:
  jzfile* _zip;        // The zip archive
  char*   _zip_name;   // Name of zip archive
 public:
  bool is_jar_file()  { return true;  }
  const char* name()  { return _zip_name; }
  ClassPathZipEntry(jzfile* zip, const char* zip_name);
  ~ClassPathZipEntry();
  ClassFileStream* open_stream(const char* name, TRAPS);
  void contents_do(void f(const char* name, void* context), void* context);
  // Debugging
  NOT_PRODUCT(void compile_the_world(Handle loader, TRAPS);)
  NOT_PRODUCT(void compile_the_world12(Handle loader, TRAPS);) // JDK 1.2 version
  NOT_PRODUCT(void compile_the_world13(Handle loader, TRAPS);) // JDK 1.3 version
  NOT_PRODUCT(bool is_rt_jar();)
  NOT_PRODUCT(bool is_rt_jar12();)
  NOT_PRODUCT(bool is_rt_jar13();)
};
c
hotspot/src/share/vm/classfile/classLoader.hpp
class LazyClassPathEntry: public ClassPathEntry {
 private:
  char* _path; // dir or file
  struct stat _st;
  MetaIndex* _meta_index;
  bool _has_error;
  volatile ClassPathEntry* _resolved_entry;
  ClassPathEntry* resolve_entry(TRAPS);
 public:
  bool is_jar_file();
  const char* name()  { return _path; }
  LazyClassPathEntry(char* path, const struct stat* st);
  ClassFileStream* open_stream(const char* name, TRAPS);
  void set_meta_index(MetaIndex* meta_index) { _meta_index = meta_index; }
  virtual bool is_lazy();
  // Debugging
  NOT_PRODUCT(void compile_the_world(Handle loader, TRAPS);)
  NOT_PRODUCT(bool is_rt_jar();)
};

现在的问题来了,在JVM启动后,启动类加载器的ClassPathEntry链表中有那些节点呢?这就要回到JVM的初始化过程了,在这个初始化过程中,也会调用ClassLoaderinitialize函数。

cpp
hotspot/src/share/vm/classfile/classLoader.cpp
void ClassLoader::initialize() {
  assert(_package_hash_table == NULL, "should have been initialized by now.");
  EXCEPTION_MARK;


  // lookup zip library entry points
  load_zip_library();
  // initialize search path
  // 设置启动类加载器的类搜索路径
  setup_bootstrap_search_path();
  if (LazyBootClassLoader) {
    // set up meta index which makes boot classpath initialization lazier
    setup_meta_index();
  }
}

其中关键点在于调用setup_bootstrap_search_path函数。

cpp
hotspot/src/share/vm/classfile/classLoader.cpp
void ClassLoader::setup_bootstrap_search_path() {
  assert(_first_entry == NULL, "should not setup bootstrap class search path twice");
  // 获取路径信息
  char* sys_class_path = os::strdup(Arguments::get_sysclasspath());
  if (TraceClassLoading && Verbose) {
    tty->print_cr("[Bootstrap loader class path=%s]", sys_class_path);
  }

  int len = (int)strlen(sys_class_path);
  int end = 0;

  // Iterate over class path entries
  // 遍历类搜索路径
  for (int start = 0; start < len; start = end) {
    // 按照分隔符来分割,这里是按照冒号(":")来分割的
    while (sys_class_path[end] && sys_class_path[end] != os::path_separator()[0]) {
      end++;
    }
    // 创建新的字符数组来保存解析到的类搜索路径
    char* path = NEW_C_HEAP_ARRAY(char, end-start+1, mtClass);
    strncpy(path, &sys_class_path[start], end-start);
    path[end-start] = '\0';
    // 更新ClassLoader的ClassPathEntry链表,这里不会检查重复,所以是直接添加
    update_class_path_entry_list(path, false);
    // 释放临时数组
    FREE_C_HEAP_ARRAY(char, path, mtClass);
    while (sys_class_path[end] == os::path_separator()[0]) {
      end++;
    }
  }
}
void ClassLoader::update_class_path_entry_list(char *path,
                                               bool check_for_duplicates) {
  struct stat st;
  if (os::stat(path, &st) == 0) {
    // File or directory found
    ClassPathEntry* new_entry = NULL;
    Thread* THREAD = Thread::current();
    // 创建新的ClassPathEntry对象
    new_entry = create_class_path_entry(path, &st, LazyBootClassLoader, CHECK);
    // The kernel VM adds dynamically to the end of the classloader path and
    // doesn't reorder the bootclasspath which would break java.lang.Package
    // (see PackageInfo).
    // Add new entry to linked list
    if (!check_for_duplicates || !contains_entry(new_entry)) {
      // 添加到链表中
      add_to_list(new_entry);
    }
  }
}

setup_bootstrap_search_path函数中,先是调用Arguments::get_sysclasspath()函数来获取路径信息,然后对路径进行分割处理,并依次对每个路径调用update_class_path_entry_list函数来创建并添加ClassPathEntry对象。

至于Arguments::get_sysclasspath()函数返回的内容是怎么来的,这比较复杂,现在只需要知道是从环境变量和命令参数中获取来的就可以了。

update_class_path_entry_list函数中,先是调用create_class_path_entry函数来创建ClassPathEntry对象,然后调用add_to_list函数来添加到链表中。

create_class_path_entry
add_to_list
<
>
cpp
hotspot/src/share/vm/classfile/classLoader.cpp
ClassPathEntry* ClassLoader::create_class_path_entry(char *path, const struct stat* st, bool lazy, TRAPS) {
  JavaThread* thread = JavaThread::current();
  // 如果要懒加载,
  if (lazy) {
    // 则创建LazyClassPathEntry
    return new LazyClassPathEntry(path, st);
  }
  ClassPathEntry* new_entry = NULL;
  if ((st->st_mode & S_IFREG) == S_IFREG) { // 如果是普通文件
    // Regular file, should be a zip file
    // Canonicalized filename
    char canonical_path[JVM_MAXPATHLEN];
    // 处理文件路径
    if (!get_canonical_path(path, canonical_path, JVM_MAXPATHLEN)) {
      // This matches the classic VM
      THROW_MSG_(vmSymbols::java_io_IOException(), "Bad pathname", NULL);
    }
    char* error_msg = NULL;
    jzfile* zip;
    {
      // enable call to C land
      ThreadToNativeFromVM ttn(thread);
      HandleMark hm(thread);
      // 打开zip格式文件
      zip = (*ZipOpen)(canonical_path, &error_msg);
    }
    if (zip != NULL && error_msg == NULL) { // 如果是zip格式文件,比如jar
      //
      new_entry = new ClassPathZipEntry(zip, path);
      if (TraceClassLoading) {
        tty->print_cr("[Opened %s]", path);
      }
    } else { // 如果是其他文件,不是jar文件则报错(哪怕名字是jar,但格式不是jar也会报错)
      ResourceMark rm(thread);
      char *msg;
      if (error_msg == NULL) { // 不存在报错信息
        msg = NEW_RESOURCE_ARRAY(char, strlen(path) + 128); ;
        // 打印报错信息
        jio_snprintf(msg, strlen(path) + 127, "error in opening JAR file %s", path);
      } else { // 已存在报错信息
        // 截取报错信息,不要太长了
        int len = (int)(strlen(path) + strlen(error_msg) + 128);
        msg = NEW_RESOURCE_ARRAY(char, len); ;
        // 打印报错信息
        jio_snprintf(msg, len - 1, "error in opening JAR file <%s> %s", error_msg, path);
      }
      // 抛出类加载错误
      THROW_MSG_(vmSymbols::java_lang_ClassNotFoundException(), msg, NULL);
    }
  } else { // 如果是目录
    // Directory
    new_entry = new ClassPathDirEntry(path);
    if (TraceClassLoading) {
      tty->print_cr("[Path %s]", path);
    }
  }
  return new_entry;
}
cpp
hotspot/src/share/vm/classfile/classLoader.cpp
void ClassLoader::add_to_list(ClassPathEntry *new_entry) {
  if (new_entry != NULL) {
    if (_last_entry == NULL) {
      _first_entry = _last_entry = new_entry;
    } else {
      _last_entry->set_next(new_entry);
      _last_entry = new_entry;
    }
  }
}

create_class_path_entry函数中,根据路径情况和是否懒加载来创建上面提到过的三种不同类型的ClassPathEntry对象。在add_to_list函数中,就是基本的链表添加节点的操作,比较简单。

总结

本文分析了Java中类加载器的源码实现,可能会觉得整体内容比较割裂,不是很连贯。别急,后面紧接着就会分析类加载过程(请参考Java中的类加载过程),从而把本文将到的各个类加载器过程串联起来。