Java类加载机制
类加载就是将类的.class文件的二进制数据读入到内存,并放在运行时数据区的方法区,并在堆内存中创建Class对象,用来封装方法区内二进制文件流的数据结构。
类加载的最终产品就是在堆内存中的Class对象
类生命周期
1. 加载:找class文件在哪
- 通过一个类的全限定名来获取二进制字节流
- 将字节流转化为方法区的运行时数据结构
- 在堆中创建一个表示该类的
java.lang.Class
对象,作为访问方法区中运行时数据的入口
2. 验证:验证class文件格式规范、语义分析、引用验证、字节码验证
- 文件格式验证,检验字节流是否符合class文件格式规范
- 元数据验证,堆字节码描述的信息进行语义分析
- 字节码验证,分析控制流和数据流语序语义是否正确
- 符号引用验证,确保动作能正确执行
3. 准备:分配内存、设置类static修饰的变量初始值
4. 解析:类、接口、字段、类方法等解析
5. 初始化:为静态变量赋值;执行静态代码块
6. 使用:创建实例对象
7. 卸载:从JVM方法区中卸载
2.3.4合称连接
1.加载
- 通过一个类的全限定名来获取二进制字节流
- 将外部二进制流结构转换为JVM所需的格式存储在方法区
- 在java堆中生成一个代表这个类的Class对象,作为访问方法区中二进制流的入口
2.验证
- 文件格式验证,验证字节流是否符合Class文件格式规范
- 元数据验证,堆字节码描述的信息进行语义分析,保障信息符合Java语言规范
- 字节码验证,通过数据流和控制流分析语义是否合法、合逻辑
- 符号引用验证,确保解析动作顺利执行
3.准备
- 进行内存分配,静态变量设置初始值(默认的初始值,用户指定的赋值在初始化阶段,但是如果字段被final static同时修饰,那么也会在准备阶段进行用户指定的赋值)
4.解析
- 把类中的符号引用,转换为直接引用
- 符号引用:类,接口,字段,类方法,接口方法,方法类型,方法句柄和调用限定符, 它们保存在常量池
- 直接引用:直接指向目标的指针
5.初始化
为静态变量赋予正确的值
- 如果这个类还没有被加载和连接,则要先加载并连接该类
- 假如该类的直接父亲还没有被初始化,则先初始化直接父类
- 假如类中有初始化语句,则依次执行这些初始化语句
类初始化的触发时机
- 当虚拟机启动时,初始化用户指定的类(main)
- 当遇到用于新建目标类实例的指令时(new), 初始化目标类
- 当遇到静态方法的指令时,初始化静态方法所在类
- 当遇到访问静态属性的指令时,初始化静态属性所在的类
- 子类的初始化会触发父类的初始化
- 如果一个接口定义了default方法, 那么直接或间接的实现该接口类的初始化方法, 会触发该接口的初始化
- 使用反射API对某个类进行反射调用时会触发初始化这个类
- 当调用MethodHandle实例时,初始化该MethodHandle指向的方法所在的类
类不会初始化(可能加载)
- 通过子类引用父类的静态字段只会触发父类的初始化,而不会触发子类的初始化
- 定义对象数组, 不会触发该类的初始化
- 常量在编译期间会存入调用类的常量池,本质上没有直接引用定义常量的类,不会触发定义常量类的初始化
- 通过类名获取Class对象, 不会触发类的初始化
- 通过Class.forName加载指定类,如果指定参数initialize为false时,不会触发初始化
- 通过ClassLoader默认的loadClass方法,也不会触发初始化动作
类加载器
类加载器负责装入类,搜索各个位置的类资源.
一个Java程序运行,至少有三个类加载器实例,负责不同类的加载
- Bootstrap Loader 核心类库加载器,由C语言实现,加载JDK核心类库
- Extension Class Loader 拓展类库加载器,加载JDK拓展包
- application class loader 用户应用程序加载器,加载classpath路径下的包
类不会重复加载
类的唯一性:同一个类加载器,类名一样,代表同一个类.
双亲委托机制
某一个类加载器想要去加载一个类,并不会立刻去加载,而是委托给父亲,如果父亲上面还有父亲,则继续向上委托,最终到达核心类库加载器,如果核心类库无法记载,再向下一个个委托.
即,有父类交给父类加载,没父类了开始加载,无法加载向下委托,全都不能加载则失败
通过双亲委派机制
- 建立类的层级关系可以避免类的重复加载,当父亲已经加载该类时,就没必要再加载一次了.
- 优先加载核心类库, 这样防止用户恶意的同名类,因为永远是核心的类先被加载.