news 2026/4/17 13:12:09

【JVM核心机制】深度解析:类加载+运行数据区+垃圾回收

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【JVM核心机制】深度解析:类加载+运行数据区+垃圾回收

文章目录

  • 目录
    • 前言
    • 一、JVM 核心模块全局总览
    • 二、JVM 类加载机制深度解析
      • 1. 类加载生命周期(7个阶段)
      • 2. 类加载器与双亲委派模型
        • 2.1 类加载器层次结构对比表
        • 2.2 双亲委派模型核心解析
          • (1)核心原理
          • (2)核心优势
          • (3)破坏场景
        • 2.3 自定义类加载器代码示例
    • 三、JVM 运行数据区(内存模型)
      • 1. 运行数据区核心分类对比表
      • 2. 核心内存区域详细解析
        • 2.1 堆(Heap):对象实例的核心存储区域
        • 2.2 虚拟机栈:方法调用的栈帧容器
        • 2.3 方法区(元空间):类元数据的存储区域
    • 四、JVM 垃圾回收(GC)核心机制
      • 1. 核心前提:判断对象是否可回收
        • (1)GC Roots 核心组成
        • (2)Java 引用类型(影响对象可回收性)
      • 2. 垃圾回收算法
      • 3. 垃圾回收器
      • 4. GC 触发条件
      • 5. 内存溢出(OOM)实战示例
        • (1)堆内存溢出(最常见)
        • (2)虚拟机栈溢出(栈深度超限)
    • 五、总结与生产实践建议

目录

前言

若对您有帮助的话,请点赞收藏加关注哦,您的关注是我持续创作的动力!有问题请私信或联系邮箱:funian.gm@gmail.com

Java 程序的跨平台运行依赖于 Java 虚拟机(JVM)的支撑,而类加载机制、运行数据区(内存模型)、垃圾回收(GC)是 JVM 的三大核心基础模块。类加载负责将字节码文件加载到 JVM 并转化为可执行结构,运行数据区是程序运行时的内存载体,垃圾回收则自动释放无用内存、避免内存泄漏。这三大模块共同决定了 Java 程序的运行效率、稳定性以及内存使用效率,也是排查内存溢出(OOM)、内存泄漏等问题的关键。

一、JVM 核心模块全局总览

先通过汇总表格建立三大模块的整体认知,明确其定位与关联:

核心模块核心作用核心组件/流程关联关系核心问题
类加载机制.class字节码文件加载到 JVM,转化为 Class 对象并初始化1. 类加载生命周期(7步)
2. 类加载器层次(4层)
3. 双亲委派模型
类加载的结果(Class 对象)存储在运行数据区的方法区,类的实例对象存储在堆中类加载失败、类重复加载、双亲委派破坏
运行数据区程序运行时的内存分配与存储载体1. 线程私有区域(程序计数器、虚拟机栈、本地方法栈)
2. 线程共享区域(堆、方法区/元空间)
垃圾回收主要针对线程共享区域(堆+元空间)进行内存释放内存溢出(OOM)、内存泄漏、栈溢出(StackOverflowError)
垃圾回收(GC)自动识别并释放无用内存,避免内存泄漏1. 可回收对象判断算法
2. 垃圾回收算法
3. 垃圾回收器
4. GC 触发条件
依赖运行数据区的内存布局,针对堆的分代结构采用不同回收策略垃圾回收频繁(性能损耗)、内存溢出(回收不及时)、GC 停顿时间过长

二、JVM 类加载机制深度解析

类加载机制是 JVM 将外部.class字节码文件(二进制文件)加载到内存中,经过验证、准备、解析、初始化等步骤,最终转化为可使用的 Class 对象的过程,具有动态加载特性(并非程序启动时一次性加载所有类,而是按需加载)。

1. 类加载生命周期(7个阶段)

类的生命周期分为 7 个阶段,其中加载、验证、准备、解析、初始化为类加载的核心流程,使用和卸载为后续生命周期,具体如下表:

