jvm2类加载器子系统与SPI

类加载器子系统分两节课讲解:类加载、类加载器

上节课已经深入讲解了类加载,这节课开始讲解类加载器

类加载器子系统 = 三层类加载器 + 双亲委派

类加载器

JVM中有两种类型的类加载器,由C++编写的及由Java编写的。除了启动类加载器(Bootstrap Class Loader)是由C++编写的,其他都是由Java编写的。由Java编写的类加载器都继承自类java.lang.ClassLoader。

JVM还支持自定义类加载器。后面会将。

各种类加载器之间存在着逻辑上的父子关系,但不是真正意义上的父子关系,因为它们直接没有从属关系。

0000056666

启动类加载器

因为启动类加载器是由C++编写的,通过Java程序去查看显示的是null

因此,启动类加载器无法被Java程序调用

启动类加载器不像其他类加载器有实体,它是没有实体的,JVM将C++处理类加载的一套逻辑定义为启动类加载器


查看启动类加载器的加载路径

1
2
3
4
URL[] urLs = Launcher.getBootstrapClassPath().getURLs();
for (URL urL : urLs) {
System.out.println(urL);
}

也可以通过-Xbootclasspath指定


从openjdk源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
int JNICALL
JavaMain(void * _args)
{
……
mainClass = LoadMainClass(env, mode, what);
……
}

static jclass
LoadMainClass(JNIEnv *env, int mode, char *name)
{
jmethodID mid;
jstring str;
jobject result;
jlong start, end;
jclass cls = GetLauncherHelperClass(env);
NULL_CHECK0(cls);
if (JLI_IsTraceLauncher()) {
start = CounterGet();
}
NULL_CHECK0(mid = (*env)->GetStaticMethodID(env, cls,
"checkAndLoadMain",
"(ZILjava/lang/String;)Ljava/lang/Class;"));

str = NewPlatformString(env, name);
CHECK_JNI_RETURN_0(
result = (*env)->CallStaticObjectMethod(
env, cls, mid, USE_STDERR, mode, str));

if (JLI_IsTraceLauncher()) {
end = CounterGet();
printf("%ld micro seconds to load main class\n",
(long)(jint)Counter2Micros(end-start));
printf("----%s----\n", JLDEBUG_ENV_ENTRY);
}

return (jclass)result;
}

jclass
GetLauncherHelperClass(JNIEnv *env)
{
if (helperClass == NULL) {
NULL_CHECK0(helperClass = FindBootStrapClass(env,
"sun/launcher/LauncherHelper"));
}
return helperClass;
}

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;
}
}
return findBootClass(env, classname);
}

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);
if (k == NULL) {
return NULL;
}

if (TraceClassResolution) {
trace_class_resolution(k);
}
return (jclass) JNIHandles::make_local(env, k->java_mirror());
JVM_END

这套逻辑做的事情就是通过启动类加载器加载类sun.launcher.LauncherHelper,执行该类的方法checkAndLoadMain,加载main函数所在的类,启动扩展类加载器、应用类加载器也是在这个时候完成的

扩展类加载器

查看类加载器加载的路径

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
ClassLoader classLoader = ClassLoader.getSystemClassLoader().getParent();

URLClassLoader urlClassLoader = (URLClassLoader) classLoader;

URL[] urls = urlClassLoader.getURLs();
for (URL url : urls) {
System.out.println(url);
}
}

可以通过java.ext.dirs指定

应用类加载器

默认加载用户程序的类加载器

查看类加载器加载的路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String[] args) {
String[] urls = System.getProperty("java.class.path").split(":");

for (String url : urls) {
System.out.println(url);
}

System.out.println("================================");

URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();

URL[] urls1 = classLoader.getURLs();
for (URL url : urls1) {
System.out.println(url);
}
}

可以通过java.class.path指定

自定义类加载器

继承类java.lang.ClassLoader

具体细节见课堂上操作实战

线程上下文类加载器

类加载器创建链

上面已经讲了启动类加载器没有实体,只是将一段加载逻辑命名成启动类加载器。启动类加载器做的事情是:加载类sun.launcher.LauncherHelper,执行该类的方法checkAndLoadMain……启动类、扩展类、应用类加载器逻辑上的父子关系就是在这个方法的调用链中生成的?

1、\openjdk\jdk\src\share\classes\sun\launcher\LauncherHelper.java

核心代码:ClassLoader.getSystemClassLoader();

