JVM中有两种类型的类加载器,由C 编写的及由Java编写的。除了启动类加载器(Bootstrap Class Loader)是由C 编写的,其他都是由Java编写的。由Java编写的类加载器都继承自类java.lang.ClassLoader。
各种类加载器之间存在着逻辑上的父子关系
启动类加载器BootClassLoader
启动类加载器是由C 编写,启动类加载器核心就是以下逻辑
java.c,入口LoadMainClass
LoadMainClass 主要调用的是checkAndLoadMain,GetLauncherHelperClass作用是找GetLauncherHelperClass类,而checkAndLoadMain就在GetLauncherHelperClass里
checkAndLoadMain主要是加载main函数所在的类,启动扩展类加载器、应用类加载器也是在这个时候完成的
代码语言:javascript复制int JNICALL
JavaMain(void * _args)
{
...
mainClass = LoadMainClass(env, mode, what);
...
}
static jclass
LoadMainClass(JNIEnv *env, int mode, char *name)
{
jclass cls = GetLauncherHelperClass(env);
...
NULL_CHECK0(mid = (*env)->GetStaticMethodID(env, cls,
"checkAndLoadMain",
"(ZILjava/lang/String;)Ljava/lang/Class;"));
...
}
jclass
GetLauncherHelperClass(JNIEnv *env)
{
if (helperClass == NULL) {
NULL_CHECK0(helperClass = FindBootStrapClass(env,
"sun/launcher/LauncherHelper"));
}
return helperClass;
}
总结:这套逻辑做的事情就是通过启动类加载器加载sun.launcher.LauncherHelper类,执行该类的方法checkAndLoadMain,最终完成加载main函数所在的类
双亲委派
双亲委派:如果一个类加载器收到了加载某个类的请求,则该类加载器并不会去加载该类,而是把这个请求委派给父类加载器,每一个层次的类加载器都是如此,因此所有的类加载请求最终都会传送到顶端的启动类加载器;只有当父类加载器在其搜索范围内无法找到所需的类,并将该结果反馈给子类加载器,子类加载器会尝试去自己加载。
打破双亲委派
因为在某些情况下父类加载器需要委托子类加载器去加载class文件。受到加载范围的限制,父类加载器无法加载到需要的文件
以jdbc为例,由于jdbc定义在jdk当中的,而其实现由各个数据库的服务商来提供,那么问题就来了,DriverManager(也由jdk提供)要加载各个实现了jdbc接口的实现类,然后进行管理,但是DriverManager由启动类加载器加载,而其实现是由服务商提供的,由应用类加载器加载,这个时候就需要启动类加载器来委托子类来加载实现类,从而破坏了双亲委派。
打破双亲委派的意思其实就是不委派、向下委派
打破双亲委派两种方法
SPI
SPI是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类
SPI机制 向下委派打破双亲委派
自定义类加载器
自定义类加载器例子: 重写classload 伪代码:
代码语言:javascript复制// 打破双亲委派
if (name.startsWith("com.XXX")) {
// com.XXX包下的所有类不委派
c = findClass(name);
} else {
// 其他包委派
c = this.getParent().loadClass(name);
}