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

JVM 类加载过程/对象创建过程/双亲委派机制/垃圾回收机制

目录

一 类加载过程

二 JVM内存模型

三 对象的创建过程

四 双亲委派机制

五 垃圾回收机制

1 标记-清除

2 标记-复制

3 标记-整理

六 垃圾回收器

1 早期 Serial + Serial Old

2 中期 Parallel Scavenge + Parallel Old

3 过渡期 :CMS  + ParNew

4 最新:G1


一 类加载过程

类加载的过程是指将类的.class文件加载到JVM内存当中,并对数据进行处理,最终形成可以被JVM使用的Java的类型的过程。

总结流程:

  1. 加载:找 .class 文件 -> 读到内存(方法区) -> 在堆里创建 Class 对象。

  2. 链接

    • 验证:检查 .class 文件是否合法、安全。

    • 准备:为 static 变量在方法区分配内存并设置默认初始值 (0, falsenull)。(static final 常量(编译期可知)则在此阶段直接赋指定值。)

    • 解析:把常量池里的符号引用(名字)替换成直接引用(内存地址/偏移量)。

  3. 初始化:执行 <clinit>() 方法,给 static 变量赋程序设定的值,执行 static {} 块中的代码。(首次主动使用时触发)

    • <clinit> 是 JVM 自动生成的类初始化方法,在类加载的“初始化”阶段执行,用于执行静态变量赋值和静态代码块,在这个阶段才会把用户指定的初始值覆盖掉之前准备阶段赋予的零值

面试官问题:对于类加载的过程,你是如何理解的?

我:首先,类加载的核心目的是将类的.class文件加载到JVM内存当中,对数据进行操作后形成可以被JVM使用的Java的类型的过程。这部分操作分为三个阶段,加载,链接,初始化,而链接又分为验证,准备,解析。加载阶段JVM会寻找Class文件,将类的字节码数据(包括类结构信息,字段,方法,常量池等)解析后存入方法区当中,在堆当中创建Class对象,这个对象是java.lang.Class的实例,作为反射入口并提供访问方法区当中类元数据的接口。链接当中的验证阶段会对应的检查.class文件是否安全合法,准备阶段会对static变量在方法区分配内存,并设置默认值,解析阶段会将方法区类的常量池的符号引用转变为直接引用(存在延迟解析策略),初始化阶段JVM会执行<clinit>()方法,会给static变量赋予程序设定的值(执行存在static的代码块)。

补充

  • 加载阶段会将Class文件中的常量池内容加载到方法区当中变为运行时常量池
  • static final在主备阶段就会为变量赋予程序设定的值
  • 实例变量及其所属的对象数据,只有在程序执行到 new 指令时才会在堆中动态分配内存并初始化。
  • main 方法作为Java程序的执行入口,其栈帧在虚拟机栈中创建,负责启动程序逻辑,并可能触发后续的对象创建和堆内存分配。
  • 延迟解析是JVM内部的自动化优化策略,目的是避免在类加载阶段解析所有可能未使用的符号引用。
  • 在JDK7及之前,永久代是对方法区的具体实现,并且永久代存储在堆内存当中,他的大小受JVM堆参数的限制。在JDK8及之后方法区这个概念依旧存在,但是不再使用永久代,方法区的实现被替代成元空间,元空间不再使用堆内存使用的是本地内存。

AI分点

它的核心目的是将编译后的Java类(.class文件)加载到JVM内存中,经过转换和处理,最终形成JVM可以直接使用的Java类型(即Class对象)。整个过程主要分为三个阶段:加载(Loading)、链接(Linking)、初始化(Initialization),其中链接阶段又细分为验证(Verification)、准备(Preparation)、解析(Resolution)

  1. 加载:

    • JVM通过类加载器查找并读取.class文件的二进制字节流。

    • 将字节流所代表的静态结构解析并转换为方法区(在HotSpot等JVM中常由元空间实现)内的运行时数据结构,存储类结构、字段、方法、常量池等信息。

    • 堆内存中创建一个java.lang.Class对象。该对象是访问方法区中类元数据的入口,也是Java反射机制的基石。

    • 将Class文件中的常量池加载到方法区,转换为运行时常量池

  2. 链接:

    • 验证: 对加载的字节码进行严格检查,确保其符合JVM规范、格式正确、逻辑安全,不会危害虚拟机自身。

    • 准备: 在方法区中为类变量(static变量)分配内存空间,并设置该数据类型的默认初始值(零值,如00L0.0f0.0dnullfalse)。(可选补充:对于final static修饰的基本类型和String字面量常量,在此阶段就会被直接赋值为程序中定义的值)。

    • 解析: 将运行时常量池中的符号引用(如类/接口全名、字段名和描述符、方法名和描述符)替换为直接引用(指向目标在内存中的具体指针、偏移量或句柄)。解析动作可以发生在初始化之前,也可能采用延迟策略,等到该符号引用第一次被主动使用时才进行

  3. 初始化:

    • 这是类加载的最后一步,开始执行用户定义的Java初始化代码。

    • JVM执行编译器自动生成的类构造器<clinit>()方法。这个方法由类中所有类变量赋值语句静态代码块(static {}块)按源代码顺序合并而成。

    • <clinit>()方法的主要作用是为类变量赋予程序中定义的初始值(覆盖准备阶段设置的零值),并执行静态块中的逻辑。

    • 关键点: JVM会确保一个类的<clinit>()方法在多线程环境下被正确地加锁同步(线程安全),且只被执行一次。