1
2
3
4
5
6
7
8
9
10
public enum LauncherHelper {
……
private static final ClassLoader scloader = ClassLoader.getSystemClassLoader();
……
public static Class<?> checkAndLoadMain(boolean printToStderr,
int mode,
String what) {
……
mainClass = scloader.loadClass(cn);
……

2、\openjdk\jdk\src\share\classes\java\lang\ClassLoader.java

核心代码:sun.misc.Launcher.getLauncher();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
   public static ClassLoader getSystemClassLoader() {
initSystemClassLoader();
if (scl == null) {
return null;
}
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkClassLoaderPermission(scl, Reflection.getCallerClass());
}
return scl;
}

private static synchronized void initSystemClassLoader() {
if (!sclSet) {
if (scl != null)
throw new IllegalStateException("recursive invocation");
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
……

3、\openjdk\jdk\src\share\classes\sun\misc\Launcher.java

核心代码:

  • private static Launcher launcher = new Launcher();

  • extcl = ExtClassLoader.getExtClassLoader();

  • loader = AppClassLoader.getAppClassLoader(extcl);

  • Thread.currentThread().setContextClassLoader(loader);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class Launcher {
private static URLStreamHandlerFactory factory = new Factory();
private static Launcher launcher = new Launcher();
private static String bootClassPath =
System.getProperty("sun.boot.class.path");

public static Launcher getLauncher() {
return launcher;
}

private ClassLoader loader;

public Launcher() {
// Create the extension class loader
ClassLoader extcl;
try {
extcl = ExtClassLoader.getExtClassLoader();
} catch (IOException e) {
throw new InternalError(
"Could not create extension class loader", e);
}

// Now create the class loader to use to launch the application
try {
loader = AppClassLoader.getAppClassLoader(extcl);
} catch (IOException e) {
throw new InternalError(
"Could not create application class loader", e);
}

// Also set the context class loader for the primordial thread.
Thread.currentThread().setContextClassLoader(loader);
……

4、扩展类加载器的创建流程

1
2
3
4
5
6
7
8
9
10
11
12
public static ExtClassLoader getExtClassLoader() throws IOException
{
……
return new ExtClassLoader(dirs);
……

public ExtClassLoader(File[] dirs) throws IOException {
super(getExtURLs(dirs), null, factory);
}

URLClassLoader(URL[] urls, ClassLoader parent,
AccessControlContext acc) {

第二个参数传的是null,其实就是parent=null

5、应用类加载器的创建流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static ClassLoader getAppClassLoader(final ClassLoader extcl)
throws IOException {
final String s = System.getProperty("java.class.path");
final File[] path = (s == null) ? new File[0] : getClassPath(s);

// Note: on bugid 4256530
// Prior implementations of this doPrivileged() block supplied
// a rather restrictive ACC via a call to the private method
// AppClassLoader.getContext(). This proved overly restrictive
// when loading classes. Specifically it prevent
// accessClassInPackage.sun.* grants from being honored.
//
return AccessController.doPrivileged(
new PrivilegedAction<AppClassLoader>() {
public AppClassLoader run() {
URL[] urls =
(s == null) ? new URL[0] : pathToURLs(path);
return new AppClassLoader(urls, extcl);
}
});
}

AppClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent, factory);
}

应用类、扩展类加载器的父子关系就是这样建立的

类加载器加载的类如何存储

006b

双亲委派

如果一个类加载器收到了加载某个类的请求,则该类加载器并不会去加载该类,而是把这个请求委派给父类加载器,每一个层次的类加载器都是如此,因此所有的类加载请求最终都会传送到顶端的启动类加载器;只有当父类加载器在其搜索范围内无法找到所需的类,并将该结果反馈给子类加载器,子类加载器会尝试去自己加载。

每个类加载器的搜索范围上面已经讲过了

778

打破双亲委派

因为在某些情况下父类加载器需要委托子类加载器去加载class文件。受到加载范围的限制,父类加载器无法加载到需要的文件,以Driver接口为例,由于Driver接口定义在jdk当中的,而其实现由各个数据库的服务商来提供,比如mysql的就写了MySQL Connector,那么问题就来了,DriverManager(也由jdk提供)要加载各个实现了Driver接口的实现类,然后进行管理,但是DriverManager由启动类加载器加载,只能记载JAVA_HOME的lib下文件,而其实现是由服务商提供的,由系统类加载器加载,这个时候就需要启动类加载器来委托子类来加载Driver实现,从而破坏了双亲委派。

类似这样的情况就需要打破双亲委派。

打破双亲委派的意思其实就是不委派、向下委派

自定义类加载器

SPI

是一种服务发现机制。它通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。这一机制为很多框架扩展提供了可能,比如在Dubbo、JDBC中都使用到了SPI机制

线程上下文类加载器

1、是什么

一种特殊的类加载器,可以通过Thread获取,基于此可实现逆向委托加载

2、为什么

为了解决双亲委派的缺陷而生

3、怎么做

1
2
3
4
5
//获取
Thread.currentThread().getContextClassLoader()

// 设置
Thread.currentThread().setContextClassLoader(new Classloader_4());

沙箱安全

跟Linux的权限机制有点像

1596186739409-9de7875f-b980-40cf-a392-e7218ee1a7d8


看openjdk源码会看到有这样的判断AccessController.doPrivileged

1596186486602-8ec36a19-d6c9-4914-967c-a223ba45aafb


随便搜了一下,35个文件有80处安全判断

1596186837719-26edf8c5-2f06-489d-89cf-80ac278e51f6


比如我定义了一个类名为String所在包为java.lang,因为这个类本来是属于jdk的,如果没有沙箱安全机制的话,这个类将会污染到我所有的String,但是由于沙箱安全机制,所以就委托顶层的bootstrap加载器查找这个类,如果没有的话就委托extsion,extsion没有就到aapclassloader,但是由于String就是jdk的源代码,所以在bootstrap那里就加载到了,先找到先使用,所以就使用bootstrap里面的String,后面的一概不能使用,这就保证了不被恶意代码污染

1596186237329-9d5d9073-ce88-4836-8a5c-f6c5be51aeef