生命周期阶段核心作用关键细节是否必须执行异常可能性
加载(Loading)1. 根据类的全限定名(如java.lang.String)查找.class字节码文件
2. 将字节码文件读取到内存
3. 在方法区中生成对应的 Class 对象(作为访问该类的入口)
1. 类加载器负责查找字节码(本地文件、网络、动态生成等)
2. Class 对象存储在方法区,实例对象存储在堆中
是(类未找到、字节码损坏等抛出ClassNotFoundException
验证(Verification)校验字节码文件的合法性,确保其符合 JVM 规范,不危害 JVM 安全1. 格式验证(是否符合.class文件格式)
2. 语义验证(类结构是否合法,如是否有父类)
3. 字节码验证(指令是否合法,避免非法操作)
4. 符号引用验证(符号引用是否能解析为直接引用)
是(校验失败抛出VerifyError
准备(Preparation)为类的静态变量分配内存并设置默认初始值(零值)1. 仅处理静态变量,实例变量在对象实例化时分配(堆中)
2. 默认初始值:int→0、String→null、boolean→false
3. 若静态变量被final修饰(常量),直接赋值为显式值(如public final static int a=10,准备阶段赋值为10,非0)
解析(Resolution)将类中的符号引用(如类名、方法名的字符串形式)转化为直接引用(如内存地址、指针的具体地址)1. 符号引用:编译期生成,无实际内存地址
2. 直接引用:运行期生成,指向实际内存地址
3. 解析时机:默认在准备阶段后、初始化前,可延迟到初始化后(动态绑定场景)
是(解析失败抛出NoClassDefFoundErrorNoSuchMethodError等)
初始化(Initialization)执行类的静态代码块(static{})和静态变量的显式赋值(非默认值)1. 初始化触发条件(主动使用,被动使用不触发):
- 创建类的实例(new User()
- 调用类的静态方法(User.staticMethod()
- 访问类的静态变量(User.staticVarfinal常量除外)
- 反射调用(Class.forName("com.test.User")
- 初始化子类时,父类会先初始化
- 启动类(包含main()方法的类)
2. 静态代码块按编写顺序执行,且仅执行一次
是(静态代码块/静态变量赋值异常抛出ExceptionInInitializerError
使用(Using)程序通过 Class 对象创建实例、调用方法/变量,使用类的功能1. 实例对象存储在堆中,栈帧中的引用指向堆对象
2. 多次创建实例,仅对应一个 Class 对象(方法区中)
是(运行时异常,如NullPointerException
卸载(Unloading)当类不再被使用时,释放 Class 对象占用的方法区内存,完成类的生命周期结束类卸载的3个条件(缺一不可):
1. 该类的所有实例对象已被回收(堆中无该类对象)
2. 加载该类的类加载器已被回收
3. 该类的 Class 对象无任何引用(如反射引用已释放)

2. 类加载器与双亲委派模型

2.1 类加载器层次结构对比表

JVM 提供了 3 种内置类加载器,同时支持自定义类加载器,按层次结构从上到下如下:

类加载器类型父类加载器核心职责加载资源来源是否为Java实现权限范围
启动类加载器(Bootstrap ClassLoader)无(C++实现,不属于Java类体系)加载 JVM 核心类库(Java 基础类)JAVA_HOME/jre/lib目录(如rt.jarcore.jar否(C++实现)最高,仅能加载指定目录下的核心类库
扩展类加载器(Extension ClassLoader)启动类加载器加载 Java 扩展类库JAVA_HOME/jre/lib/ext目录或java.ext.dirs系统属性指定目录是(Java实现)中等,加载扩展类库
应用程序类加载器(Application ClassLoader)扩展类加载器加载应用程序的业务代码(项目 ClassPath 下的类)项目src/main/resourceslib依赖包、ClassPath 目录是(Java实现)普通,加载应用业务类(默认类加载器)
自定义类加载器(Custom ClassLoader)应用程序类加载器加载自定义来源的类(如加密字节码、网络字节码、本地非ClassPath目录类)网络、本地磁盘非默认目录、动态生成的字节码等是(继承ClassLoader实现)自定义,可按需控制类的加载逻辑(如加密解密)
2.2 双亲委派模型核心解析
(1)核心原理

双亲委派模型是 JVM 类加载的核心机制,其核心思想是:当一个类加载器收到类加载请求时,首先将请求委托给父类加载器加载,只有当父类加载器无法加载该类时,子类加载器才会尝试自己加载

具体执行流程:

  1. 应用程序类加载器收到类加载请求,先委托给扩展类加载器;
  2. 扩展类加载器收到请求,再委托给启动类加载器;
  3. 启动类加载器检查是否能加载该类(是否在核心类库目录下),若能则加载,否则返回给扩展类加载器;
  4. 扩展类加载器检查自身目录,若能加载则加载,否则返回给应用程序类加载器;
  5. 应用程序类加载器检查项目 ClassPath,若能加载则加载,否则抛出ClassNotFoundException
(2)核心优势
优势名称具体说明
沙箱安全防止核心类库被恶意篡改(如自定义java.lang.String类,由于双亲委派,会优先由启动类加载器加载核心String类,避免恶意类替换)
类的唯一性确保全限定名相同的类,仅被一个类加载器加载,避免类重复加载(如User类仅在方法区生成一个 Class 对象)
(3)破坏场景

双亲委派模型并非绝对不可破坏,常见破坏场景有:

  1. 自定义类加载器重写loadClass()方法(默认loadClass()实现双亲委派,重写后可跳过委托逻辑);
  2. SPI 机制(如 JDBC):DriverManager需加载第三方驱动类,通过线程上下文类加载器(打破双亲委派);
  3. 热部署(如 Tomcat):不同 Web 应用需加载各自的类,通过自定义类加载器隔离,打破双亲委派。
2.3 自定义类加载器代码示例
importjava.io.ByteArrayOutputStream;importjava.io.FileInputStream;importjava.io.InputStream;// 自定义类加载器:加载本地非ClassPath目录下的.class文件publicclassCustomClassLoaderextendsClassLoader{// 自定义类加载的根目录privateStringrootDir;publicCustomClassLoader(StringrootDir){// 父类加载器默认是应用程序类加载器super();this.rootDir=rootDir;}// 核心方法:重写findClass(不破坏双亲委派,推荐方式)@OverrideprotectedClass<?>findClass(Stringname)throwsClassNotFoundException{try{// 1. 将类的全限定名转为文件路径(如 com.test.User → com/test/User.class)StringclassName=name.replace(".","/")+".class";StringfilePath=rootDir+"/"+className;// 2. 读取.class文件字节数组InputStreamin=newFileInputStream(filePath);ByteArrayOutputStreamout=newByteArrayOutputStream();byte[]buffer=newbyte[1024];intlen;while((len=in.read(buffer))!=-1){out.write(buffer,0,len);}byte[]classBytes=out.toByteArray();in.close();out.close();// 3. 定义类(将字节数组转为Class对象)returndefineClass(name,classBytes,0,classBytes.length);}catch(Exceptione){thrownewClassNotFoundException("类加载失败:"+name,e);}}publicstaticvoidmain(String[]args)throwsException{// 自定义类加载器:加载 D:/custom_class 目录下的类CustomClassLoaderclassLoader=newCustomClassLoader("D:/custom_class");// 加载 com.test.User 类Class<?>userClass=classLoader.loadClass("com.test.User");// 输出类加载器信息System.out.println("类加载器:"+userClass.getClassLoader().getClass().getName());// 创建实例Objectuser=userClass.newInstance();System.out.println("实例对象:"+user.getClass().getName());}}

三、JVM 运行数据区(内存模型)

JVM 运行数据区是程序运行时内存分配的区域,根据「线程私有/线程共享」分为两大类别,其中 JDK 8 与 JDK 7 最大的区别是移除永久代(PermGen),引入元空间(Metaspace),元空间存储在本地内存(Native Memory)而非 JVM 堆内存,有效解决永久代内存溢出问题。

1. 运行数据区核心分类对比表

内存区域类型具体区域名称线程归属核心作用存储内容生命周期常见异常JDK 8 变化说明
线程私有区域程序计数器(Program Counter Register)私有1. 记录当前线程执行的字节码指令地址(行号)
2. 线程切换后能恢复到正确的执行位置
3. 支持 Native 方法(执行 Native 方法时,计数器值为undefined)
字节码指令地址、行号偏移量与线程一致无(唯一不会抛出 OOM 的内存区域)无变化
虚拟机栈(VM Stack)私有存储当前线程的方法调用栈帧,每个方法执行时创建一个栈帧,方法执行完毕栈帧出栈栈帧(包含局部变量表、操作数栈、动态链接、方法返回地址等)与线程一致1.StackOverflowError(栈深度超过虚拟机栈最大容量,如无限递归)
2.OutOfMemoryError(虚拟机栈可动态扩展,扩展时内存不足)
无变化
本地方法栈(Native Method Stack)私有与虚拟机栈功能一致,专门用于支撑 Native 方法(非Java实现的方法,如Object.hashCode())的执行Native 方法的栈帧与线程一致同虚拟机栈(StackOverflowErrorOutOfMemoryError无变化
线程共享区域堆(Heap)共享JVM 中最大的内存区域,用于存储对象实例和数组(几乎所有对象实例都在此分配)1. 对象实例(如new User()
2. 数组(如new int[10]
3. 字符串常量池(JDK 7 及以后移至此)
与 JVM 一致OutOfMemoryError(堆内存不足,无法分配新对象)无变化,仍分新生代和老年代
方法区(Method Area)共享存储类的元数据、静态变量、常量、即时编译器编译后的代码等1. 类的元数据(Class 对象、类结构信息)
2. 静态变量(static修饰的变量)
3. 常量(final修饰的变量,如字符串常量池早期版本)
4. 即时编译(JIT)后的机器码
与 JVM 一致OutOfMemoryError(方法区内存不足)JDK 8 中,方法区的实现由「永久代(PermGen)」改为「元空间(Metaspace)」,元空间存储在本地内存,默认无最大内存限制(可通过参数配置)

2. 核心内存区域详细解析

2.1 堆(Heap):对象实例的核心存储区域

堆是 JVM 中最大的内存区域,也是垃圾回收的主要目标(GC 主要回收堆内存),其内部采用「分代存储」模型,分为新生代和老年代,具体如下:

堆分代细分区域占堆内存比例核心作用垃圾回收策略回收特点
新生代(Young Generation)Eden 区80%(新生代占比)存储新创建的对象实例(绝大多数对象在此创建)标记-复制算法回收频率高、回收速度快、回收效率高,Minor GC 主要发生在此
Survivor 0(S0/From)10%(新生代占比)存储 Eden 区回收后存活的对象标记-复制算法与 S1 区互为“From”和“To”,每次 GC 后互换角色
Survivor 1(S1/To)10%(新生代占比)存储 S0 区回收后存活的对象(或反之)标记-复制算法始终有一个 Survivor 区为空,用于对象复制
老年代(Old Generation)无细分(可按区域划分)20%~50%(堆总内存占比,可配置)存储新生代中多次回收后仍存活的对象(老年对象)标记-整理算法回收频率低、回收速度慢、回收开销大,Major GC/Full GC 主要发生在此

核心规则

  1. 对象优先在 Eden 区分配,当 Eden 区内存不足时,触发 Minor GC(新生代 GC),回收 Eden 区和 S0 区的无用对象,存活对象复制到 S1 区,之后 S0 和 S1 互换角色;
  2. 当对象在 Survivor 区存活次数达到阈值(默认 15,可通过-XX:MaxTenuringThreshold配置),将晋升到老年代;
  3. 大对象(如大数组)可直接在老年代分配(避免新生代频繁 GC),可通过-XX:PretenureSizeThreshold配置大对象阈值。
2.2 虚拟机栈:方法调用的栈帧容器

虚拟机栈的核心组成是「栈帧」,每个方法执行对应一个栈帧入栈,方法执行完毕对应栈帧出栈,栈帧内部结构如下:

栈帧组成部分核心作用关键细节
局部变量表存储方法的局部变量(包括参数、局部变量)1. 容量以「变量槽(Slot)」为单位,每个 Slot 可存储基本数据类型、引用类型
2. 局部变量表在编译期确定容量,运行期不可改变
3. 方法参数按顺序存储在局部变量表中
操作数栈作为方法执行的临时数据存储和运算场地1. 采用栈结构(先进后出),用于存放运算操作数和运算结果
2. 执行字节码指令时,从局部变量表加载数据到操作数栈,运算后将结果存回局部变量表
动态链接将栈帧中的符号引用转化为直接引用1. 指向方法区中的 Class 元数据,用于调用类的方法/访问变量
2. 支持动态绑定(运行时确定调用的方法,如多态)
方法返回地址记录方法执行完毕后需要返回的指令地址1. 方法正常执行完毕:返回调用方的下一条指令地址
2. 方法异常执行完毕:通过异常表找到返回地址,无需存储在栈帧中
2.3 方法区(元空间):类元数据的存储区域

JDK 8 中,元空间(Metaspace)替代永久代(PermGen)作为方法区的实现,核心区别如下:

特性永久代(PermGen,JDK 7及以前)元空间(Metaspace,JDK 8及以后)
内存来源JVM 堆内存本地内存(Native Memory)
最大内存限制默认有上限(可通过-XX:MaxPermSize配置)默认无上限(受物理内存限制,可通过-XX:MaxMetaspaceSize配置)
内存溢出风险较高(容易因类过多、静态变量过多导致 OOM)较低(内存空间充足,可通过参数控制)
垃圾回收仅回收无用的类元数据,回收效率低回收无用类元数据,效率更高,与堆 GC 协同执行

四、JVM 垃圾回收(GC)核心机制

垃圾回收(Garbage Collection,GC)是 JVM 的自动内存管理机制,核心目标是自动识别并释放无用对象占用的内存,避免内存泄漏和内存溢出。GC 的核心流程包括:判断对象是否可回收、选择垃圾回收算法、使用垃圾回收器执行回收。

1. 核心前提:判断对象是否可回收

在执行垃圾回收前,JVM 首先需要判断哪些对象是“无用对象”(可回收对象),主流判断算法有两种:

判断算法核心原理优点缺点是否为JVM默认算法
引用计数法为每个对象分配一个引用计数器,当对象被引用时计数器+1,引用失效时计数器-1;当计数器值为0时,判定为可回收对象实现简单、判断效率高、无停顿无法解决循环引用问题(如 A 引用 B,B 引用 A,两者计数器均不为0,无法被回收)
可达性分析算法以「GC Roots」为根节点,构建对象引用链,若某个对象无法通过任何 GC Roots 到达(引用链断裂),则判定为可回收对象能解决循环引用问题,判断准确实现复杂、需要暂停所有用户线程(Stop The World,STW)是(JVM 默认算法)
(1)GC Roots 核心组成

GC Roots 是可达性分析的根节点,必须是“存活对象”,核心组成如下:

  1. 虚拟机栈中栈帧的局部变量表引用的对象(如方法中的局部变量);
  2. 本地方法栈中 Native 方法引用的对象;
  3. 方法区中类的静态变量引用的对象(如static User user = new User());
  4. 方法区中常量引用的对象(如final User user = new User());
  5. JVM 内部的核心对象(如类加载器、线程对象等)。
(2)Java 引用类型(影响对象可回收性)

Java 中的引用分为 4 种类型,不同引用类型对应对象的可回收优先级不同,具体如下表:

引用类型核心特性回收时机适用场景示例
强引用(Strong Reference)最普通的引用类型,默认所有引用都是强引用只有当强引用失效(如引用赋值为 null)时,对象才可能被回收绝大多数业务场景(存储核心业务对象)User user = new User();(user 为强引用)
软引用(Soft Reference)强度弱于强引用,用于存储非核心对象当 JVM 堆内存不足时,会回收软引用关联的对象(在 OOM 之前)缓存场景(如图片缓存、数据缓存)SoftReference<User> softRef = new SoftReference<>(new User());
弱引用(Weak Reference)强度弱于软引用,用于存储临时对象只要发生 GC,无论堆内存是否充足,都会回收弱引用关联的对象临时缓存、ThreadLocal 中的键值对、WeakHashMap 等WeakReference<User> weakRef = new WeakReference<>(new User());
虚引用(Phantom Reference)强度最弱,仅用于感知对象的回收事件无法通过虚引用获取对象实例,对象回收时会触发虚引用的通知跟踪对象回收、管理直接内存(如 NIO 堆外内存)PhantomReference<User> phantomRef = new PhantomReference<>(new User(), referenceQueue);

2. 垃圾回收算法

JVM 提供了 4 种核心垃圾回收算法,其中分代收集算法是基于前 3 种基础算法的组合,适配堆的分代结构,具体如下表:

垃圾回收算法核心原理优点缺点适用区域
标记-清除算法(Mark-Sweep)1. 标记:通过可达性分析标记所有可回收对象
2. 清除:释放标记为可回收的对象占用的内存
实现简单、无需移动对象1. 内存碎片严重(大量不连续内存块,无法分配大对象)
2. 标记和清除效率低
老年代(早期版本)
标记-复制算法(Mark-Copy)1. 将内存划分为大小相等的两个区域(From 区和 To 区)
2. 标记:标记可回收对象
3. 复制:将存活对象复制到 To 区
4. 清除:清空 From 区,之后 From 区和 To 区互换角色
1. 无内存碎片
2. 回收效率高(复制存活对象,数量少)
1. 内存利用率低(仅使用 50% 内存)
2. 若存活对象多,复制开销大
新生代(Eden 区+Survivor 区)
标记-整理算法(Mark-Compact)1. 标记:通过可达性分析标记所有可回收对象
2. 整理:将存活对象移动到内存一端,按顺序排列
3. 清除:释放存活对象右侧的所有内存
1. 无内存碎片
2. 内存利用率高(100% 利用)
整理阶段需要移动对象,开销大、效率低老年代
分代收集算法(Generational Collection)基于堆的分代结构,结合标记-复制算法(新生代)和标记-整理算法(老年代),针对不同分代采用不同回收策略1. 兼顾回收效率和内存利用率
2. 适配对象的生命周期特性(新生代对象存活时间短,老年代对象存活时间长)
实现复杂,需要区分对象分代整个堆(JVM 默认算法)

3. 垃圾回收器

垃圾回收器是垃圾回收算法的具体实现,JVM 提供了多种垃圾回收器,不同回收器适用于不同场景,核心回收器对比表如下(按主流程度排序):

垃圾回收器类型适用分代核心算法核心特点优点缺点适用场景JDK 支持版本
G1 GC(Garbage-First)新生代+老年代(整堆)标记-复制+标记-整理1. 面向堆内存分区(Region)回收,优先回收垃圾多的 Region
2. 支持可预测的 GC 停顿时间(通过-XX:MaxGCPauseMillis配置)
3. 混合回收(同时回收新生代和老年代)
1. 低停顿、高吞吐量
2. 无内存碎片
3. 支持大内存场景
实现复杂、对 CPU 资源消耗较高中大型应用、微服务、大内存场景(堆内存 > 4G)JDK 7u4+ 及以后(JDK 9 默认)
Parallel Scavenge GC(并行回收器)新生代标记-复制1. 多线程并行回收(默认线程数=CPU核心数)
2. 优先追求高吞吐量,而非低停顿
3. 与 Parallel Old GC 配合使用(老年代并行回收)
1. 吞吐量高(CPU 利用率高)
2. 回收效率高、开销低
停顿时间较长,不适合实时性要求高的场景后台任务、批处理程序、数据计算等吞吐量优先场景JDK 1.4+ 及以后(JDK 8 默认新生代回收器)
Parallel Old GC老年代标记-整理多线程并行回收,与 Parallel Scavenge GC 配套使用吞吐量高、回收效率高停顿时间较长与 Parallel Scavenge GC 配合,适用于吞吐量优先场景JDK 5+ 及以后
CMS GC(Concurrent Mark Sweep)老年代标记-清除1. 并发回收(大部分阶段与用户线程并行执行,减少 STW 时间)
2. 分 4 个阶段:初始标记→并发标记→重新标记→并发清除
停顿时间极短、实时性高1. 内存碎片严重
2. 并发阶段占用 CPU 资源,影响业务执行
3. 无法处理浮动垃圾
实时性要求高的场景(如电商交易、金融服务)JDK 1.5+ 及以后(JDK 9 标记为废弃,JDK 14 移除)
ParNew GC新生代标记-复制多线程并行回收,与 CMS GC 配套使用(唯一支持 CMS 的新生代回收器)回收效率高、支持与 CMS 配合使用停顿时间较长,吞吐量低于 Parallel Scavenge GC与 CMS GC 配合,适用于实时性要求高的场景JDK 1.4+ 及以后
Serial GC(串行回收器)新生代标记-复制单线程回收,回收期间暂停所有用户线程(STW)实现简单、资源消耗低(CPU/内存)停顿时间极长、效率低小型应用、客户端应用、测试环境(堆内存 < 1G)所有 JDK 版本
Serial Old GC老年代标记-整理单线程回收,与 Serial GC 配套使用实现简单、无内存碎片停顿时间极长小型应用、测试环境,或作为 CMS GC 失败的兜底回收器所有 JDK 版本
ZGC / Shenandoah GC新生代+老年代(整堆)标记-复制+颜色指针1. 几乎无停顿(STW 时间毫秒级)
2. 支持超大内存(百G级)
3. 并发回收,对业务影响极小
极低停顿、支持超大内存、高吞吐量对 CPU 要求高、生态不够成熟超大型应用、实时性要求极高的场景(如金融核心交易、大数据处理)ZGC(JDK 11+)、Shenandoah GC(JDK 12+)

4. GC 触发条件

不同类型的 GC(Minor GC、Major GC、Full GC)触发条件不同,具体如下表:

GC 类型核心触发条件回收区域停顿时间回收频率
Minor GC(新生代 GC)1. 新生代 Eden 区内存不足,无法分配新对象
2. 大对象直接分配失败(罕见)
新生代(Eden+S0)
Major GC(老年代 GC)1. 老年代内存不足,无法分配对象(如新生代对象晋升老年代失败)
2. 方法区(元空间)内存不足,触发老年代回收
老年代
Full GC(全局 GC)1. Major GC 执行后,内存仍不足
2. 调用System.gc()(建议性触发,JVM 可忽略)
3. 堆内存或元空间内存溢出前的最后一次回收
4. CMS GC 出现内存碎片,无法分配大对象
新生代+老年代+元空间最长最低

5. 内存溢出(OOM)实战示例

(1)堆内存溢出(最常见)
importjava.util.ArrayList;importjava.util.List;// 堆内存溢出:不断创建对象并保存引用,导致堆内存不足publicclassHeapOOMDemo{staticclassOOMObject{}publicstaticvoidmain(String[]args){List<OOMObject>list=newArrayList<>();// 无限创建对象,直到堆内存溢出while(true){list.add(newOOMObject());}}}// 运行参数:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError// 异常信息:java.lang.OutOfMemoryError: Java heap space
(2)虚拟机栈溢出(栈深度超限)
// 虚拟机栈溢出:无限递归调用,导致栈深度超过最大限制publicclassStackOverflowDemo{privateintstackDepth=0;publicvoidrecursiveCall(){stackDepth++;// 无限递归recursiveCall();}publicstaticvoidmain(String[]args){StackOverflowDemodemo=newStackOverflowDemo();try{demo.recursiveCall();}catch(Throwablee){System.out.println("栈深度:"+demo.stackDepth);e.printStackTrace();}}}// 异常信息:java.lang.StackOverflowError

五、总结与生产实践建议

  1. 类加载机制核心要点

    • 类加载核心流程为「加载→验证→准备→解析→初始化」,仅主动使用类才会触发初始化;
    • 双亲委派模型保障类的唯一性和安全性,自定义类加载器优先重写findClass()而非loadClass(),避免破坏双亲委派;
    • 类卸载需满足三个条件,否则会导致方法区(元空间)内存泄漏。
  2. 运行数据区核心要点

    • 线程私有区域生命周期与线程一致,线程共享区域生命周期与 JVM 一致;
    • 堆采用分代存储,新生代用标记-复制算法,老年代用标记-整理算法;
    • JDK 8 元空间替代永久代,存储在本地内存,需通过-XX:MaxMetaspaceSize限制最大内存。
  3. 垃圾回收核心要点

    • 可达性分析算法是 JVM 默认的对象可回收判断算法,GC Roots 是核心根节点;
    • 分代收集算法是默认回收算法,G1 GC 是 JDK 9+ 默认回收器,兼顾低停顿和高吞吐量;
    • 避免频繁 Full GC,减少 STW 对业务的影响,优先通过调优参数优化(如调整堆大小、回收器参数)。
  4. 生产实践调优建议

    • 堆内存配置:-Xms(初始堆内存)与-Xmx(最大堆内存)设置为相同值,避免内存动态扩展的开销,建议为物理内存的 1/4~1/2;
    • 回收器选型:中大型应用优先选 G1 GC,吞吐量优先场景选 Parallel Scavenge+Parallel Old,实时性优先场景(JDK 11+)选 ZGC;
    • 内存溢出排查:通过-XX:+HeapDumpOnOutOfMemoryError生成堆转储文件,使用 MAT(Memory Analyzer Tool)分析内存泄漏点;
    • 避免手动调用System.gc(),该方法仅为建议性触发,可能导致不必要的 Full GC。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 5:22:46

Luma3DS虚拟系统深度体验:从入门到精通的完美避坑指南

"为什么我的3DS装了自制软件就变砖&#xff1f;"这是很多新手玩家最担心的问题。今天我要分享的Luma3DS虚拟系统配置经验&#xff0c;将彻底改变你对3DS自制软件的认知。 【免费下载链接】Luma3DS Noob-proof (N)3DS "Custom Firmware" 项目地址: https:…

作者头像 李华
网站建设 2026/4/18 5:40:52

2025必备!本科生毕业论文AI平台TOP9测评

2025必备&#xff01;本科生毕业论文AI平台TOP9测评 2025年本科生论文写作工具测评&#xff1a;如何选择高效助手 随着人工智能技术的不断进步&#xff0c;越来越多的本科生开始借助AI平台完成毕业论文的撰写与修改。然而&#xff0c;面对市场上琳琅满目的工具&#xff0c;如何…

作者头像 李华
网站建设 2026/4/17 8:05:47

MacMon终极指南:零配置监控苹果Silicon芯片性能

MacMon终极指南&#xff1a;零配置监控苹果Silicon芯片性能 【免费下载链接】macmon &#x1f980;⚙️ Sudoless performance monitoring for Apple Silicon processors 项目地址: https://gitcode.com/gh_mirrors/mac/macmon 还在为监控苹果芯片性能而烦恼吗&#xff…

作者头像 李华
网站建设 2026/4/18 10:07:10

从零到专业:CAD设计中的字体选择艺术

从零到专业&#xff1a;CAD设计中的字体选择艺术 【免费下载链接】CAD常用字库275种字库 本仓库提供了一个包含275种常用CAD字库的资源文件&#xff0c;适用于AutoCAD和其他CAD软件。这些字库涵盖了多种字体类型&#xff0c;包括常规字体、复杂字体、手写字体、符号字体等&…

作者头像 李华
网站建设 2026/4/18 11:18:49

Linux学习终极指南:鸟哥私房菜完整PDF资源深度解析

Linux学习终极指南&#xff1a;鸟哥私房菜完整PDF资源深度解析 【免费下载链接】鸟哥的Linux私房菜完整版PDF下载 鸟哥的Linux私房菜完整版PDF下载本仓库提供《鸟哥的Linux私房菜》上下两篇的完整版PDF下载&#xff0c;包括基础学习篇和服务器篇 项目地址: https://gitcode.c…

作者头像 李华
网站建设 2026/4/18 6:27:09

CANoe中处理UDS否定响应的实战技巧

精通CANoe中的UDS否定响应处理&#xff1a;从踩坑到自动化修复的实战之路你有没有遇到过这样的场景&#xff1f;在用CANoe跑一个自动化诊断脚本时&#xff0c;一切看起来都正常——会话切换、安全解锁、发送读取请求……结果突然卡在某一步&#xff0c;报出一串神秘代码0x7F 0x…

作者头像 李华