https://blog.csdn.net/woshichuanqihan/article/details/49229641

加载

“加载”是”类加载”过程的一个阶段。在加载阶段,虚拟机需要完成以下3件事情:

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

验证

验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并不会危害虚拟机的自身安全。

1.文件格式验证:
(1)是否以魔数0xCAFEBABE开头。
(2)主、次版本号是否在当前虚拟机处理范围之内。
(3)常量池的常量中是否有不被支持的常量类型(检查常量tag标志)。
(4)指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量。
(5)CONSTANT_Utf8_info型的常量中是否有不符合UTF8编码的数据。
(6)Class文件中各个部分及文件本身是否有被删除的或附加的其他信息。......
2.元数据验证:
(1)这个类是否有父类(除了java.lang.Object之外,所有类都应当有父类)。
(2)这个类是否继承了不允许被继承的类(被final修饰的类)。
(3)如果这个类不是抽象类,是否实现了其父类或接口之中所要求实现的所有方法。
(4)类中的字段、方法是否与父类产生矛盾(例如覆盖了父类的final字段,或者出现不符合规则的方法重载,例如方法参数都一致,但返回值类型却不同等等)。......
3.字节码验证:
主要目的是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。这个阶段将对类的方法体进行校验分析,保证被校验类的方法在运行时不会产生危害虚拟机安全的事件,例如:
(1)保证任意时刻操作数栈的数据类型与指令代码序列都能配合工作,例如不会出现类似这样的情况:在操作数栈放置了一个int类型的数据,使用时却按long类型来加载入本地变量表中。
(2)保证跳转指令不会跳转到方法体以外的字节码指令上。
(3)保证方法体中的类型转换是有效的,例如可以把一个子类对象赋值给父类数据类型,但是把父类对象赋值给子类数据类型,甚至把对象赋值给与它毫无继承关系、完全不相干的一个数据类型,则是危险不合法的。
......
(Halting Problem:通过程序去校验程序逻辑是无法做到绝对准确的——不能通过程序准确的检查出程序是否能在有限时间之内结束运行。)
4.符号引用验证:
符号引用验证可以看作是类对自身以外(常量池中的各种符号引用)的信息进行匹配性校验,通常需要校验以下内容:
(1)符号引用中通过字符串描述的全限定名是否能够找到对应的类。
(2)在指定类中是否存在符合方法的字段描述符以及简单名称所描述的方法和字段。
(3)符号引用中的类、字段、方法的访问性(private、protected、public、default)是否可被当前类访问。......
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

准备

准备阶段是正式为类变量分配内存并设置类变量初始值(通常情况下是数据类型的零值)的阶段,这些变量所使用的内存都将在方法区中进行分配。这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化的时候随着对象一起分配在Java堆中。

下面来说一下初始值设置时的特殊情况: 
如果类字段的字段属性表中存在ConstantValue属性,那在准备阶段变量value就会被初始化为ConstantValue属性所指定的值,假设类变量value定义为:

    public static final int value = 123;
  • 1

编译时Javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将value赋值为123.

解析

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

符号引用(Symbolic References):符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。直接引用(Direct References):直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。如果有了直接引用,那么引用的目标一定是已经存在于内存中。
  • 1
  • 2
  • 3
  • 4

1.类或接口的解析

假设当前代码所处的类为D,如果要把一个从未解析过的符号引用N解析为一个类或接口C的引用,那虚拟机完成整个解析过程需要以下3个步骤:
(1)如果C不是一个数组类型,那虚拟机将会把代表N的全限定名传递给D的类加载器去加载这个类C。
(2)如果C是一个数组类型,并且数组的元素类型为对象,那将会按照第1点的规则加载数组元素类型。
(3)如果上面的步骤没有出现任何异常,那么C在虚拟机中实际上已经成为了一个有效的类或接口了,但在解析完成之前还要进行符号引用验证,确认D是否具有对C的访问权限。如果发现不具备访问权限,则抛出java.lang.IllegalAccessError异常。
  • 1
  • 2
  • 3
  • 4
  • 5

2.字段解析

