1.类加载的基本流程
JVM进行LoadClass的类加载器分三种:BootStrapClassLoader,ExtClassLoader,AppClassLoader。在不同的场景下使用相应的ClassLoader将.class字节码文件转换成JavaClass类对象,转换源码如下所示:
Klass对象为C++实现的对象,在JVM读取转化.class类文件中的数据后得到的结构体,即.class类文件的元数据对象。当类加载器执行loadClass方法进行类加载,SystemDictionary会读取相应的类路径下对应的.class类将其转为Klass对象,同时存入SystemDictionary系统字典哈希表结构中,key为类加载器+类名,value存的就是Klass对象。
读取字节码文件,返回Klass对象
Klass将按JVM8手册定义的.class文件格式进行读取,前4个字节为.class文件的标识符,对类文件的格式有兴趣的可以去JVM8手册查询,手册定义如下所示:
JVM对类文件读取,使用Klass对象进行存储,将Klass转为Java的Class对象返回,对象的模板——类,加载完毕了。
简而言之,类加载的流程:字节码文件=>Klass对象=>Java Class对象
2.双亲委派的实现
Hotspot8类加载机制的一大特点为双亲委派,具体实现需要从ClassLoader的定义的结构进行分析,实则就是面向对对象的灵活应用。下述介绍三种类加载器以及父类ClassLoader。
ClassLoader
下述为ClassLoader的源代码,为了突出本文的核心,对代码进行简略表示:
public abstract class ClassLoader {
//双亲委派机制,所有的类加载器必定会通过parent父类往上传
//为类加载器的基本属性
private final ClassLoader parent;
...
private ClassLoader(Void unused, ClassLoader parent) {
this.parent = parent; //指定父类加载器
...
}
}
protected ClassLoader(ClassLoader parent) {
//执行private ClassLoader(Void unused, ClassLoader parent)方法
this(checkCreateClassLoader(), parent);
}
...
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException{
try {
if (parent != null) {
//递归调用,优先父类加载
c = parent.loadClass(name, false);
} else {
//顶级父类为BootStrapClassLoader
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
//自定义的findClass,默认该方法为空,由子类实现
c = findClass(name);
}
}
}
Launcher(ExtClassLoader和AppClassLoader)
Launcher作为启动器类,JVM使用该类启动主应用程序,ExtClassLoader和AppClassLoader均为它的内部类
public class Launcher {
private ClassLoader loader;
public Launcher() {
// Create the extension class loader
ClassLoader extcl;
extcl = ExtClassLoader.getExtClassLoader();
loader = AppClassLoader.getAppClassLoader(extcl);
}
/*
* Returns the class loader used to launch the main application.
*/
public ClassLoader getClassLoader() {
return loader;
}
static class ExtClassLoader extends URLClassLoader {
public static ExtClassLoader getExtClassLoader() throws IOException
{
final File[] dirs = getExtDirs(); //获取ExtClassLoader的加载路径java.ext.dirs
try {
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);
}
});
}
}
/*
* 该类加载器只加载指定路径的类
* Creates a new ExtClassLoader for the specified directories.
*/
public ExtClassLoader(File[] dirs) throws IOException {
super(getExtURLs(dirs), null, factory);
}
private static File[] getExtDirs() {
//获取ExtClassLoader的加载路径,在系统变量中传入。
String s = System.getProperty("java.ext.dirs");
...
}
}
static class AppClassLoader extends URLClassLoader {
public static ClassLoader getAppClassLoader(final ClassLoader extcl)
throws IOException
{
//获取AppClassLoader的加载路径
final String s = System.getProperty("java.class.path");
final File[] path = (s == null) ? new File[0] : getClassPath(s);
return AccessController.doPrivileged(
new PrivilegedAction<AppClassLoader>() {
public AppClassLoader run() {
URL[] urls =
(s == null) ? new URL[0] : pathToURLs(path);
return new AppClassLoader(urls, extcl);
}
});
}
/*
* urls:AppClassLoader的加载路径
* parent:AppClassLoader的父类加载器
* Creates a new AppClassLoader
*/
AppClassLoader(URL[] urls, ClassLoader parent) {
//指定父类加载器
super(urls, parent, factory);
}
}
}
从Launcher对象构造方法可以发现Java的ExtClassLoader和AppClassLoader类加载器对象就在方法中进行实例化的。
先创建ExtClassLoader对象,将它的引用传入AppClassLoader对象中,指定extcl为AppClassLoader的parent即父类加载器。同时使用System.getProperty(“java.ext.dirs”)获取ExtClassLoader类加载器的加载路径以及System.getProperty(“java.class.path”)获取AppClassLoader类加载器的加载路径,以此确认类加载器的加载权限。到此,ExtClassLoader作为AppClassLoader的父类加载器关系已经确认。想要创建这两个类加载器对象必须先加载Launcher启动器对象,那么对象又是在哪加载的呢?
Launcher对象的加载
这里从源头开始分析,假设在Linux下运行Java程序,首先走JVM的mian.c下的main方法即程序入口方法:
int
main(int argc, char **argv)
{
int margc;
char** margv;
const jboolean const_javaw = JNI_FALSE;
#endif /* JAVAW */
#ifdef _WIN32
...
#else /* *NIXES */
margc = argc;
margv = argv;
#endif /* WIN32 */ //Linux下的主线程入口
return JLI_Launch(margc, margv,
sizeof(const_jargs) / sizeof(char *), const_jargs,
sizeof(const_appclasspath) / sizeof(char *), const_appclasspath,
FULL_VERSION,
DOT_VERSION,
(const_progname != NULL) ? const_progname : *margv,
(const_launcher != NULL) ? const_launcher : *margv,
(const_jargs != NULL) ? JNI_TRUE : JNI_FALSE,
const_cpwildcard, const_javaw, const_ergo_class);
}
JLI_Launch执行JVMInit方法初始化JVM并创建JavaMain线程,然后调用ContinueInNewThread=>ContinueInNewThread0(创建线程)=>JavaMain方法执行=>LoadMainClass(加载Main函数的类信息)
/*
* Loads a class and verifies that the main class is present and it is ok to
* call it for more details refer to the java implementation.
*/
static jclass
LoadMainClass(JNIEnv *env, int mode, char *name)
{
jmethodID mid;
jstring str;
jobject result;
jlong start, end;
//先用BootClassloader加载LauncherHelper类
//LauncherHelper,Java虚拟机还没有启动,主类没有加载完成,由它提供了所有的功能
jclass cls = GetLauncherHelperClass(env); //映射获取LauncherHelper javaClass实例
NULL_CHECK0(cls);
if (JLI_IsTraceLauncher()) {
start = CounterGet();
}
NULL_CHECK0(mid = (*env)->GetStaticMethodID(env, cls,
"checkAndLoadMain",
"(ZILjava/lang/String;)Ljava/lang/Class;"));
//将C的char数组表示的字符串转为java的String对象,让Java对象能够识别该name
str = NewPlatformString(env, name);
//然后使用LauncherHelper类调checkAndLoadMain方法加载str字符串代表的Class类
result = (*env)->CallStaticObjectMethod(env, cls, mid, USE_STDERR, mode, str);
...
return (jclass)result;
}
BootStrapClassLoader先加载LauncherHelper,再由LauncherHelper对Launcher进行加载,以此完成App/Ext ClassLoader的加载。如何完成加载的呢?请继续看LauncherHelper的CheckAndLoadMian方法
public static Class<?> checkAndLoadMain(boolean printToStderr,
int mode,
String what) {
initOutput(printToStderr);
// get the class name
String cn = null;
switch (mode) {
case LM_CLASS: //类比静态链接库.o文件
cn = what;
break;
case LM_JAR: //jar包类比.a文件(.o文件的集合)
cn = getMainClassFromJar(what);
break;
default:
// should never happen
throw new InternalError("" + mode + ": Unknown launch mode");
}
cn = cn.replace('/', '.');
Class<?> mainClass = null;
try {
//使用系统类加载器加载类
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;
}
关键在 mainClass = scloader.loadClass(cn) ,scloader为LauncherHelper的静态变量:
private static final ClassLoader scloader = ClassLoader.getSystemClassLoader(); //加载appClassLoader,初始化Laucher启动器
且看它的AppClassLoader的初始化
public static ClassLoader getSystemClassLoader() {
initSystemClassLoader(); //初始化类加载器
if (scl == null) {
return null;
}
...
return scl;
}
initSystemClassLoader:
private static synchronized void initSystemClassLoader() {
if (!sclSet) {
...
sun.misc.Launcher l = sun.misc.Launcher.getLauncher(); //获取Launcher启动器
...
}
Launcher类:
public class Launcher {
private static URLStreamHandlerFactory factory = new Factory();
private static Launcher launcher = new Launcher(); //Launcher对象的创建
private static String bootClassPath = System.getProperty("sun.boot.class.path"); //JDK系统库函数的路径
public static Launcher getLauncher() {
return launcher;
}
...
}
Launcher的类加载完成。由类构造器对Launcher对象进行实例化。到此Launcher(ext/app ClassLoader)启动器对象创建完毕。
BootStrap ClassLoader
BootStrapClassLoader是顶级父加载器,用于加载JDK系统库函数的类.以此可以推断出,创建Laucher类的LaucherHelper类必定是由它来加载的。接下来回到LoadMainClass方法:
static jclass
LoadMainClass(JNIEnv *env, int mode, char *name)
{
jmethodID mid;
jstring str;
jobject result;
jlong start, end;
//先用BootClassloader加载LauncherHelper类
//LauncherHelper,Java虚拟机还没有启动,主类没有加载完成,由它提供了所有的功能
jclass cls = GetLauncherHelperClass(env); //映射获取LauncherHelper javaClass实例
...
return (jclass)result;
}
GetLauncherHelperClass方法使用BootStrapClass去加载LauncherHelper类,获取它的JavaClass类信息:
jclass
GetLauncherHelperClass(JNIEnv *env)
{
if (helperClass == NULL) {
//顶层类加载器 BootStrapClass,去加载系统库函数 LauncherHelper
NULL_CHECK0(helperClass = FindBootStrapClass(env,
"sun/launcher/LauncherHelper"));
}
return helperClass;
}
FindBootStrapClass:
jclass
FindBootStrapClass(JNIEnv *env, const char* classname)
{
if (findBootClass == NULL) {
findBootClass = (FindClassFromBootLoader_t *)dlsym(RTLD_DEFAULT,
"JVM_FindClassFromBootLoader");
if (findBootClass == NULL) {
JLI_ReportErrorMessage(DLL_ERROR4,
"JVM_FindClassFromBootLoader");
return NULL;
}
}
//函数指针,走jvm.cpp中JVM_FindClassFromBootLoader函数
return findBootClass(env, classname);
}
jvm.cpp
//JVM_FindClassFromBootLoader
JVM_ENTRY(jclass, JVM_FindClassFromBootLoader(JNIEnv* env,
const char* name))
JVMWrapper2("JVM_FindClassFromBootLoader %s", name);
// Java libraries should ensure that name is never null...
if (name == NULL || (int)strlen(name) > Symbol::max_length()) {
// It's impossible to create this class; the name cannot fit
// into the constant pool.
return NULL;
}
TempNewSymbol h_name = SymbolTable::new_symbol(name, CHECK_NULL);
Klass* k = SystemDictionary::resolve_or_null(h_name, CHECK_NULL); //根据key查系统字典获取Klass对象
if (k == NULL) {
return NULL;
}
if (TraceClassResolution) {
trace_class_resolution(k);
}
return (jclass) JNIHandles::make_local(env, k->java_mirror());
JVM_END
查SystemDictionary获取LauncherHelper的Klass对象,然后转为JavaClass对象进行返回,LauncherHelper的类加载就完成了。
在整个流程中,并没有发现BootStrapClassLoader的实例Java对象,而是调用了
JVM_ENTRY(jclass, JVM_FindClassFromBootLoader(JNIEnv env,const char name))**
进行类加载,所以我们其实可以推测出它就是BootStrapClassLoader顶级类加载器,由C++代码实现,而不是Java实例对象,所以我们在Java程序中使用ClassLoader.getSystemClassLoader().getParent().getParent()获取的BootStrap类加载器为NULL。
到此,我们可以总结出三种类加载器的关系了:
loadClass
三种类加载器的关系和它们被加载的时机已经明确了,接下来我们可以对LoadClass进行探讨了:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
//优先父类加载
c = parent.loadClass(name, false);
} else {
//顶级父类为BootStrapClassLoader
c = findBootstrapClassOrNull(name);
}
}
if (c == null) {
//自定义的findClass,默认该方法为空
c = findClass(name);
...
}
}
return c;
}
}
当一个自定义类需要被加载,AppClassLoader调用LoadClass,然后由它的parent(ps:也就是ExtClaassLoader)执行LoadClass,直到BootStrapClassLoader,即顶级父类优先加载当前类。即递归调用。
当BootStrapClassLoader检查自己的加载路径(由System.getProperty(“sun.boot.class.path”)确认),检查后发现当前类不属于自己加载就会返回给ExtClassLoader,它检查自己的加载路径(由System.getProperty(“java.ext.dirs”)确认),检查后发现也不属于自己加载就返回给AppClassLoader,它检查自己的加载路径(由System.getProperty(“java.class.path”)确认),发现属于自己加载,然后对指定路径进行类加载。
接下来请看类加载的具体过程:findClass
/**找到并加载这个类
* Finds and loads the class with the specified name from the URL search
...
*/
protected Class<?> findClass(final String name)
throws ClassNotFoundException
{
try {
//访问权限检查
return AccessController.doPrivileged(
new PrivilegedExceptionAction<Class<?>>() {
public Class<?> run() throws ClassNotFoundException {
String path = name.replace('.', '/').concat(".class");
//获取绝对路径
Resource res = ucp.getResource(path, false);
if (res != null) {
try {
return defineClass(name, res);
}...
}
}
}, acc);
} ...
}
根据注解,findClass的作用是找到并加载这个类,我们传入类路径,如com.lwl.hello会将其解析为E:/projName/com/lwl/control.hello.class,因为类路径最终还是要落地到文件系统上进行类文件的索引。AppClassLoader确定了当前类是由自己加载的后,开始加载类,进行defineClass方法的执行。
private Class<?> defineClass(String name, Resource res) throws IOException {
long t0 = System.nanoTime();
int i = name.lastIndexOf('.');
URL url = res.getCodeSourceURL();
...
// Now read the class bytes and define the class
java.nio.ByteBuffer bb = res.getByteBuffer();
if (bb != null) {
// Use (direct) ByteBuffer:
CodeSigner[] signers = res.getCodeSigners();
CodeSource cs = new CodeSource(url, signers);
sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
//执行ClassLoader.c类的defineClass(byte[] b, int off, int len)方法
return defineClass(name, bb, cs);
} else {
byte[] b = res.getBytes();
// must read certificates AFTER reading bytes.
CodeSigner[] signers = res.getCodeSigners();
CodeSource cs = new CodeSource(url, signers);
sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
return defineClass(name, b, 0, b.length, cs);
}
}
defineClass(name, bb, cs)的执行:
protected final Class<?> defineClass(byte[] b, int off, int len)
throws ClassFormatError
{
return defineClass(null, b, off, len, null);
}
...
protected final Class<?> defineClass(String name, byte[] b, int off, int len,
ProtectionDomain protectionDomain)
throws ClassFormatError
{
protectionDomain = preDefineClass(name, protectionDomain);
String source = defineClassSourceLocation(protectionDomain);
//调用的是ClassLoader.c文件下的Java_java_lang_ClassLoader_defineClass1方法
Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
postDefineClass(c, protectionDomain);
return c;
}
Class<?> c = defineClass1(name, b, off, len, protectionDomain, source)的执行:
JNIEXPORT jclass JNICALL
Java_java_lang_ClassLoader_defineClass1(JNIEnv *env,
jobject loader,
jstring name,
jbyteArray data,
jint offset,
jint length,
jobject pd,
jstring source)
{
...
jclass result = 0;
...
//执行的是jvm.cpp中的JVM_ENTRY(jclass, JVM_DefineClassWithSource..)方法
result = JVM_DefineClassWithSource(env, utfName, loader, body, length, pd, utfSource);
...
return result;
jvm.cpp的内建函数JVM_DefineClassWithSource
JVM_ENTRY(jclass, JVM_DefineClassWithSource(JNIEnv *env, const char *name, jobject loader, const jbyte *buf, jsize len, jobject pd, const char *source))
JVMWrapper2("JVM_DefineClassWithSource %s", name);
return jvm_define_class_common(env, name, loader, buf, len, pd, source, true, THREAD);
JVM_END
jvm_define_class_common(env, name, loader, buf, len, pd, source, true, THREAD)的执行:
static jclass jvm_define_class_common(JNIEnv *env, const char *name,
jobject loader, const jbyte *buf,
jsize len, jobject pd, const char *source,
jboolean verify, TRAPS) {
...
//在SystemDictionary中根据class_name和class_loader获取Klass类对象实例
Klass* k = SystemDictionary::resolve_from_stream(class_name, class_loader,
protection_domain, &st,
verify != 0,
CHECK_NULL);
if (TraceClassResolution && k != NULL) {
trace_class_resolution(k);
}
//将Klass类对象转为JavaClass对象返回
return (jclass) JNIHandles::make_local(env, k->java_mirror());
}
resolve_from_stream读取类文件转为Klass对象:
Klass* SystemDictionary::resolve_from_stream(Symbol* class_name,
Handle class_loader,
Handle protection_domain,
ClassFileStream* st,
bool verify,
TRAPS) {
...
//就是在这里对类文件进行读取的
instanceKlassHandle k = ClassFileParser(st).parseClassFile(class_name,
loader_data,
protection_domain,
parsed_name,
verify,
THREAD);
...
return k();
}
又回到了1.类加载的基本流程类文件的转换上了,无非就是字节码文件=>Klass(C++对象)=>Java Class对象的转换。
至此类加载的全部流程源码解析结束。
3.后言
这里对类加载的流程做个比喻:在情人节这天,两个20岁的情侣去买钻戒结婚,男方跟他爸爸说我要买钻戒(LoadClass),他爸爸知道后跟他爷爷说我儿子要买钻戒(LoadClass),他爷爷检查下自己的钱包(FindClass)发现自己的钱给孙子买房了,于是返回给他爸爸说我只负责买房,我没钱了。于是,他爸爸检查自己的钱包(FindClass)发现自己的钱给儿子买车了,于是返回给他儿子说我只负责买车,我没钱了。然后他儿子检查自己的钱包(FindClass)发现自己还有点钱,便他儿子自己掏钱买了钻戒(defindClass)。最后,钻戒到手顺利结婚了。