服务器与大带宽专家 · 持牌IDC/CDN/ISP服务商
简米科技官网JIANMI TECH
资讯 2026-10-10 简米科技 3,978 字 9 分钟阅读

Java虚拟机内存模型是怎样的?JVM垃圾回收机制有哪些?

导读JVM内存模型决定了对象创建后落在哪块内存、能被谁访问,垃圾回收机制决定了这些内存何时被回收、以多快速度回收——把这两件事搞清楚,Java程序的内存溢出、频繁Full GC、响应抖动这些问题就有了排查的抓手,Java程序员写代码时几乎不用手动malloc和free,这份省心背后全靠JVM撑着,它一边把内存划分成……

JVM内存模型决定了对象创建后落在哪块内存、能被谁访问,垃圾回收机制决定了这些内存何时被回收、以多快速度回收把这两件事搞清楚,Java程序的内存溢出、频繁Full GC、响应抖动这些问题就有了排查的抓手。

Java程序员写代码时几乎不用手动malloc和free,这份省心背后全靠JVM撑着,它一边把内存划分成职责明确的几块区域,一边在后台悄悄清理不再使用的对象,理解这两套机制,不是应付面试,而是线上出问题时能快速定位的底气。

JVM内存模型到底由哪几块组成

JVM在运行时会把自己管理的内存切成若干区域,划分依据是线程共享还是线程私有。

线程共享区:堆与方法区

堆是JVM中最大的一块内存,几乎所有的对象实例和数组都在这里分配,堆内部继续分代:新生代和老年代,新生代又细分为Eden、Survivor From、Survivor To,默认比例大致为8:1:1,这个分代设计的出发点是绝大多数对象朝生夕死,把短命对象和长命对象分开管理,回收效率更高。

方法区存放类的元信息、常量、静态变量等,JDK 8是一个分水岭:之前叫永久代,占用JVM堆内存;之后改为元空间,直接使用本地内存,默认不设上限,受物理内存约束。

线程私有区:虚拟机栈、本地方法栈、程序计数器

  • 虚拟机栈:每个线程一份,方法调用时压入栈帧,栈帧里装着局部变量表、操作数栈、返回地址,栈深度超限抛StackOverflowError。
  • 本地方法栈:为native方法服务,结构和虚拟机栈类似。
  • 程序计数器:记录当前线程执行到哪条字节码,是唯一不会抛OutOfMemoryError的区域。

一张表看清各区域差异

Java虚拟机内存模型是怎样的?JVM垃圾回收机制有哪些?

区域 线程共享 主要存放内容 典型异常
堆 是 对象实例、数组 OutOfMemoryError
方法区/元空间 是 类元信息、常量、静态变量 OutOfMemoryError
虚拟机栈 否 栈帧、局部变量 StackOverflowError
本地方法栈 否 native方法栈帧 StackOverflowError
程序计数器 否 字节码行号 无

Java面试JVM内存模型常见问题拆解

面试里这块问得细,但真正拉开差距的,是能否把概念和线上现象对上号。

栈内存和堆内存到底差在哪

栈的生命周期跟着线程走,方法结束栈帧就弹出,由系统自动管理;堆的生命周期跟着对象走,什么时候回收由GC决定,栈上分配速度快、容量小;堆容量大,但分配和回收都有成本,逃逸分析开启后,如果对象没有逃出方法作用域,JIT会把它的分配从堆挪到栈,这就是标量替换和栈上分配优化。

方法区、永久代、元空间三者什么关系

方法区是规范里的概念,永久代和元空间是不同JDK版本对它的实现,JDK 7把字符串常量池移出永久代,JDK 8彻底用元空间替换永久代,元空间用本地内存,好处是类加载太多时不再直接拖垮堆,但如果不限制大小,也可能把机器内存吃光。

哪些对象会从新生代晋升到老年代

  • 对象在Survivor区每熬过一次Minor GC,年龄加1,默认到15岁晋升老年代。
  • 大对象直接进老年代,避免在Eden和Survivor之间反复复制。
  • Survivor区中相同年龄对象总和超过一半,该年龄及以上的直接晋升。
  • 动态年龄判定,由虚拟机根据实际情况决定。

垃圾回收机制靠什么判断对象该被回收

判断对象是否存活,是GC的第一步。

引用计数法为什么被淘汰

引用计数给对象加一个计数器,有引用加1,引用失效减1,归零就回收,它实现简单、判断及时,但无法处理循环引用两个对象互相引用,计数永远不归零,主流JVM没有采用它作为最终判断依据。

可达性分析与GC Roots

行业共识认为,可达性分析是判断对象存活的通用方案,思路是从一组称为GC Roots的根对象出发向下搜索,能走到的对象标记为存活,走不到的就可以回收,常见的GC Roots包括:

  • 虚拟机栈中局部变量表引用的对象
  • 方法区中静态变量引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI引用的对象
  • 活跃线程对象

