JVM内存模型

本文深入解析Java内存模型,涵盖Java堆、方法区、运行时常量池、程序计数器、Java虚拟机栈和本地方法栈。详细介绍新生代、老年代、Eden空间、Survivor空间的垃圾回收机制,及对象年龄计数器、分配担保机制等概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

线程共享内存区域

Java堆

​ 此内存区域在虚拟机启动的时候创建,几乎所有对象的实例和数组都在堆中分配内存空间。

​ Java堆是垃圾回收主要的管理区域,而且现在的垃圾收集器基本都采用分代收集算法(这是针对不同分代的特点采用适当的收集算法,提高垃圾收集的效率),所以一般为了在内存方面出发,堆可以分为老年代和新生代。

新生代中,也可以细分为Eden空间,To Survivor空间和From Survivor空间等。(垃圾收集和对象内存分配在垃圾收集再详细分析)

  • 这里分为这样的空间是因为采用了标记复制的垃圾收集算法,但是基本的标记复制算法是采用1:1的方式划分空间,这样的话会浪费掉很多内存空间的使用,所以分成了两个Survivor区和一个Eden区(默认1:1:8)
  • 当Eden区被占满或者Eden区没有办法为新对象分配足够的内存空间时,会触发垃圾回收;垃圾收集后存活的对象会被复制到From区,当From区满的时候会将存活的对象复制到To区域(此时包括来自Eden区存活的对象),然后这两个区域会交换角色。注意:Survivor区总是有一个是空的,Survivor-From 区的对象分两类,一类是年轻的对象(分代年龄小),也是复制到 Survivor-To 区,还有一类是老东西,晋升到老年代中。(参考下面两张图片,来源:《Memory Management in the Java HotSpot Virtual Machine》)
  • 上面说的会将存活对象复制到To区域说得不全面,因为有可能To区域的内存会被占满,然后还有其他存活的对象就没有办法放入到To区中,那么这里就涉及分配担保机制了,也就是Survivor区域的空间不够用的时候,这些存活对象会直接通过这个机制进入到老年代中
  • 虚拟机会给每个对象定义一个对象年龄的计数器,当年轻代的对象经过MinorGC开始复制到Survivor区的时候,对象的年龄会增加1,当对象年龄达到一定程度或者触发分配担保机制的时候会晋升到老年代当中

年轻代垃圾收集前
在这里插入图片描述
年轻代垃圾收集后
在这里插入图片描述

方法区

​ 方法区主要用于存储被虚拟机加载的类信息(Class相关信息:类名,访问修饰符,字段描述,方法描述等),常量,静态变量,即时编译器编译后的代码等数据。

​ 方法区通常被我们称作:永久代。但是这只是HotSpot VM对方法区的实现而已;在其他的VM(JRockit等)并没有永久代。

​ 注意的是,在JDK1.8中,永久代已经被移除(JDK1.7中,永久代中的运行时常量池被移到堆中),此时HotSpot VM对方法区的实现变成了元空间Metaspace ,元空间和永久代的本质是类似的,不同的地方是元空间并不在虚拟机中,而是使用本地内存(Native Memory)

在方法区中,对于很多动态生成类的情况容易出现OutOfMemoryError的异常。

