news 2026/4/23 20:03:25

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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内存最核心的部分。记住:变量要么存值,要么存地址,不会存对象本身!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/23 19:59:06

LabVIEW矿井提升机监控系统

矿井提升机作为矿山核心运输设备,其运行状态直接关系生产安全与效率。本方案采用LabVIEW图形化开发平台,搭配数据采集硬件与模拟执行机构,构建集实时监测、故障报警、数据存储、历史查询于一体的监控系统,实现提升机全流程状态可视…

作者头像 李华
网站建设 2026/4/23 19:54:09

ComfyUI-Manager:彻底改变AI绘画插件管理体验的智能解决方案

ComfyUI-Manager:彻底改变AI绘画插件管理体验的智能解决方案 【免费下载链接】ComfyUI-Manager ComfyUI-Manager is an extension designed to enhance the usability of ComfyUI. It offers management functions to install, remove, disable, and enable various…

作者头像 李华
网站建设 2026/4/23 19:53:20

深入RK平台CIF驱动:从buf_wake_up_cnt看如何精准诊断MIPI数据断流

深入RK平台CIF驱动:从buf_wake_up_cnt看如何精准诊断MIPI数据断流 在嵌入式视觉系统的开发中,MIPI数据断流问题往往是最难诊断的故障之一。RK平台的CIF驱动提供了两个关键计数器buf_wake_up_cnt和last_buf_wakeup_cnt,它们就像埋在驱动深处的…

作者头像 李华
网站建设 2026/4/23 19:52:14

告别触摸屏!用4个物理按键玩转LVGL界面(附焦点保存与恢复实战代码)

嵌入式UI实战:4个物理按键驱动LVGL界面的高阶设计模式 在智能家居控制面板、工业HMI设备等嵌入式场景中,触摸屏并非总是最佳选择。物理按键的可靠性和明确触感反馈,使其在严苛环境下依然不可替代。当你的硬件只有四个物理按键(上/…

作者头像 李华