欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

java类的加载过程以及类加载器的分析

程序员文章站 2024-02-20 13:12:46
我们知道,我们写的java代码保存的格式是 .java, java文件被编译后会转换为字节码,字节码可以在任何平台通过java虚拟机来运行,这也是java能够跨平台的原因。...

我们知道,我们写的java代码保存的格式是 .java, java文件被编译后会转换为字节码,字节码可以在任何平台通过java虚拟机来运行,这也是java能够跨平台的原因。

那jvm是如何来让我们写的java文件运行的呢? 这个问题通常的问法好像是:类是如何被加载的。

记得第一次遇见这个问题的时候,同学给我的回答是:

1.虚拟机会加载jdk里类的核心包

2.虚拟机会加载jdk里类的扩展包

3.虚拟机会加载jdk里类的系统包

4.虚拟机再会加载我们写好的java类。

初学的时候,大家都这么说,好像也没发现什么错。 最近在浏览一些博客时看到一些更为详细的讲解,如java类加载全过程,该博文有一万多的点击,但感觉还是讲得不够详细,说了类的加载过程有哪些,但没有详细的展开,说了一些类初始化的细节。 在翻读《深入理解java虚拟机》209-235页后,总结了其内容,谈谈自己对该部分的理解吧。

希望大家看了之后更能理解jvm的工作原理和java类的生产过程(类加载的过程);

类从被加载到虚拟机类存中开始,到被卸载出内存为止,它的整个生命周期包括

加载 → 验证 → 准备 → 解析 → 初始化 → 使用 → 卸载 7个部分、

下面我就来详细的说说每个部分的详细过程,再补充一下双亲委派模型。

再次之前我想补充一个名词解释,类加载器:虚拟机把 实现 类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流” 这个过程的代码称为类加载器

1. 加载

加载只是类加载过程的一个阶段而已,但往往被大家弄成了这就是类的加载过程,所以才有了博文开头时同学给我的那个回答;

希望大家不要混淆出这个很相似的名词,从而对类加载有所误读。

1.jdk在执行程序运行命令时会去jre目录中找到jvm.dll , 并初始化jvm

这时会产生一个bootstrap loader(启动类加载器)

2.bootstrap loader 自动加载 extended loader(标准扩展类加载器)

3.bootstrap loader 自动加载 appclass loader(系统类加载器)

4.最后由 appclass loader 加载 我们指定(想要运行)的 java 类

这里可以提一下双亲委派模型加载类的方式:

实现双亲委派的代码都集中在java.lang.classloader的 loadclass()方法中, 源码我就不贴出来了;

其源码大概意思如下:

1.先检查此类是否被加载过,若没有加载则调用父加载器的loadclass()方法,

2.若父加载器为空,则默认使用启动类加载器作为父加载器,

3.若父类加载失败,会抛出一个异常,然后再调用自己的findclass()方法来进行加载;

结合第一步加载可以这么理解,

1.首先要启动→ 启动类加载器,这时会调用启动类加载器的父加载器,但由于启动类加载器时所有类的父加载器,
所以其父加载器为空(相当于object是所有类的父类,这种感脚~),然后它就会调用自己的findclass方法来自启动加载 ;

2.标准扩展类加载器启动时就会借助其父类 启动类加载器 作为父加载器 来启动了;

3.系统类加载器启动时就会借助其父类 标准扩展类加载器 作为父加载器 来启动了;

4.最后我们编写的普通类就会借助其父类 系统类加载器 作为父加载器 来启动了;

2.验证

验证主要分为以下几个步骤:文件格式验证->元数据验证->字节码验证->符号引用验证

1.文件格式验证:主要是检查字节码的字节流是否符合class文件格式的规范,验证该文件是否能被当前的 jvm 所处理,
如果没问题,字节里就可以进入方法区进行保存了;

2.元数据验证:对字节码描述的信息进行语义分析,保证其描述的内容符合java语言的语法规范,能被java虚拟机识别;

3.字节码验证:该部分最为复杂,对方法体内的内容进行验证,保证代码在运行时不会做出什么危害虚拟机安全的事件;

4.符号引用验证:来验证一些引用的真实性与可行性,比如代码里面引了其他类(符号中通过字符串描述的全限定名是否能找到对应的类),这里就要去检测一下那些来究竟是否存在;或者说代码中访问了其他类的一些属性,这里就对那些属性的可以访问行进行了检验。(这一步将为后面的解析工作打下基础)

多说两句。。。 我觉得这个验证就是看class文件符不符合 jvm 的胃口 , 如果不符合 jvm 的胃口的话,无法完成加载,说明你写的代码 有毒.... 偷笑偷笑

3.准备

准备阶段会为类变量(指的是静态变量,这就是我们常说的,静态变量/方法 在类加载的时候就执行了,通过类名.静态**来调用)分配内存并设置类的初始值; 值得一提的是 如果有以下语句:

public static int i = 123 ;

在准备阶段的初始值是 0 ,而不是 123 , 是因为此时 只是分配内存空间而已, 并没有对 i 进行初始化, 真正的对 i 赋值是在 初始化 阶段;

4.解析

1.类或接口的解析;

2.字段解析;

3.类方法解析;

4.接口方法解析;

此部分内容涉及 invokedynamic指令,静态、动态语音调用 不做展开

如果解析到代码内容有问题,解析不通过将会抛出异常!

5.初始化

类初始化阶段是类加载过程中的最后一步,这才是执行类中定义的java程序代码(也可以说是字节码)。
在准备阶段,已经为变量赋过一次系统要求的初始值,到了初始化阶段会根据程序员的要求出初始化变量赋值。

java虚拟机没有严格约束什么时候开始类加载过程的第一阶段,但严格规定了有且只有5钟情况必须立即马上光速对类进行 初始化

当然加载、验证、准备需要在次之前,(解析也可以在初始化以后再开始~)

1.遇到new,get static,put static,invoke static这4条字节码指令时,假如类还没进行初始化,则马上对其进行初始化工作。
也就是三种情况:用new实例化一个对象时、读取或设置一个雷的静态字段时、执行静态方法时;

2.使用java.lang.reflect.*的方法对类进行反射调用时,如果类还没有进行过初始化,立即马上光速对其进行初始化!!!

3.初始化一个类的时候,如果其父类还没有被初始化,那么会先去初始化其父类;

4.当 jvm 启动时,用户需要指定一个要执行的主类(包含static void main(string 【】args)的那个类),则jvm会先去初始化这个类;

5.当使用jdk1.7 的动态语言支持时,如果一个java.lang.invoke.methodhandle实力最后的解析结果为 get static,put static,invoke static 的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先初始化;

小结:

介绍了类加载过程的 加载、验证、准备、解析、初始化、等5个阶段,以及虚拟机进行了哪些动作,简单叙述了类加载器的工作原理,如果有说得不妥当的地方,还以请大家批评指正,多多交流。