四种引用类型对回收的影响

  • 强引用:只要还挂着,GC绝不回收。
  • 软引用:内存不够时才回收,适合做缓存。
  • 弱引用:下次GC一定回收,ThreadLocal的Entry用的就是它。
  • Java虚拟机内存模型是怎样的?JVM垃圾回收机制有哪些?

  • 虚引用:唯一用途是对象被回收时收到通知,配合ReferenceQueue使用。

JVM垃圾回收器CMS和G1有什么区别

这是被问得最多的一组对比,两者设计目标不同,适用场景也不同。

CMS:以最短停顿为目标的并发收集器

CMS把回收拆成四个阶段:初始标记、并发标记、重新标记、并发清除,初始标记和重新标记需要暂停用户线程,并发阶段可以和业务线程一起跑,它的短板是标记-清除会产生内存碎片,而且并发阶段会占用CPU,容易触发Concurrent Mode Failure,进而退化成Serial Old全停顿回收。

G1:面向大堆的分区化收集器

G1把堆拆成大小相等的Region,不再物理上连续分代,它通过维护每个Region的回收价值,优先回收垃圾最多的区域,因此得名Garbage First,停顿时间可以设定目标值,比如-XX:MaxGCPauseMillis=200,G1会尽量在这个范围内完成回收,整体基于标记-复制算法,碎片问题比CMS轻。

主流收集器横向对比

收集器 适用场景 算法 是否可设停顿目标
Serial 单核小堆、客户端模式 复制/标记-整理 否
Parallel 吞吐量优先、批处理 复制/标记-整理 否
CMS 低延迟、JDK 9前常用 标记-清除 否
G1 大堆、低延迟、JDK 9后默认 分区复制 是
ZGC 超大堆、极低停顿 染色指针+读屏障 是

生产环境怎么选

堆小于4G且追求吞吐,Parallel够用,堆在6G到几十G,又在意响应时间,G1是默认答案,堆超过百G且停顿要求苛刻,再看ZGC或Shenandoah,近年来不少中间件把默认收集器切到G1,正是看中它可预测的停顿表现。

线上服务JVM调优实战步骤

调优的前提是先拿到数据,别上来就改参数。

第一步:摸清运行时状态

jps -l                          # 查看Java进程PID
jstat -gcutil <pid> 1000 10     # 每秒打印一次GC统计
jmap -histo:live <pid> | head -30  # 查看存活对象直方图
jstack <pid> > stack.log        # 导出线程栈

Java虚拟机内存模型是怎样的?JVM垃圾回收机制有哪些?

重点关注jstat -gcutil输出里的FGC次数、FGCT总耗时、老年代使用率,如果FGC频繁且每次回收后老年代占用降不下来,多半是内存泄漏而非参数问题。

JVM堆内存溢出怎么排查

先确认是哪种OOM:

  1. 堆内存溢出,在启动参数加-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/dump,拿到hprof文件。
  2. 用MAT或VisualVM打开dump,看支配树里哪个对象占用最大。
  3. 顺着引用链找到持有它的GC Roots,定位到具体代码。
  4. 元空间溢出则检查是否动态生成类过多,比如反射、CGLIB代理滥用。
  5. 线程数溢出检查线程池配置和是否有阻塞任务堆积。

常用调优参数参考

  • -Xms与-Xmx设成相同值,避免堆反复伸缩。
  • -Xmn控制新生代大小,官方建议占堆的1/3到1/2。
  • -XX:SurvivorRatio=8设定Eden与单个Survivor的比例。
  • -XX:+UseG1GC -XX:MaxGCPauseMillis=200启用G1并设停顿目标。
  • -XX:MetaspaceSize和-XX:MaxMetaspaceSize给元空间设上下限,防止无限膨胀。

关于JVM内存模型与垃圾回收机制的常见问答

Q:JDK 8之后字符串常量池到底在哪?

A:JDK 7就把字符串常量池从永久代挪到了堆里,JDK 8用元空间替换永久代后,字符串常量池依然留在堆中,所以大量intern操作会直接反映在堆内存占用上。

Q:Minor GC和Full GC的触发条件分别是什么?

A:Minor GC在Eden区满时触发,回收新生代,Full GC触发条件更多:老年代空间不足、元空间扩容、显式调用System.gc()、CMS的Concurrent Mode Failure等,业内专家指出,频繁Full GC通常意味着对象晋升过快或存在内存泄漏。

Q:G1适合多大的堆,小堆用G1会亏吗?

A:G1设计目标是大堆低延迟,一般建议堆在6G以上再考虑,堆太小的时候,Region数量少,G1的分区管理开销反而显得冗余,Parallel或Serial在几G以内的堆上吞吐表现更实在,据OpenJDK官方文档,G1在JDK 9之后成为服务端默认收集器,但这不等于所有场景都优于其他收集器。

JVM内存模型划定了数据存放的边界,垃圾回收机制决定了这些数据的生命周期,把分代结构、可达性分析、收集器特性三块拼在一起,线上遇到GC问题时,才有一张清晰的排查地图可循。

分享本文
本文为 简米科技官网 原创,已由运维技术专家审核。转载请注明来源:原文链接
售前咨询 服务热线 售后 邮箱