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个阶段,以及虚拟机进行了哪些动作,简单叙述了类加载器的工作原理,如果有说得不妥当的地方,还以请大家批评指正,多多交流。

——  谢谢阅读  

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券