作为一名 Java 开发者,你可能天天听到 JVM,但你是否真正思考过:我们写好的代码,到底是怎么被计算机执行的?各种对象和变量又是如何分配在内存中的?
今天这篇博客,我们就从宏观到微观,把 JVM 的核心架构和内存结构彻底串联起来,帮你建立一个整体的认知。
一、初识JVM:到底什么是Java虚拟机?
在深入内存之前,我们先回忆一下什么是JVM。
JVM是Java程序的运行环境 ,它不直接运行我们编写的Java源码,而是承担三大核心职责:执行字节码、管理程序运行内存、自动垃圾回收。
二、JVM如何运行一个Java程序?
整个流程依托JVM的四大核心组件协同工作:**类加载子系统、运行时数据区(内存结构)、执行引擎、本地方法接口.**在介绍具体的执行流程之前,先介绍一下这几个组件的功能。
-
**类加载子系统:**它负责把 .class 字节码文件,从硬盘读到 JVM 内存里,并变成可以运行的 Java 类。
-
**运行时数据区:**JVM 的内存区域,用来存数据、存代码、存方法调用。
-
**执行引擎:**把字节码变成能运行的指令然后执行,进行垃圾回收。
-
本地方法接口:让 Java 调用底层 C/C++ 写的本地方法。

运行流程:
JVM 运行程序的过程串起来看,其实就是一个 "加载 → 存储 → 执行 → 调用系统能力" 的过程。
首先,我们编写的 Java 代码会经过编译器编译成 .class 字节码文件。这个字节码并不能直接被操作系统执行,因此程序启动时,JVM 会先通过类加载系统把需要用到的 Class 文件加载到内存中。
类加载完成后,类的元数据信息,比如类名、父类、接口、字段、方法等,会被存放到运行时数据区中的方法区。
接下来程序开始运行。运行过程中创建的对象会被分配到堆 中;方法调用时会创建栈帧并压入虚拟机 栈 ;每个线程还会有一个程序计数器 ,用于记录当前执行到了哪条字节码指令,以便在线程切换后能够继续从正确的位置执行。如果代码调用了 Native 方法,那么对应的方法执行信息会进入本地方法栈。
此时,运行时数据区已经准备好了程序执行所需的数据,但这些字节码本身仍然不能直接运行。因此 JVM 的执行引擎会开始工作,它负责将字节码翻译成当前操作系统和 CPU 能识别的机器指令,然后交给 CPU 执行。
三、核心重点:JVM五大内存结构深度解析

我们可以将五大区域分为两类:线程私有内存 、线程共享内存,这是理解内存特性的关键。
线程私有阵营(生命周期与线程相同)
-
程序计数器(PC Register)
-
作用:记录当前线程正在执行的下一条字节码指令的地址,解释器据此读取指令。
-
为什么私有? 多个线程并发执行时,CPU 会频繁切换线程。为了让线程切换回来后能准确找到上次执行到哪了,每个线程必须有一个独立的计数器。
-
-
虚拟机栈(JVM Stack)
- 作用 :主管 Java 方法的调用。每次方法调用都会创建一个栈帧(包含局部变量表、操作数栈、动态链接、方法返回地址等),方法执行完即出栈销毁。
-
本地方法栈(Native Method Stack)
- 作用 :与虚拟机栈类似,专门为 JVM 调用底层 C/C++ 的
Native方法服务。
- 作用 :与虚拟机栈类似,专门为 JVM 调用底层 C/C++ 的
线程共享阵营(全程序可见,涉及垃圾回收)
-
堆(Heap)
- 作用 :存储所有的对象实例和数组,是 JVM 中最大的一块内存,也是垃圾回收(GC)绝对的核心战场。
-
方法区(Method Area)
-
作用 :存储已被虚拟机加载的类的元数据信息(全类名、父类、字段/方法信息、静态变量等)。
-
历史演进(面试高频):
-
JDK 1.8 之前 :由永久代(PermGen)实现,占用的是 JVM 内存。因为大小有限,加载类过多时极易引发
OOM(内存溢出)。字符串常量池在 JDK 1.7 之前也在这里。 -
JDK 1.8 之后 :彻底废除永久代,改用元空间(Metaspace)实现。元空间直接使用本地内存(操作系统内存) ,只要系统内存够大,几乎不会再因加载类过多而 OOM。此时,字符串常量池被移到了堆中。
-
-
四 JVM四大引用类型
背景与问题
JDK 1.2 之前其实只有一种引用关系,也就是我们平时使用的强引用。当时对象的状态只有两种:有引用就存活,没有引用就回收。
但这种机制过于简单,无法满足实际业务场景的需求。例如缓存中的对象,我们希望内存充足时保留,内存紧张时再释放;又或者某些对象被回收时,希望能够收到通知并执行资源清理工作。
因此 JVM 引入了强引用、软引用、弱引用和虚引用四种引用类型,本质上是为垃圾回收器提供更细粒度的对象生命周期管理能力。
不同的引用强度代表对象不同的存活优先级:
强引用表示对象必须存活;
软引用表示内存不足时可以回收;
弱引用表示只要发生 GC 就可以回收;
虚引用则用于在对象被回收时接收通知,执行额外的资源释放操作。
所以四种引用解决的核心问题就是:
让对象的回收不再只有"回收"和"不回收"两种状态,而是能够根据业务场景灵活控制对象的生命周期和回收时机,从而实现更精细化的内存管理。
强引用
平常 Object obj = new Object() 这种就是强引用。 只要强引用存在,GC 永远不会回收这个对象,哪怕内存溢出 OOM 也不会动它;只有手动把引用置为 null、跳出作用域断开引用,对象才会被回收。
软引用--解决缓存问题
描述有用但不是必须的对象,内存充足时 GC 不会回收;一旦堆内存不够、快要 OOM 之前,会把所有软引用对象全部回收释放内存。
弱引用
只要触发 GC,不管内存够不够,弱引用指向的对象立刻被回收。
虚引用
**虚引用不是为了访问对象,而是为了在对象被回收时收到通知然后做一些善后工作。**通常会配合引用队列使用,当对象被判定为可回收时,JVM会将对应的虚引用加入引用队列,程序可以监听这个队列并执行额外的资源清理工作。例如堆外内存、文件句柄、Socket连接等。