当前位置: 首页 > news >正文

类的生命周期

类生命周期

概述

类的生命周期可以大致分为加载连接初始化使用卸载,其中连接阶段又可以细分为验证准备解析这三个阶段。其中作为程序员,接触最多的是使用阶段,例如:通过反射或 new 的方式获得一个新对象。

image-20240530202237765

加载阶段

  1. 类加载器会根据类的全限定名通过不同的渠道(本地文件、动态代理生成、网络传输)以二进制流的方式获取字节码信息。
  2. JVM 会在类加载器将类加载完之后,把字节码中的信息保存到方法区。
  3. JVM 会将字节码的信息保存到内存的方法区中,此时会生成一个 InstanceKlass 对象,保存类的所有信息,包含了实现特定功能的例如多态的信息。
  4. 同时,JVM 还会在堆中生成一份与方法区中数据类似的 java.lang.Class 对象,其作用是在 Java 代码中去获取类的信息以及存储静态字段的数据(在 JDK 8之后)。

对于程序开发人员,其只需要访问堆中的 Class 对象而不是访问方法区中的所有信息。JVM 可以很好的控制开发者的访问数据的范围。

查看内存中的对象

先新建一个程序并执行

import java.io.IOException;public class Hello {public static final int num = 0;public static void main(String[] args) throws InterruptedException, IOException {Hello hello = new Hello();hello.hello();// 防止程序执行后直接退出System.in.read();}public  void hello() {System.out.println("Hello!");}
}

可以使用JDK自带的hsdb工具查看 Java 虚拟机内存的信息。工具位于 JDK 安装目录的 lib 文件中的 sa-jdi.jar 中。由于该jar内部有多个启动程序,所以需要指定其中的 HSDB 进行启动

java -cp sa-jdi.jar sun.jvm.hotspot.HSDB

注意:上述启动方法仅限于 JDK 8及以下版本,在8之上的版本,在 lib 目录下是无法找到 sa-jdi.jar 这个文件的,这是因为jhsdb 工具在 JDK 17 中已经取代了以前的 hsdb 工具。

如果已经配置JAVA_HOME环境变量,可以直接使用下面命令启动

jhsdb hsdb

若没有配置环境变量,则需要将jhsdb替换成jdk所在bin目录下的jhsdb.exe,用双引号包围起来,例如"C:\Program Files\Java\jdk-17\bin\jhsdb.exe" hsdb

启动成功之后会出现一个白色的窗口

image-20240529211636371

此时点击 File ,再点击 Attach to HotSpot Process…连接某个指定的 Java进程。如果不知道运行的程序的进程号是多少,可以使用jps命令在cmd窗口进行查看

image-20240529212043695

输入要查看的进程号,然后点击ok

image-20240529214414729

然后点击 Tools ,再点击 Object Histogram

image-20240529214508881

就会来到一个搜索页面,在输入框内输入类全限定名,按下 ENTER 键即可查询到运行的程序

image-20240529220112652

双击即可进入对象内部,再点击 Insepct 进行查看

image-20240529220247313

找到两处标红的地方,分别对应的就是上面描述的方法区对象与堆区对象。其中的num对应的是java中所定义的静态变量。

image-20240529220847173

连接阶段

  • 验证:验证内容是否满足“Java 虚拟机规范”,若不满足则不允执行,防止危害 JVM
  • 准备:给程序中的静态变量赋初始值
  • 解析:将常量池中的符号引用替换成指向内存的直接引用

验证

该阶段主要检测 Java 字节码文件是否遵守规范中的约束。该阶段一般不需要程序员进行参与。

验证主要会包含以下四个部分:

  1. 文件格式验证,比如文件是否以0xCAFEBABE开头,主次版本号是否满足当前Java虚拟机版本要求。
  2. 元信息验证,例如类必须有父类(super不能为空),即使程序员不主动继承父类,Java也会默认让所有类都统一继承 Object 类。
  3. 验证程序执行指令的语义,比如方法内的指令执行中跳转到不正确的位置。
  4. 符号引用验证,例如是否访问了其他类中的private方法等。

准备

该阶段会给静态变量分配相应的内存并设置默认的初始值。

数据类型(默认)初始值
int0
long0L
short0
char‘\u0000’
byte0
booleanfalse
double0.0
引用数据类型null

凡是都有个但是,如果该静态变量是由final修饰的基本数据类型,其在准备阶段会将代码中的值进行赋值,并且要求被final修饰的静态变量必须赋予初始值,否则编译器就会报错。

image-20240529223721143

解析

该阶段主要就是将常量池找中的符号引用替换为直接引用,即不再使用编号,而是直接使用内存中的地址进行访问具体的数据

image-20240530201947681

初始化阶段

该阶段会执行静态代码块中的代码,并将为静态变量赋值,并执行字节码文件中的 clinit 部分的字节码指令。一个类一般只会加载和初始化一次。

下面的几种方式会触发类的初始化:

  1. 访问一个类的静态变量或静态方法,注意其变量若是被final修饰且等号右边是常量则不会被初始化。
  2. 调用Class.forName(String className)方法(注意,其重载方法Class<?> forName(String name, boolean initialize, ClassLoader loader),第二个参数可以传值控制类是否需要初始化)
    image-20240609092808093
  3. 通过new的方式去创建一个该类的对象时。
  4. 执行 Main 方法的当前类。
  5. 子类的初始化 clinit 调用之前,会优先调用父类的 clinit 初始化方法。
  6. final 修饰的变量赋值为非常量时,会执行 clinit 方法进行初始化

测试类是否被初始化,可以在运行代码的时候添加-XX:+TraceClassLoading这行JVM参数将加载并初始化的类打印输出到控制台(-XX:+TraceClassUnloading将卸载的类打印输出在控制台)。

下面几种情况不会触发类的初始化

  1. 无静态代码块且无静态变量赋值语句。
  2. 有静态变量的声明,但无赋值语句。
  3. 静态变量的定义使用 final 关键字,会导致这些变量在准备阶段便直接初始化。
  4. 直接访问父类的静态变量,不会触发子类的初始化。
  5. 数组创建不会导致数组中元素类进行初始化。

相关文章:

  • YOLOv2训练详细实践指南
  • C++开发中的DUMP文件:解决崩溃与性能问题的利器(全文字数2w+)
  • 时间序列:A TIME SERIES IS WORTH 64 WORDS: LONG-TERM FORECASTING WITH TRANSFORMERS
  • 【实战中提升自己】 防火墙篇之VPX部署–L2TP over IPSEC
  • CTF--eval
  • 控制反转(IoC)和依赖注入(DI)实现及常用注解
  • 怎样利用 macOS 自带功能快速进行批量重命名文件教程
  • 服务器内存规格详解
  • 饭店管理系统(下篇):程序打包为exe给用户使用
  • 2. kubernetes操作概览
  • Gradle相关配置文件的关系、作用及使用方式
  • 【时时三省】(C语言基础)选择结构程序设计习题1
  • Python异步编程入门:Async/Await实战详解
  • vector常用的接口和底层
  • AI对话高阶玩法:解锁模型潜能的实用案例教程
  • 消息中间件面试题
  • 开源TTS项目GPT-SoVITS,支持跨语言合成、支持多语言~
  • java面向对象06:封装
  • cmd 终端输出乱码问题 |Visual Studio 控制台输出中文乱码解决
  • Day08【基于预训练模型分词器实现交互型文本匹配】
  • 经济日报:“关税讹诈”拦不住中国制造升级
  • 网络社群的早期历史及其启示
  • 恒安集团创始人许连捷逝世:白手起家缔造百亿纸品巨头,个人曾捐赠超10亿
  • 如何应对国际贸易形势变化?长三角四省市主要领导密集部署
  • 习近平结束对越南、马来西亚和柬埔寨国事访问回到北京
  • 14岁男孩膀胱内现52颗磁力珠,专家呼吁关注青春期少年心理健康