一文让你理解Class类加载机制

发布时间:2020-05-30 19:47:12 作者:wx5d9ed7c8443c3
来源:网络 阅读:146

一文让你理解Class类加载机制

理解类加载机制

Class文件是各种编译器编译生成的二进制文件,在Class文件中描述了各种与该类相关的信息,但是Class文件本身是一个静态的东西,想要使用某个类的话,需要java虚拟机将该类对应的Class文件加载进虚拟机中之后才能进行运行和使用。

举个例子,Class文件就好比是各个玩具设计商提供的设计方案,这些方案本身是不能直接给小朋友玩的,需要玩具生产商根据方案的相关信息制造出具体的玩具才可以给小朋友玩。那么不同的设计商有他们自己的设计思路,只要最终设计出来的方案符合生产商生产的要求即可。生产商在生产玩具时,首先会根据自己的生产标准对设计商提交来的方案进行阅读,审核,校验等一系列步骤,如果该方案符合生产标准,则会根据方案创建出对应的模具,当经销商需要某个玩具时,生产商则拿出对应的模具生产出具体的玩具,然后把玩具提交给经销商。

对于java而言,虚拟机就是玩具生产商,设计商提交过来的方案就是一个个的Class文件,方案创建的模具就
总的来说,类的加载过程,包括卸载在内的整个生命周期共有以下7个阶段:

一文让你理解Class类加载机制

加载、验证、准备、初始化、卸载这5个阶段的顺序是确定的,但是解析阶段不一定,在某些情况下解析可以在初始化之后再执行,为了支持java的运行时绑定,也成为动态绑定或晚期绑定。invokedynamic指令就是用于动态语言支持,这里“动态”的含义是必须等到城市实际运行到这条指令的时候,解析动作才开始执行。

加载

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

加载阶段中“通过类的全限定名获得该类的二进制字节流”这个动作,被放到java虚拟机外部实现,目的是最大限度的让应用程序去决定该如何获取所需的类,而实现该动作的代码模块就是类加载器(ClassLoader),JVM提供了3种类加载器:

加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区中了。

验证

加载完成后,紧接着(更确切的说是交叉执行)虚拟机会对加载的字节流进行验证。虚拟机如果不检查输入的字节流,对其安全信任的话,很可能会因为载入了有害的字节流而导致系统崩溃。验证阶段大致会完成4中不同的检验动作:

文件格式验证

文件格式验证主要是校验该字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机所接受。这个阶段包括但不限于以下验证点:

元数据验证

元数据验证主要是对字节流中的描述信息(描述符)进行语义分析,以确保其描述的信息符合java语言规范的要求。这个阶段包括但不限于以下验证点:

字节码验证

字节码验证主要是对类的方法体进行分析,确保在方法运行时不会有危害虚拟机的事件发生。这个阶段包括但不限于以下验证点:

符号引用验证

符号引用验证主要是对类自身以外的信息进行匹配新校验,包括常量池中的各种符号引用。这个阶段包括但不限于以下验证点:

准备

准备阶段是为类变量(static)在方法区中分配内存并设置初始值(默认值,如int的默认值为0)的阶段。实例变量将会在对象实例化时随着对象一起分配在java堆中。为类变量设置初始值跟该变量是否有final修饰符有关系。 如果没用final进行修饰,如下列的代码:

// 准备阶段执行完成后,value变量的值为int的“零值”,即:0
// 把value赋值为10的putstatic指令是程序被编译后,
// 存放于类构造器<clinit>()方法中的,所以具体赋值的操作会在初始化阶段执行
public static int value = 10;

如果使用了final进行修饰,如下列的代码:

// 如果类字段的字段属性表中有ConstantValue属性,
// 则会在准备阶段将变量的值初始化为ConstantValue所指定的值
// 准备阶段执行完成后,VALUE变量的值被赋值为20
public final static int VALUE = 20;

解析

解析阶段是虚拟机将常量池中的符号引用替换为直接引用的过程,在该阶段将会进行符号引用的校验。 符号引用是Class文件中用来描述所引用目标的符号,可以是任何形式的字面量。 直接引用是虚拟机在内存中引用具体类或接口的,可以是直接指向目标的指针,相对偏移量或者是一个能间接定位到目标的句柄。 简单的来说,符号引用是Class类文件用来定位目标的,直接引用是虚拟机用来在内存中定位目标的。

一文让你理解Class类加载机制

初始化

初始化阶段是执行类的构造器方法<clinit>()的过程,类的构造方法由类变量的赋值动作和静态语句块按照在源文件中出现的顺序合并而成,该合并操作由编译器完成。

java虚拟机规范严格规定了有且只有一下5中情况必须立即对类进行初始化:

  1. 遇到new,getstatic,putstatic,invokestatic这4条字节码指令时,生成这4条指令的最常见的java代码场景是:使用new实例化对象时,读取或设置类的静态字段时,调用一个类的静态方法时
  2. 使用java.lang.reflect对类进行反射调用时,如通过Class.forName()创建对象时
  3. 当初始化一个类时,如果父类还没有初始化,则要先触发父类的初始化,即先要执行父类的构造器方法<clinit>()
  4. 启动虚拟机时,需要初始化包含main方法的类
  5. 在JDK1.7中,如果java.lang.invoke.MethodHandler实例最后的解析结果REFgetStatic、REFputStatic、REF_invokeStatic的方法句柄,并且这个方法句柄对应的类没有进行初始化

