尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

JVM运行时数据区详解:变量、对象和类信息到底存在哪里

JVM运行时数据区详解:变量、对象和类信息到底存在哪里
📅 发布时间:2026/7/26 21:18:51

JVM运行时数据区详解:变量、对象和类信息到底存在哪里

写 Java 代码时,我们通常只关注变量的值和对象的行为,很少去思考它们在 JVM 中到底如何存放。

inta=10;

程序运行之后,a并不是一直存在于.class文件中,而是会随着方法执行进入 JVM 的运行时数据区。

再比如:

Objectobj=newObject();

这里既有变量引用obj,也有通过new创建的对象实例,它们的生命周期和存储位置并不相同。

理解 JVM 运行时数据区,可以帮助我们搞清楚几件事:局部变量为什么方法结束后自动消失,对象为什么需要 GC 回收,静态变量、类信息、对象实例分别存放在哪里。这也是理解 Java 内存模型和垃圾回收机制的基础。

目录

  • 全景图:五个区域各司其职
  • 程序计数器
  • 虚拟机栈
  • 本地方法栈
  • 堆
  • 方法区
  • 实战:变量去哪了
  • 五个区域的差异
  • 小结

全景图:五个区域各司其职

JVM 规范把运行时内存划分为五个区域。从线程的角度看,可以分成两类:

线程私有(每个线程独占一份,线程创建时分配,线程销毁时回收):

  • 程序计数器
  • 虚拟机栈
  • 本地方法栈

线程共享(所有线程共用,JVM 启动时创建):

  • 堆
  • 方法区

注意:静态变量(类字段)在 HotSpot JDK 8+ 中随 Class 对象存放在堆中,而非方法区。

程序计数器

这是五个区域里最小的一块,也是唯一不会抛 OOM 的区域。

程序计数器保存当前线程下一条需要执行的字节码地址。JVM 的执行引擎执行完一条字节码后,会从程序计数器取出下一条的行号,继续执行。

每个线程都有自己的程序计数器,互不干扰。这也是"线程私有"的含义——线程 A 执行到第 10 行,线程 B 可能执行到第 50 行,各自记录,互不影响。

可以把它理解成书签。你在一本书里读到第 30 页,夹了个书签;别人读到第 80 页,也夹了个书签。书签各自独立,标记的是各自读到哪了。

唯一需要注意的:如果当前执行的是 native 方法,程序计数器的值为空(Undefined)。因为 native 方法的执行交给操作系统了,JVM 管不着。

虚拟机栈

虚拟机栈是我们最常接触的内存区域,也是理解 Java 方法调用的关键。

每当一个方法被调用,JVM 会为这个方法创建一个栈帧(Stack Frame),压入虚拟机栈。方法执行完毕,栈帧弹出。一个线程在同一时刻只有栈顶的栈帧是活跃的——正在执行的那个方法。

栈帧里装什么

每个栈帧包含四个部分:

组成说明
局部变量表存方法的局部变量和方法参数。基本类型直接存值,引用类型存引用(指针)
操作数栈方法执行过程中的临时计算空间,比如做加法时先 push 两个数,再执行 iadd
动态链接指向运行时常量池中该方法的引用,支持多态
方法返回地址方法执行完后回到调用者的哪一行继续执行

其中最重要的是局部变量表。你写的每一个局部变量,不管是int a = 10还是String s = "hello",都存在这里。基本类型存的是值本身,引用类型存的是指向堆中对象的引用。

方法调用时创建栈帧,方法结束时栈帧销毁。局部变量的生命周期和方法调用一样长——方法返回,栈帧弹出,局部变量自然就没了。

两种异常

虚拟机栈有两种出错场景:

StackOverflowError:方法调用太深,栈帧不断压入,栈空间撑爆了。最常见的原因是递归没有终止条件。