首先解析字段表内class_index项中索引的CONSTANT_Class_info符号引用,也就是字段所属的类或接口的符号引用,如果解析完成,将这个字段所属的类或接口用C表示,虚拟机规范要求按照如下步骤对C进行后续字段的搜索。
(1)如果C 本身就包含了简单名称和字段描述符都与目标相匹配的字段,则返回这个字段的直接引用,查找结束。
(2)否则,如果C中实现了接口,将会按照继承关系从下往上递归搜索各个接口和它的父接口如果接口中包含了简单名称和字段描述符都与目标相匹配的字段,则返回这个字段的直接引用,查找结束。
(3)否则,如果C 不是java.lang.Object的话,将会按照继承关系从下往上递归搜索其父类,如果在父类中包含了简单名称和字段描述符都与目标相匹配的字段,则返回这个字段的直接引用,查找结束。
(4)否则,查找失败,抛出java.lang.NoSuchFieldError异常。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

如果查找过程成功返回了引用,将会对这个字段进行权限验证,如果发现不具备对字段的访问权限,将抛出java.lang.IllegalAccessError异常。

如果有一个同名字段同时出现在C的接口和父类中,或者同时在自己的父类或多个接口中出现,那编译器可能拒绝编译,并提示”The field xxx is ambiguous”。

3.类方法解析

首先解析类方法表内class_index项中索引的CONSTANT_Class_info符号引用,也就是方法所属的类或接口的符号引用,如果解析完成,将这个类方法所属的类或接口用C表示,虚拟机规范要求按照如下步骤对C进行后续类方法的搜索。
(1)类方法和接口方法符号引用的常量类型定义是分开的,如果在类方法表中发现class_index中索引的C 是个接口,那就直接抛出java.lang.IncompatibleClassChangeError异常。
(2)如果通过了第一步,在类C 中查找是否有简单名称和描述符都与目标相匹配的方法,如果有则返回这个方法的直接引用,查找结束。
(3)否则,在类C的父类中递归查找是否有简单名称和描述符都与目标相匹配的方法,如果有则返回这个方法的直接引用,查找结束。
(4)否则,在类C实现的接口列表以及他们的父接口中递归查找是否有简单名称和描述符都与目标相匹配的方法,如果存在相匹配的方法,说明类C是一个抽象类这时查找结束,抛出java.lang.AbstractMethodError异常。
(5)否则,宣告方法查找失败,抛出java.lang.NoSuchMethodError。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

最后,如果查找成功返回了直接引用,将会对这个方法进行权限验证,如果发现不具备此方法的访问权限,则抛出java.lang.IllegalAccessError异常。

4.接口方法解析

首先解析接口方法表内class_index项中索引的CONSTANT_Class_info符号引用,也就是方法所属的类或接口的符号引用,如果解析完成,将这个接口方法所属的接口用C表示,虚拟机规范要求按照如下步骤对C进行后续接口方法的搜索。
(1)与类解析方法不同,如果在接口方法表中发现class_index中的索引C是个类而不是个接口,那就直接抛出java.lang.IncompatibleClassChangeError异常。
(2)否则,在接口C中查找是否有简单名称和描述符都与目标相匹配的方法,如果有则返回这个方法的直接引用,查找结束。
(3)否则,在接口C的父接口中递归查找,直到java.lang.Object类(查找范围包括Object类)为止,看是否有简单名称和描述符都与目标相匹配的方法,如果有则返回这个方法的直接引用,查找结束。
(4)否则,宣告方法查找失败,抛出java.lang.NoSuchMethodError。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

由于接口中所有的方法默认都是public的,所以不存在访问权限的问题,因此接口方法的符号解析应当不会抛出java.lang.IllegalAccessError异常。

初始化

类初始化阶段是类加载过程的最后一步,到了这个阶段才真正开始执行类中定义的Java程序代码(或者说是字节码)。在准备阶段,变量已经赋过一次系统要求的初始值,而在初始化阶段,则根据程序员通过程序制定的主观计划去初始化类变量和其他资源。 
需要注意以下几点:

1.编译器收集的顺序是由语句在源文件中出现的顺序决定的,静态语句块中只能访问到定义在静态语句块之前的变量,而定义在它之后的变量,在前面的静态语句块可以赋值,但不能访问,代码解释如下:

