java的类加载总共分为5步。

1、加载

2、验证

3、准备

4、解析

5、初始化

 

加载:

(1)通过一个类的全限类名获取到它的一个二进制字节流

(2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构

(3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入

Class<?> aClass = Class.forName(“com.entery.User”);

验证

验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息

符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

Java语言本身是相对安全的语言(依然是相对于C/C++来说),使用纯粹的Java代码无

法做到诸如访问数组边界以外的数据、将一个对象转型为它并未实现的类型、跳转到不存在

的代码行之类的事情,如果这样做了,编译器将拒绝编译。但前面已经说过,Class文件并不

一定要求用Java源码编译而来,可以使用任何途径产生,甚至包括用十六进制编辑器直接编

写来产生Class文件。在字节码语言层面上,上述Java代码无法做到的事情都是可以实现的,

至少语义上是可以表达出来的。虚拟机如果不检查输入的字节流,对其完全信任的话,很可

能会因为载入了有害的字节流而导致系统崩溃,所以验证是虚拟机对自身保护的一项重要工

作。

1.文件格式验证

2.元数据验证

3.字节码验证

4.符号引用验证

 

准备

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存

都将在方法区中进行分配。这个阶段中有两个容易产生混淆的概念需要强调一下,首先,这

时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将

会在对象实例化时随着对象一起分配在Java堆中。其次,这里所说的初始值“通常情况”下是

数据类型的零值,假设一个类变量的定义为:

那变量value在准备阶段过后的初始值为0而不是123,因为这时候尚未开始执行任何Java

方法,而把value赋值为123的putstatic指令是程序被编译后,存放于类构造器<clinit>()方

法之中,所以把value赋值为123的动作将在初始化阶段才会执行。表7-1列出了Java中所有基

本数据类型的零值。

 

上面提到,在“通常情况”下初始值是零值,那相对的会有一些“特殊情况”:如果类字段

的字段属性表中存在ConstantValue属性,那在准备阶段变量value就会被初始化为

ConstantValue属性所指定的值,假设上面类变量value的定义变为:

public static final int value=123;

编译时Javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据

ConstantValue的设置将value赋值为123。

 

 

解析

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程

 

符号引用(Symbolic References):符号引用以一组符号来描述所引用的目标,符号可

以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的

内存布局无关,引用的目标并不一定已经加载到内存中。各种虚拟机实现的内存布局可以各

不相同,但是它们能接受的符号引用必须都是一致的,因为符号引用的字面量形式明确定义

在Java虚拟机规范的Class文件格式中。

直接引用(Direct References):直接引用可以是直接指向目标的指针、相对偏移量或是

一个能间接定位到目标的句柄。直接引用是和虚拟机实现的内存布局相关的,同一个符号引

用在不同虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目

标必定已经在内存中存在。

1.类或接口的解析

假设当前代码所处的类为D,如果要把一个从未解析过的符号引用N解析为一个类或接

口C的直接引用,那虚拟机完成整个解析的过程需要以下3个步骤:

1)如果C不是一个数组类型,那虚拟机将会把代表N的全限定名传递给D的类加载器去

加载这个类C。在加载过程中,由于元数据验证、字节码验证的需要,又可能触发其他相关

类的加载动作,例如加载这个类的父类或实现的接口。一旦这个加载过程出现了任何异常,

解析过程就宣告失败。

2)如果C是一个数组类型,并且数组的元素类型为对象,也就是N的描述符会是类

似“[Ljava/lang/Integer”的形式,那将会按照第1点的规则加载数组元素类型。如果N的描述符

如前面所假设的形式,需要加载的元素类型就是“java.lang.Integer”,接着由虚拟机生成一个

代表此数组维度和元素的数组对象。

3)如果上面的步骤没有出现任何异常,那么C在虚拟机中实际上已经成为一个有效的类

或接口了,但在解析完成之前还要进行符号引用验证,确认D是否具备对C的访问权限。如

果发现不具备访问权限,将抛出java.lang.IllegalAccessError异常。

2.字段解析

要解析一个未被解析过的字段符号引用,首先将会对字段表内class_index

[2]

项中索引的

CONSTANT_Class_info符号引用进行解析,也就是字段所属的类或接口的符号引用。如果在

解析这个类或接口符号引用的过程中出现了任何异常,都会导致字段符号引用解析的失败。

如果解析成功完成,那将这个字段所属的类或接口用C表示,虚拟机规范要求按照如下步骤

对C进行后续字段的搜索。

1)如果C本身就包含了简单名称和字段描述符都与目标相匹配的字段,则返回这个字段

的直接引用,查找结束。

2)否则,如果在C中实现了接口,将会按照继承关系从下往上递归搜索各个接口和它的

父接口,如果接口中包含了简单名称和字段描述符都与目标相匹配的字段,则返回这个字段

的直接引用,查找结束。

3)否则,如果C不是java.lang.Object的话,将会按照继承关系从下往上递归搜索其父

类,如果在父类中包含了简单名称和字段描述符都与目标相匹配的字段,则返回这个字段的

直接引用,查找结束。

4)否则,查找失败,抛出java.lang.NoSuchFieldError异常。

如果查找过程成功返回了引用,将会对这个字段进行权限验证,如果发现不具备对字段

的访问权限,将抛出java.lang.IllegalAccessError异常。

在实际应用中,虚拟机的编译器实现可能会比上述规范要求得更加严格一些,如果有一个同名字段同时出现在C的接口和父类中,或者同时在自己或父类的多个接口中出现,那编

译器将可能拒绝编译。在代码清单7-4中,如果注释了Sub类中的“public static int A=4;”,接

口与父类同时存在字段A,那编译器将提示“The field Sub.A is ambiguous”,并且拒绝编译这

段代码。

初始化 

类初始化阶段是类加载过程的最后一步,前面的类加载过程中,除了在加载阶段用户应

用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化

阶段,才真正开始执行类中定义的Java程序代码(或者说是字节码)。

在准备阶段,变量已经赋过一次系统要求的初始值,而在初始化阶段,则根据程序员通

过程序制定的主观计划去初始化类变量和其他资源,或者可以从另外一个角度来表达:初始

化阶段是执行类构造器<clinit>()方法的过程。我们在下文会讲解<clinit>()方法是怎

么生成的,在这里,我们先看一下<clinit>()方法执行过程中一些可能会影响程序运行行

为的特点和细节,这部分相对更贴近于普通的程序开发人员

[1]

。 

主要就是把程序员自己设置的值赋值给对应的变量 然后就可以正式使用了。

  文章内容来于《java虚拟机》属于个人笔记学习。

posted on 2021-04-22 16:57  陈某某z  阅读(221)  评论(0)    收藏  举报