JAVA 类的加载过程 与类加载器
注:本文仅为个人在阅读《深入理解Java虚拟机》以及查阅资料之后的简单摘要,不保证 正确性!! 不保证 严谨性 !!
概要
Java类的加载分为三个阶段:1.类的加载
2. 连接
3. 类的初始化
。加载阶段
主要负责将class文件转换成内存中的数据结构;连接阶段
主要负责校验、纠错,符号引用的转化,对类的静态变量分配内存,赋予默认值;类的初始化阶段
,主要收集静态代码块,为类的静态变量赋予初始值。
Java类加载器,顾名思义负责加载类,具体来说,类的加载器负责了Java类的加载过程中的类的加载
部分, 默认不执行类的连接与初始化,除非重写loadClass。
正文
什么情况会导致类的初始化?
- 通过new关键字而直接产生的对象
- 方位类的静态变量或者静态方法
- 对某个类进行反射操作会导致类的初始化
- 初始化子类会导致父类的初始化
- 执行main函数会导致入口类被初始化
什么情况不会导致类的初始化?
- 构造某个对象数组,不会导致成员对象类被初始化
- 引用类的静态常量不会导致初始化
类的加载
- 通过某种方式,获得class文件的对应字节流
- 将字节流转换成元空间的元数据(Klass对象)
类的连接
- 验证:字节码、元数据、文件格式的验证
- 准备:为类变量分配内存,赋予默认值
- 解析:符号引用 变成 直接引用
类的初始化
执行<clinit>()方法,所有的类变量会被赋予正确的值
JVM三大类加载器
-
bootstrap classloader
根加载器,加载java核心类库
java.lang.*就是由它加载,它无法被获取(比如通过String.class.getClassLoader() 是无法获取的)
-
ext classloader
扩展加载器,加载jre/lib/ext子目录下的类库
-
application classloader
系统加载器,加载classpath下的类库资源(这可以某种程度上解释为什么我们在配置java环境的时候,需要配置classpath(_))
自定义类加载器默认的父类加载器就是app classloader
自定义类加载器
- 继承Classloader
- 重写loadClass方法----可以破坏双亲委托
- 重写findClass方法---不能破坏双亲委托
- 自定义将class文件转换成字节流的方式
- defineClass,返回class对象(此class对象不是klass对象)
- 让自定义加载器来加载类
- 绕开app classloader
- 在构造方法里面指定父类加载器为null,或者是ext classLoader
- 重写loadClass
- 绕开app classloader
双亲委托机制
- 类加载的loadClass方法是对外的类加载api
- loadClass方法实现了向上递归访问父类加载器,因此称为双亲委托
类加载空间与运行时包
- 类加载空间由父加载器+自己构成
- 运行时包=类加载空间+全限定类名
- 笔记待完善
class的卸载/回收
- 该类的所有实例已经被GC
- 加载该类的ClassLoader被回收
- 该类的class实例没有在其他地方有引用
上下文加载器
- ContextClassLoader 出现的原因
- 一些SPI接口,把服务交给了厂商去实现,自己只是搭了个架子。而通过约定(比如约定具体实现类存储路径等),SPI接口在架子里面查找实现类,并加载访问。由于一些SPI接口,属于核心类库,它们所用的加载器是bootstrap classloader,而具体实现类一般是位于classpath下的,也就是说应该被app classloader加载,而bootstrap classloader无法加载他们。为了解决这个问题,产生了ContextClassLoader。
- ContextClassLoader绕开了双亲委托机制,而可以通过Thread来直接访问。