大家好,又见面了,我是你们的朋友全栈君。
目录
- 前言
- 一、从JVM源码看类加载器
- 1.1 Java层面的类加载器
- 1.2 JVM是如何启动的
- 1.3 C 层面的类加载器
- 1.3.1 初始化BootStrapClassLoader
- 1.3.2 BootStrapClassLoader如何加载类
- 二、总结
前言
关于JVM类加载的基础理论知识,请参照《深入理解Java虚拟机》读书笔记(六)–虚拟机类加载机制(上)和《深入理解Java虚拟机》读书笔记(六)–虚拟机类加载机制(下)。
一、从JVM源码看类加载器
注:使用的是openjdk8
1.1 Java层面的类加载器
我们都知道在Java类加载中,除了BootStrap加载器,App和Ext加载器都是Java实现的,具体实现在sun.misc.Launcher中:
代码语言:javascript复制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初始化如下:
代码语言:javascript复制 //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函数:
代码语言:javascript复制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,并且初始化调用函数:
代码语言:javascript复制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函数:
代码语言:javascript复制typedef struct {
CreateJavaVM_t CreateJavaVM;
GetDefaultJavaVMInitArgs_t GetDefaultJavaVMInitArgs;
GetCreatedJavaVMs_t GetCreatedJavaVMs;
} InvocationFunctions;
关于jvmpath,需要再回到JLI_Launch函数中,在该函数中会调用CreateExecutionEnvironment函数创建执行上下文,在其中会初始化jvmpath:
代码语言:javascript复制CreateExecutionEnvironment(&argc, &argv,jrepath, sizeof(jrepath),jvmpath, sizeof(jvmpath),jvmcfg, sizeof(jvmcfg));
同样的在java_md.c中找到该函数win平台的实现:
代码语言:javascript复制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] = '