至此,类就完成了加载过程,可以被JVM用来创建实例、访问静态成员、调用方法等。

二 JVM内存模型

JVM管理内存的物理划分,包含5个核心区域

  1. 堆 (Heap)

    • 存储内容:所有对象实例和数组

  2. 方法区 (Method Area)

    • 存储内容:类元信息、常量池、静态变量、JIT 编译代码

  3. 虚拟机栈 (JVM Stack)

    • 存储内容:栈帧(局部变量表、操作数栈、动态链接、方法出口)

  4. 本地方法栈 (Native Method Stack)

    • 存储内容:Native 方法(如 C/C++ 代码)的执行状态

  5. 程序计数器 (PC Register)

    • 存储内容:当前线程执行的字节码指令地址

三 对象的创建过程

阶段关键操作JVM 子系统
1. 类加载检查验证类是否加载,未加载则触发类加载过程类加载器
2. 内存分配在堆中分配内存(指针碰撞/空闲列表/TLAB)内存管理器
3. 内存初始化所有字段置零值(0/null/false)执行引擎
4. 设置对象头写入 Mark Word、Klass 指针等元数据执行引擎
5. 执行 <init>初始化字段 → 构造代码块 → 构造函数(父类优先)执行引擎
6. 建立引用关联将堆中对象地址绑定到栈帧的局部变量运行时数据区协作

四 双亲委派机制

阿里二面:双亲委派机制?原理?能打破吗?-CSDN博客

什么是双亲委派机制?

“类加载请求优先委派给父加载器”,只有所有父加载器都无法完成加载时(返回 null 或抛出 ClassNotFoundException),子加载器才会尝试自己加载。

  • 通过递归委派,最终由 最顶层的启动类加载器(Bootstrap) 优先尝试加载。

  • 确保类的加载从最高层级向下传递,形成严格的层次结构。

解决的问题:

  • 安全性:防止用户自定义类冒充核心类(如伪造 java.lang.String)。

  • 唯一性:避免同一个类被不同加载器重复加载(破坏 equals()instanceof 等行为)。

  • 有序性:明确类加载的责任边界(如核心类 → 扩展类 → 应用类 → 自定义类)。

通过这种机制,Java实现了类加载的层次结构。它可以确保类的加载是有序的(从最高级的类加载器向下),避免了重复加载、可以保证安全性,确保Java当中的核心类库,只能由启动类加载器加载,从而防止用户自定义同名类被加载。并且可以自定义类加载器,实现特定的加载策略。

