Java中的类加载机制

类加载机制

1、我们自己编写的Java代码是如何在各种各样的操作系统上运行起来的?

在这里插入图片描述

Java文件通过javac编译成class文件,这种中间码被称为字节码。然后由JVM加载字节码,运行时由解释器将字节码解释为一行行的机器码来执行。在程序运行期间,即时编译器会针对热点代码将该部分字节码编译成机器码以获得更高的执行效率。在整个运行时间,解释器和即时编译器的相互配合,使Java程序几乎能够达到和编译型语言一样的执行速度。

大部分普通程序员可以接触到的应该是JVM加载字节码的这个过程,被称为类加载。

2、类加载流程的目的?

在宏观上看,就是把一份被javac编译过的class文本文件通过加载,生成某种形式的Class数据结构进入内存,程序可以调用这个数据结构来构造出object。这个过程是在运行时进行的,这也是Java动态拓展性的根基。

3、

在这里插入图片描述

说明:

  • 这张图表现了一个类的生命周期,完整一点的话,我们可以在最开始加上javac编译阶段。而“类加载”只包括加载、连接、初始化这三个过程。

  • 需要区分“类加载”和“加载”,“加载”只是类加载的第一个环节。

  • 解析部分是灵活的,它可以在初始化环节之后再进行,实现所谓的“后期绑定”,其他环节的顺序不可改变。

在这里插入图片描述

这里的class文件,不一定指的是本地文件,泛指各种来源的二进制流,比如说来自于网络、数据库甚至是即时生成的class文件。其中,动态代理技术就是使用了即时计算出来的class,然后实例化代理对象。

在这里插入图片描述

第一个步骤,对文件格式的验证,其实是发生在加载阶段的,如果通过才能顺利加载。顺利加载后,此时方法区中虽然已经存在了该class的静态结构,对中也存在了该class类型的对象,但是这并不代表着JVM已经完全认可了这个类。如果程序想要使用这个类就必须进行连接。而连接的第一步就是进一步的对这个类进行验证。

连接中的第一个验证是元数据的验证,第二个是字节码的验证,简单概括就是对class静态结构进行语法和语义上的分析,保证其不会产生危害虚拟机的行为。如果这两个验证通过,那么虚拟机会姑且认为该class是安全的,但这并不意味着验证已经完全结束了,还有一道对符号引用进行验证的步骤,它是在解析阶段发生的,解析阶段可以在初始化之前或者之后进行,所以验证其实包含了很多的步骤,分散在各个不同的阶段内,这一点是上面这张图画的不准确的地方。

此外验证的内容会不断发展的,除了上述提到的验证内容,还会有更多的验证策略。

在元数据字节码验证通过之后,虚拟机会姑且认为该class是安全的,这时候将会进入准备阶段。

在这里插入图片描述

准备阶段做的处理其实不复杂,就是为该类中定义的静态变量赋0值,注意这里是静态变量,而不是成员变量。

这里有一个比较容易混淆的概念:虚拟机内存规范中定义了方法区这种抽象概念,HotSpot这种主流的虚拟机在JDK8之前,使用了永久代这种具体的实现方式来实现方法区。在JDK8及以后弃用了永久代这种实现方式,采用元空间这种直接内存来取代。

所以说,我们常常看到有人说“JDK8及以后采用元空间来代替方法区”这种说法是不对的,因为方法区是抽象概念,元空间是实现方式。

在JDK8之前,类的元信息、常量池、静态变量等都存储在永久代这种具体实现中;而在JDK8及以后,常量池、静态变量被移除了“方法区”,转移到了“堆”中,元信息以来存储在方法区中,但是具体的实现方式改为了元空间。

准备阶段完成之后,进入解析阶段。

在这里插入图片描述

这一阶段主要做的事情是:将符号引用替换为直接引用。

符号引用、直接引用是什么呢?

当一个Java类被编译成class之后呢,假如这个类被称为A,并且在A中引用了B,那么在编译阶段,A是不知道B有没有被编译的,而且此时,B也一定没有被加载,所以A肯定不知道B的实际地址。那么A怎么才能找到B呢?

此时在A的class文件中,将使用一个字符串S来代替B的地址,S就被称为符号引用。在运行时,如果A发生了类加载,到解析阶段会发现B还未被加载,那么将会触发B的类加载,将B加载到虚拟机中。此时A中B的符号引用将会被替换成B的实际地址,这被称为直接引用。这样A就能真正的调用的B了。

但是事情没有那么简单,Java中的多态机制体现一个东西,Java通过后期绑定的方式来实现多态,那么后期绑定这个概念又是如何实现的呢?

其实就是这里的动态解析。

如果A调用的B是一个具体的实现类,那么就被称为静态解析,因为解析的目标类很明确。

如果上层的Java代码使用了多态,这里的B可能是一个抽象类或接口,那么B可能有两个具体的实现类C和D,此时B的具体实现并不明确,当然也就不确定使用那么具体类的直接引用来进行替换。

既然不知道,那么就等一等吧…直到运行过程中发生了调用,此时虚拟机调用栈中会将得到具体的类型信息,这时候再进行解析,就能用明确的直接引用来替换符号引用了。

这也就是为什么解析阶段有时会发生在初始化阶段之后,这就是动态解析,用它来实现了后期绑定。

当解析步骤完成意味着整个连接部分的完成,这就是说外部加载的Java类已经成功地引入到了程序中。

接下来就进入了初始化阶段。

在这里插入图片描述

初始化阶段简单概括就是:此时会判断代码中是否存在主动的资源初始化操作,如果有的话就执行。

这里所说的主动的资源初始化操作,不是指的是构造函数,而是class层面的,比如说成员变量的赋值动作、静态变量的赋值动作以及静态代码块的逻辑,而只有现实的调用new指令,才会调用构造函数进行对象的实例化,这是对象层面的,而这不要混淆。

在这里插入图片描述

最后让我们来回顾一下,从JVM的角度来看,加载阶段的读取二进制流这个动作以及初始化阶段这两个部分是开放了主导权给用户的,用户可以自由控制。而剩下的所有部分,都是由虚拟机全权包揽的,由其内部来完成。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jackson Xi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值