当前位置: 首页 > news >正文

Java对象内存分配全解:从new Student()到this关键字,一张图看懂对象在内存中的完整生命周期

当你写下这行代码时:

Student stu = new Student();

你有没有想过,这短短一行代码,在内存中到底发生了什么?

  • 对象存在哪里?

  • stu变量里存的到底是什么?

  • 为什么方法里修改对象的属性,外面的对象也跟着变了?

  • this到底指向谁?

如果你对这些问题似懂非懂,那今天这篇文章就是为你准备的。

我们将从内存角度,一步步拆解Java对象的完整生命周期,彻底搞懂面向对象底层原理。

一、Java虚拟机三大内存区域(快速回顾)

在开始之前,先快速回顾一下Java虚拟机的三个核心内存区域:

区域作用特点
栈内存存储方法调用和局部变量方法调用入栈,执行完毕出栈
堆内存存储new出来的对象、数组所有new创建的空间都在这里
方法区存储类的字节码信息(.class文件)类加载时进入,虚拟机关闭才释放

重要:方法区的字节码信息不会随方法执行完毕而消失,它一直存在,直到关闭虚拟机。

二、创建对象的完整七步流程

我们以最常见的代码为例:

Student stu = new Student();

这行代码在内存中经历了七个步骤

第一步:加载class字节码文件

程序启动后,Student.class文件被加载到方法区。类的所有信息(属性、方法)都存在这里,等待被使用。

第二步:声明局部变量

栈内存中,为变量stu开辟空间。这个变量将来要存储对象的地址。

第三步:在堆中开辟空间

因为new关键字,在堆内存中开辟一块空间。假设这块空间的地址是0x0011。这块空间,就是我们平时说的“对象”。

对象空间里存什么?

  • 所有的成员属性(name、age等)

  • 所有成员方法在方法区中的地址(方便调用时快速找到)

第四步:属性默认初始化

给成员属性赋予默认值:

  • int0

  • double0.0

  • booleanfalse

  • 引用类型(String、数组等)→null

第五步:属性显式初始化

如果定义成员变量时直接赋值了(比如int age = 18;),就在这一步把默认值替换成你写的值。

第六步:构造方法初始化

调用构造方法给成员变量赋值。如果用的是有参构造,这一步就会把参数值赋给属性;如果是无参构造,这一步可能什么也不做。

注意:构造方法只是创建对象过程中的一步,不是全部。

第七步:将地址赋值给变量

将堆中对象的地址0x0011,赋值给栈中的变量stu

从此,stu这个变量,就“指向”了堆中的对象。

理解这一点很重要stu不是对象本身,它只是存了对象的地址。就像你手里的门牌号,不是房子本身,但能帮你找到房子。

三、对象的访问流程

访问属性

stu.name = "小诗诗";
  1. 从栈中找到stu,拿到它存的地址0x0011

  2. 通过地址找到堆中的对象

  3. 修改或读取对应属性的值

调用方法

stu.study();
  1. stu拿到地址0x0011

  2. 找到堆中的对象

  3. 对象里存了study方法在方法区的地址

  4. 跳转到方法区,执行study方法的字节码

  5. study方法入栈执行

四、对象的生命周期与回收规则

规则一:方法出栈,局部变量消失

main方法执行完毕出栈后,栈中的stu变量也随之消失。

规则二:没有变量引用的对象,被回收

当没有任何变量指向堆中的对象时,这个对象就会被JVM垃圾回收器回收。因为已经找不到它了,留着也没用。

规则三:方法区的字节码,直到虚拟机关闭才消失

类加载一次就够了,字节码信息会一直存在,直到关闭IDEA或关闭Java程序。

五、对象作为方法参数传递的内存原理

这是面试中经常问到的问题,也是理解“为什么方法内修改对象会影响原对象”的关键。

来看这段代码:

public class MemoryDemo { public static void main(String[] args) { Student stu = new Student(); stu.name = "小诗诗"; stu.age = 23; change(stu); System.out.println(stu.name + "..." + stu.age); // 输出:小丹丹...19 } public static void change(Student stu) { stu.name = "小丹丹"; stu.age = 19; } }

内存执行过程

  1. 创建对象main方法中,stu变量存着对象的地址0x0011,对象属性是“小诗诗”和23。

  2. 调用change方法change方法入栈,形参stu也在栈中开辟空间。调用时,把实参stu中存的地址0x0011复制一份,传给形参stu

  3. 两个变量指向同一个对象:现在栈中有两个stu变量(一个是main的,一个是change的),它们存的地址都是0x0011,指向同一个堆对象。

  4. 修改对象change方法中,通过形参stu找到堆中对象,把name改成“小丹丹”,age改成19。这是直接修改堆中的对象

  5. 方法出栈change方法执行完毕,形参stu消失。但堆中的对象已经被修改了。

  6. 回到main方法mainstu变量还在,它存的地址还是0x0011,访问到的就是修改后的值。

核心结论

Java中传递对象参数时,传递的是对象的内存地址,不是对象本身。

传递地址后,多个变量指向同一个对象。任何一个变量修改对象,其他变量访问到的都是修改后的值。

六、this关键字的内存本质

this是Java中一个非常关键的关键字,它的作用有两个:

  1. 区分同名变量:当成员变量和局部变量同名时,用this.访问成员变量

  2. 代表当前对象:哪个对象调用了方法,this就指向哪个对象

内存理解:this就是调用者的地址

public class Student { String name; int age; public void study() { int age = 10; // 局部变量 System.out.println(age); // 10(就近原则) System.out.println(this.age); // 成员变量的值 } }

测试代码:

public class Test { public static void main(String[] args) { Student s1 = new Student("小诗诗", 18); Student s2 = new Student("小丹丹", 19); s1.study(); // 输出:10 18 s2.study(); // 输出:10 19 } }

内存执行过程

  1. 创建两个对象:堆中有两个独立的对象,地址分别是0x00110x0022

  2. s1调用study方法study方法入栈,方法内部自动记录调用者的地址。这个地址就是this,此时this = 0x0011

  3. 打印age:不带thisage,是局部变量,输出10。

  4. 打印this.agethis.age就是通过this存的地址0x0011,找到s1对象的age,输出18。

  5. s2调用study方法study方法再次入栈,这次this = 0x0022,找到s2对象的age,输出19。

核心结论

this的本质,就是当前方法调用者的内存地址。

  • 哪个对象调用了方法,this就指向哪个对象

  • 同一个类的多个对象,共享方法区的同一份方法字节码,依靠this区分“是谁在调用”

七、总结:一张图看懂对象内存分配

内存区域存储内容生命周期
栈内存局部变量(存地址)方法执行完就消失
堆内存对象实例(属性+方法地址)无引用时被回收
方法区类字节码虚拟机关闭才消失

核心理解

  • 变量存的是地址,不是对象本身

  • 对象传参传的是地址,所以方法内修改会影响原对象

  • this就是调用者的地址,用来区分“谁在调用”

写在最后

面向对象是Java的灵魂,而理解内存中的对象,是掌握面向对象的必经之路。

http://www.jsqmd.com/news/518673/

相关文章:

  • 基于Python的仿淘宝系统毕设
  • VS2022+PCL环境配置避坑指南:vcpkg一键安装后这些细节要注意
  • Go 语言的“刻意贫穷“:为什么宁可写 30 行选项模式,也拒绝默认参数?
  • c语言第一次作业
  • Python豆瓣图书数据可视化平台 Flask框架 可视化 爬虫 书籍 大数据 机器学习 计算机毕业设计(建议收藏)✅
  • Seurat单细胞测序实战:从原始数据到细胞亚群注释的完整流程解析
  • Vue3 + TS项目上线后,如何用20行代码优雅地提醒用户刷新页面?
  • 批量读取Excel生成.mat矩阵
  • 基于Python的农业设备租赁系统毕业设计源码
  • 领航追随法:车辆编队的智慧指挥官
  • 五次多项式与改进Sigmoid混合曲线融合的平行泊车路径规划代码,克服双重缺陷,满足曲率约束条件
  • 卫星轨道六要素详解:从火箭残骸到GPS卫星的追踪原理
  • 避坑指南:Xcode 15下OC与Swift混编的5个常见编译错误及修复方法
  • YOLOv8改进之Involution:反转卷积思想,核在空间上共享但在通道上特异,减少冗余
  • AI 辅助编程革命:如何利用 GitHub Copilot 等工具重塑开发效率
  • 光伏锂电池储能功率协调控制系统仿真 [1]左侧光伏Boost控制部分:采用扰动观察法来进行MP...
  • Pollinations.AI 免费文生图实战:5分钟搞定自定义图片生成(附完整API参数指南)
  • 基于vue+python智能医疗辅助就诊系统
  • 手把手教你用Gitee+奇安信代码卫士扫描Java-sec-code靶场(含详细漏洞修复指南)
  • 计算机毕业设计:Python图书数据可视化分析系统 Flask框架 可视化 爬虫 书籍 大数据 机器学习(建议收藏)✅
  • ESP32以太网配置门户库:W6100+ConfigPortal一体化方案
  • YOLOv8改进系列:C2f模块全面升级——从C2f到C2f-Faster、C2f-DCN的高效变体实战
  • 基于Webots的轮腿机器人仿真模型:包括轮足设计、PID运动控制及运动学逆解算法,支持多种动...
  • SQLMap Tamper脚本开发指南:从修改到编写你的第一个绕过脚本
  • 分享创业失败后加入格行科技随身WiFi代理项目的成功经验,介绍代理邀请码888886的优势与机遇 - 格行招商部总监张总
  • ArcScene点云可视化进阶:如何自定义RGB颜色映射打造专业级三维效果
  • GhostConv:YOLOv8 的轻量化利器,通过廉价线性变换实现高效目标检测
  • trae的ai终端执行都要在前面加上
  • YOLOv8巅峰之作:引入DynamicConv动态卷积,自适应能力暴涨,小目标检测精度提升显著
  • 无线功率传输三相两电平逆变器供电的无刷直流电机仿真 Matlab/simulink仿真(201...