每个类加载器有独立的加载范围

  • 启动类加载器:加载 JRE/lib 核心库(如 rt.jar

  • 扩展类加载器:加载 JRE/lib/ext 扩展库

  • 应用类加载器:加载用户类路径(-classpath 指定的路径)

    • 自定义类加载器:开发者自定义的路径(如网络、加密文件等)

当需要加载一个类时,子类加载器收到请求会向上委派,直到启动类加载器Bootstrap,加载成功则返回结果,失败将返回null表示无法加载,下一级接收到返回值null,然后在下一级类加载器的路径下尝试,如果所有的父类都失败,子类加载器调用自身的findClass()进行加载,如果自身的加载机制仍然无法加载该类,则会抛出ClassNotFoundException异常。

打破双亲委派机制:

为何:标准的双亲委派模型在某些场景下不够灵活

方案:可以自定义一个类加载器,继承自ClassLoader类,并重写loadClass方法。在LoadClass方法当中我们可以自定义类的加载逻辑。

五 垃圾回收机制

概念:垃圾回收是JVM的一种内存管理机制,他会自动回收不再被使用的对象占用的内存空间,从而避免手动释放内存的操作。

在堆内存当中,从垃圾回收的范围上说,一般分为两种,正对新生代的垃圾回收动作,叫做MinorGC(也叫做YoungGC),针对老年代的垃圾回收动作,叫做MajorGC,由于MajorGc发生的时候,通常也会伴随着MinorGC。FullGC(针对整个堆内存)

1 可达性分析算法思想

从一系列被称为GC Roots的根对象出发,沿着对象之间的引用链进行搜索。所有能被GC Roots直接或间接引用到的对象,就是存活对象;反之,任何GC Roots都无法到达的对象,就是可回收的垃圾对象

2 三种核心回收算法 (标记-清除 | 标记-复制 | 标记-整理)

这三种算法都是基于可达性分析来确定对象是否存活。

1 标记-清除

从GCRoots出发遍历整个对象图,标记出所有的存活对象(对象头设置标志位),扫描内存,将未被标记的对象占用的内存块加入空闲列表,产生不连续内存碎片。当碎片无法满足大内存分配时,触发 Full GC 并切换为标记-整理算法。

空闲列表是管理碎片化内存的核心数据结构,用于解决标记-清除算法产生的内存碎片问题。空闲列表是一个记录堆内存当中所有空闲内存块位置和大小的双向链表。

清除阶段不会物理擦除垃圾对象数据,而是将其占用的内存块加入空闲列表。后续分配新对象时,从空闲列表搜索合适碎片分配。若无足够连续碎片,则触发 Full GC 执行标记-整理算法重组内存。

2 标记-复制

将新生代内存分为三个区域,一个是Eden(伊甸区),一个是Survivor0(From空间),一个是Survivor1(To空间)。Eden满时触发MoniorGC,标记Eden区和From区的存活对象,将存活对象复制到To区,更新所有指向这些对象的引用地址,清空Eden区和From区,交换From/To的角色。

这里复制到To区会将年龄+1,达到阈值会晋升老年代。 “Eden 区占80%,Survivor0/1各占10%”

3 标记-整理

从GCRoots出发遍历整个对象图,标记所有的存活对象,将所有的存活对象向内存起始端滑动,使其连续排列,同时更新对象的内存引用。最后回收标记对象区域外的碎片空间。

六 垃圾回收器

垃圾回收器的发展:

1 早期 Serial + Serial Old

Serial 是工作在新生代的垃圾回收器(也称 Serial New),采用标记-复制算法;其搭档 Serial Old 负责老年代回收,采用标记-整理算法。两者均为单线程工作模式,垃圾回收时触发 STW(Stop-The-World)所有用户线程暂停,待回收完成后恢复。

2 中期 Parallel Scavenge + Parallel Old

作为 Serial 的多线程升级版,Parallel Scavenge(新生代)和 Parallel Old(老年代)在 STW 期间并行执行垃圾回收。

3 过渡期 :CMS  + ParNew

ParNew是Parallel Scavenge的并发增强版,搭配CMS使用,CMS作为首个并发老年代收集器。

  • 初始标记(STW):标记GCRoots直接关联的对象
  • 并发标记:用户线程与标记线程并发执行
  • 重新标记(STW):修正并发期间的引用变更。
  • 并发清除:清理垃圾对象。(与用户线程并发执行)

4 最新:G1

传统分代实现的是老年代与新生代的物理隔离。

G1分区:Eden/Suriver/Old逻辑分代+物理统一(同一个区域的2024个)

java -XX:+PrintCommandLineFlags -version CMD查看垃圾回收器版本


知识点补充: 实例变量与局部变量的区别

特性实例变量成员变量(广义)
修饰符不能有 static可以包含 static 或非 static
存储位置堆(对象实例内部)静态变量在方法区,实例变量在堆=

相关文章:

  • 大模型微调(Fine-tuning)概览
  • Vue-Leaflet地图组件开发(四)高级功能与深度优化探索
  • 基于51单片机的温度和液位监测系统(串口传输)
  • Vue 性能优化
  • kicad运行时出错,_Pnext->_Myproxy = nullptr;访问内存出错
  • 自我实现的量子隐喻:在可能性场域中动态拓展涌现节点
  • 安装前端vite框架,后端安装fastapi框架
  • Multisim仿真Buck电路基本拓扑
  • 进程和线程区别、管道和套接字、共享变量、TCP三次握手,是否可以少一次握手、子进程和主进程区别和API——Nodejs
  • Spring Cloud Gateway 全面学习指南
  • LabVIEW电路板焊点自动检测
  • 力扣刷题(第五十八天)
  • 【测开面试题】八股文总结
  • Kafka 可靠性保障:消息确认与事务机制(二)
  • 路由器端口映射怎么设置?本地固定内网IP给外面网络连接访问
  • MongoDB文档查询:从基础到进阶的探索之旅
  • Flask蓝图
  • AI 社交和AI情绪价值的思考 -延申思考2 -全局记忆
  • LLMs:《WebDancer: Towards Autonomous Information Seeking Agency》翻译与解读
  • PC16550 UART接收中断处理完整示例代码
  • 推广普通话实践总结/长沙网站seo哪家公司好
  • 合肥网站建设开发/长沙靠谱的关键词优化
  • 做注册任务的网站有哪些/域名
  • 用html5做的静态网站网站/免费发广告帖子的网站
  • 如何在自己网站开发互动视频教程/做广告的怎么找客户
  • 网站开发 文件上传慢/360免费建站教程