public class Test {static {i = 0;                       //给变量赋值可以正常编译通过System.out.print(i);         //编译器会提示“非法向前引用”}static int i = 1;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

2.初始化方法执行的顺序,虚拟机会保证在子类的初始化方法执行之前,父类的初始化方法已经执行完毕,因此在虚拟机中第一个被执行的类初始化方法一定是java.lang.Object。另外,也意味着父类中定义的静态语句块要优先于子类的变量赋值操作,例如:

static class Parent {public static int A = 1;static {A = 2;}}static class Sub extends Parent {public static int B = A;}public static void main(String[] args) {System.out.println(Sub.B);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

执行的结果。字段B的值将会是2而不是1。

3.clinit ()方法对于类或接口来说并不是必须的,如果一个类中没有静态语句块,也没有对变量的赋值操作,那么编译器可以不为这个类生成clinit()方法。

4.接口中不能使用静态语句块,但仍然有变量初始化的操作,因此接口与类一样都会生成clinit()方法,但与类不同的是,执行接口的初始化方法之前,不需要先执行父接口的初始化方法。只有当父接口中定义的变量使用时,才会执行父接口的初始化方法。另外,接口的实现类在初始化时也一样不会执行接口的clinit()方法。

5.虚拟机会保证一个类的clinit()方法在多线程环境中被正确的加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的clinit()方法,其他线程都需要阻塞等待,直到活动线程执行类初始化方法完毕。

二、class文件加载过程

过程:加载----->验证------>准备------>解析------>初始化

加载 发生的变化:

(1)、根据类的全名限定符,获取class二进制流(保存到硬盘的class文件,不是class二进制流获取的唯一方式。也有可能从网络中获取等)

(2)、将类的静态存储结构转化为方法区的运行时动态存储结构

(3)、在内存的堆中生成对应的class对象,作为方法区的入口

验证要做的事情:

(1)、class文件格式验证(class文件来源不唯一(自己也可以手写),有可能格式正确损坏虚拟机)

(2)、元数据验证(是否符合类的定义规范,例如是否继承java.lang.Object)

(3)、字节码验证(类中方法的控制流是否合法)

(4)、符号引用验证(转换为直接引用动作是否合法)

准备发生的事情:为类变量在方法区分配内存,并初始化类变量(“零值”初始化)

解析要干的事情:将常量池的符号引用替换为直接引用

初始化:在准备阶段已经对类变量进行初始化了,这里的初始化是执行类构造器<clinit>。<clinit>()方法是编译器自动收集类中所有类变量的赋值动作和静态代码块而产生的方法(无论类变量和静态代码块的位置是什么样,都是先执行类变量的赋值动作,再执行静态代码块)

初始化触发的条件,有且只有四个(主动引用)

(1)、new(实例化对象)、getstatic(获取类变量的值,被final修饰的除外,他的值在编译器时放到了常量池)、putstatic(给类变量赋值)、invokestatic(调用静态方法)

(2)、使用java.lang.reflect包的方法对类进行反射调用方法

(3)、初始化类的时候,如果他的父类还没有初始化,要先初始化父类

(4)、虚拟机启动时,含有main方法的类,会被先初始化

被动引用(除了上面引用类的四个条件会触发类的初始化,其他对类的引用都不会触发类的初始化):

(1)、在第三方类中,使用子类引用父类的类变量,不会初始化子类

(2)、在第三方类中,通过数组定义来应用类,不会初始化类

(3)、在第三方类中,引用类的常类变量(同时被final和static修饰的变量),不会触发类的初始化(因为在第三方类的编译之后,常量就被放在第三方类的常量池中了)

三、虚拟机的内存分配情况

(1)、虚拟机栈:每个class类对应一个虚拟机栈帧(组成:局部变量表、操作数栈、返回地址、动态链接),类私有

(2)、堆:存放对象

(3)、方法区:存放类信息、常量、类变量、即时编译器编译后的代码

(4)、常量池:是方法区的一部分,主要有字面量(常量和字符串)和符号引用(类和接口的符号引用、字段的名称和描述的符号引用、方法的名称和描述的符号引用)

类加载的过程(加载、验证、准备、解析、初始化)相关推荐

  1. 类加载过程(加载+验证+准备+解析+初始化)

    2019独角兽企业重金招聘Python工程师标准>>> JVM把class文件加载的内存,并对数据进行校验.转换解析和初始化,最终形成JVM可以直接使用的Java类型的过程就是加载机 ...

  2. Trembling ! Java类的加载过程详解(加载验证准备解析初始化使用卸载)

    [1]类的生命周期 一个类从加载进内存到卸载出内存为止,一共经历7个阶段: 加载->验证->准备->解析->初始化->使用->卸载 其中,类加载包括5个阶段: 加载 ...

  3. 类加载的三个阶段——加载、链接以及初始化,类的主动使用和被动使用

