第25篇:Java JVM入门:内存模型与垃圾回收,理解JVM底层
第25篇:Java JVM入门:内存模型与垃圾回收,理解JVM底层
📌 专栏:Java底层核心进阶
💡 前言
很多Java开发者日常CRUD开发,熟练使用各种语法和框架,却始终对JVM底层一知半解。遇到内存溢出、程序卡顿、GC频繁、服务雪崩等线上问题时,完全无从下手,更别说JVM调优。

读完本文,你将彻底搞懂:
-
JVM整体架构及各模块核心职责
-
堆、栈、方法区、程序计数器等内存区域的作用与特点
-
垃圾回收GC的核心逻辑与触发条件
-
三大经典GC算法的原理、优缺点及适用场景
-
内存泄漏与内存溢出的区别、成因及解决思路
一、JVM 整体架构概述
JVM(Java Virtual Machine,Java虚拟机)是一款跨平台的虚拟机,是Java程序实现一次编译,到处运行的核心。Java源码编译为.class字节码文件后,由JVM解析、加载、执行,屏蔽了底层操作系统、硬件的差异。
标准JVM整体架构分为三大核心模块 + 运行时内存区,四大模块协同完成程序运行:
1. 类加载器(ClassLoader)
负责加载磁盘中的.class字节码文件,将类信息、方法信息、常量等资源加载到JVM内存中,分为启动类加载器、扩展类加载器、应用类加载器,遵循双亲委派模型。
2. 执行引擎
JVM的“CPU”,负责解析字节码指令并执行,包含解释器(即时解释执行)、JIT编译器(热点代码即时编译优化,提升运行效率)、垃圾回收器(负责内存垃圾回收)。
3. 本地方法接口(JNI)
对接本地Native方法(C/C++编写的方法),弥补Java底层操作短板,比如系统调用、硬件操作等。
4. 运行时数据区(核心重点)
也就是我们常说的JVM内存模型,程序运行过程中所有数据、变量、对象、指令信息均存储在此区域,是本文核心讲解内容。
二、JVM 运行时内存模型(五大区域)
根据《Java虚拟机规范》,JVM运行时内存划分为五大核心区域,按照线程归属可分为两类:线程私有区域(随线程创建而生、线程销毁而回收)、线程共享区域(全局共享,JVM启动创建、关闭销毁)。
1. 程序计数器(Program Counter Register)
归属:线程私有
核心作用:记录当前线程正在执行的字节码指令地址,用于线程切换后恢复执行位置。
Java是多线程语言,CPU通过时间片轮转切换线程,当线程暂停执行时,程序计数器会记录当前执行进度,线程重新获取CPU时间片后,精准接续执行。
核心特点:
-
JVM中唯一不会发生OutOfMemoryError(内存溢出)的区域
-
内存占用极小、结构简单、线程隔离、无垃圾回收
-
生命周期与当前线程完全一致
2. Java虚拟机栈(JVM栈/栈内存)
归属:线程私有
核心作用:存储方法执行的临时数据,每调用一个方法就会创建一个栈帧,方法执行完毕栈帧自动出栈销毁。
栈帧中包含:局部变量表、操作数栈、动态链接、方法返回地址等信息,我们日常定义的局部变量、基本数据类型变量均存储在栈中。
核心特点:
-
先进后出的栈结构,执行效率极高
-
生命周期跟随线程,线程结束栈内存立即释放
-
栈空间固定,可通过JVM参数-Xss设置大小
-
栈溢出会抛出StackOverflowError(常见于递归死循环、方法嵌套过深)
3. 本地方法栈
归属:线程私有
核心作用:作用与虚拟机栈类似,专门为JVM调用的Native本地方法服务,存储本地方法执行的栈帧信息。
核心特点:
-
同样会抛出StackOverflowError和OutOfMemoryError
-
底层由C/C++实现,Java开发者基本无需手动操作
4. 堆内存(Heap)—— GC主战场
归属:线程共享
核心作用:JVM中最大的内存区域,所有通过new关键字创建的对象、数组均存储在堆中,是垃圾回收的核心区域。
为了提升GC效率,堆内存被细分为:新生代(Young)、老年代(Old)。其中新生代又分为Eden区、From Survivor、To Survivor,默认比例8:1:1。
核心特点:
-
全局线程共享,所有线程均可访问堆中的对象
-
内存空间大、生命周期长,随JVM启动和关闭
-
频繁发生GC,用于回收失效对象
-
堆内存不足会抛出OutOfMemoryError(堆内存溢出)
5. 方法区(Method Area)
归属:线程共享
核心作用:存储JVM加载的类元数据,包含类名、方法名、字段信息、常量池、静态变量、即时编译代码等全局静态资源。
JDK1.8是重要分水岭:JDK1.8之前方法区存在永久代,JDK1.8之后移除永久代,使用元空间(Metaspace)替代,元空间使用本地内存,不再占用JVM堆内存,大幅降低了方法区溢出概率。
核心特点:
-
全局共享,存储静态、全局、类级别数据
-
极少触发GC,仅回收部分废弃常量和无用类
-
元空间溢出会抛出OutOfMemoryError
三、垃圾回收(GC)核心原理
Java语言最大的优势之一就是自动垃圾回收,无需开发者手动申请和释放内存,由JVM的垃圾回收器自动识别、回收内存中无效对象,避免内存浪费。
1. GC核心定义
GC(Garbage Collection)即垃圾回收,指JVM自动扫描堆内存,识别没有任何有效引用指向的对象,清空其占用的内存空间,实现内存复用,防止内存堆积。
2. 垃圾判定依据:GC Roots
JVM通过可达性分析算法判定对象是否存活:以GC Roots为起始节点遍历内存,能遍历到的对象为存活对象,无法遍历到的对象即为垃圾对象,可被回收。
常见GC Roots:
-
虚拟机栈中引用的对象
-
方法区中静态变量、常量引用的对象
-
本地方法栈Native引用的对象
-
活跃线程的引用对象
3. GC的触发条件
GC不会随意触发,仅在满足特定条件时自动执行,分为Minor GC(新生代GC)和Full GC(全局GC)。
(1)Minor GC 触发条件
新生代Eden区内存空间不足,无法为新对象分配内存时,触发Minor GC,回收新生代无效对象,频率高、速度快、耗时短。
(2)Full GC 触发条件
-
老年代内存空间不足
-
Minor GC后存活对象过多,无法放入Survivor区,直接进入老年代
-
方法区/元空间内存不足
-
手动调用System.gc()(仅建议,不保证立即执行)
-
JVM出现内存预警、并发GC失败等异常场景
注意:Full GC会回收整个堆内存(新生代+老年代),耗时极长、会造成程序STW(暂停所有用户线程),线上项目需尽量避免频繁Full GC。
四、三大经典GC回收算法详解
GC算法是垃圾回收的核心,所有垃圾回收器均基于三大基础算法优化迭代而来,分别是:标记-清除、标记-复制、标记-整理。
1. 标记-清除算法(Mark-Sweep)
执行流程:分为两步,第一步标记,遍历内存标记所有存活对象;第二步清除,清空所有未被标记的垃圾对象。
优点:算法简单、实现容易,无需移动对象位置。
缺点:
-
产生大量内存碎片,回收后的内存分散不连续
-
内存碎片过多时,大对象无法分配连续内存,容易触发OOM
-
两次遍历内存,效率偏低
适用场景:老年代低频GC场景
2. 标记-复制算法(Mark-Copy)
执行流程:将内存划分为两个大小相等的区域,平时只使用其中一块。GC时标记所有存活对象,将存活对象完整复制到另一块空白内存中,复制完成后直接清空原内存区域。
新生代8:1:1的分区设计,就是为了适配标记复制算法,大幅提升回收效率。
优点:
-
无内存碎片,内存空间连续规整
-
只需标记存活对象,回收效率极高
缺点:
-
内存利用率低,永久浪费一半内存空间
-
存活对象较多时,复制成本极高、效率下降
适用场景:新生代高频GC(存活对象少、垃圾对象多)
3. 标记-整理算法(Mark-Compact)
执行流程:第一步标记所有存活对象,第二步将所有存活对象向内存一端移动、紧凑排列,最后直接清空边界外的全部垃圾内存。
优点:
-
无内存碎片,内存连续规整
-
内存利用率100%,无空间浪费
缺点:
-
需要移动所有存活对象,开销大、速度慢
-
STW时间更长,对程序性能影响较大
适用场景:老年代GC(存活对象多、垃圾少,不适合复制算法)
五、内存泄漏 vs 内存溢出:成因与解决方案
日常开发中最常见的两个内存问题:内存泄漏(Memory Leak)和内存溢出(OOM),二者因果关联,内存泄漏长期堆积最终必然导致内存溢出。
1. 内存泄漏(Memory Leak)
定义:程序中存在无效对象被持续引用,GC无法回收这些本该销毁的对象,导致内存被无效占用、无法释放,内存使用率持续升高。
常见成因:
-
静态集合类(static List/Map)长期持有对象引用
-
未关闭资源:IO流、数据库连接、Redis连接、线程池未关闭
-
匿名内部类、非静态内部类持有外部类引用
-
全局变量滥用,对象使用完毕未置空
-
自定义缓存无过期策略,数据无限堆积
解决思路:
-
及时关闭所有IO、连接、线程等资源,使用try-with-resources自动关闭流
-
缓存设置过期时间、淘汰策略,使用弱引用存储缓存对象
-
避免静态集合无限存储数据,定期清理无效数据
-
使用内存分析工具(MAT、JProfiler)定位泄漏对象
2. 内存溢出(OOM,OutOfMemoryError)
定义:JVM内存空间全部被占满,没有多余内存分配给新对象,程序无法继续运行,直接抛出异常崩溃。
常见成因:
-
长期内存泄漏堆积,耗尽可用内存
-
一次性创建超大对象、超大集合,批量读取超大文件
-
死循环不断创建新对象,无销毁逻辑
-
JVM内存参数配置过小,无法支撑业务并发量
解决思路:
-
优先排查内存泄漏问题,修复代码漏洞
-
优化代码逻辑,避免循环创建对象、一次性加载海量数据
-
调整JVM参数,合理扩容堆内存、元空间大小
-
分批次、分页处理大数据,避免一次性加载入内存
六、总结与后续预告
本文核心总结
-
JVM内存分为线程私有(程序计数器、虚拟机栈、本地方法栈)和线程共享(堆、方法区)两大类型,各区域各司其职
-
堆是GC主战场,分为新生代、老年代,不同区域适配不同回收算法
-
三大GC算法各有优劣:复制算法适配新生代,标记整理适配老年代,标记清除易产生内存碎片
-
内存泄漏是缓慢堆积的隐患,内存溢出是最终爆发的结果,调优核心是杜绝泄漏、优化GC
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)