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

深入理解JVM类加载与垃圾回收机制

一、引言:为什么类加载与GC是Java核心?

Java虚拟机的类加载机制和垃圾回收机制是Java体系的基石,理解它们对于编写高性能应用、诊断线上问题以及通过技术面试都至关重要。据统计,在中高级Java面试中,类加载和GC相关问题的出现概率超过90%!本文将带你深入理解这两大核心机制,从基础到进阶,从理论到实战。


二、类加载机制深度解析

2.1 类加载全过程

Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这个过程被称作类的加载。

// 示例:观察类加载过程
public class ClassLoadingDemo {static {System.out.println("主类初始化");}public static void main(String[] args) {System.out.println("开始执行main方法");// 主动引用示例new StaticFieldAccess();}
}class StaticFieldAccess {static {System.out.println("StaticFieldAccess类初始化");}static final String CONSTANT = "常量";static String staticField = "静态字段";
}

2.2 类加载的五个阶段

2.2.1 加载(Loading)
  • 通过类的全限定名获取定义此类的二进制字节流

  • 将字节流所代表的静态存储结构转换为方法区的运行时数据结构

  • 在内存中生成一个代表这个类的Class对象

2.2.2 验证(Verification)
  • ​文件格式验证​​:验证字节流是否符合Class文件格式规范

  • ​元数据验证​​:对类的元数据信息进行语义校验

  • ​字节码验证​​:通过数据流和控制流分析,确定程序语义是合法的

  • ​符号引用验证​​:发生在解析阶段,确保解析动作能正常执行

2.2.3 准备(Preparation)
// 准备阶段示例
public class PreparationPhase {// 准备阶段:value=0(零值)// 初始化阶段:value=123public static int value = 123;// 准备阶段:CONSTANT=123(直接赋值)public static final int CONSTANT = 123;
}
2.2.4 解析(Resolution)

将常量池内的符号引用替换为直接引用的过程

2.2.5 初始化(Initialization)

执行类构造器<clinit>()方法的过程,真正开始执行类中定义的Java程序代码

2.3 类加载器体系

// 类加载器层次结构示例
public class ClassLoaderHierarchy {public static void main(String[] args) {// 获取系统类加载器ClassLoader systemLoader = ClassLoader.getSystemClassLoader();System.out.println("系统类加载器: " + systemLoader);// 获取扩展类加载器ClassLoader extLoader = systemLoader.getParent();System.out.println("扩展类加载器: " + extLoader);// 获取启动类加载器(通常为null,由C++实现)ClassLoader bootstrapLoader = extLoader.getParent();System.out.println("启动类加载器: " + bootstrapLoader);// 查看当前类的类加载器System.out.println("当前类加载器: " + ClassLoaderHierarchy.class.getClassLoader());}
}

2.4 双亲委派模型

​工作原理​​:

  1. 当前类加载器首先检查请求的类是否已被加载

  2. 若未加载,委派给父类加载器去完成