以下几种情况,不会触发类初始化:

一文让你理解Class类加载机制

是Class文件加载进虚拟机中的类,生产的玩具就是类的实例对象。

因此,从Class文件到对象需要经过的步骤大致为: Class文件-->类-->实例对象 而类的加载机制,就是负责将Class文件转换成虚拟机中的类的一个过程。

总的来说,类的加载过程,包括卸载在内的整个生命周期共有以下7个阶段:

一文让你理解Class类加载机制

加载、验证、准备、初始化、卸载这5个阶段的顺序是确定的,但是解析阶段不一定,在某些情况下解析可以在初始化之后再执行,为了支持java的运行时绑定,也成为动态绑定或晚期绑定。invokedynamic指令就是用于动态语言支持,这里“动态”的含义是必须等到城市实际运行到这条指令的时候,解析动作才开始执行。

加载

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

加载阶段中“通过类的全限定名获得该类的二进制字节流”这个动作,被放到java虚拟机外部实现,目的是最大限度的让应用程序去决定该如何获取所需的类,而实现该动作的代码模块就是类加载器(ClassLoader),JVM提供了3种类加载器:

加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区中了。

验证

加载完成后,紧接着(更确切的说是交叉执行)虚拟机会对加载的字节流进行验证。虚拟机如果不检查输入的字节流,对其安全信任的话,很可能会因为载入了有害的字节流而导致系统崩溃。验证阶段大致会完成4中不同的检验动作:

文件格式验证 

文件格式验证主要是校验该字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机所接受。这个阶段包括但不限于以下验证点:

元数据验证 

元数据验证主要是对字节流中的描述信息(描述符)进行语义分析,以确保其描述的信息符合java语言规范的要求。这个阶段包括但不限于以下验证点:

字节码验证 

字节码验证主要是对类的方法体进行分析,确保在方法运行时不会有危害虚拟机的事件发生。这个阶段包括但不限于以下验证点:

符号引用验证 

符号引用验证主要是对类自身以外的信息进行匹配新校验,包括常量池中的各种符号引用。这个阶段包括但不限于以下验证点:

准备

准备阶段是为类变量(static)在方法区中分配内存并设置初始值(默认值,如int的默认值为0)的阶段。实例变量将会在对象实例化时随着对象一起分配在java堆中。为类变量设置初始值跟该变量是否有final修饰符有关系。 如果没用final进行修饰,如下列的代码:

// 准备阶段执行完成后,value变量的值为int的“零值”,即:0
// 把value赋值为10的putstatic指令是程序被编译后,
// 存放于类构造器<clinit>()方法中的,所以具体赋值的操作会在初始化阶段执行
public static int value = 10;

如果使用了final进行修饰,如下列的代码:

// 如果类字段的字段属性表中有ConstantValue属性,
// 则会在准备阶段将变量的值初始化为ConstantValue所指定的值
// 准备阶段执行完成后,VALUE变量的值被赋值为20
public final static int VALUE = 20;

解析

解析阶段是虚拟机将常量池中的符号引用替换为直接引用的过程,在该阶段将会进行符号引用的校验。 符号引用是Class文件中用来描述所引用目标的符号,可以是任何形式的字面量。 直接引用是虚拟机在内存中引用具体类或接口的,可以是直接指向目标的指针,相对偏移量或者是一个能间接定位到目标的句柄。 简单的来说,符号引用是Class类文件用来定位目标的,直接引用是虚拟机用来在内存中定位目标的。

一文让你理解Class类加载机制

初始化

初始化阶段是执行类的构造器方法<clinit>()的过程,类的构造方法由类变量的赋值动作和静态语句块按照在源文件中出现的顺序合并而成,该合并操作由编译器完成。

java虚拟机规范严格规定了有且只有一下5中情况必须立即对类进行初始化:

  1. 遇到new,getstatic,putstatic,invokestatic这4条字节码指令时,生成这4条指令的最常见的java代码场景是:使用new实例化对象时,读取或设置类的静态字段时,调用一个类的静态方法时
  2. 使用java.lang.reflect对类进行反射调用时,如通过Class.forName()创建对象时
  3. 当初始化一个类时,如果父类还没有初始化,则要先触发父类的初始化,即先要执行父类的构造器方法<clinit>()
  4. 启动虚拟机时,需要初始化包含main方法的类
  5. 在JDK1.7中,如果java.lang.invoke.MethodHandler实例最后的解析结果REFgetStatic、REFputStatic、REF_invokeStatic的方法句柄,并且这个方法句柄对应的类没有进行初始化

以下几种情况,不会触发类初始化:

一文让你理解Class类加载机制

推荐阅读:
  1. 一文让你轻松了解全文检索
  2. 一文让你秒懂Mybatis的SqlSession运行原理

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

class 程序员 java

上一篇:Dockerfile 定制镜像

下一篇:S2D HCI 声明磁盘

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》