常见的场景就是JSP页面较多的情况(JSP第一次运行需要进行编译)、CGLib增强和动态语言(Groovy

运行时常量池

​ 运行时常量池是属于方法区的一部分(JDK7后被移到堆中)。其用于存储编译期生成的字面量和符号引用。

程序运行期间可以通过String.intern() 将变量放入运行时常量池中

线程私有的内存区域

程序计数器

程序计数器是当前线程执行的代码的行号指示器

  • 当执行Native方法的时候,该计数器值是空的(undefined)
  • 该内存区域是唯一一个没有规定OutOfMemoryError 情况的区域

Java虚拟机栈

​ Java虚拟机栈的生命周期与当前线程相同;虚拟机栈是Java方法执行时的内存模型,在方法执行时会创建栈帧存放局部变量表、操作数栈、动态链接和方法出口等信息。

​ 在虚拟机栈中,我们平常主要关注的是局部变量表:

  1. 局部变量表的内存空间在编译期间完成分配

  2. 局部变量表包括基本数据类型、指向字节码指令的地址和对象的引用(即reference

    • 对象的引用主要有句柄和直接指针的两种方式:

      • 句柄:使用句柄的方式时,JVM会在Java堆中开辟一部分内存作为句柄池,虚拟机栈中的reference中储存的就是句柄的内存地址,句柄则包含了该对象的实例数据和类型数据的内存地址。

        类型数据:该类型数据是在方法区中的Class类信息和静态变量等数据

      • 直接指针:顾名思义就是虚拟机栈中的reference中储存的是该对象的实例数据和类型数据的内存地址。

    • 两种方式各有各的优劣势:

      • 句柄:其优势之处就是稳定,当垃圾收集器回收内存中的对象时,该对象要么存活在年轻代要么晋升到老年代,这个时候该对象的内存地址就会发生改变,此时虚拟机栈中reference 的指针就不需要发生改变,只需要改变句柄中对象的内存地址即可。
      • 直接指针:直接指针访问对象的速度会比句柄快,因为它少了一次指针定位的开销。在大量的对象访问的时候,直接指针的效率是相当的客观。

线程请求的栈深度大于虚拟机所允许的最大深度,会抛出StackOverflowError异常

这里的意思是:Java方法的从执行到结束就相当于将一个栈帧在虚拟机栈中的入栈和出栈的过程;当一个死循环的递归方法调用时,就会抛出StackOverflowError;因为死循环的递归,会不断的往虚拟机栈中压入栈帧,当达到最大深度时,则抛出该异常。

虚拟机在动态扩展时,无法为栈分配足够的内存空间时会抛出OutOfMemoryError异常

这里有容易会和上面那个混淆,唯一需要分清的是当栈空间不够分配的时候:这种情况下是因为内存不足还是栈的深度不够的问题就能分清楚这两种情况了。


本地方法栈

​ 本地方法栈跟Java虚拟机栈的作用基本上是相同的,不同之处是Java虚拟机栈服务的是Java方法,而本地方法栈服务的对象是Native方法

1. 用户与身体信息管理模块 用户信息管理: 注册登录:支持手机号 / 邮箱注册,密码加密存储,提供第三方快捷登录(模拟) 个人资料:记录基本信息(姓名、年龄、性别、身高、体重、职业) 健康目标:用户设置目标(如 “减重 5kg”“增肌”“维持健康”)及期望周期 身体状态跟踪: 体重记录:定期录入体重数据,生成体重变化曲线(折线图) 身体指标:记录 BMI(自动计算)、体脂率(可选)、基础代谢率(根据身高体重估算) 健康状况:用户可填写特殊情况(如糖尿病、过敏食物、素食偏好),系统据此调整推荐 2. 膳食记录与食物数据库模块 食物数据库: 基础信息:包含常见食物(如米饭、鸡蛋、牛肉)的名称、类别(主食 / 肉类 / 蔬菜等)、每份重量 营养成分:记录每 100g 食物的热量(kcal)、蛋白质、脂肪、碳水化合物、维生素、矿物质含量 数据库维护:管理员可添加新食物、更新营养数据,支持按名称 / 类别检索 膳食记录功能: 快速记录:用户选择食物、输入食用量(克 / 份),系统自动计算摄入的营养成分 餐次分类:按早餐 / 午餐 / 晚餐 / 加餐分类记录,支持上传餐食照片(可选) 批量操作:提供常见套餐模板(如 “三明治 + 牛奶”),一键添加到记录 历史记录:按日期查看过往膳食记录,支持编辑 / 删除错误记录 3. 营养分析模块 每日营养摄入分析: 核心指标计算:统计当日摄入的总热量、蛋白质 / 脂肪 / 碳水化合物占比(按每日推荐量对比) 微量营养素分析:检查维生素(如维生素 C、钙、铁)的摄入是否达标 平衡评估:生成 “营养平衡度” 评分(0-100 分),指出摄入过剩或不足的营养素 趋势分析: 周 / 月营养趋势:用折线图展示近 7 天 / 30 天的热量、三大营养素摄入变化 对比分析:将实际摄入与推荐量对比(如 “蛋白质摄入仅达到推荐量的 70%”) 目标达成率:针对健
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值