JVM类加载过程

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 相信有一部分java程序员不是太清楚虚拟机是如何将类从java代码变成class文件,再从class文件到到内存,再将我们写的程序转化成具体的程序的,这里就总结下这个过程。

前言:相信有一部分java程序员不是太清楚虚拟机是如何将类从java代码变成class文件,再从class文件到到内存,再将我们写的程序转化成具体的程序的,这里就总结下这个过程。


一.类加载的时机



了解java类是如何被加载的之前,我们有必要知道虚拟机是什么时候选择加载java类,下面说下虚拟机规定的几种必须加载java类的场景(有且只有这些场景会加载类)。


1.遇到一些字节码指令时,需要去加载相应得类。


new指令:这个创建对象的指令,所以需要加载对应的类。

getstatic指令:这是获取静态变量值的指令,所以也需要加载对应的类。

putstatic指令:这是为静态变量设置值的指令,所以也需要加载对应的类。

invokestatic指令:这是个专用于调用静态方法的方法调用指令,所以也是需要加载对应的类。


2.使用反射包(java.lang.reflect)中的方法进行反射调用时,如果对应类型没有初始化过则必须先进行初始化.


3.初始化时如果发现父类没有初始化过,则需要先初始化父类。这个操作与super关键字密切相关,与invokespecial指令密切相关(super关键字的底层实现就是invokespecial指令)。


4.虚拟机其多功能时,用户需要指定一个包含main方法的主类,虚拟机必须先初始化这个类。


5.当使用JDK7加入的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例的最后解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial这四种类型的方法句柄,且这个方法句柄对应的类未被加载过,应先加载对应的类。


6.当一个接口中定义了default方法时(JDK8特性),在接口的实现类发生初始化之前时,应该先初始化该接口。


二.类加载的过程



一个类从java代码到在内存中消失要经历以下几个过程:加载->验证->准备->解析->初始化->使用->卸载。这其中我们最为熟悉的可能就是使用与卸载了,使用自然就是我们的代码被运行起来了。定义的代码跑出了想要的结果,卸载基本可以对应到垃圾收集这个过程,当前类使用完毕,先是该类的对象被回收,最后该类的类型信息也会被回收(被回收的概率很低),那其他五个过程呢,相信很多人都是不太明了,下面介绍下这五个过程。


20210306083547406.png


1.加载


加载的过程:


①通过类的全限定名获取定义此类的二进制字节流,假设这个二进制字节流是class文件,这个第一步就是根据全限定名从本地内存获取到class文件。②将上一步获得的class文件信息加载到运行时数据结构。运行时数据结构包括堆、栈、方法区、等等这些,把加载的class信息存储到这些结构中。③当加载完成class文件后,虚拟机需要为这个类型信息生成一个java.lang.Class对象,作为方法区这个类的数据访问入口,这个对象就是我们在用反射时获取到的某个java对象的Class对象,值得说到的是每个java类被虚拟机加载后,都会有一个对应的Class对象产生,且以后再去生成该类的Class对象其实都是调用原始生成的那一个,这是一个单例模式产生的对象。


20210306094847384.png


二进制字节流的获取方式?


加载过程的第一步就是去获取二进制字节流,上面我们假设这个二进制字节流是class文件,其实二进制字节流还有很多种获取方式。①从class文件中获取,这也是我们比较常用的一种。②也可以从zip、jar、war、ear包中获取,这几种都是我们常用的打包方式(jar<war<ear)。③也可以从网络中获取,如Web Applet。④使用动态代理运行时计算生成。⑤由其他文件生成如JSP。⑥从数据库或者加密的文件中读取等等。等等总之二进制字节流的获取方式很多,只要保证被虚拟机加载时是能被虚拟机识别的class文件就行。


知道了加载的过程,具体是如何加载的呢?


虚拟机其实是使用类加载器来对类进行加载的,往后一点我们会单独介绍类加载这块,现在只需要知道class对象都是被虚拟机中的类加载器加载进虚拟机的。


2.验证


验证阶段顾名思义,就是虚拟机加载完class文件后,来验证这个class文件是否是一个合法正常可执行的文件,如果对于class文件的结构比较清晰,这块还是很好理解的。


验证的过程


①文件格式验证:校验class的魔数是否是0xCAFEBABE,验证主版本是否是可执行范围之内,因为虚拟机是拒绝执行高于当前版本的class文件的。验证常量池中的常量是否有不背支持的类型。验证的第一阶段主要验证的就是class格式以及文件的正确性。

②元数据验证:验证字节码信息中的类是否符合“java语言规范”,验证这个类是否有父类,除了Object,其他所有类都应该有父类,验证该类是否继承了不能被继承的类,比如final修饰的类是不能继承的。若该类实现了接口、继承了抽象类验证该类是否重写了所有的抽象方法等等。

