java类加载器通俗理解

2021-02-24 16:21:46 浏览数 (1)

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);
}

0 人点赞