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

Java内存入门讲解:从变量和对象开始

Java内存入门讲解:从变量和对象开始

一、先忘掉复杂概念,记住三个"地方"

想象你的程序运行时,有三个"地方"可以存放东西:

┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ 栈内存 │ │ 堆内存 │ │ 方法区 │ │ (方法执行) │ │ (放对象) │ │ (放类信息) │ └─────────────┘ └─────────────┘ └─────────────┘ 简单理解: • 栈:像笔记本,记着正在做的事(方法执行) • 堆:像仓库,存放具体的东西(对象) • 方法区:像说明书,存放类是怎么定义的

二、基础类型 vs 引用类型(最重要的区别)

2.1 基础类型变量:值直接放在变量里

intage=18;doubleprice=99.5;booleanisPass=true;

内存中的样子:

栈内存 ┌─────────────────┐ │ age │ 18 │ ← 值直接存在变量里 ├─────────────────┤ │ price │ 99.5 │ ├─────────────────┤ │ isPass │ true │ └─────────────────┘

2.2 引用类型变量:变量里存的是"地址"

Students1=newStudent();

内存中的样子:

栈内存 堆内存 ┌──────────────┐ ┌─────────────┐ │ s1 │ 0x001 │────────→│ Student对象 │ └──────────────┘ │ name: null │ │ age: 0 │ └─────────────┘ 地址:0x001

关键理解:s1这个变量里存的不是对象本身,而是对象在堆内存中的"门牌号"(0x001)

三、结合代码看内存变化

示例1:基础类型和引用类型的区别