    1. 类加载 1.1 加载:查找并加载字节码文件. 1.2 链接:分为三个阶段:①验证,文件格式验证:对魔数(保证是字节码类型文件)验证以及jdk版本号和当前虚拟机是否匹配:元数据验证:父类是否可以继 ...

  4. 类加载顺序及加载过程详解

    转自: 类加载顺序及加载过程详解 下文笔者讲述类的加载顺序及加载过程的详解说明,如下所示 java创建对象的方式分为以下四种 new 反射克隆反序列化 class对象获取的方式分享 //没有完成初始化 ...

  5. 类加载器及其加载过程

    一.内存结构概述 内存结构简图: 内存结构详细图: 中文: 英文: 二.类加载器与加载过程 类加载器子系统作用 图解: 描述: 1.类加载器子系统负责从文件系统或者网络中加载class文件,class ...

  6. JVM 类加载机制与加载过程

    JVM的类加载机制:在代码编译后,就会生成JVM(Java虚拟机)能够识别的二进制字节流文件(*.class).而JVM把Class文件中的类描述数据从文件加载到内存,并对数据进行校验.转换解析.初始 ...

  7. 【Android 逆向】Dalvik 函数抽取加壳 ( Dalvik 下的函数指令抽取与恢复 | dex 函数指令恢复时机点 | 类加载流程 : 加载、链接、初始化 )

    文章目录 前言 一.Dalvik 下的函数指令抽取与恢复 二.dex 函数指令恢复时机点 1.dex 函数指令恢复 2.Android 源码中搜索 dexFindClass 函数 3.类加载流程 : ...

  8. java的连接 初始化_java类从加载、连接到初始化过程详解

    Java代码在编译后会转化成Java字节码,字节码被类加载器加载到JVM里,JVM执行字节码,最终需要转化成汇编指令在CPU上执行,Java中所使用的并发机制依赖于JVM的实现和CPU的指令. 类加载 ...

  9. 老调重弹:JDBC系列之驱动加载原理全面解析)

    前言 最近在研究Mybatis框架,由于该框架基于JDBC,想要很好地理解和学习Mybatis,必须要对JDBC有较深入的了解.所以便把JDBC 这个东东翻出来,好好总结一番,作为自己的笔记,也是给读 ...

  10. 老调重弹:JDBC系列 之 驱动加载原理全面解析

    前言 最近在研究Mybatis框架,由于该框架基于JDBC,想要很好地理解和学习Mybatis,必须要对JDBC有较深入的了解.所以便把JDBC 这个东东翻出来,好好总结一番,作为自己的笔记,也是给读 ...

最新文章

  1. 五元一次方程组计算器_人教版初中数学七年级下册列一元一次不等式解实际问题公开课优质课课件教案视频...
  2. 【转】C#基础概念之“什么是反射?”
  3. Codeforces Round #494 (Div. 3)【未完结】
  4. CTF- Reverse迷宫题类型方法特征讲解
  5. 再见,Postman...
  6. MySQL exists的用法介绍
  7. 20172311 2017-2018-2 《程序设计与数据结构》第八周学习总结
  8. mysql多表查询语句_mysql查询语句 和 多表关联查询 以及 子查询
  9. codevs1521 华丽的吊灯
  10. linux 删除N天文件
  11. python判断图片相似度_Python比较两个图片相似度的方法
  12. Ubuntu18.04连蓝牙键盘后,搜狗输入法导致输入卡顿问题
  13. C语言题目教室数量编程,北京理工大学网教室C语言编程题库及答案(绝对经典).doc...
  14. java实习周记_计算机java开发实习周记20篇
  15. Python爬虫抓取网站模板的完整版实现
  16. 使用selenium爬取搜狗微信文章
  17. Java方法的重载和重写
  18. 一般纳税人税额计算_一般纳税人应纳税额如何计算?
  19. 懂的都懂,那些好用的“WEB安全”网站
  20. JAVA串口通信开发

热门文章

  1. Mac小知识——MAC电脑如何查看IP
  2. Claris FileMaker Pro更新至19.2.1.14中文版
  3. Elmedia Player Pro Mac使用技巧,Elmedia键盘快捷键
  4. HashSet、LinkedHashSet和TreeSet三者区别与联系
  5. docker下的Mysql镜像的使用方法
  6. C++ template 学习归纳总结4
  7. iOS微信分享在6plus上遇到一个坑
  8. Bootstrap3 表单-输出内联表单
  9. 【风马一族_php】NO2_php基础知识
  10. 就地升级Lync Server 到Skype for Business Server