Class 文件结构
Java 字节码文件主要包括以下几部分:
- 魔数与class 文件的版本
- 常量池
- 访问标志:表示该 class 的属性和访问类型,该 class 是类还是接口,是不是 public,是否被 final 修饰
- 类索引、父类索引、接口索引
- 字段表属性:用于描述接口或类中描述的变量。比如变量的作用域(public、peivate),是否是静态变量(static),可变性(final),数据类型(基本类型、对象、数组)等等
- 方法表属性:和字段表类似,描述的是方法的属性
- 属性表属性:用于描述某些特定场景专有的信息
常量池
常量池主要存放两大类:字面量和符号引用。字面量类似于Java的常量的概念,如文本字符串,final常量等。符号引用包括以下三种:
- 类和接口的全限定名
- 变量的名称和描述符号
- 方法的名称和描述符
JVM 是在类加载阶段才开始进行动态链接的,也就是说这些字段和符号引用只有在运行期转换后才能获取真正的内存入口地址
Java 类加载机制
类加载主要分为三个阶段:加载、连接和初始化;连接阶段又可分为:验证、准备和解析
加载阶段:查找并加载类的二进制数据
加载阶段主做三件事情:
- 通过类的全限定名来获取类的二进制字节流
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在 Java 堆中生成一个 java.lang.class 对象,作为对方法区中这些数据的访问入口
加载阶段总结:查找并加载类的二进制数据,并在 JVM 堆中创建一个 class 对象,并且将类的信息保存至方法区
连接阶段
验证:验证是否符合 Java 规范和 JVM 的规范
验证阶段主要进行的是对文件格式的验证、元数据验证、字节码验证和符号引用验证
准备:为类的静态变量分配内存,并初始化为默认值
准备阶段正式为变量分配内存并设置变量的初始值,这些内存都是在方法区中分配的。
- 在准备阶段进行内存分配的只有类变量(static),实例变量会在对象实例化时随着对象在堆中一起分配内存
- 在准备阶段为变量赋的初始值都是默认值(0, null, 0L, false)
解析:把类中的符号引用转换为直接引用
解析阶段是虚拟机将常量池内符号引用转换为直接引用的过程,符号引用包括类和接口的全限定名、变量的名称和描述符号、方法的名称和描述符。直接引用就是指向目标的指针。
初始化:为静态变量赋予正确的初始值
初始化阶段主要就是为静态变量赋予初始值
初始化时机:
- 使用 new 创建类的实例
- 访问类或接口的静态变量或对静态变量赋值
- 访问类的静态方法
- 反射
- 初始化某个子类其父类也会初始化
- 启动类
类加载器 类加载机制
类加载器
BootStrapClassLoader -> ExtClassLoader -> AppClassLoader -> User ClassLoader
类加载机制:双亲委派机制
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。
- 当 AppClassLoader 要加载一个 class 时,他首先不会自己尝试去加载这个类,而是将加载类的任务委托给父类 ExtClassLoader 去完成
- 当 ExtClassLoader 要加载一个 class 时,他首先不会自己尝试加载这个类,而是把类加载请求委托给 BootStrapClassLoader 去完成
- 如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;
- 若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException。