第3.1篇-类加载器
类加载器可以装载类,这些类被HotSpot VM装载后,都以InstanceKlass实例表示(其实还可能是更具体的InstanceRefKlass、InstanceMirrorKlass或InstanceClassLoaderKlass实例)。涉及到主要的类加载器有启动类加载器/引导类加载器(Bootstrap ClassLoader)、扩展类加载器(Extension ClassLoader)和应用类加载器(Application ClassLoade)。
1、引导类加载器/启动类加载器
引导类加载器由ClassLoader类实现,这个ClassLoader类是用C++语言编写的,负责将<JAVA_HOME>/lib目录、-Xbootclasspath选项指定的目录或系统属性sun.boot.class.path指定的目录下的核心类库加载到内存中。
用C++语言定义的类加载器及重要的函数如下:
源代码位置:openjdk/hotspot/src/share/vm/classfile/classLoader.hpp class ClassLoader::AllStatic { private: // ClassPathEntry类指针,ClassPathEntry用于表示单个classpath路径, // 所有的ClassPathEntry实例以链表的形式关联起来,_first_entry表示链表的第一个实例 static ClassPathEntry* _first_entry; // 表示链表的最后一个实例 static ClassPathEntry* _last_entry; // 用于保存已经加载过的包名 static PackageHashtable* _package_hash_table; // ... // 加载类 static instanceKlassHandle load_classfile(Symbol* h_name,TRAPS); // 设置加载路径 static void setup_bootstrap_search_path(); public: // 初始化类加载器 static void initialize(); // ... }
通过_first_entry链表保存这个类加载器可以加载的一些类路径。在HotSpot VM启动时会通过调用ClassLoader::setup_bootstrap_search_path()函数来设置。
load_classfile()函数可以根据类名加载类,具体实现如下:
源代码位置:openjdk/hotspot/src/share/vm/classfile/classLoader.cpp instanceKlassHandle ClassLoader::load_classfile(Symbol* h_name, TRAPS) { // 获取类名 const char* class_name = h_name->as_C_string(); .... stringStream st; st.print_raw(h_name->as_utf8()); st.print_raw(".class"); // 获取文件名 const char* file_name = st.as_string(); ClassLoaderExt::Context context(class_name, file_name, THREAD); // ClassFileStream表示Class文件的字节流 ClassFileStream* stream = NULL; int classpath_index = 0; ClassPathEntry* e = NULL; instanceKlassHandle h; { //从第一个ClassPathEntry开始遍历所有的ClassPathEntry e = _first_entry; while (e != NULL) { stream = e->open_stream(file_name, CHECK_NULL); // 如果检查返回false则返回null,check()函数默认返回true if (!context.check(stream, classpath_index)) { return h; // NULL } // 如果找到目标文件则跳出循环 if (stream != NULL) { break; } e = e->next(); ++classpath_index; } } //如果找到了目标Class文件 if (stream != NULL) { // 构建一个ClassFileParser实例 ClassFileParser parser(stream); // 构建一个ClassLoaderData实例 ClassLoaderData* loader_data = ClassLoaderData::the_null_class_loader_data(); Handle protection_domain; TempNewSymbol parsed_name = NULL; // 解析并加载class文件,注意此时并未开始链接 instanceKlassHandle result = parser.parseClassFile(h_name,loader_data,protection_domain,parsed_name,false,CHECK_(h)); ... // 调用ClassLoader的add_package函数,把当前类的包名加入到_package_hash_table中 if (add_package(name, classpath_index, THREAD)) { h = result; } } return h; }
每个类加载器都对应着一个ClassLoaderData实例,通过ClassLoaderData::the_null_class_loader_data()函数获取引导类加载器对应的ClassLoaderData实例。
因为ClassPath有多个,所以ClassPathEntry通过单链表结构连接起来,同时在ClassPathEntry类中还声明了一个虚函数open_stream()。这样就可以通过循环遍历链表上的结构,直到查找到某个类路径下名称为name的Class文件为止,这时候open_stream()函数会返回定义此类的Class文件的ClassFileStream实例。
parseClassFile()函数就是解析Class文件中的类、字段、常量池等信息,然后转换为C++内部的对等表示,如类元信息存储在InstanceKlass实例中,常量池信息存储在ConstantPool实例中。下一章开始会详细介绍parseClassFile()函数解析Class文件的过程。
调用add_package()函数保存已经解析完成的类,避免重复加载解析。
2、扩展类加载器
扩展类加载器由sun.misc.Launcher$ExtClassLoader类实现,负责将<JAVA_HOME >/lib/ext目录或者由系统变量-Djava.ext.dir所指定的目录中的类库加载到内存中。
用Java语言编写的扩展类加载器的实现如下:
源代码位置:openjdk/jdk/src/share/classes/sun/misc/Launcher.java static class ExtClassLoader extends URLClassLoader { // 构造函数 public ExtClassLoader(File[] dirs) throws IOException { super(getExtURLs(dirs), null, factory); // 为parent字段传递的参数为null } public static ExtClassLoader getExtClassLoader() throws IOException { final File[] dirs = getExtDirs(); // 获取要加载类的加载路径 ... return new ExtClassLoader(dirs); // 实例化扩展类加载器 ... } 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; } ... }
在ExtClassLoader类的构造函数中调用父类的构造函数时,传递的第2个参数的值为null,这个值最终会赋值给parent字段,当这个字段的值为null时,java.lang.ClassLoader类中实现的loadClass()方法会调用findBootstrapClassOrNull()方法加载类,最终会调用C++实现的ClassLoader类中的相关函数加载类。
3、系统类加载器/应用类加载器
应用类加载器由sun.misc.Launcher$AppClassLoader类实现,负责将由系统环境变量-classpath、-cp或系统属性java.class.path指定的路径下的类库加载到内存中。
用Java语言编写的扩展类加载器的实现如下:
源代码位置:openjdk/jdk/src/share/classes/sun/misc/Launcher.java static class AppClassLoader extends URLClassLoader { // 构造函数 AppClassLoader(URL[] urls, ClassLoader parent) { super(urls, parent, factory); // parent通常是ExtClassLoader对象 } 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); ... return new AppClassLoader(urls, extcl); } public Class loadClass(String name, boolean resolve) throws ClassNotFoundException { ... return (super.loadClass(name, resolve)); } ... }
在Launcher类的构造方法中实例化应用类加载器AppClassLoader时,会调用getAppClassLoader()方法获取应用类加载器,传入的参数是一个扩展类加载器ExtClassLoader对象,这样应用类加载器的父加载器就变成了扩展类加载器(与父加载器并非继承关系)。用户自定义的无参类加载器的父类加载器默认是AppClassloader类加载器。
4、构造类加载器实例
HotSpot VM在启动过程中会在<JAVA_HOME>/lib/rt.jar包里面的sun.misc.Launcher类中完成扩展类加载器和应用类加载器的实例化,也会调用C++语言编写的ClassLoader类的initialize()函数完成应用类加载器的初始化。
HotSpot VM在启动时会初始化一个重要的变量,定义如下:
源代码位置:openjdk/hotspot/src/share/vm/classfile/systemDictionary.cpp oop SystemDictionary::_java_system_loader = NULL;
这个属性保存应用类加载器实例,HotSpot VM在加载主类时会使用这个类加载器加载主类。属性在compute_java_system_loader()函数中初始化,调用链路如下:
JavaMain() java.c InitializeJVM() java.c JNI_CreateJavaVM() jni.cpp Threads::create_vm() thread.cpp SystemDictionary::compute_java_system_loader() systemDictionary.cpp
compute_java_system_loader()函数的实现如下:
源代码位置:openjdk/hotspot/src/share/vm/classfile/systemDictionary.cpp void SystemDictionary::compute_java_system_loader(TRAPS) { KlassHandle system_klass(THREAD, WK_KLASS(ClassLoader_klass)); JavaValue result(T_OBJECT); // 调用java.lang.ClassLoader类的getSystemClassLoader()方法 JavaCalls::call_static( &result, // 调用Java静态方法的返回值存储在result中 KlassHandle(THREAD, WK_KLASS(ClassLoader_klass)), // 调用的目标类为java.lang.ClassLoader vmSymbols::getSystemClassLoader_name(), // 调用目标类中的目标方法为getSystemClassLoader() vmSymbols::void_classloader_signature(), // 调用目标方法的方法签名 CHECK ); // 获取调用getSystemClassLoader()方法的返回值并保存到_java_system_loader属性中 // 初始化属性为应用类加载器/AppClassLoader _java_system_loader = (oop)result.get_jobject(); }
通过JavaClass::call_static()函数调用java.lang.ClassLoader类的getSystemClassLoader()方法。JavaClass::call_static()函数非常重要,它是HotSpot VM调用Java静态方法的API。
下面看一下getSystemClassLoader()方法的实现,如下:
源代码位置:openjdk/jdk/src/share/classes/java/lang/ClassLoader.java private static ClassLoader scl; public static ClassLoader getSystemClassLoader() { initSystemClassLoader(); if (scl == null) { return null; } return scl; } private static synchronized void initSystemClassLoader() { if (!sclSet) { sun.misc.Launcher l = sun.misc.Launcher.getLauncher(); // 获取Launcher对象 if (l != null) { scl = l.getClassLoader(); // 获取应用类加载器AppClassLoader对象 ... } sclSet = true; } }
如上方法及变量定义在java.lang.ClassLoader类中。
在initSystemClassLoader()方法中调用Launcher.getLauncher()方法获取Launcher对象,这个对象已保存在launcher这个静态变量中,这个变量的定义如下:
源代码位置:openjdk/jdk/src/share/classes/sum/misc/Launcher.java private static Launcher launcher = new Launcher();
在定义静态变量时就会初始化Launcher对象,调用的Launcher构造函数如下:
源代码位置:openjdk/jdk/src/share/classes/sun/misc/Launcher.java private ClassLoader loader; public Launcher() { // 首先创建了扩展类加载器 ClassLoader extcl; try { extcl = ExtClassLoader.getExtClassLoader(); } catch (IOException e) { throw new InternalError("Could not create extension class loader", e); } // 以ExtClassloader为父加载器创建了AppClassLoader try { loader = AppClassLoader.getAppClassLoader(extcl); } catch (IOException e) { throw new InternalError("Could not create application class loader", e); } // 设置默认线程上下文加载器为AppClassloader Thread.currentThread().setContextClassLoader(loader); } public ClassLoader getClassLoader() { return loader; }
如上方法及变量都定义在sum.misc.Lanucher类中。
在Launcher类的构造方法中创建ExtClassLoader与AppClassLoader对象,而loader变量被初始化为AppClassLoader对象,最终在initSystemClassLoader()函数中调用getClassLoader()方法返回的就是这个对象。HotSpot VM最终可以通过_java_system_loader属性获取AppClassLoader对象,通过AppClassLoader对象中的parent属性获取ExtClassLoader对象。
参考文章:
(1)类加载器的实现
(2)类的预加载
(3)Java类的加载