前言
学习过程关注以下问题
- 类的生命周期是怎样的?
- 类的加载时机,即哪些情况下会触发类加载?
- 其中哪些类的引用会触发初始化,哪些不会?
- 类的加载目录有哪些?
- JDK有哪些类加载器?
如何扩展自己的类加载器? - 有哪些优秀的类加载器?设计思路是什么?
- 重点:类加载过程,内存布局是怎样的?
- 堆
- 方法区:静态变量、常量池、代码块、类元信息、运行时常量池、栈
类的生命周期
加载 loading
- 通过一个类的全限定名来获取定义此类的二进制字节流
- 将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构
- 在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口
连接 linking
-
验证 verification
- 文件格式验证
- 元数据验证:类的继承、方法、字段等是否符合语义
- 字节码验证:对类的方法提进行验证,保证不会出现危害虚拟机安全的行为
- 符号引用验证,在解析阶段将符号饮用转化为直接引用
- 可以通过-Xverify:none参数来关闭此验证,如果认为加载的类都是安全的
-
准备 preparation
- 正式为类变量分配内存并设置类变量初始值,这些内存都将在方法区分配(1.8之后,类的静态变量存放到堆中,放置于Class实例对象的末尾)
- 一个有意思的例子
public static int value1 = 123; public static final int value2 = 123; // 对于value1,准备阶段时值为默认值0(还有一段赋值value1 = 123代码被编译到类构造器<clinit>) // 对于value2,准备阶段时值就是123。为什么? // 编译时Javac将会为value生成ConstantValue属性(回忆下类文件结构),在准备阶段虚拟机就会根据ConstantValue将value赋值为123
-
解析 resolution
- 虚拟机将常量池内的符号引用替换为直接引用的过程
- 符号引用(Symbolic References):符号引用以一组符号来描述所引用的目标,符号可以试任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到内存。
- 符号引用包含:类或接口、字段、类方法、接口方法、其他
- 直接引用(Direct References):直接引用可以是直接指向目标的指针、相对偏移亮或是一个能间接定位到目标的句柄。直接引用试与虚拟机实现的内存布局相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同,如果有了直接引用,那引用的目标必定已经在内存中存在。
-
todo 方法和属性属于父类字节码是怎样的?
初始化 initialization
- 必须进行初始化的情况
- 当虚拟机启动时,初始化用户指定的主类,就是启动执行的 main 方法所在的类;
- 当遇到用以新建目标类实例的 new 指令时,初始化 new 指令的目标类,就是 new 一 个类的时候要初始化;
- 当遇到调用静态方法的指令(invokestatic)时,初始化该静态方法所在的类;
- 当遇到访问静态字段的指令(getstatic、putstatic)时,初始化该静态字段所在的类;
- 子类的初始化会触发父类的初始化;
- 如果一个接口定义了 default 方法,那么直接实现或者间接实现该接口的类的初始化, 会触发该接口的初始化;
- 使用反射 API 对某个类进行反射调用时,初始化这个类,其实跟前面一样,反射调用要 么是已经有实例了,要么是静态方法,都需要初始化;
- 当初次调用 MethodHandle 实例时,初始化该 MethodHandle 指向的方法所在的类。
- 类初始化阶段是类加载过程的最后一步,前面除了自定义类加载器是用户程序参与外,这里才真正执行类中定义的Java程序代码(<Clinit>()类构造方法)
网友评论