类加载器可以加载类,这些类被HotSpot加载后,都以Klass对象表示。涉及到的主要的类加载器有启动类加载器/引导类加载器(Bootstrap ClassLoader)、扩展类加载器(Extension ClassLoader)和应用类加载器/系统类加载器(Application ClassLoader)。
1、引导类加载器/启动类加载器
引导类加载器由ClassLoader类实现,这个ClassLoader类是用C++语言实现的,负责将 <JAVA_HOME>/lib目录、 -Xbootclasspath选项指定的目录或系统属性sun.boot.class.path指定的目录下的核心类库加载到内存中。
用C++语言定义的类加载器及重要的函数如下:
源代码位置:hotspot/src/share/vm/classfile/classLoader.hpp
class ClassLoader::AllStatic {
private:
// First entry in linked list of ClassPathEntry instances
// ClassPathEntry类指针,ClassPathEntry用于表示单个classpath路径,
// 所有的ClassPathEntry实例以链表的形式关联起来,_first_entry表示链表的第一个实例
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 instanceKlassHandle load_classfile(Symbol* h_name,TRAPS);
// 设置加载路径
static void setup_bootstrap_search_path();
public:
// 初始化类加载器
static void initialize();
// ...
}
通过_first_entry链表保存这个类加载器可以加载的一些类路径。在虚拟机启动时会通过调用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对象。
调用add_package()将已经解析过的类进行保存,避免重复加载解析。逻辑实现并不复杂,这里不在介绍。
parseClassFile()方法就是解析Class文件中的类、字段、常量池等信息,然后转换为C++内部的对等表示,如类元信息存储在InstanceKlass实例中,常量池信息存储在ConstantPool中,部分的C++对等实现(类模型)在之前已经介绍过,这里不再介绍。后续会详细介绍parseClassFile()方法解析Class文件的过程。
2、扩展类加载器
扩展类加载器用Java语言编写,由sun.misc.Launcher$ExtClassLoader类实现,负责将 <JAVA_HOME >/lib/ext目录或者由系统变量-Djava.ext.dir所指定的目录中的类库加载到内存中。
扩展类加载器ExtClassLoader的实现如下:
源代码位置:openjdk/jdk/src/share/classes/sun/misc/Launcher.java
static class ExtClassLoader extends URLClassLoader { /** * create an ExtClassLoader. The ExtClassLoader is created * within a context that limits which files it can read */ public static ExtClassLoader getExtClassLoader() throws IOException { final File[] dirs = getExtDirs(); // 获取要加载类的加载路径 ... return new ExtClassLoader(dirs); // 实例化扩展类加载器 ... } /* * Creates a new ExtClassLoader for the specified directories. */ public ExtClassLoader(File[] dirs) throws IOException { super(getExtURLs(dirs), null, factory); // parent传递的参数为null,所以并不是引导类加载器 } 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、系统类加载器/应用类加载器
系统类加载器用Java语言编写,由sun.misc.Launcher$AppClassLoader类实现,负责将系统环境变量-classpath、-cp或系统属性java.class.path指定的路径下的类库加载到内存中。
扩展类加载器AppClassLoader的实现如下:
源代码位置:openjdk/jdk/src/share/classes/sun/misc/Launcher.java
static class AppClassLoader extends URLClassLoader {
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);
}
/*
* Creates a new AppClassLoader
*/
AppClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent, factory); // parent是一个扩展类加载器实例
}
/**
* Override loadClass so we can checkPackageAccess.
*/
public Class loadClass(String name, boolean resolve)throws ClassNotFoundException{
// ...
return (super.loadClass(name, resolve));
}
// ...
}
在Launcher类的构造函数中实例化系统类加载器AppClassLoader时,会调用getAppClassLoader()方法获取系统类加载器,传入的参数是一个扩展类加载器ExtClassLoader实例,这样系统类加载器的父加载器就变成了扩展类加载器(与父加载器并非继承关系)。用户自定义的无参类加载器的父类加载器默认就是AppClassloader类加载器。
4、构造类加载器实例
HotSpot在启动过程中会在<JAVA_HOME>/lib/rt.jar包里面的sun.misc.Launcher类中完成扩展类加载器和系统类加载器的实例化,也会进行引导类加载器的初始化,也就是调用C++语言编写的ClassLoader类的initialize()方法。
HotSpot在初始化时,会初始化一个重要的变量,定义如下:
源代码位置:hotspot/src/share/vm/classfile/systemDictionary.cpp oop SystemDictionary::_java_system_loader = NULL;
这个属性保存系统类加载器实例,HotSpot在加载主类时会使用这个类加载器加载主类。属性在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
方法的实现如下:
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属性中
_java_system_loader = (oop)result.get_jobject(); // 初始化属性为系统类加载器/应用类加载器/AppClassLoader
}
通过JavaClass::call_static()方法调用java.lang.ClassLoader类的getSystemClassLoader()方法。JavaClass::call_static()方法非常重要,它是HotSpot调用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(); // 获取类加载器实例 // ... } sclSet = true; } }
如上方法及变量定义在java.lang.ClassLoader类中。
在getSystemClassLoader()函数中调用Launcerh.getLauncher()方法获取Launcher实例,实例通过静态变量launcher来保存,静态变量的定义如下:
源代码位置:openjdk/jdk/src/share/classes/sum/misc/Launcher.java private static Launcher launcher = new Launcher();
调用l.getClassLoader()方法获取类加载器实例,如下:
源代码位置:openjdk/jdk/src/share/classes/sum/misc/Launcher.java
public ClassLoader getClassLoader() {
return loader; // 返回的loader就是Launcher类的loader,也就是应用类加载器AppClassLoader
}
auncher()类的构造函数如下:
源代码位置:openjdk/jdk/src/share/classes/sun/misc/Launcher.java
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 {
// 以ExtClassloader作为父加载器创建了AppClassLoader
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.
// 默认线程上下文加载器为AppClassloader
Thread.currentThread().setContextClassLoader(loader);
}
如上方法及变量定义在sumn.misc.Lanucher类中。
可以看到有对ExtClassLoader与AppClassLoader实例创建的逻辑,这样HotSpot就可以通过_java_system_loader属性获取AppClassLoader实例,通过AppClassLoader实例中的parent属性使用ExtClassLoader。
相关文章的链接如下:
1、在Ubuntu 16.04上编译OpenJDK8的源代码
关注公众号,有HotSpot源码剖析系列文章!
参考文章:
(1)类加载器的实现
(2)类的预加载
(3)Java类的加载
