赞
踩
类加载的过程分为 5 个阶段:加载、验证、准备、解析、初始化。
以如下代码为例,对 Java 类加载的过程进行阐述。
- package com.zero.demo;
-
- public class SuperClassDemo {
-
- static {
- System.out.println("SuperClass Static Block!");
- }
-
- public static int VALUE = 11;
-
- private String str = "Hello World";
-
- private int value;
-
- public int getValue() {
- return value;
- }
-
- public void setValue(int value) {
- this.value = value;
- }
- }
加载(Loading)是类加载(Class Loading)过程的其中一个阶段。
在加载阶段,JVM 需要完成 3 个步骤
◉ 通过类的全限定名来获取这个类的二进制字节流。
◉ 将字节流转化为方法区的运行时数据结构。
◉ 在内存中生成一个代表这个类的 java.lang.Class 对象,作为这个类在方法区的访问入口。
以 SuperClassDemo 为例,加载阶段的过程,如下图所示:
不过,对于数组类而言,数组类本身不通过类加载器创建,它是由 JVM 直接在内存中动态构造出来的,但是数组的元素类型仍然需要依靠类加载器去创建。
验证是连接阶段的第一步,目的是保证加载的字节码是合法的。
验证阶段包含 4 个动作:文件格式验证、元数据验证、字节码验证和符号引用验证。
文件格式验证
验证字节流是否符合 Class 文件格式的规范,包括以下这些内容:
(1)是否以魔数 0xCAFEBABE 开头
(2)版本号是否跟 JVM 兼容
(3)检查常量池的常量tag标志
(4)……
元数据验证
对字节码描述的信息进行语义分析,包括以下这些内容:
(1)是否有父类,除了 java.lang.Object,所有的类都有父类
(2)是否继承了不允许被继承的类,例如:final 修饰的类
(3)如果不是抽象类,是否实现了其父类或接口中的所有方法
(4)……
字节码验证
验证程序语义是合法的、符合逻辑的,对类的方法体(Class 文件中的 Code 属性)进行校验分析。
例如:检验方法体的类型转换
List<String> list = new ArrayList<>();
符号引用验证
验证类中引用的资源(外部类、方法、变量)是否存在,访问权限是否合法。
否则,JVM 会抛出 java.lang.IncompatibleClassChangeError 的子类异常,例如:
(1)java.lang.IllegalAccessError
(2)java.lang.NoSuchFieldError
(3)java.lang.NoSuchMethodError
(4)……
准备是连接阶段的第二步,目的是为静态变量(被 static 修饰的变量)分配内存,初始化默认值。
Java 中所有基本数据类型的默认值,如下表所示:
类型 | 默认值 |
int | 0 |
long | 0L |
short | (short)0 |
char | '\u0000' |
byte | (byte)0 |
boolean | false |
float | 0.0f |
double | 0.0d |
reference | null |
注:JVM 中 boolean 类型映射为 int 类型,false 用 0 表示,true 用 1 表示,由于 int 默认值为 0,因此 boolean 的默认值为 false。
以 SuperClassDemo 为例,VALUE 将会被初始化为 0。
- // 即使代码中 VALUE 赋值为 11,不过在准备阶段,VALUE 的值为 0
- public static int VALUE = 11;
不过,如果 VALUE 的代码改成如下所示,VALUE 将会被初始化为 11。
- // 这里涉及到 ConstantValue属性 的相关概念
- public final static int VALUE = 11;
解析是连接阶段的第二步,目的是将接口、变量、方法的符号引用转换为直接引用。
符号引用
以一组符号来描述所引用的目标,可以理解为,以文字形式来描述引用关系。
例如,SuperClassDemo 中的符号引用,通过 javap -verbose 命令打印,如下图所示:
- // 以这行代码为例,分析符号引用
- System.out.println("SuperClass Static Block!");
直接引用
可以理解为,指向存放目标的内存地址的引用。
初始化是类加载过程的最后一个步骤,就是执行类构造器 <clinit>() 方法的过程。在此阶段,JVM 会执行执行类中编写的Java程序代码,对类的静态变量,静态代码块执行初始化操作。
例如,SuperClassDemo 中被 static 修饰的变量和代码块。
- // 按顺序执行
-
- // 将会打印出 “SuperClass Static Block!” ,而且只会打印一次
- static {
- System.out.println("SuperClass Static Block!");
- }
-
- // 在准备阶段,VALUE 的值为 0,在初始化阶段,VALUE 的值为 11
- public final static int VALUE = 11;
除此之外,<clinit> 的执行顺序是,父类的 <clinit> 方法总是在子类 <clinit> 方法之前被调用。
例如,ChildClassDemo 继承于 SuperClassDemo,如下所示:
- package com.zero.demo;
-
- public class ChildClassDemo extends SuperClassDemo {
- static {
- System.out.println("ChildClass Static Block!");
- }
-
- public static void main(String[] args) {
- ChildClassDemo child = new ChildClassDemo();
- }
- }
mian() 方法的执行结果,如下图所示:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。