③字节码验证:前面第一步验证了文件格式,保证是个可以被虚拟机执行的class文件,第二步验证了该类的书写是否规范,这一步就是要验证程序的逻辑是否有问题了,通过数据流控制流分析确定程序语义是合法的、符合逻辑的等。比如:验证操作数栈上的类型与指令都能配合工作。验证方法中的类型转换是否有效等等。

④符合引用验证:经过前面一通的验证,现在class文件已经威胁不到虚拟机的运行,但是仍不能保障可以正常被虚拟机执行,我们还要确定类中的引用是否是这是有效的引用,比如:符号引用中全限定名对应的类是否是真实存在,如果类存在了,还应验证类中是否有需要的方法、字段存在。如果存在了,还需要压制对应的类、方法、字段是否是当前类可以访问的。



20210306095057967.png


3.准备

准备阶段的工作其实比较单一,就是为类变量(static修饰的变量) 赋初始值,数据类型是0,boolean类型是false,引用类型是null。需要注意的是这里只是位类变量赋值,不会为实例变量赋初始值,实例变量的初始值要等到对象创建时,而且虽然这时是位类变量赋值,但是一般不会真正为这个类变量赋实际值,除非该类变量是final修饰的。列出一张各种数据的初始值(类变量与Class对象JDK8之后都在堆中)。


20210306095846285.png


4.解析


解析要干什么?


解析就是把运行时常量池中的符号引用解析成直接引用,在解析阶段将符号引用转化为直接引用也叫静态连接,在运行时再将符号引用转化为直接饮用叫动态连接,那问题来了符号引用是个啥?我们应该知道运行时常量池中存储的信息主要就是符号引用与字面量。符号引用指向的是class文件中或者也有可能是常量池中的一块地址,比如Object obj = new Object();当碰到new指令时,虚拟机会去解析new关键字后面的方法所指向的符号引用,这个引用其实是指向了运行时常量池中的方法表信息,把这个符号引用解析成直接引用就是将该符号引用指向的内存加载进虚拟机执行。构造器对应的方法代码加载进内存,自然就是进入了虚拟机栈中,成为一个栈帧,方法执行完就会创建出一个Object的对象,该对象在堆中,把这个堆中的地址赋给obj,此时obj就成了一个直接引用。


从上面的文字已经可以看出,虚拟机是在碰到了需要解析的符号引用时才发生了解析,那么有哪些情况需要出发解析呢?


《java虚拟机规范》中规定,在执行new、annewarray、multiannewarray、getfield、putfield。。。等17个用于操作符号引用的字节码指令时应该对符号引用进行解析。


下面举三个例子说明下虚拟机是如何把符号引用转换为直接引用的:


①类或接口的解析

假设当前所处的类为D,有一个从未被解析过的符号引用N,他对应的类或接口是C。那么解析这个符号引用N将有以下几步:第一步,如果C不是数据类型,那么会把符号引用N代表的全限定名传递给虚拟机进行加载C类,出现异常的话,解析失败。第二步:如果C是数组,且数组元素是对象,则按照第一种情况加载所属类,然后虚拟机会生成一个代表数组和元素的数组对象。第三步:验证C的访问权限,确认下D是否能够访问到C,可以的话,则转化为直接引用了。


②字段的解析

字段的解析,首先要保证该字段所在的表已经被解析了,字段存在字段表中,字段表中会存储该字段所属类信息,虚拟机会先去解析这个类,假设这个类是C,若C已经解析完成,那么字段解析有如下过程:第一步:若C已经被解析过了,在C中搜索简单名称与字段描述符均相匹配的字段,找到了,则返回该字段的直接引用。第二步:经过第一步没有找到,则去C的接口中去查找,从下而上找,查找简单名称与字段描述符均匹配的字段。找到则返回。第三步:若是第二步仍未找到,则是去其父类中寻找,过程与第二部类似。第四步:如果查到了返回引用过程中会对权限进行验证,未通过或者未查到都会报异常。


③方法的解析

方法的解析,也是首先需要解析该方法所属类,用C表示这个类,若所属类完成了解析。第一步:检查C是否是个接口,是接口直接报错,接口中的方法时不能直接运行的,没有方法体。第二步:通过了第一步的校验,则会在当前类C中查找有无简单名称与方法的描述符都匹配的方法,有则返回直接引用。第三步:若是第二步查找失败,则去查找父类,自下而上。类似于字段的解析。第四步:若是第三步失败了,则去查找接口,自下而上,若是成功了,则报错,因为接口中未实现的方法调用是没有意义的。

以上三个例子都说明了解析这个过程到底要干什么,代码里的引用方开始都是个符号引用,需要将符号引用指向的内容加载进内存,变成直接引用,虚拟机才能使用。


5.初始化

这个过程功能也是比较单一的,就是为了给类级别的信息赋值,前面我们已经说过,在“准备”阶段,会为类变量赋初始值,但是我们自己如果设置了值,并不会在“准备阶段”赋给类变量,这一阶段就是虚拟机收集类级别信息,包含类变量、类代码块啊等等,根据代码为他们复制的过程。

20210306183355581.png