publicclassMemoryDemo1{publicstaticvoidmain(String[]args){// 1. 基础类型变量inta=10;// 栈:a = 10// 2. 引用类型变量Studentstu=newStudent();// 栈:stu = 0x001// 堆:地址0x001存放Student对象stu.name="小明";// 堆中的name被赋值stu.age=18;// 堆中的age被赋值// 3. 赋值操作的区别intb=a;// 复制值:b = 10b=20;// a还是10,b变成20Studentstu2=stu;// 复制地址:stu2也指向0x001stu2.name="小红";// stu.name也变成"小红"(同一个对象!)System.out.println(stu.name);// 输出"小红"}}

内存变化过程:

第1步:int a = 10; 栈:┌───┬────┐ │ a │ 10 │ └───┴────┘ 第2步:Student stu = new Student(); 栈:┌─────┬───────┐ 堆:┌─────────────┐ │ stu │ 0x001 │────────→│ name: null │ └─────┴───────┘ │ age: 0 │ └─────────────┘ 地址: 0x001 第3步:stu2 = stu; 栈:┌──────┬───────┐ 堆:┌─────────────┐ │ stu │ 0x001 │────┐ │ name: "小红" │ ├──────┼───────┤ └→ │ age: 18 │ │ stu2 │ 0x001 │───────→│ │ └──────┴───────┘ └─────────────┘

示例2:方法调用时的内存变化

publicclassMemoryDemo2{publicstaticvoidmain(String[]args){intnum=5;// 步骤1changeNum(num);// 步骤2System.out.println(num);// 步骤5:输出5(没变!)Studentstu=newStudent();// 步骤3stu.name="小明";changeName(stu);// 步骤4System.out.println(stu.name);// 步骤6:输出"小红"(变了!)}staticvoidchangeNum(intn){n=100;// 这只是修改了栈中的n,不影响main里的num}staticvoidchangeName(Students){s.name="小红";// 通过地址找到堆中的对象,修改了真正的对象}}

内存变化过程:

调用changeNum(num)时: main栈帧 changeNum栈帧 ┌─────────┐ ┌─────────┐ │ num: 5 │ │ n: 5 │ ← 复制了值 └─────────┘ └─────────┘ n = 100后: ┌─────────┐ ┌─────────┐ │ num: 5 │ │ n: 100 │ ← 只改了自己的 └─────────┘ └─────────┘ 方法结束:changeNum栈帧销毁,num还是5 调用changeName(stu)时: main栈帧 changeName栈帧 堆 ┌─────────┐ ┌─────────┐ ┌──────────┐ │ stu:0x001│─────→│ s:0x001 │───────→ │name:"小明"│ └─────────┘ └─────────┘ └──────────┘ s.name = "小红"后: ┌─────────┐ ┌─────────┐ ┌──────────┐ │ stu:0x001│─────→│ s:0x001 │───────→ │name:"小红"│ └─────────┘ └─────────┘ └──────────┘ 方法结束:changeName栈帧销毁,但堆中的对象已经被改了

核心结论:

  • 基础类型传递的是(复制一份)
  • 引用类型传递的是地址(指向同一个对象)

四、对象的创建过程(一步步看内存)

publicclassMemoryDemo3{publicstaticvoidmain(String[]args){// 第1步:声明变量Dogdog;// 栈中开辟空间,但还没指向任何对象// 第2步:创建对象dog=newDog();// 堆中创建Dog对象,把地址给dog// 第3步:给属性赋值dog.name="旺财";// 修改堆中的对象dog.age=3;// 第4步:调用方法dog.bark();// 通过地址找到对象,执行方法}}classDog{Stringname;// 引用类型属性,默认nullintage;// 基础类型属性,默认0voidbark(){System.out.println(name+"汪汪叫");}}

完整内存图:

栈内存 堆内存 ┌─────────────────┐ ┌─────────────────────┐ │ main方法栈帧 │ │ Dog对象 (地址0x001) │ ├─────────────────┤ ├─────────────────────┤ │ dog │ 0x001 │───────→│ name │ 0x002 ──┐ │ └─────────────────┘ │ age │ 3 │ │ └─────────────────┘ │ │ ┌─────────────────────┘ ↓ 方法区(字符串常量池) ┌─────────────┐ │ "旺财" │ ← 字符串对象 └─────────────┘

五、常见误区解释

误区1:new出来的东西都在堆里

// 正确理解Dogdog=newDog();// dog变量在栈(地址)// Dog对象在堆(具体数据)

误区2:字符串是基础类型

// 错误理解Strings="hello";// 以为是基础类型?// 正确理解:String是引用类型Strings1="hello";Strings2="hello";// s1和s2指向同一个字符串对象(方法区的字符串常量池)

误区3:方法结束所有内存都释放

publicvoidtest(){Dogdog=newDog();// dog变量在栈// 方法结束:栈中的dog变量消失// 但是:堆中的Dog对象还在!// 需要垃圾回收器来清理}

六、一个完整示例:学生管理系统

publicclassStudentSystem{publicstaticvoidmain(String[]args){// 创建3个学生对象Students1=newStudent();s1.name="张三";s1.score=90;Students2=newStudent();s2.name="李四";s2.score=85;Students3=newStudent();s3.name="王五";s3.score=95;// 打印所有学生printStudent(s1);printStudent(s2);printStudent(s3);// 最高分的学生Studenttop=getTopStudent(s1,s2,s3);System.out.println("最高分:"+top.name);}staticvoidprintStudent(Students){// s指向堆中的某个Student对象System.out.println(s.name+":"+s.score);}staticStudentgetTopStudent(Studenta,Studentb,Studentc){Studentmax=a;// max也指向堆中的对象if(b.score>max.score)max=b;if(c.score>max.score)max=c;returnmax;// 返回的是地址}}classStudent{Stringname;intscore;}

内存布局:

栈内存(main方法) 堆内存 ┌─────┬───────┐ ┌─────────────┐ │ s1 │ 0x001 │─────────────→│ name:"张三" │ ├─────┼───────┤ │ score:90 │ │ s2 │ 0x002 │─────┐ └─────────────┘ ├─────┼───────┤ │ ┌─────────────┐ │ s3 │ 0x003 │──┐ └───────→│ name:"李四" │ ├─────┼───────┤ │ │ score:85 │ │ top │ 0x003 │←─┘ └─────────────┘ └─────┴───────┘ ┌─────────────┐ │ name:"王五" │ │ score:95 │ └─────────────┘

七、记忆口诀

  1. 基础类型存值,引用类型存址

    • int、double、boolean:值直接放变量里
    • 类、数组、接口:变量里是地址
  2. 栈管运行,堆放对象

    • 栈:方法调用、局部变量
    • 堆:new出来的东西
  3. 赋值等于复制

    • 基础类型:复制值(独立)
    • 引用类型:复制地址(共享)
  4. 方法参数传递

    • 基础类型:传值的副本(改不了原变量)
    • 引用类型:传地址的副本(能改对象内容)

八、练习题(检验理解)

// 说出下面代码的输出结果publicclassTest{publicstaticvoidmain(String[]args){intx=10;inty=x;y=20;System.out.println(x);// 输出?Personp1=newPerson();p1.age=30;Personp2=p1;p2.age=40;System.out.println(p1.age);// 输出?change(p1);System.out.println(p1.age);// 输出?}staticvoidchange(Personp){p.age=50;}}classPerson{intage;}// 答案:10, 40, 50

掌握了这些,你就理解了Java内存最核心的部分。记住:变量要么存值,要么存地址,不会存对象本身!

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

相关文章:

  • 字符串匹配的AC自动机,你知道有哪三种写法吗?
  • Open WebUI:让AI工具调用像对话一样自然的智能平台
  • 零基础如何快速总结视频教程,3步包教包会避常见坑可直接上手
  • 别再只用train_test_split了!用sklearn的KFold和StratifiedKFold搞定5折交叉验证(附完整代码)
  • AI写论文的秘密武器!4款AI论文生成工具,让论文写作更轻松!
  • Informer预测结果怎么导出成CSV?保姆级教程教你从.npy文件到可视化图表
  • 告别迷茫!手把手教你用CCS和SysConfig搞定TI AM273x开发环境(附避坑指南)
  • mast3r slam(3)提取特征保存地图,重新加在重定位,和anyloc对比 - MKT
  • 保姆级教程:用mplfinance和Tushare绘制A股专业K线图(附完整代码)
  • 哪些降重软件可以同时降低查重率和AIGC疑似率?2026年深度实测推荐一些可以用于论文降重的全能软件
  • 北京性价比轻食哪家评分高? - 中媒介
  • Ubuntu Server 22.04.3 LTS 新机到手:5分钟搞定root密码、SSH远程和sudo免密(保姆级教程)
  • 深入AD9364的时钟树:从40MHz晶振到1280MHz BBPLL,详解SPI配置背后的频率合成逻辑
  • 拯救你的B站记忆:m4s-converter让缓存视频重获新生
  • 无人驾驶中的控制算法选型:为什么MPC比PID更能“预见”延迟?(基于自行车模型详解)
  • 谷歌浏览器插件「Brower-Books」: 把整个浏览器变成你的「云端书架」
  • 支付中心怎么设计?一次讲清支付单、渠道单、状态机、回调处理与对账补单
  • STM32F103驱动移远EC200N-CN 4G Cat.1模组,从硬件接线到TCP透传的保姆级避坑指南
  • 零代码小程序制作平台有哪些? - 码云数智
  • 小程序商城怎么制作?注册、搭建、上线全流程 - 码云数智
  • 中小型制造企业ENOVIA许可证成本控制的务实技巧
  • 保姆级教程:在STM32F429上从官网下载FreeRTOS 10.4.6源码并完成移植(附完整源码包)
  • 毕业不再“爆肝”:如何用百考通AI将论文写作变成结构化工程
  • 试用支持postgresql wire协议的duckdb服务器duckgres
  • 别再手动调图了!用ScottPlot在WinForm里实现鼠标滚轮+右键拖拽缩放(附完整源码)
  • 从WebRTC到Speex:聊聊那些开源语音引擎里的AEC模块是怎么工作的
  • 微信小程序商城怎么制作?新手零基础教程 - 码云数智
  • 市场知名的玻璃管转子流量计厂家排名 - 品牌企业推荐师(官方)
  • 告别SPI配置烦恼:手把手教你用Python脚本批量读写AD9361寄存器
  • 【射影几何】交比:从线段分割到透视不变的核心法则