JVM类加载过程科普

时间:2022-07-25
本文章向大家介绍JVM类加载过程科普,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

前言

我们知道一个 java 类想要被执行就必须被加载到内存中,而加载的过程呢有大体可以分为 加载、连接、初始化、使用、卸载,五部分,下面,我们就一起看一下各个部分 JVM 都做了什么。

首先,我们将这段代码编程成 class 文件,然后运行。


/**
 * Created by shengjk1 on 2020/8/30.
 */
public class Test {
	public static int b=7;
		
	public static void main(String[] args) {
		System.out.println("b = " + b);
		System.out.println("执行完毕");
	}
}

加载

其实就是从非内存的位置到内存中的一个过程。主要做三件事

  1. 通过一个类的全限定名获取定义此类的二进制字节流
  2. 将这个字节流所代表的的静态存储结构转化为方法区的运行时数据结构
  3. 在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口

这个过程呢,也主要是依靠类加载器来完成的

类加载器

引导类加载器: Bootstrap ClassLoader 启动类

  1. c/c++ 语言实现的,嵌套在 JVM 内部
  2. 用来加载 Java 核心类库( JAVA_HOME/jre/lib/rt.jar、resource.jar 或 sun.boot.class.path 路径下的内容 ),用于提供 JVM 自身需要的类
  3. 没有父加载器
  4. 为 加载扩展类和应用程序类加载器 的父加载器
  5. 出于安全考虑,Bootstrap 启动类加载器只加载包名为 java、javax、sun等开头的类

获取 classLoader 是 null 就是 Bootstrap ClassLoader

扩展类加载器

Java 语言编写,派生于ClassLoader 类。

系统属性 java.ext.dir 或者 JDK安装目录 jre/lib/ext

应用程序加载器( 系统类加载器,AppClassLoader )

Java 语言编写,派生于 ClassLoader 类,父类为 扩展类加载器

负责加载环境变量 classpath 或系统属性 java.class.path 下的类库

该类加载器是程序中默认的类加载器,一般来说,Java应用的类都是由它来完成加载

连接

至此为止,我们的 Test.class 就加载到了 JVM 中,而 JVM 呢为了自身的安全会进行一系列的校验。校验的过程叫做 验证

验证

目的在于确保 Class 文件的字节流中包含信息符合当前虚拟机的要求,保证被加载类的正确性,不危害虚拟机自身的安全。

文件格式验证、元数据验证、字节码验证、符号引用验证

准备

验证完毕之后,就开始为执行 Test.class 做准备了

为类变量分配内存并设置类变量初始值的阶段。准备了类的变量初始值之后,虚拟机会把该类的虚方法表也一同初始化完毕

所以此时会为类变量 b 分配内存空间然后设置初始值 b=0。而在 JDK8 以后,类变量会被分配在 Java 堆中

解析

准备阶段完成了,但我们的 Test 类要想被执行还差最重要的一步:解析

将常量池中符号引用转换为直接引用. ( 有了直接引用,那引用的目标必定已经在虚拟机的内存中存在了 )

什么是直接引用呢,其实就是真实的内存地址,就是把我们 b 的引用设置为内存地址,后面调用的时候就可以直接访问内存地址了,不需要再进行解析。

初始化

在连接阶段完成之后,就开始正式执行 main 方法了,由于 main 方法是 public static 的,所以 JVM 会判断 Test 类是否已经初始化了,如果没有则对其进行初始化

一般在下面这 6 中情况下会进行初始化

  1. 遇到 new、getstatic、putstatic或 invokestatic 时,如果未初始化则先初始化( 1. new 2.读取或设置一个类的静态字段 (被 final 修饰、已经在编译期把结果放入常量池的静态字段除外) 3. 调用一个类的静态方法 )
  2. 使用 java.lang.reflect 包的方法对类进行反射调用时,如果未初始化则先初始化
  3. 当初始化类时,如果其父类未初始化则先触发其父类初始化
  4. 当虚拟机启动时,用户需要指定一个要执行的主类,虚拟机会先初始化这个类
  5. 当使用 动态语言支持时,如果 java.lang.invoke.MethodHandle 的解析结构为 REF_*static、REF_new*句柄,并且这个句柄对应类没有进行初始化,需要先初始化
  6. 当有 默认方法 接口的实现类发生了初始化,则该接口要在其初始化之前初始化

而我们的 main 方法满足 1 条 invokestatic

初始化过程中会对所有的类变量进行赋值操作,会执行静态代码块,此时我们 b 就等于 7 了

使用

至此为止,才真正的开始执行 main 方法。会通过系统调用将 b 的值打印到控制台

卸载

Test 类运行完毕之后,为防止其继续占用内存,会将其卸载,也就是从内存中将其删除。

总结

其实,基本上每个阶段都是交叉执行的,而验证、准备、解析,有统称为连接

技术创作101训练营