【JAVA】【JVM篇】【类加载机制】
1. 概述
Class文件由类装载器装载后,在JVM中将形成一份描述Class结构的元信息对象,通过该元信息对象可以获知Class的结构信息:如构造函数,属性和方法等,Java允许用户借由这个Class相关的元信息对象间接调用Class对象的功能。
虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。
2. 什么是类加载
类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。
类加载器并不需要等到某个类被“首次主动使用”时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误。
3. Java类的生命周期
类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载七个阶段。它们的顺序如下图所示:
其中类加载的过程包括了加载、验证、准备、解析、初始化五个阶段。
下面就一个一个去分析一下这几个过程。
(1).加载
”加载“是”类加载机制”的第一个过程,加载是一个读取Class文件,将其转换某种静态数据结构存储在方法区内,并在堆中生成一个便于用户调用额Java.lang.Class类型的对象的过程。
注意: 这里的Class文件不仅仅指本地文件,也可能来自网络。
(2).验证
验证的主要作用就是确保被加载的类的正确性。也是连接阶段的第一步。说白了也就是我们加载好的.class文件不能对我们的虚拟机有危害,所以先检测验证一下。他主要是完成四个阶段的验证:
(a)文件格式的验证:验证.class文件字节流是否符合class文件的格式的规范,并且能够被当前版本的虚拟机处理。这里面主要对魔数、主版本号、常量池等等的校验(魔数、主版本号都是.class文件里面包含的数据信息、在这里可以不用理解)。
(b)元数据验证:主要是对字节码描述的信息进行语义分析,以保证其描述的信息符合java语言规范的要求,比如说验证这个类是不是有父类,类中的字段方法是不是和父类冲突等等。
(c)字节码验证:这是整个验证过程最复杂的阶段,主要是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。在元数据验证阶段对数据类型做出验证后,这个阶段主要对类的方法做出分析,保证类的方法在运行时不会做出威海虚拟机安全的事。
(d)符号引用验证:它是验证的最后一个阶段,发生在虚拟机将符号引用转化为直接引用的时候
但是如果这个符号引用指向的是一个多态对象,则不会发生符号引用替换成直接引用。(后期会说双亲委派机制)
(3).准备
准备阶段主要为类变量分配内存并设置初始值。这些内存都在方法区分配。在这个阶段我们只需要注意两点就好了,也就是类变量和初始值两个关键词:
(1)类变量(static)会分配内存,但是实例变量不会,实例变量主要随着对象的实例化一块分配到java堆中,
(2)这里的初始值指的是数据类型默认值,而不是代码中被显示赋予的值。
比如: public static int value = 1; //在这里准备阶段过后的value值为0,而不是1。赋值为1的动作在初始化阶段。
JDK1.8之后准备阶段发生了哪些变化?
让我们看两张图:
主要区别是在准备阶段,常量池与静态变量放在了堆的里面。而类的元信息等放入了元空间内,而元空间在本地内存中存储,不占用JVM虚拟机内存。
(4).解析
解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行替换成直接引用。。什么是符号引用和直接引用呢?
(a)符号引用: 假如在类加载的过程中A类调用B类,这个时候B类还没有被加载,那这个时候A类会用一个符号来代替对B类的引用,这个就是符号引用。
(b)直接引用: 假如在类加载的过程中A类调用B类,这个时候B类还没有被加载,会直接选择直接加载B类,然后等B类加载完成进入堆中,然后A类把符号引用替换成B类的真正地址这个叫符号引用替换为直接引用。
但是在解析的过程中A类并不会吧所有的对象都会从符号引用换成直接引用,原因是因为Java中存在类多态的形式,比如接口或抽象类的子类对象不止一个的时候,A类并不知道应该加载C子类还是D子类的时候就不会完成符号引用的替换。而是在运行时期采用动态解析完成符号引用替换成直接引用。看图:
(5).初始化
这是类加载机制的最后一步,在这个阶段,java程序代码才开始真正执行。我们知道,在准备阶段已经为类变量赋过一次值。在初始化阶端,程序员可以根据自己的需求来赋值了。
在初始化阶段,主要为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。在Java中对类变量进行初始值设定有两种方式:
①声明类变量是指定初始值
②使用静态代码块为类变量指定初始值
类初始化的多种方式
(a)创建类的实例,也就是new的方式
(b)访问某个类或接口的静态变量,或者对该静态变量赋值
(c)调用类的静态方法
(d)反射(如 Class.forName(“com.shengsiyuan.Test”))
(e)初始化某个类的子类,则其父类也会被初始化
(f)Java虚拟机启动时被标明为启动类的类( JavaTest),直接使用 java.exe命令来运行某个主类