publicclassStackOverflowDemo{publicstaticvoidmain(String[]args){recurse(0);}staticvoidrecurse(intdepth){// 每调一次,压入一个栈帧,直到栈空间不够recurse(depth+1);}}// 运行结果:java.lang.StackOverflowError

OutOfMemoryError:如果虚拟机栈支持动态扩展,扩展时内存不够了就会抛这个错。不过现在的 JVM 实现通常在创建线程时就固定了栈大小,所以更多见的是 StackOverflowError。

本地方法栈

本地方法栈和虚拟机栈几乎一样,区别在于它服务的是native 方法——用 C/C++ 写的方法,通过 JNI 调用。

比如Thread.currentThread()、Object.hashCode()这些方法底层都是 native 实现。调用这些方法时,压入的是本地方法栈,不是虚拟机栈。

在 HotSpot JVM 的实现里,本地方法栈和虚拟机栈合二为一了,不单独区分。所以面试的时候提一句"本地方法栈为 native 方法服务"就够了。

堆

堆是 JVM 中用于存放对象实例和数组的主要区域,也是 GC 主要工作的地方。

所有对象实例和数组都在堆上分配。你每次写new Object()、new int[100],对象的内存都来自堆。前面说的虚拟机栈里存的是引用,真正的对象本体在堆里。

来看一个简单的例子:

publicvoidcreate(){Objectobj=newObject();}

调用create()时,发生了两件事:

  1. 在虚拟机栈的栈帧中,局部变量obj存了一个引用(指针)
  2. 在堆中,分配了一块内存,存放Object实例

obj是引用,存在栈里;对象本体,存在堆里。方法结束后栈帧销毁,obj这个引用就没了。但对象还在堆里,直到 GC 发现它没有引用指向了,才会回收。

为什么需要 GC

C++ 里,你new了一个对象,用完之后必须自己delete,不然内存就泄漏了。Java 把这个责任交给了 GC(垃圾回收):你只管new,GC 帮你判断哪些对象没人用了,然后回收它们的内存。

GC 判断对象"没人用"的依据是可达性分析。从一组叫"GC Roots"的根节点出发(比如虚拟机栈里的局部变量、类的静态字段),沿着引用链往下找,能到达的对象就是"活的",到达不了的就是"死的",可以回收。

GC Roots │ ├── 虚拟机栈中的局部变量 │ └── obj ──→ Object实例(可达,不回收) │ └── 类的静态字段 └── list ──→ ArrayList实例(可达,不回收) └── 其中引用的对象(可达,不回收) 堆中没有任何引用指向的对象 → 不可达,GC 回收

这里先关注对象为什么需要 GC,具体的垃圾回收算法和分代设计会在后面的 GC 章节展开。

方法区

方法区存储的是类的元信息:类名、字段信息、方法信息、方法的字节码、运行时常量池。

当你写了一个class User,JVM 第一次用到这个类时,会通过类加载器把.class文件读进来,在方法区中创建这个类的元数据。之后你每次new User(),堆里分配的是对象实例,但"User 这个类长什么样"的信息始终在方法区里。

类加载完成后,JVM 会保存对应的类元信息。之后无论创建多少个对象实例,这些实例共享同一份类定义。

永久代和元空间

方法区是一个逻辑概念,JVM 规范定义了它,但不同版本的 HotSpot JVM 实现方式不同:

  • JDK 7 及之前:用永久代(PermGen)实现,放在 JVM 堆内存里,大小固定,容易 OOM
  • JDK 8 起:改为元空间(Metaspace),使用本地内存(操作系统内存),默认不限大小

永久代的问题在于它属于 JVM 堆内存中的一块固定区域,类信息增长后容易出现空间不足。JDK 8 引入元空间后,将类元数据放到了本地内存中,减少了固定大小带来的限制。

字符串常量池也从永久代移到了堆里,这是 JDK 7 就完成的变更。

实战:变量去哪了

来看一段代码,分析每个变量在运行时存在哪个区域:

publicclassMemoryDemo{// 静态字段属于类变量// HotSpot JDK8+ 中存放在堆中的 Class 对象相关区域privatestaticintcount=0;publicstaticvoidmain(String[]args){// args 引用 → 虚拟机栈(main 方法栈帧)// args 数组对象 → 堆count++;createObject();}publicstaticObjectcreateObject(){// 局部变量 a → 虚拟机栈(createObject 方法栈帧)inta=10;// obj 引用 → 虚拟机栈// Object 实例 → 堆Objectobj=newObject();returnobj;}}

逐个拆解:

变量存储位置说明
count堆(Class 对象相关区域)静态字段属于类变量,HotSpot JDK8+ 中随 Class 对象存放在堆中
args引用虚拟机栈main 方法的局部变量
args数组对象堆通过new String[]创建
a虚拟机栈createObject 方法的局部变量,基本类型直接存值
obj引用虚拟机栈createObject 方法的局部变量,存的是指针
Object 实例堆通过new创建的对象

一个规律:基本类型的局部变量直接存在栈帧的局部变量表里,方法结束就没了。对象的引用存在栈帧里,但对象本体在堆里,方法结束后引用没了,对象等着 GC 来回收。

五个区域的差异

区域线程归属存储内容是否 GC可能的异常
程序计数器私有字节码行号否无
虚拟机栈私有栈帧(局部变量、操作数栈)否StackOverflowError / OOM
本地方法栈私有native 方法栈帧否StackOverflowError / OOM
堆共享对象实例、数组是OOM: Java heap space
方法区共享类信息、常量池是(类卸载)OOM: Metaspace

记住这张表,面试时问"堆和栈的区别"、“哪些区域会 GC”、“哪些会抛 OOM”,都能从这里找到答案。

小结

JVM 将不同生命周期的数据放入不同区域:方法调用产生的数据随着栈帧销毁,对象实例交给 GC 管理,类元信息则伴随类加载存在。

但分区只是静态的内存模型。堆里的对象创建了,什么时候算"没人用了",GC 用什么算法判断可达性,分代模型是怎么回事——这些问题,是下一篇的事了。

相关新闻

  • 终极暗黑破坏神II角色编辑器:3步打造完美游戏体验
  • 【路径规划】基于改进的智能水滴算法求解送取货且带时间窗的车辆路径与调度优化问题matlab代码
  • 2026金九银十|Java后端面试题大全(附答案详解),一篇通关后端面试

最新新闻

  • css选择器有哪些?优先级分别是什么?哪些属性可以继承?
  • CC1010硬件同步与前置码检测:低功耗无线通信的可靠接收基石
  • Farewell OI!
  • 庆阳市防水补漏_2026甘肃东部黄土高原城市漏水维修攻略与五大正规团队推荐 - 雨婺虹房屋维修
  • 【JAVA毕设源码分享】基于SpringBoot+Vue的在线商场后台管理系统的设计与实现(程序+文档+代码讲解+一条龙定制)
  • 2026回头才醒悟:树洞陪玩平台安全隐私不踩坑 - 时时资讯

日新闻

  • 大连理工大学与东京大学联手打造的“主动型AI助手“
  • 170.2026年国家级科研瓶颈:超精密单点金刚石切削(SPDT)光学表面生成
  • SongBloom:革命性歌曲生成框架深度解析——如何通过交织自回归与扩散模型创作完整音乐

周新闻

  • 大连理工大学与东京大学联手打造的“主动型AI助手“
  • 170.2026年国家级科研瓶颈:超精密单点金刚石切削(SPDT)光学表面生成
  • SongBloom:革命性歌曲生成框架深度解析——如何通过交织自回归与扩散模型创作完整音乐

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号