  3. 只有当父类加载器反馈无法完成时,子加载器才会尝试加载

​代码实现​​:

// 双亲委派模型代码体现(ClassLoader.loadClass方法)
protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException {synchronized (getClassLoadingLock(name)) {// 首先检查类是否已加载Class<?> c = findLoadedClass(name);if (c == null) {try {if (parent != null) {// 委托给父加载器c = parent.loadClass(name, false);} else {// 委托给启动类加载器c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {// 父加载器无法完成加载}if (c == null) {// 自己尝试加载c = findClass(name);}}if (resolve) {resolveClass(c);}return c;}
}

三、垃圾回收机制全面剖析

3.1 对象存活判断算法

3.1.1 引用计数法(Reference Counting)
// 引用计数法的循环引用问题
public class ReferenceCountingGC {public Object instance = null;private static final int _1MB = 1024 * 1024;private byte[] bigSize = new byte[2 * _1MB];public static void main(String[] args) {ReferenceCountingGC objA = new ReferenceCountingGC();ReferenceCountingGC objB = new ReferenceCountingGC();// 相互引用objA.instance = objB;objB.instance = objA;// 无法被回收,但如果使用引用计数法则无法识别objA = null;objB = null;System.gc(); // 但Java使用可达性分析,可以正确回收}
}
3.1.2 可达性分析算法(Root Searching)

​GC Roots包括​​:

  • 虚拟机栈中引用的对象

  • 方法区中类静态属性引用的对象

  • 方法区中常量引用的对象

  • 本地方法栈中JNI引用的对象

  • Java虚拟机内部的引用

3.2 垃圾收集算法

3.2.1 标记-清除算法(Mark-Sweep)
  • ​优点​​:实现简单

  • ​缺点​​:产生内存碎片,分配大对象时效率低

3.2.2 标记-复制算法(Mark-Copy)
// 新生代Eden区和Survivor区的复制过程演示
public class CopyAlgorithmDemo {private static final int _1MB = 1024 * 1024;/*** VM参数:-Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails*/public static void main(String[] args) {byte[] allocation1 = new byte[2 * _1MB];byte[] allocation2 = new byte[2 * _1MB];byte[] allocation3 = new byte[2 * _1MB];byte[] allocation4 = new byte[4 * _1MB]; // 出现Minor GC}
}
3.2.3 标记-整理算法(Mark-Compact)
  • 适合老年代,避免内存碎片问题

  • 移动对象需要更新引用,效率相对较低

3.3 经典垃圾收集器

收集器

区域

算法

特点

适用场景

Serial

新生代

复制

单线程,Stop The World

客户端模式

ParNew

新生代

复制

多线程版Serial

服务端模式

Parallel Scavenge

新生代

复制

吞吐量优先

后台运算

Serial Old

老年代

标记-整理

Serial老年代版

客户端模式

Parallel Old

老年代

标记-整理

Parallel Scavenge老年代版

吞吐量优先

CMS

老年代

标记-清除

低延迟,并发收集

B/S系统

G1

全堆

标记-整理+复制

分区收集,可预测停顿

大内存服务


四、面试高频问题精讲

Q1:什么是类加载的双亲委派模型?有什么好处?

​答​​:双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器。工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成。

​好处​​:

  1. ​安全性​​:防止核心API被篡改

  2. ​避免重复加载​​:保证类的唯一性

  3. ​结构清晰​​:类加载器层次关系明确

Q2:什么情况下会触发类的初始化?

​答​​:

  1. 遇到new、getstatic、putstatic、invokestatic字节码指令

  2. 使用反射对类进行反射调用时

  3. 初始化一个类时发现其父类还没有初始化

  4. 虚拟机启动时指定的主类

  5. 使用JDK7动态语言支持时的方法句柄

Q3:如何判断一个对象是否可以回收?

​答​​:Java使用可达性分析算法。从GC Roots对象作为起点,向下搜索引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。

Q4:CMS和G1垃圾收集器的区别?

​答​​:

特性

CMS

G1

区域划分

新生代+老年代

将堆划分为多个Region

算法

标记-清除

整体标记-整理,局部复制

停顿时间

较短但不可预测

可预测的停顿时间模型

内存碎片

会产生

整体上避免了碎片问题

适用场景

响应速度要求高的系统

大内存、多处理器系统

http://www.dtcms.com/a/395650.html

相关文章:

  • Ethernet/IP转ProfiNet网关选型指南:欧姆龙PLC对接研祥工控机最佳实践
  • Java 面试高频手撕题清单
  • 【论文阅读】Long-VLA:释放视觉语言动作模型在机器人操作中的长时程能力
  • Python poplib 库全解析:POP3 邮件收取的完整指南
  • DanceTrack数据集介绍
  • 【无标题】话题学习笔记1,话题基本了解
  • 【论文阅读】OpenVLA:一个开源的视觉-语言-动作模型
  • 科技信息差(9.22)
  • Zotero中进行文献翻译【Windows11】【新版,目前没发现bug】
  • 单细胞数据分析:单细胞计数矩阵(Seurat)
  • Hyperf使用视图
  • React何时用函数组件(Hooks),何时用类组件?(错误边界用类组件Error Boundary)
  • VMware虚拟机ubuntu20.04共享文件夹突然无法使用
  • 流行AI工具的分类与比较
  • 哪些行业需要使用时序数据库?
  • PyTorch 神经网络工具箱简明笔记
  • Pytorch目录细查
  • VMware的Ubuntu与windows共享文件夹
  • RK3588-ubuntu server
  • EPLAN绘制安全回路核心步骤
  • 仁合医疗靠谱吗?——社会责任担当,科技赋能医疗
  • R语言 生物信息 GEO 数据集 GPL5175 平台中一个探针的 gene_assignment 字段内容解读
  • ReactPress 2.0 — 基于 React、Next.js 和 NestJS 构建的现代化全栈发布平台
  • 52Hz——FreeRTOS学习笔记
  • 回归分析:数据驱动时代的 “因果纽带” 与 “预测锚点”—— 技术深潜与方法论破局
  • 宇树go2 gazebo仿真
  • Golang 赋值运算符与短声明 (= 与 :=)使用场景
  • 数据库造神计划第二十天---视图
  • Java 异步支付的 “不安全” 风险点控制
  • 百饮X 北森 | 康师傅百饮事业AI领导力教练Mr. Sen落地实践分享