1. 类加载时机
jvm需将Class加载到jvm中才能执行指定程序, 那么java代码什么时候触发jvm加载Class呢? 一般是第一次用到这个类的时候加载(热加载就不说了)
- 用户代码主动new对象
- 反射动作中new对象
- 调用类的静态方法
- 调用类的静态属性
- 子类的加载触发父类加载
2. 类加载过程
类加载包含5个步骤, 分别为加载, 验证, 准备, 解析, 初始化
加载
将字节流转化成Class对象, 字节流可以来自任何地方, ClassLoader.loadClass
- 在方法区生成Class对象, 作为类的元数据
验证
- class是否符合当前jvm的版本要求(class文件的魔数)
- 校验class文件的各种指令是符合jvm规范, 比如对jvm造成损害
准备
- 按照类变量出现的顺序给赋值(各种类型的默认值)
- 如果是final static修饰的类变量, 则会根据编译期生成的常量池的常量赋值
解析
这一步主要将符号引用转换成直接引用, 符号引用与内存布局无关, 直接引用与jvm的内存布局有关
- 接口引用, 类引用, 方法引用, 属性引用...
初始化
执行类初始化<cinit>, 给类变量赋值(真正的值)
ClassLoader
BootstrapClassLoader --- ExtClassLoader --- AppClassLoader --- UserClassLoader
- BootstrapClassLoader 加载java核心包中的类
- ExtClassLoader 加载lib/ext路径中的类
- AppClassLoader 加载用户的类
类加载规则: 如果类A是 类加载AA加载的, 则A中引用的类也由AA加载
双亲委派模式
当触发类加载时, 首先查询缓存是否已经加载过, 加载过则完事了, 如果没加载则委派给父ClassLoader, 让父ClassLoader尝试加载, 如果父ClassLoader加载不了(返回null), 则当前类加载器尝试去加载类
为什么是要首先委派给父ClassLoader
- 防止String类会存在多个Class对象在jvm中, 而判断对象是否相等 == 首先判断这个类的加载器是否相同
线程上下文ClassLoader
按照双亲委派模式可以解决大部分问题, 但是SPI遇到问题啦~~~
按照双亲委派模式, mysql jdbc接口的实现类com.mysql.jdbc.Driver就加载不了了, 为什么呢?
ServiceLoader加载mysql如下: ServiceLoader.load --> Class.forName("com.mysql.jdbc.Driver"), 而ServiceLoader是BootstrapClassLoader加载的, 必然com.mysql.jdbc.Driver也是BootstrapClassLoader加载, 但是BootstrapClassLoader并不知道怎么加载mysql的Driver类, 因此按照双亲委派模式走不通了.