6.总结类加载过程


类的加载过程经过了加载、验证、准备、解析、初始化等这些步骤,才可以正常的被使用,加载主要是将class文件中信息加载进入虚拟机,验证则是对class文件的格式,内容,流程,语法等进行校验,准备阶段是位类变量赋初始值,解析阶段是将符号引用转化为直接引用的过程。初始化则是为类变量赋值的动作。然后我们就可以正常使用类的信息了。


相关文章
|
2月前
|
安全 Java 应用服务中间件
JVM常见面试题(三):类加载器,双亲委派模型,类装载的执行过程
什么是类加载器,类加载器有哪些;什么是双亲委派模型,JVM为什么采用双亲委派机制,打破双亲委派机制;类装载的执行过程
JVM常见面试题(三):类加载器,双亲委派模型,类装载的执行过程
|
1月前
|
缓存 前端开发 Java
JVM知识体系学习二:ClassLoader 类加载器、类加载器层次、类过载过程之双亲委派机制、类加载范围、自定义类加载器、编译器、懒加载模式、打破双亲委派机制
这篇文章详细介绍了JVM中ClassLoader的工作原理,包括类加载器的层次结构、双亲委派机制、类加载过程、自定义类加载器的实现,以及如何打破双亲委派机制来实现热部署等功能。
42 3
|
1月前
|
小程序 Oracle Java
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
这篇文章是关于JVM基础知识的介绍,包括JVM的跨平台和跨语言特性、Class文件格式的详细解析,以及如何使用javap和jclasslib工具来分析Class文件。
41 0
JVM知识体系学习一:JVM了解基础、java编译后class文件的类结构详解,class分析工具 javap 和 jclasslib 的使用
|
2月前
|
存储 算法 Java
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
本文介绍了 JVM 的内存区域划分、类加载过程及垃圾回收机制。内存区域包括程序计数器、堆、栈和元数据区,每个区域存储不同类型的数据。类加载过程涉及加载、验证、准备、解析和初始化五个步骤。垃圾回收机制主要在堆内存进行,通过可达性分析识别垃圾对象,并采用标记-清除、复制和标记-整理等算法进行回收。此外,还介绍了 CMS 和 G1 等垃圾回收器的特点。
112 0
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
|
3月前
|
存储 算法 Java
JVM组成结构详解:类加载、运行时数据区、执行引擎与垃圾收集器的协同工作
【8月更文挑战第25天】Java虚拟机(JVM)是Java平台的核心,它使Java程序能在任何支持JVM的平台上运行。JVM包含复杂的结构,如类加载子系统、运行时数据区、执行引擎、本地库接口和垃圾收集器。例如,当运行含有第三方库的程序时,类加载子系统会加载必要的.class文件;运行时数据区管理程序数据,如对象实例存储在堆中;执行引擎执行字节码;本地库接口允许Java调用本地应用程序;垃圾收集器则负责清理不再使用的对象,防止内存泄漏。这些组件协同工作,确保了Java程序的高效运行。
27 3
|
3月前
|
C# UED 开发者
WPF动画大揭秘:掌握动画技巧,让你的界面动起来,告别枯燥与乏味!
【8月更文挑战第31天】在WPF应用开发中,动画能显著提升用户体验,使其更加生动有趣。本文将介绍WPF动画的基础知识和实现方法,包括平移、缩放、旋转等常见类型,并通过示例代码展示如何使用`DoubleAnimation`创建平移动画。此外,还将介绍动画触发器的使用,帮助开发者更好地控制动画效果,提升应用的吸引力。
173 0
|
4月前
|
存储 前端开发 Java
(二)JVM成神路之剖析Java类加载子系统、双亲委派机制及线程上下文类加载器
上篇《初识Java虚拟机》文章中曾提及到:我们所编写的Java代码经过编译之后,会生成对应的class字节码文件,而在程序启动时会通过类加载子系统将这些字节码文件先装载进内存,然后再交由执行引擎执行。本文中则会对Java虚拟机的类加载机制以及执行引擎进行全面分析。
|
4月前
|
Java Perl
JVM内存问题之如何统计在JVM的类加载中,每一个类的实例数量,并按照数量降序排列
JVM内存问题之如何统计在JVM的类加载中,每一个类的实例数量,并按照数量降序排列
|
4月前
|
存储 安全 Java
开发与运维引用问题之JVM类加载过程如何解决
开发与运维引用问题之JVM类加载过程如何解决
31 0
|
1月前
|
存储 安全 Java
jvm 锁的 膨胀过程?锁内存怎么变化的
【10月更文挑战第3天】在Java虚拟机(JVM)中,`synchronized`关键字用于实现同步,确保多个线程在访问共享资源时的一致性和线程安全。JVM对`synchronized`进行了优化,以适应不同的竞争场景,这种优化主要体现在锁的膨胀过程,即从偏向锁到轻量级锁,再到重量级锁的转变。下面我们将详细介绍这一过程以及锁在内存中的变化。
37 4