类的加载、连接和初始化过程

本文详细介绍了Java虚拟机中的类加载过程,包括加载、连接和初始化。加载涉及将类的.class文件读入内存并创建Class对象;连接包括验证、准备和解析阶段;初始化则指在特定条件下对类进行初始化。主动使用类的情况包括创建实例、访问静态变量、反射、初始化子类等,而被动使用则不会触发初始化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

类的加载

类的加载是指将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在内存中创建一个java.lang.Class对象(HotSpot虚拟机将其放在方法区,其他虚拟机有自己的实现,并没有严格的规范说明Class对象位于哪里),用来封装类在方法区内的数据结构。

  • 加载类的方式

    • 从本地系统中直接加载
    • 通过网络下载.class文件
    • 从zip,jar等归档文件中加载.class文件
    • 从专有数据库中提取.class文件
    • 从Java源文件动态编译为.class文件

连接

  • 验证:确保被加载类的正确性

    确保被加载的字节码文件没有被恶意篡改,符合JVM对字节码的格式要求
  • 准备:为类的静态变量分配内存,并将其初始化为默认值

    此时将所有静态变量分配内存,赋值为JVM规定的默认值,如int赋值为0,String赋值为null;
  • 解析:把类中的符号引用转换为直接引用

    在类型的常量池中寻找类、接口、字段和方法的符号引用,把这些符号引用转换成直接引用的过程。

初始化

1.主动使用

所有Java虚拟机实现了每个Java类和接口只有被Java程序“首次主动使用”才会对其进行初始化,该初始化只能进行一次。

1.1 创建类的实例

如 Test test = new Test();

1.2 访问某个类或者接口的静态变量、对该静态变量赋值或者调用类的静态方法(助记符)

在JVM层面上,字节码对静态变量的访问借助助记符getstatic这样的一个指令进行处理,对静态变量赋值借助putstatic指令进行处理。而对调用类的静态方法则借助invokestatic指令进行处理,这三种指令都会导致类或者接口的初始化。

1.3 反射(如Class.forName(全限定类名))

Test test = (Test)Class.forName("com.xxx.otherTest").newInstance();
或者
Test test = Test.class.newInstance();

1.4 初始化一个类的子类

public class Test{
	public static void main(String[] args){
		System.out.println(Children.str);
	} 
}

class Parent{
	public static String str = "Parent";
	static{
		System.out.println("Parent initial");
	}
}

class Children extends Parent{
	public static String str = "Children";
	static{
		System.out.println("Children initial");
	}
}

输出结果:
在这里插入图片描述
对子类Children的初始化也会往上初始化Parent,若Parent往上还有其他类型,以此类推不断往上进行初始化。

1.5 Java虚拟机启动时被标明为启动类的类(包含Main方法的类)

1.6 JDK1.7开始提供的动态语言支持

java.lang.invoke.MethodHandle实例的解析结果REF_getStatic,REF_putStatic,REF_invokeStatic句柄对应的类没有初始化,则初始化。

2.被动使用

除开以上7种情况外,其他对java类或者接口的使用都不会导致其初始化。
如:

public class Test{
	public static void main(String[] args){
		System.out.println(Children.str);
	} 
}

class Parent{
	public static String str = "Parent";
	static{
		System.out.println("Parent initial");
	}
}

class Children extends Parent{
	static{
		System.out.println("Children initial");
	}
}

输出结果:
输出结果
可以看出只有父类Parent被初始化,并没有对子类Children进行初始化,这是因为静态变量str是在父类中被定义的,虽然是子类Children对其进行调用,但是实际上是对父类的一个主动使用。

又或者:

public class Test{
	public static void main(String[] args){
		System.out.println(finalTest.str);
	} 
}

class finalTest{
	public static final String str = "Parent";
	static{
		System.out.println("finalTest initial");
	}
}

输出结果:
在这里插入图片描述
finalTest中定义的常量在编译期间就被放到Test调用该常量方法的常量池中(在编译器可以确定的常量值),本质上,调用类并没有直接引用到定义常量的类,因此不会触发定义常量类的初始化。
此外,还有对象数组的创建也不会导致类的初始化。

----------------------------------------------------分割线---------------------------------------------------------------
分类:JVM学习心得

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值