0. 你将获得什么?
- java虚拟机(JVM)的启动过程。
- 如何加载一个类。
- 系统类是什么时候被加载的,用户写的类又是什么时候被加载的。
- 双亲委派机制。
- java虚拟机第一个加载的类是什么?
1. 什么是类装载子系统?
虚拟机的类加载器子系统负责从文件系统或者网络中加载Class文件,对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的java.lang.Class,并将其放置于一块叫做方法区的内存区域。
Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口
3. 如何加载一个类?类加载子系统工作原理
加载一个类主要分为3个步骤:加载(loading)、链接(linking)、初始化(initialization)。其中链接又分为验证、准备、解析。
3.1 加载
- 通过一个类的
全限定名
获取定义此类的二进制字节流
(以物理磁盘为例),这个二进制字节流我们在上一篇文章中已经详细讲解过。 - 多种类加载器利用双亲委派机制将这个字节流所代表的静态存储结构转化为
方法区
的运行时数据结构 - 在内存中生成一个代表这个类的java. lang.
Class
对象,作为方法区这个类的各种数据的访问入口
3.2 链接
3.2.1 验证
目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求
,保证被加载类的正确性
,不会危害虚拟机自身安全。(虚拟机要求:例如字节码文件以cafebabe开头等)
- 主要包括
四种验证
,文件格式验证,元数据验证,字节码验证,符号引用验证。(验证出错会报VerifyError错误)
3.2.2 准备
为类变量分配内存并且设置该类变量的默认初始值。
- 这里不包含用final修饰的static,因为final在编译的时候就会分配了,准备阶段会显式初始化;
- 这里不会为实例变量分配初始化,实例变量是会随着对象一起分配到Java堆中。
3.2.3 解析
将常量池内
的符号引用转换为直接引用的过程。
- 符号引用就是一组符号来描述所引用的目标。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
解析动作主要针对类或接口、字段、类方法、接口方法、方法类型等
。对应常量池中的CONSTANT_ Class_ info、 CONSTANT_ Fieldref_ info、 CONSTANT Methodref_ info等。
3.3 初始化
初始化阶段就是执行类构造器方法<clinit>()
的过程。
- 此方法不需定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句(也就是static修饰的)合并而来。Clinit构造器会把显示初始化和构造代码块初始化合并在一起构成构造器方法,如果没有类变量(静态变量)的赋值动作或者是静态代码块语句那么就不会生成这个clinit方法了.
- 构造器方法中指令按语句在源文件中出现的
顺序执行,不区分是静态字段还是静态代码块
- 若该类具有父类,JVM会保证子类的< clinit >()执行前,父类的< clinit >()已经执行完毕。
4. 双亲委派机制
4.1 各种ClassLoader的介绍
- Bootstrap classLoader:主要负责加载核心的类库(java.lang.*等),构造ExtClassLoader和APPClassLoader。我们已经见识到了,创建JVM的时候有一步就是创建启动类加载器,然后加载核心类库到内存中。
- ExtClassLoader:主要负责加载jre/lib/ext目录下的一些扩展的jar。
- AppClassLoader:主要负责加载应用程序的主函数类。
4.2 双亲委派机制流程图
这张图很好的讲解了双亲委派机制,核心是询问是否已经加载过时自底向上,如果都没有加载过则自顶向下加载。
4.3 双亲委派机制优点
这种设计有个好处是,如果有人想替换系统级别的类:String.java。篡改它的实现,在这种机制下这些系统的类已经被Bootstrap classLoader加载过了(为什么?因为当一个类需要加载的时候,最先去尝试加载的就是BootstrapClassLoader),所以其他类加载器并没有机会再去加载,从一定程度上防止了危险代码的植入。
本文是《JVM看这篇就够了!》专栏的一部分,如果觉得对您有帮助的话,不妨给作者点一个赞。