文章目录
- 1. 类文件
- 2. 类的生命周期
- 3. 类加载
- 3.1 加载
- 3.2 验证
- 3.3 准备
- 3.4 解析
- 3.5 初始化
- 4. 类加载器
- 4.1 JVM 角度区分两种类加载器
- 4.2 Java 程序中系统提供的三种类加载器
- 4.3 类加载器的双亲委派模型
- 5. 类文件结构(扩展阅读)
- 5.1 魔数与文件版本
- 5.2 常量池
- 5.3 访问标志
- 5.4 类索引、父类索引与接口索引集合
- 5.5 字段表集合、方法表集合、属性表集合
加载-验证-准备-解析-初始化-使用-卸载
某些情况下,解析可以在初始化之后;比如程序支持动态绑定。
加载阶段-验证阶段-准备阶段-解析阶段-初始化阶段
加载阶段主要是使用类加载器,根据类的全限定名,获取类的二进制字节流;
验证阶段对文本格式、元数据、字节码、符号引用进行验证;
准备阶段在非堆为静态变量分配内存,设置初始值零值;
解析阶段将常量池内的符号引用替换为直接引用;
初始化阶段开始执行字节码,初始化类变量和其他资源,对静态变量进行赋值等。
加载只是类加载的第一个阶段,类加载全过程包括加载、验证、准备、解析、初始化。
通过类的全限定名获取定义此类的二进制字节流,将字节流代表的静态存储结构转化为运行时数据结构,在内存中生成 java.lang.Class 对象
。通过类全限定名获取二进制字节流的方式可以是
(1) 从 jar、war、war、zip 包读取
(2) 从网络读取
(3) 动态代理
(4) 由其他文件生成
(5) 从数据库读取
验证是类加载阶段重要非必须阶段,可以通过 -Xverify:none
关闭这一耗时措施,来缩短类加载时长。
验证类文件的字节流中信息符合当前 JVM 要求,确保对当前 JVM 是安全的
。(1) 文件格式验证
验证字节流是否符合类文件格式的规范,确保都能被当前 JVM 处理
(2) 元数据验证
对类的元数据信息进行语义校验,确保都符合 Java 语言规范
(3) 字节码验证
验证类的方法体,通过数据流和控制流分析,验证类方法体的字节码,确保程序语义合法且符合逻辑;
(4) 符号引用验证
确保解析动作能正常执行
类型检查:
方法体 Code 属性的属性表增加名称为 StackMapTable 的属性,描述方法体中所有基本块开始时的本地变量表和操作栈应有的状态,字节码验证期间可以通过校验 StackMapTable 属性中的记录是否合法即可。
数据流验证复杂,JDK6和JDK7 实现了类型检查,JDK7 之后对于主版本号大于 50 的类文件,使用类型检查完成数据流分析校验。
准备是在非堆为类的静态变量分配内存,设置静态变量初始值
;public static int valuie = 123;
一般的,在准备阶段,静态变量在非堆分配内存,准备阶段静态变量初始值为零值。初始化阶段会赋值 123 给 value。
public static final int valuie = 1;
特殊情况,final 修饰的静态变量,编译时会为 value 生成 ConstantValue 属性,在准备阶段就会根据 ConstantValue 对 value 赋值为 1。
八大基础数据类型零值为 (boolean)false,(byte)0,(char)’\u0000’,(short)0,(int)0,(float)0.0f,(long)0,(double)0.0d;引用类型零值为 (reference)null;
解析阶段是 JVM 将常量池内的符号引用替换为直接引用的过程
。
符号引用 | 常量类型 |
---|---|
类或接口 | CONSTANT_Class_info |
字段 | CONSTANT_Fieldref_info |
类方法 | CONSTANT_Methodref_info |
接口方法 | CONSTANT_InterfaceMethodref_info |
方法类型 | CONSTANT_MethodType_info |
方法句柄 | CONSTANT_MethodHandle_info |
调用点限定符 | CONSTANT_InvokeDynamic_info |
(1) 符号引用
以一组符号来描述所引用的目标,符号可以是任意形式的字面量。
(2) 直接引用
直接指向目标的指针、相对偏移量,或是间接定位到目标的句柄。
加载、验证、准备、解析前四个阶段都由 JVM 主导;初始化阶段开始执行字节码程序
。
类初始化的场景
(1) 遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时,需要先触发其初始化;
(2) java.lang.reflect 对类进行反射调用时,若类没有进行过初始化,则需要触发初始化;
(3) 初始化一个类时,若父类没有初始化,则父类需要触发初始化
(4) JVM 启动时需要指定一个主类,需要首先初始化此类;
(5) java.lang.invoke.MethodHandle 实例解析后的结果是 REF_getStatic、REF_putStatic、REF_invokeStatic 的方法句柄,方法句柄对应的类没有被初始化,则先初始化对应的类
类加载的加载阶段,是根据类的全限定名获取描述此类的二进制字节流。实现加载动作的代码模块就是类加载器
类加载器和类本身共同保证其在 JVM 的唯一性。同一个类文件使用不同的类加载器处理,得到的类不相等。
应用程序类加载器又称系统类加载器
,防止概念混淆,文中其他部分引用该加载器,统称为系统类加载器。
一般的,程序中没有自定义类加载器时,默认使用系统类加载器。
自定义类加载器是程序开发者自定义的非启动类加载器。
类别 | 程序可直接引用 | 支持加载的类库 |
---|---|---|
启动类加载器 | 否 | JAVA_HOME\lib 目录下的类库 和参数 -Xbootclasspath 指定路径中的类库 |
扩展类加载器 | 是 | JAVA_HOME\lib\ext 目录下的类库 和系统变量 java.ext.dirs 指定路径中的类库 |
系统类加载器 | 是 | 系统变量 classpath 指定路径中的类库 |
自定义类加载器 | 是 |
类加载器的层次关系
称之为类加载器的双亲委派模型。类加载器之间存在优先级,使得类也有了优先级,能保证被加载的类在各种类加载器环境中都是同一个类。
前面不是提到类加载器和类本身保证其在 JVM 的唯一性
,类通过委派来保证,其能够被更顶端的类加载器所加载。
双亲委派模型是官方推荐的类加载器实现方式,不是强制约束,某些场景下不是双亲委派模型。比如线程提供了上下文类加载器,可以在程序中指定使用某种类加载器。
类文件结构包含魔数与文件版本
、常量池
、访问标志
、类索引,夫类索引和接口索引集合
、字段表集合
、方法表集合
、属性表集合
。
类文件格式支持无符号数和表两种数据类型,类文件的本质就是表。
类文件伪结构的两种数据类型:
无符号数是基本数据类型,表是由多个无符号数或其他表组成的复合数据类型。
无符号数,以 u1、u2、u4、u8 分别代表 1 个、2 个、4 个、8 个字节的无符号数。可以用来描述数字、索引引用、数量值或字符串值。
类文件是以 8 为字节为基础的二进制流,用记事本打开一个类文件,部分字节信息如下所示。
cafe babe 0000 0034 0013 0a00 0400 0f0a
0004 0010 0700 1107 0012 0100 063c 696e
6974 3e01 0003 2829 5601 0004 436f 6465
...
魔数
魔数
,固定十六进制值为 0xcafebabe文件版本
类文件版本
,次版本号十六进制值为 0x0000,主版本号十六进制值为 0x0034,转为十进制版本号就是 52.0。常量池存放字面量和符号引用
字面量
符号引用
常量池结束后,紧接着的两个字节用来描述访问标志
;类索引描述当前类,父类索引描述继承了那个类,接口索引集合描述实现了那些接口
。由这三项数据来确定当前类的继承关系;Java 单继承,最多只一个父类, java.lang.Object 没有父类。类索引和父类索引用基本数据类型就够了;java.lang.Object 的父类索引为 0。
Java 多实现,支持实现多个接口,所以要用集合
字段表集合
private static int threadInitNumber;
private volatile int threadStatus = 0;
修饰符(访问标志 access_flags)
字段修饰符放在访问标志中,例如 public、static、final、volatile、transient
名称 (名称索引 name_index)
一般的简单名称(例如:方法名 notify,equals,字段名 obj),全限定名(例如: java/lang/Object)
描述符 (描述符索引 descriptor_index)
描述字段的数据类型、方法的参数列表和返回值
属性表集合
存储额外的信息,描述零项或多项额外信息。
方法表集合
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
private native void start0();
属性表集合
方法表与字段表区别在于访问标志。
volatile、transient 不能修饰方法,所以方法表的访问标志不包含 ACC_VOLATILE 和 ACC_TRANSIENT 标志。
synchronized、native、strictfp、abstract 可以修饰方法,方法表标志增加了 ACC_SYNCHRONIZED、ACC_NATIVE、ACC_STRICTFP、ACC_ABSTRACT。
Powered By niaonao