一个 class 文件被加载到内存中需要经过 3 大步:装载、链接、初始化。
装载 装载是指 Java 虚拟机查找 .class 文件并生成字节流,然后根据字节流创建 java.lang.Class 对象的过程。
链接 链接过程分为 3 步:验证、准备、解析。
验证: 初始化 这是 class 加载的最后一步,这一阶段是执行类构造器方法的过程,并真正初始化类变量。
1.文件格式检验:检验字节流是否符合 class 文件格式的规范,并且能被当前版本的虚拟机处理。
2.元数据检验:对字节码描述的信息进行语义分析,以保证其描述的内容符合 Java 语言规范的要求。
3.字节码检验:通过数据流和控制流分析,确定程序语义是合法、符合逻辑的。
4.符号引用检验:符号引用检验可以看作是对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验。
准备: 准备是链接的第 2 步,这一阶段的主要目的是为类中的静态变量分配内存,并为其设置“0值”。比如:
public static int value = 100;
在准备阶段,JVM 会为 value 分配内存,并将其设置为 0。而真正的值 100 是在初始化阶段设置。并且此阶段进行内存分配的仅包括类变量,而不包括实例变量(实例变量将会在对象实例化时随着对象一起分配在 Java 堆中)。
有一种情况比较特殊–静态常量,比如:
public static final int value = 100;
以上代码会在准备阶段就为 value 分配内存,并设置为 100。
Java 中基本类型的默认”0值“如下:
基本类型(int、long、short、char、byte、boolean、float、double)的默认值为 0;
引用类型默认值是 null;
解析 解析是链接的最后一步,这一阶段的任务是把常量池中的符号引用转换为直接引用,也就是具体的内存地址。在这一阶段,JVM 会将常量池中的类、接口名、字段名、方法名等转换为具体的内存地址。
在 main 方法中通过 invokevirtual 指令调用了 print 方法,“Foo.print:()V"就是一个符号引用,当
main 方法执行到此处时,会将符号引用“Foo.print:()V”解析(resolve)成直接引用,可以将直接引用理解为方法真正的内存地址。
对于符号引用和直接引用,可以将其与生活中的微信聊天进行类比,在微信好友列表中,保存的是好友的名称或者别名(也就是符号引用),当我们真正给某个好友发消息时,计算机(JVM)会根据好友的名称找到对象计算机的 IP 地址(直接引用)并成功将消息发送给这一地址。
初始化 这是 class 加载的最后一步,这一阶段是执行类构造器方法的过程,并真正初始化类变量。比如:
public static int value = 100;
在准备阶段 value 被分配内存并设置为 0,在初始化阶段 value 就会被设置为 100。
总结一下对象的初始化顺序如下:
静态变量/静态代码块 -> 普通代码块 -> 构造函数