共计 5790 个字符,预计需要花费 15 分钟才能阅读完成。
关注“Java 后端技术全栈”
回复“000”获取大量电子书
类加载器
类加载器是很多人认为很硬的骨头。其实也没那么可怕,请听老田缓缓道来。
在装载 (Load) 阶段,通过类的全限定名获取其定义的二进制字节流,须要借助类装载器实现,顾名思义,就是用来装载 Class 文件的。
下面咱们自定义一个 String 出了问题,问题在于 JVM
不晓得咱们想用哪个类,于是 JVM
就定义了个标准。
把这品种装载器分成几类。
Bootstrap ClassLoader
负责加载 $JAVA_HOME 中 jre/lib/rt.jar
里所有的 class 或 Xbootclassoath
选项指定的 jar 包。由 C ++ 实现,不是 ClassLoader
子类。
Extension ClassLoader
负责加载 Java
平台中扩大性能的一些 jar 包,包含 $JAVA_HOME 中 jre/lib/*.jar
或 -Djava.ext.dirs
指定目录下的 jar 包。
App ClassLoader
负责加载 classpath
中指定的 jar 包及 Djava.class.path
所指定目录下的类和 jar 包。
Custom ClassLoader
通过 java.lang.ClassLoader
的子类自定义加载 class,属于应用程序依据本身须要自定义的 ClassLoader
,如tomcat
、jboss
都会依据 j2ee
标准自行实现ClassLoader
。
图解类加载
加载准则
查看某个类是否曾经加载:程序是自底向上,从 Custom ClassLoader
到BootStrap ClassLoader
逐层查看,只有某个 Classloader
已加载,就视为已加载此类,保障此类只所有 ClassLoader
加载一次。
加载的程序:加载的程序是自顶向下,也就是由下层来逐层尝试加载此类。
ClassLoader 类剖析
java.lang.ClassLoader
中很重要的三个办法:
loadClass
办法
findClass
办法
defineClass
办法
loadClass 办法
1 public Class<?> loadClass(String name) throws ClassNotFoundException {2 return loadClass(name, false);
3 }
4 protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
5 // 应用了同步锁,保障不呈现反复加载
6 synchronized (getClassLoadingLock(name)) {
7 // 首先查看本人是否曾经加载过
8 Class<?> c = findLoadedClass(name);
9 // 没找到
10 if (c == null) {11 long t0 = System.nanoTime();
12 try {
13 // 有父类
14 if (parent != null) {
15 // 让父类去加载
16 c = parent.loadClass(name, false);
17 } else {
18 // 如果没有父类,则委托给启动加载器去加载
19 c = findBootstrapClassOrNull(name);
20 }
21 } catch (ClassNotFoundException e) {
22 // ClassNotFoundException thrown if class not found
23 // from the non-null parent class loader
24 }
25
26 if (c == null) {
27 // If still not found, then invoke findClass in order
28 // to find the class.
29 long t1 = System.nanoTime();
30 // 如果都没有找到,则通过自定义实现的 findClass 去查找并加载
31 c = findClass(name);
32
33 // this is the defining class loader; record the stats
34 sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
35 sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
36 sun.misc.PerfCounter.getFindClasses().increment();
37 }
38 }
39 // 是否须要在加载时进行解析
40 if (resolve) {41 resolveClass(c);
42 }
43 return c;
44 }
45 }
正如 loadClass
办法所展现的,当类加载申请到来时,先从缓存中查找该类对象,如果存在间接返回,如果不存在则交给该类加载去的父加载器去加载,假使没有父加载则交给顶级启动类加载器去加载,最初假使仍没有找到,则应用 findClass()
办法去加载(对于 findClass()
稍后会进一步介绍)。
从 loadClass
实现也能够晓得,如果不想从新定义加载类的规定,也没有简单的逻辑,只想在运行时加载本人指定的类,那么咱们能够间接应用 this.getClass().getClassLoder.loadClass("className")
,这样就能够间接调用ClassLoader
的loadClass
办法获取到 class 对象。
findClass 办法
1 protected Class<?> findClass(String name) throws ClassNotFoundException {2 throw new ClassNotFoundException(name);
3 }
在 JDK1.2
之前,在自定义类加载时,总会去继承 ClassLoade
r 类并重写loadClass
办法,从而实现自定义的类加载类,然而在 JDK1.2
之后已不再倡议用户去笼罩 loadClass
() 办法,而是倡议把自定义的类加载逻辑写在 findClass()
办法中。
从后面的剖析可知,findClass()
办法是在 loadClass
() 办法中被调用的,当 loadClass
() 办法中父加载器加载失败后,则会调用本人的 findClass()
办法来实现类加载,这样就能够保障自定义的类加载器也合乎双亲委托模式。
须要留神的是,ClassLoader
类中并没有实现 findClass()
办法的具体代码逻辑,取而代之的是抛出 ClassNotFoundException
异样。同时应该晓得的是,findClass
办法通常是和 defineClass
办法一起应用的。
defineClass 办法
1 protected final Class<?> defineClass(String name, byte[] b, int off, int len,
2 ProtectionDomain protectionDomain) throws ClassFormatError{3 protectionDomain = preDefineClass(name, protectionDomain);
4 String source = defineClassSourceLocation(protectionDomain);
5 Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
6 postDefineClass(c, protectionDomain);
7 return c;
8 }
defineClass()
办法是用来将 byte 字节流解析成 JVM
可能辨认的 Class 对象。
通过这个办法不仅可能通过 class 文件实例化 class 对象,也能够通过其余形式实例化 class 对象,如通过网络接管一个类的字节码,而后转换为 byte 字节流创立对应的 Class 对象。
如何自定义类加载器
用户依据需要本人定义的。须要继承自ClassLoader
, 重写办法findClass()
。
如果想要编写本人的类加载器,只须要两步:
- 继承
ClassLoader
类 - 笼罩
findClass(String className)
办法
**ClassLoader**
超类的 loadClass
办法用于将类的加载操作委托给其父类加载器去进行,只有当该类尚未加载并且父类加载器也无奈加载该类时,才调用 findClass
办法。
如果要实现该办法,必须做到以下几点:
1. 为来自本地文件系统或者其余起源的类加载其字节码。
2. 调用 ClassLoader
超类的 defineClass
办法,向虚拟机提供字节码。
浅谈双亲委派模型
这个在面试中也是频率相当高。
如果一个类加载器在接到加载类的申请时,先查找是否曾经加载过,如果没有被加载过,它首先不会本人尝试去加载这个类,而是把这个申请工作委托给父类加载器去实现,顺次递归。
如果父类加载器能够实现类加载工作,就胜利返回;只有父类加载器无奈实现此加载工作时,才本人去加载。
劣势
Java 类随着加载它的类加载器一起,具备了一种带有优先级的档次关系。
比方,Java 中的 Object 类,它寄存在 rt.jar 之中, 无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因而 Object 在各种类加载环境中都是同一个类。
如果不采纳双亲委派模型,那么由各个类加载器本人取加载的话,那么零碎中会存在多种不同的 Object 类。
突破双亲委派模型的案例
tomcat
tomcat 通过 war 包进行利用的公布,它其实是违反了双亲委派机制准则的。简略看一下 tomcat 类加载器的层次结构。
对于一些须要加载的非根底类,会由一个叫作 WebAppClassLoader 的类加载器优先加载。等它加载不到的时候,再交给下层的 ClassLoader 进行加载。这个加载器用来断绝不同利用的 .class 文件,比方你的两个利用,可能会依赖同一个第三方的不同版本,它们是互相没有影响的。
如何在同一个 JVM 里,运行着不兼容的两个版本,当然是须要自定义加载器能力实现的事。
那么 tomcat 是怎么突破双亲委派机制的呢?
能够看图中的 WebAppClassLoader,它加载本人目录下的 .class 文件,并不会传递给父类的加载器。然而,它却能够应用 SharedClassLoader 所加载的类,实现了共享和拆散的性能。
然而你本人写一个 ArrayList,放在利用目录里,tomcat 仍然不会加载。它只是自定义的加载器程序不同,但对于顶层来说,还是一样的。
OSGi
OSGi 已经十分风行,Eclipse 就应用 OSGi 作为插件零碎的根底。
OSGi 是服务平台的标准,旨在用于须要长运行工夫、动静更新和对运行环境毁坏最小的零碎。
OSGi 标准定义了很多对于包生命周期,以及基础架构和绑定包的交互方式。这些规定,通过应用非凡 Java 类加载器来强制执行,比拟王道。
比方,在个别 Java 应用程序中,classpath 中的所有类都对所有其余类可见,这是毋庸置疑的。然而,OSGi 类加载器基于 OSGi 标准和每个绑定包的 manifest.mf 文件中指定的选项,来限度这些类的交互,这就让编程格调变得十分的怪异。但咱们不难想象,这种与直觉相违反的加载形式,必定是由专用的类加载器来实现的。
随着 jigsaw 的倒退(旨在为 Java SE 平台设计、实现一个规范的模块零碎),我集体认为,当初的 OSGi,意义曾经不是很大了。
OSGi 是一个宏大的话题,你只须要晓得,有这么一个简单的货色,实现了模块化,每个模块能够独立装置、启动、进行、卸载,就能够了。
SPI
Java 中有一个 SPI 机制,全称是 Service Provider Interface,是 Java 提供的一套用来被第三方实现或者扩大的 API,它能够用来启用框架扩大和替换组件。
前面会再专门针对这个写一篇文章,这里就不细说了。
举荐浏览:
《零碎架构:简单零碎的产品设计与开发》.pdf
《一线架构师实际指南》.pdf
《循序渐进 Linux(第 2 版)》.pdf
关注公众号“Java 后端技术全栈”
收费获取 500G 最新学习材料