当前位置:首页 » 《休闲阅读》 » 正文

JVM类加载机制--敲门篇_m0_52464332的博客

22 人参与  2022年01月03日 08:53  分类 : 《休闲阅读》  评论

点击全文阅读


引言

在开始之前先来了解一下编写的java代码如何在操作系统上运行起来。
图片来自b站寒食君

java文件通过javac编译成class文件生成字节码,JVM会加载字节码,运行时解释器将字节码解释成一行行机器码进行执行,在程序运行期间,即时编译器会针对热点的代码将该部分字节码编译成机器码已获得更高的执行效率,在整个运行时,解释器和即时编译器互相配合,使java程序集合能达到和编译语言一样的执行速度。

在上面这段话中,包含了许多现阶段我未接触的技术点,但是今天了解的就是jvm加载字节码这个过程,此过程被称为java类加载的过程。今天,看我入门java类加载,如果没入门,那权当敲门了!

java类生命周期

图片来自b站寒食君
该图表示了一个类的生命周期,完整一点,可以加上最开始的javac编译阶段。而“类加载”只包括加载,连接,初始化三个过程。
注意区别“类加载”与“加载”,加载只是类加载的第一个环节。
有时候解析阶段发生在初始化阶段之后,具体原因见解析阶段。

类的初始化

最早接触类的加载,应该是在学习反射反射的时候,那就先来看一下如何使用一个类,也就是类的初始化过程,再详细看一下如何加载到连接初始化一个类。
有5种情况会对类进行初始化:
1)遇到new、getstatic、putstatic或invokestatic这4条字节码指令。
常见场景:使用new创建一个对象,操作静态变量或静态方法。

Demo demo = new Demo(); // 使用new创建一个对象
Demo.FIELD = 1; // 设置静态变量值
System.out.println(Demo.FIELD); // 使用静态变量
Demo.method(); // 执行静态方法

2)通过反射进行调用

Class clazz = Class.forName(“com.dfyang.aop.utils.Demo”);

3)当初始化一个类时,先初始化其父类(这个容易理解,毕竟子类会继承父类)
4)当虚拟机启动时,虚拟机需要初始化主类,也就是程序的入口

public static void main(String[] args) {
    System.out.println("程序入口");
}

@SpringBootApplication
public class ZooCommunityApplication {
    public static void main(String[] args) {
        SpringApplication.run(ZooCommunityApplication.class, args);
    }
}

5)java.lang.invoke.MethodHandle实例最后解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄(这里仅了解)

这里我们已经知道了我们经常使用的类是如何进行初始化,接下来我们再来看看类的加载过程。

类加载

加载是读取Class文件,将其转化为某种静态数据结构存储在方法去内,并在堆中生成一个便于用户调用的java.lang.Class类型的对象的过程。

有图有真相,图形化编码方式建立脑神经索引。
在这里插入图片描述

字节码文件的来源:

从本地文件系统中直接加载。
通过网络获取,比如Web Applet应用。
从打包中获取。jar、war等。
运行时计算生成,比如动态代理技术。

连接阶段

连接:验证

在这里插入图片描述

目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,保证被加载类的正确性,不会危害到虚拟机自身安全。主要包括4种验证:文件格式验证、元数据验证、字节码验证、符号引用验证。
其实,文件格式验证发生在加载阶段,验证主要进行元数据,字节码验证,符号引用验证主要发生在解析阶段。
总而言之,验证其实发生在各个阶段,jvm会进行严格的验证,同时验证类型官方也在不断扩展。

连接:准备阶段

该阶段为类变量分配内存并设置该类变量的默认初始值,比如对象引入设置为null,int型设置为0。
这里不会包含使用final修饰的static,因为final在编译时就会分配内存了,准备阶段会显示初始化。
这里也不会为实例变量分配内存以及初始化,类变量会分配在方法区中,而实例变量会随着对象一起分配在java堆中。

(由于jdk8以前,类元信息常量池静态变量等存储在永久代,方法区实现了永久代。jdk8以后常量池,静态变量存储在堆中,类的元空间存储在元空间,方法区实现了元空间,该说法是否正确,由于笔者暂时未学习内存模型及相关知识,具体的尚未探究,暂时存疑)

连接: 解析阶段

将常量池内的符号引用转换为直接引用的过程。

理解为类A中引用了类B,在编译阶段使用字符S表示B的地址,在A加载时到解析阶段,触发B的递归加载,此时A中的符号引用S被替换为直接引用B的实际地址。

我以为事情就这么结束了,然而事情刚刚开始!!

我们初学多态时遇到过,java通过后期绑定的方式来实现多态,那么后期绑定如何实现的呢,以下说一下我初次学习的简单理解。
动态绑定其实就是解析阶段的动态解析,接着上面所说的,A调用的B是一个具体的实现类的话,就成为静态解析。此时解析的目标类很明确,只要进行加载获取目标类的加载地址即可替换成功。
假如上层java代码使用了多态,那么B可能是一个抽象类或者接口,那么它可能有两个具体的实现类C和D,此时B的具体实现并不明确,当然也就不知道使用的是哪一个具体类的直接引用来替换,既然不知道,那就等运行过程中发生调用,此时虚拟机调用栈中将得到具体的的类型信息,这时候进行解析,就可以用明确的直接引用进行符号替换,也就是动态解析。这就是为啥有时候解析会发生在初始化阶段之后。

此时连接部分完成,外部加载的java类成功引入程序中。

再谈类的初始化

初始化阶段此时会判断代码中是否存在主动地资源初始化操作
主动初始化不是指的构造函数,而是class层面的,比如成员变量赋值动作,静态变量的赋值动作,以及静态代码块的逻辑。
而只有显示调用new指令,才会调用构造函数进行对象的初始化,这是对象层面。
那么,类的加载过程终于终于敲门结束,那么既然敲了门那么我们再透过门缝往里面再瞧一瞧,我们简单看一下实例化子类时,父类与子类中的静态代码块、实例代码块、静态变量、实例变量、构造函数的执行顺序是怎样的?

Java父类与子类中静态代码块 实例代码块 静态变量 实例变量 构造函数执行顺序

详细顺序为:

1.父类静态代码块与父类静态变量赋值(取决于代码书写顺序)

2.子类静态代码块与子类静态变量赋值(取决于代码书写顺序)

3.父类实例变量赋值与父类代码块(取决于代码书写顺序)

4.父类构造函数

5.子类实例变量赋值与子类代码块(取决于代码书写顺序)

6.子类构造函数

ok,那么本次JVM类加载机制–敲门篇暂告段落,预知门后世界如何,待我苦修内功,带你华山论剑!
我是Code_Pianist,一位初学java的修士,关注我,苦修内功,华山论剑!
本篇参考资料:
文章部分图片来自:b站寒食君
参考内容来自:b站寒食君,DFYoung,leunging,《深入理解java虚拟机》等多方资料汇总


点击全文阅读


本文链接:http://zhangshiyu.com/post/32569.html

加载  初始化  变量  
<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1