整体概览
类加载
- 在Java代码中,类型的加载、连接、初始化过程都是在程序运行期间完成的
- 提供了更大的灵活性,增加了更多的可能性
类的加载、连接与初始化
加载:查找并加载类的二进制数据
-
连接
- 验证:确保被加载类的正确性
- 准备:为类的静态变量分配内存,并将其初始化为默认值
- 解析:把类中的符号引用转换为直接引用
初始化:为类的静态变量赋予正确的初始值
类的加载
- 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在内存中创建一个java.lang.Class对象(规范并未说明Class对象位于哪里,HotSpot虚拟机将其放在了方法区中)用来封装类的方法区内的数据
- 加载class文件的方式
- 从本地系统中直接加载
- 通过网络下载.class文件
- 从zip,jar等归档文件中加载.class文件
- 从专有数据库中提取出.class文件
- 将Java源文件动态编译为.class文件
- 类的加载的最终产品是位于内存中的Class对象
- 类加载器不需要等到某个类被“首次主动使用”时再加载它,JVM规范允许类加载器在预料某个类将要被使用时预先加载它,如果与先加载过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误
- 如果这个类一直没被程序主动使用,那类加载器就不会报告错误
类的连接
- 将已经读入到内存的类的二进制数据合并到虚拟机的运行时环境中去
- 将类与类之间的关系确定好,并且对字节码相关的处理、验证、校验等操作通过加载连接完成
验证
- 类文件的结构检查
- 语义检查
- 字节码验证
- 二进制兼容性的验证
准备
- 准备阶段,Java虚拟机为类的静态变量分配内存,并设置默认的初始值。例如以下示例,在准备阶段将为int类型的静态变量a分配4个字节的内存空间,并且赋予默认值0,为long类型的静态变量b分配8个字节的内存空间,并且赋予默认值0.
public class Sample{
private static int a = 1;
public static long b;
static {
b = 2;
}
}
类的初始化
初始化途径
- 在初始化阶段,Java虚拟机执行类的初始化语句,为类的静态变量赋予初始值。在程序中,静态变量的初始化有两种途径:
- 在静态变量的声明处进行初始化
- 在静态代码块中进行初始化
- Java虚拟机按照初始化语句的类文件的先后顺序依次执行它们
例如以下代码,静态变量a和b都被显式初始化,而静态变量c没有被显式初始化,它将保持默认值0,按照先后顺序a最终将取值为4
public class Sample{
private static int a = 1;
public static long b;
public static long c;
static {
b = 2;
}
static {
a = 4;
}
}
初始化时机
所有的Java虚拟机实现必须在每个类或接口被Java程序“首次主动使用”时才初始化他们
-
主动使用(七种)
- 创建类的实例
- 访问某个类或接口的静态变量,或者对该静态变量赋值
- 调用类的静态方法
- 反射(如Class.forName("com.test.Test"))
- 初始化一个类的子类
- Java虚拟机启动时被表明为启动类的类(Java Test)
- JDK1.7开始提供的动态语言支持:java.lang.invoke.MethodHandle实例的解析结果REF_getStatic,REF_putStatic,REF_invokeStatic句柄对应的类没有初始化,则初始化
除了以上七中情况,其他使用Java类的方式都被看作是对类的被动使用,都不会导致类的初始化
参考资料:
圣思园JVM课程