类的生命周期
“家宴准备了西式菜”,即家(加载)宴(验证)准备(准备)了西(解析)式(初始化)菜
- 加载(Loading),实现网络加载、热加载等,需要自定义ClassLoader时在这个阶段覆写方法
- 通过类的全限定名获取定义此类的二进制字节流(这部分被JVM团队抽象为ClassLoader,提供给用户进行扩展)[7]
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
- 校验(Verification),检查字节是否格式完好(例如魔数0xCAFEBABE),确保代码不会执行非法操作
- 准备(Preparing),为static变量分配空间、初始化零值
- 解析(resolution) ,符号引用(如指向类或者对象域的引用)会在运行时被解析为直接引用
- 初始化(Initialization),执行类的初始化方法(静态块代码执行,static字段赋值)
为了支持Java语言的运行时绑定特性,解析阶段可能在初始化阶段之后才开始。
ClassLoader
介绍
类加载器是
- 虚拟机团队把类加载阶段中“通过类的全限定名获取定义此类的二进制字节流”这个动作放到JVM外部去实现
- 以便应用程序自己决定如何获取所需要的类
实现这个动作的代码模块被抽象为了“类加载器ClassLoader”。也就是说
- JVM团队通过ClassLoader把加载阶段的部分功能外包给应用开发者自己扩展实现。[1]
ClassLoader既然暴露给开发者,那就需要有一定的基本原则,防止一些JVM的重要类加载被干扰,因此引入了双亲委派模型(Parent-Delegation Model) 。
双亲委派模型(Parent-Delegation Model)
双亲委派模型的结构如图。
public void printClassLoaders() throws ClassNotFoundException {
System.out.println("Classloader of this class:"
+ PrintClassLoader.class.getClassLoader());
System.out.println("Classloader of Logging:"
+ Logging.class.getClassLoader());
System.out.println("Classloader of ArrayList:"
+ ArrayList.class.getClassLoader());
}
执行以上代码将得到如下的输出结果,从执行结果中可以看出,不同的类所用的ClassLoader是不同的。
Class loader of this class:sun.misc.Launcher$AppClassLoader@18b4aac2
Class loader of Logging:sun.misc.Launcher$ExtClassLoader@3caeaf62
Class loader of ArrayList:null
类加载器分为如下几种[1][2]:
- 启动类加载器(Bootstrap ClassLoader)
- 负责加载JDK的内部类(rt.jar等),位于
$JAVA_HOME/jre/lib - native代码写的,负责加载其它ClassLoader,是所有ClassLoader的父加载器
- 负责加载JDK的内部类(rt.jar等),位于
- 扩展类加载器(Extension ClassLoader)
- 负责加载
$JAVA_HOME/lib/ext或者java.ext.dirs系统变量所指的位置
- 负责加载
- 应用程序类加载器(Application ClassLoader)
- 也称系统类加载器,加载-classpath或-cp目录下的类,是Extension ClassLoader的子加载器
双亲委派模型的细节可以参考 这里 ,大致意思就是将类加载的工作优先交给父加载器处理,类似于Object这样的内部核心类确保只能有一套。特点:
- 系统类防止内存中出现多份同样的字节码
- 防止核心类被篡改,保证Java程序安全稳定运行
- 可见性,子加载器可以看到父加载器加载的类,但是父加载器看不到下面的(因此JDBC才需要打破Parent-Delegation Model)
双亲委派模型的实现代码参考JDK源码 ClassLoader#loadClass(String name)
ClassLoader的各个方法
- loadClass,JVM会调用它,可以理解为ClassLoader的入口。根据全限定名去加载类,双亲委派模型的执行过程就在这个方法里。双亲处理不了的时候,当前ClassLoader会执行findClass方法
- 重写这个方法可以打破双亲委派模式,可以支持热加载
- findClass,根据全限定名去加载类,会调用defineClass方法
- 重写这个方法可以从网络上加载Class字节码
- defineClass,把字节码转换成Class,我们不需要重写,也没法重写这个方法(因为已经被定义为final)
自定义ClassLoader的使用场景
- 资源隔离
- tomcat,海若
- 依赖冲突处理
- pandora:每个中间件用自定义的加载器,中间件根据配置信息将需要暴露出的class放到一个hashmap里,给业务代码持有
- 热部署
- tomcat、spring boot devtools 参考
- 代码保护
- 不得不打破双亲委派——JDBC,详见 JDBC是如何打破双亲委派模式的
参考
2、Class Loaders in Java | Baeldung
3、java - How Tomcat Classloader separates different Webapps object scope in same JVM? - Stack Overflow
4、万万没想到,面试中,连 ClassLoader类加载器 也能问出这么多问题….. - Java知音号 - 博客园
5、java - Why the book says JDBC damage the Parental delegation model? - Stack Overflow
6、Why do JDBC and Tomcat destroy the parental delegation model? - Katastros
7、Class loading - IBM Documentation
8、Chapter 5. Loading, Linking, and Initializing