【Java】深入浅出讲解类加载过程(类生命周期)

2 篇文章 0 订阅

前言

Java 类加载过程是 Java 程序运行时加载和链接类的重要组成部分。本文将详细讲解类加载过程的原理,包括类的生命周期、类加载器和类加载过程的各个阶段。

Java 类的生命周期

一:生命周期阶段

在 Java 虚拟机(JVM)中,类的生命周期可分为以下七个阶段:
在这里插入图片描述

  1. 加载(Loading)
  2. 验证(Verification)
  3. 准备(Preparation)
  4. 解析(Resolution)
  5. 初始化(Initialization)
  6. 使用(Using)
  7. 卸载(Unloading)

类加载的过程主要分为三个部分:加载连接初始化
其中“ 连接 ”部分,又分为:验证准备解析 三个部分

二、类加载器(ClassLoader)

类加载器负责将类的字节码文件从文件系统、网络等来源加载到 JVM 中。Java 提供了如下三种类加载器:

  1. 启动类(Bootstrap)加载器:由 C++ 实现,负责加载 Java 最核心的类库,如 java.lang.*
  2. 扩展类(Extension)加载器:加载 {JAVA_HOME}/lib/ext 目录下的类库。
  3. 应用类(Application)加载器:加载系统类路径下的类库。

  加载这一步主要是通过类加载器完成的。类加载器有很多种,当我们想要加载一个类的时候,具体是哪个类加载器加载由 双亲委派机制 决定(我们也能打破双亲委派机制)。

  双亲委派机制和类加载器也是非常重要的知识点,这部分内容在 深入了解双亲委派机制 这篇文章中有详细解读。感兴趣的朋友可以跳转观看,不过在本片文章中,大家只需要知道这种机制存在即可。

三、类加载过程详解

  1. 加载(Loading)

加载阶段是类加载过程的第一步,主要完成以下工作:

  • 通过类加载器将字节码文件读取到 JVM 内存中
  • 将字节流所代表的静态存储结构转换为方法区的运行时数据结构。
  • 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

虚拟机规范对这三点的要求并不具体,因此虚拟机实现与具体应用的灵活度都是相当大的。

  这种灵活度对于开发者来说主要体现在第一步,由于虚拟机规范并没有规定二进制字节流的来源,开发者可以从以下几个渠道获取:

  • 从zip包中获取,这就是以后jar、ear、war格式的基础
  • 从网络中获取,典型应用就是Applet
  • 运行时计算生成,典型应用就是动态代理技术
  • 由其他文件生成,典型应用就是JSP,即由JSP生成对应的.class文件
  • 从数据库中读取,这种场景比较少见
  1. 验证(Verification)

验证是连接的第一步,验证阶段目的是为了确保Class文件的字节流包含的信息符合当前虚拟机的要求,确保Java虚拟机不受恶意代码的攻击。验证阶段大致上会完成下面4个阶段的检查动作:

  • 文件格式检查:验证字节码文件结构、魔数、JVM 版本等。
  • 元数据检查:验证类、字段、方法的结构、修饰符等是否合法。
  • 字节码验证:验证方法体中的字节码指令是否合法,确保不会破坏 Jvm 的内存结构。
  • 符号引用验证:确保符号引用指向的类、字段、方法存在且权限合法。

  文件格式验证这一阶段是基于该类的二进制字节流进行的,主要目的是保证输入的字节流能正确地解析并存储于方法区之内,格式上符合描述一个 Java 类型信息的要求。除了这一阶段之外,其余三个验证阶段都是基于方法区的存储结构上进行的,不会再直接读取、操作字节流了。

  1. 准备(Preparation)

类静态字段:
  准备阶段为类中的静态字段分配内存并设置初始值,这些变量所使用的内存都将在方法区中进行分配。默认情况下,基本数据类型的初始值为 0,引用数据类型的初始值为 null。注意,静态字段的显式赋值操作在初始化阶段完成。

  假设一个类变量的定义为:public static int num = 123;那变量value在准备阶段过后的初始化值为0而不是123,因为这时尚未开始执行任何Java方法,而把value赋值为123的指令是程序被编译后存放在类构造器()方法之中,所以把value赋值为123的动作将在初始化阶段才会执行。

类常量字段:
  在JAVA字节码文件中,常量字段具有ConstantValue属性,存放于常量池中,ConstantValue属性持有常量的具体值,在准备阶段该常量就会被赋与具体值(并非Java字节码引起的)

类实例变量:
此阶段无操作,实例变量将会在对象实例化时随着对象一起分配在Java堆中

  1. 解析(Resolution)

解析阶段将在常量池中的符号引用替换为直接引用。包括类、接口、字段和方法的解析。

  • 符号引用。即一个字符串,但是这个字符串给出了一些能够唯一性识别一个方法,一个变量,一个类的相关信息。
  • 直接引用。可以理解为一个内存地址,或者一个偏移量。比如类方法,类变量的直接引用是指向方法区的指针;而实例方法,实例变量的直接引用则是从实例的头指针开始算起到这个实例变量位置的偏移量

举个例子来说,现在调用方法test(),这个方法的地址是101010,那么test就是符号引用,101010就是直接引用。

在解析阶段,虚拟机会把所有的类名,方法名,字段名这些符号引用替换为具体的内存地址或偏移量,也就是直接引用。

  1. 初始化(Initialization)

初始化阶段为类中的静态字段和静态代码块分配用户指定的初始值。这些指定值来源于字节码文件中的 ConstantValue 属性。执行顺序如下:

  • 父类的静态字段和静态代码块
  • 子类的静态字段和静态代码块
  1. 使用(Using)和卸载(Unloading)

类在被使用的过程中可能会被卸载,当一个类不再被引用时,它就会成为垃圾回收的对象。

卸载类需要满足 3 个要求:

  • 该类的所有的实例对象都已被 GC,也就是说堆不存在该类的实例对象。
  • 该类没有在其他任何地方被引用
  • 该类的类加载器的实例已被 GC

四、总结

Java 类加载过程是软件工程中的重要组成`部分。了解这个过程有助于我们深入理解 Java 应用程序的运行机制,提高代码性能,以及解决类加载方面的问题。本文到这里就结束了,感谢大家观看。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小王笃定前行

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

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

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

打赏作者

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

抵扣说明:

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

余额充值