JVM——详解类加载过程

8 篇文章 1 订阅
本文详细介绍了Java的类加载过程,包括加载、链接、初始化三个步骤。加载中讲解了类加载器的工作方式,如双亲委派机制,以及Bootstrap、ExtClassLoader和AppClassLoader的角色。链接阶段涉及验证、准备和解析三个子步骤。初始化则是对静态变量的赋值。文中还讨论了类加载的触发条件,并提供了加载器和初始化的示例代码。
摘要由CSDN通过智能技术生成

一、过程概述

java 源文件编译后会生成一个 .class文件存储在硬盘上。

在程序运行时,会将用到的类文件加载到 JVM 内存中。从磁盘到内存的过程总共分为三个步骤:加载、连接、初始化。

  1. Loading
  2. Linking
  3. Initializing

二、Loading

Loading 过程是把一个 class 文件加载到内存中去。

2.1 类加载器

JVM 加载类的方式是按需动态加载,采用双亲委派机制

认识几种系统提供的类加载器:

  1. Bootstrap 启动类加载器,负责加载指定目录——JAVA_HOME/lib下的 rt.jar、charset.jar 等核心类库,由 C++实现,是JVM 的不可分割的一部分。类加载范围可以在 Launcher 类中查看:
    在这里插入图片描述

  2. ExtClassLoader 扩展类加载器,负责加载指定目录——JAVA_HOME/lib/ext 下的扩展包,或者也可以由 -Djava.ext.dirs 参数指定。类加载范围可以在 Launcher 类中查看:
    在这里插入图片描述

  3. AppClassLoader 应用类加载器,加载用户应用的 classpath 下的 class 文件,这是应用程序的默认类加载器,用户自定义的类都是通过这个类加载器来加载。类加载范围可以在 Launcher 中查看:
    在这里插入图片描述

  4. 自定义类加载器,开发者自定义的 ClassLoader,继承自 ClassLoader 抽象类,并重写 findClass(…) 。

类加载器(除 C++实现的 Bootstrap 外)本身就是一个普通的 class,JVM 所有的 class 都是被类加载器加载到内存的。

public class TestLoadClass {
    public static void main(String[] args) {
        System.out.println(TestLoadClass.class.getClassLoader());
        System.out.println(TestLoadClass.class.getClassLoader().getParent());
        // Bootstrap 由 C++ 实现,Java 中没有具体的类与之对应,故返回 null
        System.out.println(TestLoadClass.class.getClassLoader().getParent().getParent());
    }
}
output:
sun.misc.Launcher$AppClassLoader@58644d46
sun.misc.Launcher$ExtClassLoader@4554617c
null

2.2 双亲委派机制

JVM 加载类时处于安全考虑,基础类和扩展类等,都必须由指定的类加载器来加载,不同的类加载器有自己的命名空间,同一个类,如果由不同的类加载器加载,会在内存中存在多份类对象。也正因如此,JVM 的类加载机制要求诸如 java.lang.Object 这种基础类必须由最基础的 Bootstrap 来加载。

因此,整个 Loading 的过程就成了:底层类加载器收到类加载请求后,必须将请求层层传递给父级加载器检查,确认是否应该由父级加载器加载,若由于父加载器的指定类路径中没有该类文件,就会再层层向下返回,最终才会去加载:
双亲委派机制流程

Tip:注意,AppClassLoader 的父加载器是 ExtClassLoader,ExtClassLoader的父加载器是Bootstrap 加载器。
这里的父加载器并不是 Java 多态中语法的 extends 继承关系,而是一种架构上的层级关系,AppClassLoader 和 ExtClassLoader 之间没有任何继承关系,它们在语法上,都继承自 ClassLoader 抽象类,实际上 ClassLoader 中维护了一个 ClassLoader parent 引用,这才是 “双亲” 的真实面目,即 AppClassLoader 的父加载器实际上是其内部的一个组合对象

2.3 类在内存中的结构

加载的时候,创建了两块内容,第一块内容是把 Xxx.class 二进制扔到内存中,第二块内容是生成一个 Class 类的对象,该对象中的变量会指向前一块的实际内存地址(这一步实际上是Resolution 的过程)。

三、Linking

Linking,连接,这是一个大的步骤,其中又分为三个小步骤:

  1. Verification 验证
  2. Preparation 准备
  3. Resolution 解析

验证:校验 class 文件格式是否符合 jvm 规范,如开头的魔数等。
准备:将 class 中的静态变量赋默认值,所谓默认值,举个例子,int 类型默认值是 0,String 类型默认值是 null。
解析:把class 文件常量池中的符号引用转化为直接内存地址

四、Initializing

初始化,区别于 Linking 中的 Preparation ,此过程将静态变量赋初始值。比如:

public static int num = 8;

这个 num 静态变量会在 Preparation 阶段赋值 0 ,在 Initializing 阶段赋值 8。

Java初始化的时机,JVM规范中有明确规定,自然,加载也一定是在初始化之前完成。JVM规范中定义了以下这些必须初始化完毕的场景:

  1. new
  2. 读取和设置 static 变量,只触发直属类的初始化(例如,子类直接引用父类中的 static 变量,只初始化父类)
  3. 调用 static 方法
  4. 子类初始化之前,父类必须完成初始化
  5. main 函数所在的类在执行之前必须完成初始化
  6. 通过反射获取类信息

注意:对于 static final 类型的属性,在编译之后即存储在字节码文件的常量池中,直接引用它不会触发初始化。

记忆技巧:

  1. new 是创建对象,而对象的创建一定需要类信息的支持
  2. static 变量和方法,又称为类变量、类方法,它们都需要类指针来访问。
  3. main也属于static 方法
  4. 子类是父类的扩展,子类都要初始化了,父类当然一定要先初始化
  5. 反射是使用类信息的一种技术,当然需要先初始化类
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值