笔记:《深入理解Java虚拟机》第七章-虚拟机类加载机制

本文详细介绍了Java虚拟机的类加载机制,包括加载、验证、准备、解析和初始化五个阶段,以及类加载的时机和类加载器的双亲委派模型。深入探讨了类在虚拟机中的生命周期,主动引用与被动引用的区别,以及类加载过程中的关键步骤。

虚拟机类加载机制

1 概述

	虚拟机类加载机制:把描述类的数据从Class文件加载到内存。并对数据进行校验、转换解析和初始化。最终形成
可以被虚拟机直接使用的Java类型。

2 类加载的时机

	类从被加载到内存中,到卸载,生命周期包括:加载、验证、准备、解析、初始化、使用、卸载7个阶段。如图2.1
所示。
	其中类加载过程为5个阶段:加载、验证、准备、解析、初始化。

在这里插入图片描述

图2.1 类的生命周期
类必须进行“初始化”的5种情况:
	(1)遇到new、getstatic、putstatic或invokestatic这四条指令时,若类没有初始化,则必须进行初始化。
NB:生成这4条指令的常见场景是:使用new关键字实例化对象、读取或设置一个类的静态字段(被final修饰、已在编
译期把结果放入常量池的静态对象除外)、以及调用一个类的静态方法时。
	(2)使用java.lang.reflect包的方法对类进行反射调用时,若类未初始化,则进行初始化。
	(3)初始化一个类时,若其父类未进行初始化,则先进行父类初始化。
	(4)当虚拟启动时,用户须指定一个主类(包含main方法),先初始化这个主类。
	(5)当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果
REF_getStaic、REF_putStatic、REF_invokeStatic的方法句柄,若句柄对应的类未初始化,则初始化。	

以上5种称为主动引用,所有引用类的方式都不会触发初始化的被称为被动引用,3个例子如下:

example 1:

public class SuperClass {

	static {
		System.out.println("SuperClass init!");
	}
	
	public static int value = 123;
}

public class SubClass extends SuperClass{
	static {
		System.out.println("SubClass init!");
	}
}

public class NotInitialization {

	public static void main(String[] args) {
		System.out.println(SubClass.value);
	}
}
/*
	输出:SuperClass init!
*/

example 2:

public class SuperClass {

	static {
		System.out.println("SuperClass init!");
	}
	
	public static int value = 123;
}

public class NotInitialization {

	public static void main(String[] args) {
		SuperClass[] scs = new SuperClass[10];
	}
}
/*
	无输出
*/

example 3:

public class ConstantClass {

	static {
		System.out.println("Constant init!");
	}
	
	public static final String HELLOWORLD = "hello world";

}

public class NotInitialization {

	public static void main(String[] args) {
		System.out.println(ConstantClass.HELLOWORLD);
	}
}
/*
	输出: hello world
*/

接口的加载过程与类稍有些不同,就是前面5种情况的第3种,一个接口初始化时并不要求其父接口完成初始化,只有真正用到父接口时才会初始化。

3 类加载过程

3.1 加载

主要完成3件事情:

  1. 通过类的全限定名获取定义这个类的二进制字节流。
  2. 将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构。
  3. 在内存中生成一个代表该类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

3.2 验证

目的:为了确保Class文件的字节流中包含的信息符合虚拟机的要求,并且不会危害虚拟的安全。
主要包括4个阶段的验证:

  1. 文件格式验证
  2. 元数据验证
  3. 字节码验证
  4. 符号引用验证

3.3 准备

准备阶段是正式为类变量分配内存以及设置初始值的阶段。
强调1:这里进行分配内存的仅是staic修饰的类变量,不包括实例变量
强调2:一般情况下,这里设置的初始值是零值

	public static int value = 123; //在准备阶段,value分配内存,并设置为0

Java所有基本数据类型的零值如图3.1。
在这里插入图片描述

图3.1 基本数据类型的零值

强调3:特殊情况是指类字段的字段属性表存在ConstantValue属性(即是static、final、基本数据类型或String),则初始值设为指定值

	public static final int value = 123; //在准备阶段,value分配内存,并设置为123

3.4 解析

目的:将常量池中的符号引用转换成直接引用。
符号引用和直接引用的区别:

  • 符号引用:可以是任何形式的字面量,只要能无歧义的定位到目标即可。符号引用与虚拟机的内存分布无关,引用的目标不一定已经加载到内存中。
  • 直接引用:可以是直接指向目标的指针、偏移量或一个能间接定位到目标的句柄。直接引用与虚拟机的内存分布相关,如果有了直接引用,则目标一定存在于内存之中。

解析动作针对7类符号引用:类或接口、字段、类方法、接口方法、方法类型、方法句柄、调用点限定符。

3.5 初始化

目的:作为类加载过程的最后一步,开始真正执行类中定义的Java代码(字节码),并且会根据程序的主观顺序去初始化。
换个角度,初始化阶段就是执行类的方法的过程,特点如下:

  • 特点1:方法是由编译期自动收集类中所有的类变量初始化动作和静态代码块的语句产生的,编译期收集的顺序是语句在源文件中出现的顺序,静态代码块中只能访问到定义在它之前的变量,而对于在它之后的变量,只能赋值,不能访问。
  • 虚拟机保证在子类方法执行之前,父类的方法已经被执行,所以第一个被执行的一定是java.lang.Object的方法
  • 接口中不能使用静态代码块,并且接口中的方法不需要先执行父接口的方法,接口的实现类也不需要执行接口的方法
  • 如果多个线程同时去初始化一个类,那么只有一个类去执行这个类的方法,其他线程阻塞等待,并且同一个类加载器下,一个类只会被初始化一次。

4 类加载器

4.1 类与类加载器

对于任意一个类,都需要由它和它的类加载器一同确定其在Java虚拟机中的唯一性,每个类加载器都有一个独立的类名称空间。
换言之,比较两个类是否“相等”,只有在这两个类由同一个类加载器加载的前提下才有意义;否则即使这两个类来自同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,则这两个类必定不同。

4.2 双亲委派模型

从Java虚拟机的角度看,只存在两种不同的类加载器:一种是启动类加载器,属于虚拟机自身的一部分;另一种则是其他加载器。
从Java开发人员来看,分为三种类加载器:

  1. 启动类加载器:负责将存放在<JAVA_HOME>\lib目录下的,或使用-Xbootclasspath参数指定的路径中的类库加载到虚拟机内存中。
  2. 扩展类加载器:负载加载<JAVA_HOME>\lib\ext目录下的,或使用java.ext.dirs系统变量指定的类库。
  3. 应用程序类加载器:负责加载用户路径ClassPath上指定的类库。

这三者的关系如图4.1。
在这里插入图片描述

图4.1 类加载器的双亲委派模型

这里类加载的关系是使用组合(而不是继承)的方式复用父加载器的代码。

双亲委派模型的工作工程:如果一个类加载器收到类加载请求,首先将这个请求委派给父加载器去执行。只有当父加载器无法完成这个请求时(在它的搜索范围没有找到所需要的类),子加载器才会尝试去加载。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值