Java虚拟(三)
上上一章我们讲了Java虚拟机的类加载,上一章我们又学习了运行时数据区,知道了类从哪里来,又要到哪里去,接下来我们探索类读取到虚拟机当中之后的变化。
在面试当中常常会闻到类加载相关的问题。
老生常谈 7个步骤:加载、验证、准备、解析、初始化、使用、卸载
第一步已经说过了,
验证:
- 防止动态获取生成的字节码数据流有问题
- 文件格式验证
1. 是否以魔数开头
2. 主次版本号是否在当前Java虚拟机接受范围之内
3. 常量池的常量中是否有不被支持的常量类型
4. 指向常量的各种索引值是否有误 - 元数据验证
- 是否有父类
- 父类是否继承了不允许被继承的类
- 字节码验证和符号引用验证
准备:
- 为类中的所有静态变量分配内存空间,并为其设置一个初始值(由于还没有产生对象,实例变量将不再此操作范围内,不包括实例变量)初始值为对应数据类型的零值
- final类型会被赋值
解析:
- 将常量池中所有的符号引用转为直接引用(得到类或者字段、方法在内存中的指针或者偏移量,以便直接调用该方法)。这个阶段可以在初始化之后再执行。
- 检查对方法或者字段的访问
- public
- protected
- private
- 类或接口的解析
- 字段解析
- 方法解析
- 接口方法解析
初始化:
- 通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。
- 定义对象数组,不会触发该类的初始化。
- 常量在编译期间会存入调用类的常量池中,本质上并没有直接引用定义常量的类,不会触 发定义常量所在的类。
- 通过类名获取 Class 对象,不会触发类的初始化。
- 通过 Class.forName 加载指定类时,如果指定参数 initialize 为 false 时,也不会触发类初 始化,其实这个参数是告诉虚拟机,是否要对类进行初始化。
- 通过 ClassLoader 默认的 loadClass 方法,也不会触发初始化动作。
- 先执行父类的方法,再执行子类
- 父接口中的变量被使用时才会执行
- 多个实现类调用,加锁同步
用一段代码去展示类在虚拟机运行时区域中如何存储,其他不做讨论。
在这里插入代码片
public class HelloWorld {
private int a = 1;
private String name = "hello world!";
private static int b = 2;
private static String des = "java";
private final int c = 8;
private final String value = "1.8";
public static void main(String[] args) {
HelloWorld helloWorld = new HelloWorld();
helloWorld.demo();
}
public void demo(){
System.out.println(a);
}
}
- 首先这个Java文件会被编译成.class文件 然后会被加载到虚拟机当中(内部类会被编译成一个独立的class文件)
- 类中的import 信息等会加载到方法区的
- 类的修饰信息
- 类的字段信息
- 类的方法信息
将.class文件加载到虚拟机当中是为了方便使用,因此,方法区中会记录与当前类有关的所有信息,所有元数据
虚拟机运行调用main方法时会创建main线程,由此会创建属于main线程的虚拟机栈、本地方法栈、程序计数器,并将main方法入栈。
new 关键字获取class信息 创建类实例并存入堆当中 至此 一个类中的数据信息大概就都加载完成了
Java虚拟机(五)