目录
前言
关于JVM类加载的基础理论知识,请参照《深入理解Java虚拟机》读书笔记(六)–虚拟机类加载机制(上)和《深入理解Java虚拟机》读书笔记(六)–虚拟机类加载机制(下)。
一、从JVM源码看类加载器
注:使用的是openjdk8
1.1 Java层面的类加载器
我们都知道在Java类加载中,除了BootStrap加载器,App和Ext加载器都是Java实现的,具体实现在sun.misc.Launcher中:
public class Launcher{ private static Launcher launcher = new Launcher(); private static String bootClassPath = System.getProperty("sun.boot.class.path"); ...... public Launcher() { Launcher.ExtClassLoader var1; try { //没有显示设置父类加载器,为BootStrapClassLoader var1 = Launcher.ExtClassLoader.getExtClassLoader(); } catch (IOException var10) { throw new InternalError("Could not create extension class loader", var10); } try { //设置ExtClassLoader为父类加载器 this.loader = Launcher.AppClassLoader.getAppClassLoader(var1); } catch (IOException var9) { throw new InternalError("Could not create application class loader", var9); } //设置TCCL Thread.currentThread().setContextClassLoader(this.loader); ...... } ...... }
在Launcher类中,有一个静态私有成员变量launcher的赋值,调用本例的构造方法生成一个Launcher实例。因为launcher是一个类级别属性,所以这个操作会被收敛到类构造器<clinit>()方法,在该类被加载的初始化阶段被执行。
在Launcher的构造方法中,分别初始化了Launcher.ExtClassLoader和Launcher.AppClassLoader加载器,将Launcher.loader属性(ClassLoader.getSystemClassLoader方法返回的就是这个属性)设置为了AppClassLoader,并且将TCCL设置为了AppClassLoader。ExtClassLoader和AppClassLoader初始化如下:
//ExtClassLoader static class ExtClassLoader extends URLClassLoader { private static volatile Launcher.ExtClassLoader instance; public static Launcher.ExtClassLoader getExtClassLoader() throws IOException { if (instance == null) { Class var0 = Launcher.ExtClassLoader.class; synchronized(Launcher.ExtClassLoader.class) { if (instance == null) { instance = createExtClassLoader(); } } } return instance; } } //AppClassLoader static class AppClassLoader extends URLClassLoader { final URLClassPath ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this); public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException { final String var1 = System.getProperty("java.class.path"); final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1); return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() { public Launcher.AppClassLoader run() { URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2); return new Launcher.AppClassLoader(var1x, var0); } }); } }
从初始化方法就可以看出,ExtClassLoader没有显示设置父加载器,所以其父类加载器是BootStrap,AppClassLoader设置ExtClassLoader为自己的父加载器。
注:加载器的父子关系不是继承上的父子关系,而是通过成员变量引用,以组合的方式实现的父子关系
1.2 JVM是如何启动的
程序的主要入口点在main.c,代码中有大量的条件编译,我们直接看JLI_Launch函数:
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函数定义在java.h中,java.c中有该函数的实现,其中会调用LoadJavaVM函数,LoadJavaVM函数对于不同的平台(win、mac、solaris等)有不同的实现,我们这里看看windows的版本,实现在java_md.c中,主要是从jvmpath加载dll,并且初始化调用函数:
jboolean LoadJavaVM(const char *jvmpath, InvocationFunctions *ifn) { //加载Microsoft环境c运行时库,提供必要的函数库调用和启动函数 //后面创建线程启动JVM就使用的c运行时库函数_beginthreadex LoadMSVCRT(); /* 根据jvmpath加载dll文件 */ if ((handle = LoadLibrary(jvmpath)) == 0) { JLI_ReportErrorMessage(DLL_ERROR4, (char *)jvmpath); return JNI_FALSE; } /* Now get the function addresses */ ifn->CreateJavaVM = (void *)GetProcAddress(handle, "JNI_CreateJavaVM"); ifn->GetDefaultJavaVMInitArgs = (void *)GetProcAddress(handle, "JNI_GetDefaultJavaVMInitArgs"); if (ifn->CreateJavaVM == 0 || ifn->GetDefaultJavaVMInitArgs == 0) { JLI_ReportErrorMessage(JNI_ERROR1, (char *)jvmpath); return JNI_FALSE; } return JNI_TRUE; }
加载Microsoft环境c运行时库后,会根据jvmpath加载jvm的dll文件(在jre目录存有动态链接文件,若将jrebinserver下的jvm.dll移除,也启动不了JVM)。InvocationFunctions定义在java.h中,有三个JNI函数:
typedef struct { CreateJavaVM_t CreateJavaVM; GetDefaultJavaVMInitArgs_t GetDefaultJavaVMInitArgs; GetCreatedJavaVMs_t GetCreatedJavaVMs; } InvocationFunctions;
关于jvmpath,需要再回到JLI_Launch函数中,在该函数中会调用CreateExecutionEnvironment函数创建执行上下文,在其中会初始化jvmpath:
CreateExecutionEnvironment(&argc, &argv,jrepath, sizeof(jrepath),jvmpath, sizeof(jvmpath),jvmcfg, sizeof(jvmcfg));
同样的在java_md.c中找到该函数win平台的实现:
void CreateExecutionEnvironment(int *pargc, char ***pargv, char *jrepath, jint so_jrepath, char *jvmpath, jint so_jvmpath, char *jvmcfg, jint so_jvmcfg) { ...... /* 寻找要使用的JRE路径*/ if (!GetJREPath(jrepath, so_jrepath)) { JLI_ReportErrorMessage(JRE_ERROR1); exit(2); } /*获取jvm类型*/ jvmtype = CheckJvmType(pargc, pargv, JNI_FALSE); jvmpath[0] = '