Java 虚拟机设计团队有意将类加载阶段中的"通过一个类的全限定名来获取描述该类的二进制字节流"这个动作放到 Java 虚拟机外部来实现,以便让应用程序自己来决定如何去获取所需的类,实现这个动作的代码称之为"类加载器 (Class Loader) "。
由于 JDK9 引入了模块化新特性,所以 JDK9 前后的类加载实现也略有区别,本文将分开讲解。
首先基于 JDK8 来讲解类加载机制。
JDK8
双亲委派模型
java.lang.ClassLoader 抽象类的 loaderClass() 方法定义了类加载的流程:
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
65public Class<?> loadClass(String name) throws ClassNotFoundException { return loadClass(name, false); } protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // loadClass() 方法是通过 sycchronized 关键字来保证线程安全性的 // 每个 class name 会对应1个锁 // 若多个线程同时加载 class name 相同的类,因为锁的存在,仅有1个线程会获得执行 // 若多个线程同时加载 class name 不同的类,因为各线程持有的锁是不同的,则线程间互不影响 synchronized (getClassLoadingLock(name)) { // 首先检查一下该类是否已被加载过,若被加载过,则直接返回,避免重复加载 Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { // 若父加载器存在,则由父加载器加载 if (parent != null) { c = parent.loadClass(name, false); } else { // 若父加载器不存在,则由 Bootstrap ClassLoader 加载 // 进入到这个分支,说明父加载器为 null // 父加载器为 null 的 ClassLoader 为扩展类加载器 (Extension ClassLoader) c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } // 若父加载器和 Bootstrap ClassLoader 均加载失败 // 则调用当前类加载器的 findClass()方法 if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } // 是否加载之后进行解析 if (resolve) { resolveClass(c); } return c; } } // 获取 class name 对应的类加载器锁 protected Object getClassLoadingLock(String className) { Object lock = this; if (parallelLockMap != null) { Object newLock = new Object(); lock = parallelLockMap.putIfAbsent(className, newLock); if (lock == null) { lock = newLock; } } return lock; }
上述类加载过程即为 Java 的双亲委派模型,其工作过程是: 若一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父加载器去完成,每一个层次的类加载器均是如此,因此所有的加载请求最终都应该传递到最顶层的启动类加载器中,只有当父加载器反馈无法自己完成这个加载请求(它的搜索范围内没有找到所需的类)时,子加载器才会尝试自己完成加载。
使用双亲委派模型来组织类加载器之间的关系,一个显而易见的好处是 Java 中的类随着它的类加载器一起具备了一种带有优先级的层次关系。例如 java.lang.Object,它存在于 rt.jar 之中,无论哪一个类加载器要加载这个类,最终都会委派给处于模型最顶端的启动类加载器进行加载,因此 Object 类在程序的各种类加载器环境中都能够保证是同一个类。
反之,如果没有使用双亲委派模型,都由各个类加载器自行去加载的话,如果用户自己也编写了1个名为 java.lang.Object 的类,并放在程序的 classPath 中,那系统中就会存在多个不同的 Object 类,Java 类型体系中最基础的行为也就无从保证,应用程序将会变得一片混乱。
同时,loadClass() 方法使用 sycchronized 关键字来保证线程安全性,并维护了1个 ConcurrentHashMap<String, Object> parallelLockMap 来保存各个 class name 对应的 Object 锁,每个 class name 均对应1个 Object 锁的好处是,仅有加载同一个 class 的线程会互斥,加载不同 class 的线程不会互斥。试想一下,如果只传入1个 Object 作为公共锁,则所有执行 loadClass() 方法的线程在同一时刻仅能有1个获得执行,加载不同 class 的线程也会因此互斥(但该种情况是无需互斥的),造成方法执行效率低下。
由 loadClass() 方法的执行逻辑可知,类加载请求最终都会走到 findBootstrapClassOrNull() 方法,最先由启动类加载器 (Bootstrap ClassLoader) 来尝试类加载。
启动类加载器 (Bootstrap ClassLoader)
1
2
3
4
5
6
7
8
9
10private Class<?> findBootstrapClassOrNull(String name) { if (!checkName(name)) return null; return findBootstrapClass(name); } // return null if not found private native Class<?> findBootstrapClass(String name);
可以看到,findBootstrapClass() 方法是1个 native 方法,所以启动类加载器 (Bootstrap ClassLoader) 是由 C++来实现的。
启动类加载器 (Bootstrap ClassLoader) 负责加载存放在 <JAVA_HOME>lib 目录,或者被 -Xbootclasspath 参数所指定的路径中存放的,而且是 Java 虚拟机能够识别的(按照文件名识别,如 rt.jar、tools.jar,名字不符合的类库即使放在 lib 目录下也不会被加载)类库加载到虚拟机的内存中。
接着看一下 ClassLoader 抽象类的继承子类:
扩展类加载器 (Extension ClassLoader)
先看一下 ExtClassLoader 继承类:
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
67static class ExtClassLoader extends URLClassLoader { private static volatile Launcher.ExtClassLoader instance; // 双重校验创建 ExtClassLoader 单例 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; } private static Launcher.ExtClassLoader createExtClassLoader() throws IOException { try { return (Launcher.ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction<Launcher.ExtClassLoader>() { public Launcher.ExtClassLoader run() throws IOException { File[] var1 = Launcher.ExtClassLoader.getExtDirs(); int var2 = var1.length; for(int var3 = 0; var3 < var2; ++var3) { MetaIndex.registerDirectory(var1[var3]); } // 最终通过构造器方法创建 ExtClassLoader 实例 return new Launcher.ExtClassLoader(var1); } }); } catch (PrivilegedActionException var1) { throw (IOException)var1.getException(); } } void addExtURL(URL var1) { super.addURL(var1); } public ExtClassLoader(File[] var1) throws IOException { // 可以看到,ExtClassLoader 的父加载器为 null super(getExtURLs(var1), (ClassLoader)null, Launcher.factory); SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this); } private static File[] getExtDirs() { // 扫描系统变量 java.ext.dirs 指定的路径 String var0 = System.getProperty("java.ext.dirs"); File[] var1; if (var0 != null) { StringTokenizer var2 = new StringTokenizer(var0, File.pathSeparator); int var3 = var2.countTokens(); var1 = new File[var3]; for(int var4 = 0; var4 < var3; ++var4) { var1[var4] = new File(var2.nextToken()); } } else { var1 = new File[0]; } return var1; } ... }
可以看出,扩展类加载器 (Extension ClassLoader) 是 sun.misc.Launcher 的静态内部类,其负责加载 <JAVA_HOME>libext 目录中或者系统变量 java.ext.dirs 指定的路径下的所有类库。根据"扩展类加载器"这个名称就可以推断出这是一种 Java 系统类库的扩展机制,JDK 的开发团队允许用户将具有通用性的类库放置在 ext 目录下以扩展 Java SE 的功能。
在 JDK9 以后,这种扩展机制被模块化带来的天然扩展能力所取代。由于扩展类加载器是由 Java 代码实现的,开发者可以直接在程序中使用扩展类加载器来加载 Class 文件。
因为启动类加载器 (Bootstrap ClassLoader) 不是由 Java 代码实现的,所以扩展类加载器 (Extension ClassLoader) 的父加载器并不是启动类加载器,而是 null。也就是说,在 Java 这个地盘上,扩展类加载器 (Extension ClassLoader) 就是祖师爷。
应用程序类加载器 (Application ClassLoader)
接着看一下应用程序类加载器 (Application ClassLoader):
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
46static 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); // 这里的 var0 就是传入的 AppClassLoader 的父加载器 return new Launcher.AppClassLoader(var1x, var0); } }); } AppClassLoader(URL[] var1, ClassLoader var2) { super(var1, var2, Launcher.factory); this.ucp.initLookupCache(this); } public Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException { int var3 = var1.lastIndexOf(46); if (var3 != -1) { SecurityManager var4 = System.getSecurityManager(); if (var4 != null) { var4.checkPackageAccess(var1.substring(0, var3)); } } if (this.ucp.knownToNotExist(var1)) { Class var5 = this.findLoadedClass(var1); if (var5 != null) { if (var2) { this.resolveClass(var5); } return var5; } else { throw new ClassNotFoundException(var1); } } else { // 虽然复写了 loadClass() 方法,但本质上还是调用的 ClassLoader 类的 loadClass() 方法 return super.loadClass(var1, var2); } }
接着看一下,何处调用了 getAppClassLoader() 方法,以确定 AppClassLoader 的父加载器是谁?
由上图可知,Launcher 构造函数中调用了 getAppClassLoader() 方法,同时指定 ExtClassLoader 类作为 AppClassLoader 的父加载器。
那 Launcher 又是在何处实例化呢?
根据 Launcher 类的构造方法,其 getClassLoader() 方法的返回值就是 ApplicationClassLoader。
综上,应用程序类加载器 (Application ClassLoader) 是sun.misc.Launcher 的静态内部类,其负责加载用户类路径 (ClassPath) 上所有的类库,它的父加载器为 ExtClassLoader。同时应用程序类加载器是 ClassLoader 类中的 getSystemClassLoader() 方法的返回值,所以有些场合也称它为"系统类加载器"。
自定义类加载器 (Custom ClassLoader)
根据 ClassLoader 的 loadClass() 方法,如果想实现自己的类加载逻辑,可以继承 ClassLoader 类,并覆写 findClass() 方法,按照 loadClass() 方法的逻辑,如果父加载器加载失败,会自动调用自身的 findClass() 方法来完成加载,这样即不影响用户按照自己的意愿去加载类,又可以保证新写出来的类加载器是符合双亲委派规则的。
因为自定义类加载器 (Custom ClassLoader) 是继承自 ClassLoader 的,其创建实例的时候,必然先伴随着父类 ClassLoader 的创建。
1
2
3
4
5
6// 构造函数会调用 getSystemClassLoader() 方法指定父加载器 // getSystemClassLoader() 方法的返回值为 ApplicationClassLoader protected ClassLoader() { this(checkCreateClassLoader(), getSystemClassLoader()); }
综上,可以得出类加载器间的关系:
- 启动类加载器 (Bootstrap ClassLoader) 由 C++ 实现,无父加载器;
- 扩展类加载器 (Extension ClassLoader) 由 Java 实现,父加载器为 null;
- 应用程序类加载器 (Application ClassLoader) 由 Java 实现,父加载器为 Extension ClassLoader;
- 自定义类加载器 (Custom ClassLoader) 由用户基于 Java 实现,父加载器为 Application ClassLoader。
需要注意的是,上述类加载器之间的父子关系不是以继承 (Inheritance) 的关系来实现的,而是使用组合 (Composition) 关系来复用父加载器的代码。
讲完了 JDK8 版本,接着基于 JDK11 来讲解类加载机制。
JDK11
在 JDK9 中引入了 Java 模块化系统 (Java Platform Module System, JPMS) 是对 Java 技术的一次重要升级,为了能够实现模块化的关键目标–可配置的封装隔离机制,Java 虚拟机对类加载架构也做出了对应的变动调整,才能使模块化系统得以顺利地运行。
先看一下 ClassLoader 类的继承子类:
启动类加载器 (Boot ClassLoader)
1
2
3
4
5
6
7
8
9
10
11
12private static class BootClassLoader extends BuiltinClassLoader { // 其父加载器为 null BootClassLoader(URLClassPath bcp) { super(null, null, bcp); } @Override protected Class<?> loadClassOrNull(String cn) { return JLA.findBootstrapClassOrNull(this, cn); } };
平台类加载器 (Platform ClassLoader)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15private static class PlatformClassLoader extends BuiltinClassLoader { static { if (!ClassLoader.registerAsParallelCapable()) throw new InternalError(); } // Platform ClassLoader 的父加载器为 BootClassLoader PlatformClassLoader(BootClassLoader parent) { super("platform", parent, null); } private Package definePackage(String pn, Module module) { return JLA.definePackage(this, pn, module); } }
应用程序类加载器 (Application ClassLoader)
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
32private static class AppClassLoader extends BuiltinClassLoader { static { if (!ClassLoader.registerAsParallelCapable()) throw new InternalError(); } final URLClassPath ucp; // AppClassLoader 的父加载器为 PlatformClassLoader AppClassLoader(PlatformClassLoader parent, URLClassPath ucp) { super("app", parent, ucp); this.ucp = ucp; } @Override protected Class<?> loadClass(String cn, boolean resolve) throws ClassNotFoundException { SecurityManager sm = System.getSecurityManager(); if (sm != null) { int i = cn.lastIndexOf('.'); if (i != -1) { sm.checkPackageAccess(cn.substring(0, i)); } } // 调用父类 BuiltinClassLoader 的 loadClass() 方法 return super.loadClass(cn, resolve); } ... }
接着看一下 BuiltinClassLoader 的 loadClass() 方法:
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@Override protected Class<?> loadClass(String cn, boolean resolve) throws ClassNotFoundException { Class<?> c = loadClassOrNull(cn, resolve); if (c == null) throw new ClassNotFoundException(cn); return c; } protected Class<?> loadClassOrNull(String cn, boolean resolve) { synchronized (getClassLoadingLock(cn)) { // check if already loaded Class<?> c = findLoadedClass(cn); if (c == null) { // 判断该类归属于哪个系统模块 LoadedModule loadedModule = findLoadedModule(cn); // 如果该类有指定模块 if (loadedModule != null) { // package is in a module BuiltinClassLoader loader = loadedModule.loader(); if (loader == this) { if (VM.isModuleSystemInited()) { c = findClassInModuleOrNull(loadedModule, cn); } } else { // delegate to the other loader c = loader.loadClassOrNull(cn); } // 如果该类没有指定模块 } else { // 若存在父加载器,则交由父加载器负责加载 if (parent != null) { c = parent.loadClassOrNull(cn); } // check class path if (c == null && hasClassPath() && VM.isModuleSystemInited()) { c = findClassOnClassPathOrNull(cn); } } } if (resolve && c != null) resolveClass(c); return c; } }
自定义类加载器 (Custom ClassLoader)
因为自定义类加载器 (Custom ClassLoader) 是继承自 ClassLoader 的,其创建实例的时候,必然先伴随着父类 ClassLoader 的创建。
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
31protected ClassLoader() { this(checkCreateClassLoader(), null, getSystemClassLoader()); } @CallerSensitive public static ClassLoader getSystemClassLoader() { switch (VM.initLevel()) { case 0: case 1: case 2: // the system class loader is the built-in app class loader during startup return getBuiltinAppClassLoader(); case 3: String msg = "getSystemClassLoader cannot be called during the system class loader instantiation"; throw new IllegalStateException(msg); default: // system fully initialized assert VM.isBooted() && scl != null; SecurityManager sm = System.getSecurityManager(); if (sm != null) { checkClassLoaderPermission(scl, Reflection.getCallerClass()); } return scl; } } // 故自定义类加载器的父加载器为 AppClassLoader static ClassLoader getBuiltinAppClassLoader() { return ClassLoaders.appClassLoader(); }
综上,可以得出类加载器间的关系:
- 启动类加载器 (Boot ClassLoader) 由 Java 实现,父加载器为 null;
- 平台类加载器 (Platform ClassLoader) 由 Java 实现,父加载器为 Boot ClassLoader;
- 应用程序类加载器 (Application ClassLoader) 由 Java 实现,父加载器为 Platform ClassLoader;
- 自定义类加载器 (Custom ClassLoader) 由用户基于 Java 实现,父加载器为 Application ClassLoader。
自定义类加载器 (Custom ClassLoader) 加载类时,会调用 ClassLoader 的 loadClass() 方法,由于双亲委派机制,向上传导给其父加载器 Application ClassLoader。
父加载器 Application ClassLoader 覆写了 loadClass() 方法,然后转到其父类 BuiltinClassLoader 的 loadClass() 方法,进而调用 BuiltinClassLoader 的 loadClassOrNull() 方法。
BuiltinClassLoader 的 loadClassOrNull() 方法也是逐级往上抛,交由其父加载器先尝试类加载。
逐步传导到 Boot ClassLoader,然后调用其 loadClassOrNull() 方法:
1
2
3
4
5
6
7
8@Override protected Class<?> loadClassOrNull(String cn) { return JLA.findBootstrapClassOrNull(this, cn); } // 最终是通过 JavaLangAccess 来实现类的加载 private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
需要注意的是: JDK11 中的启动类加载器 (Boot ClassLoader) 和 JDK8 里面的启动类加载器 (Bootstrap ClassLoader) 不是一个东西。
不知不觉,越写越多,破坏双亲委派模型的案例还未讲解,后面专门再写一篇来讲吧。
本文到此结束,感谢阅读!
最后
以上就是大意舞蹈最近收集整理的关于基于源码深入了解Java的类加载机制(JDK8和JDK11双版本)的全部内容,更多相关基于源码深入了解Java内容请搜索靠谱客的其他文章。
发表评论 取消回复