Java内存入门讲解从变量和对象开始一、先忘掉复杂概念记住三个地方想象你的程序运行时有三个地方可以存放东西┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ 栈内存 │ │ 堆内存 │ │ 方法区 │ │ (方法执行) │ │ (放对象) │ │ (放类信息) │ └─────────────┘ └─────────────┘ └─────────────┘ 简单理解 • 栈像笔记本记着正在做的事方法执行 • 堆像仓库存放具体的东西对象 • 方法区像说明书存放类是怎么定义的二、基础类型 vs 引用类型最重要的区别2.1 基础类型变量值直接放在变量里intage18;doubleprice99.5;booleanisPasstrue;内存中的样子栈内存 ┌─────────────────┐ │ age │ 18 │ ← 值直接存在变量里 ├─────────────────┤ │ price │ 99.5 │ ├─────────────────┤ │ isPass │ true │ └─────────────────┘2.2 引用类型变量变量里存的是地址Students1newStudent();内存中的样子栈内存 堆内存 ┌──────────────┐ ┌─────────────┐ │ s1 │ 0x001 │────────→│ Student对象 │ └──────────────┘ │ name: null │ │ age: 0 │ └─────────────┘ 地址0x001关键理解s1这个变量里存的不是对象本身而是对象在堆内存中的门牌号0x001三、结合代码看内存变化示例1基础类型和引用类型的区别publicclassMemoryDemo1{publicstaticvoidmain(String[]args){// 1. 基础类型变量inta10;// 栈a 10// 2. 引用类型变量StudentstunewStudent();// 栈stu 0x001// 堆地址0x001存放Student对象stu.name小明;// 堆中的name被赋值stu.age18;// 堆中的age被赋值// 3. 赋值操作的区别intba;// 复制值b 10b20;// a还是10b变成20Studentstu2stu;// 复制地址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){intnum5;// 步骤1changeNum(num);// 步骤2System.out.println(num);// 步骤5输出5没变StudentstunewStudent();// 步骤3stu.name小明;changeName(stu);// 步骤4System.out.println(stu.name);// 步骤6输出小红变了}staticvoidchangeNum(intn){n100;// 这只是修改了栈中的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步创建对象dognewDog();// 堆中创建Dog对象把地址给dog// 第3步给属性赋值dog.name旺财;// 修改堆中的对象dog.age3;// 第4步调用方法dog.bark();// 通过地址找到对象执行方法}}classDog{Stringname;// 引用类型属性默认nullintage;// 基础类型属性默认0voidbark(){System.out.println(name汪汪叫);}}完整内存图栈内存 堆内存 ┌─────────────────┐ ┌─────────────────────┐ │ main方法栈帧 │ │ Dog对象 (地址0x001) │ ├─────────────────┤ ├─────────────────────┤ │ dog │ 0x001 │───────→│ name │ 0x002 ──┐ │ └─────────────────┘ │ age │ 3 │ │ └─────────────────┘ │ │ ┌─────────────────────┘ ↓ 方法区字符串常量池 ┌─────────────┐ │ 旺财 │ ← 字符串对象 └─────────────┘五、常见误区解释误区1new出来的东西都在堆里// 正确理解DogdognewDog();// dog变量在栈地址// Dog对象在堆具体数据误区2字符串是基础类型// 错误理解Stringshello;// 以为是基础类型// 正确理解String是引用类型Strings1hello;Strings2hello;// s1和s2指向同一个字符串对象方法区的字符串常量池误区3方法结束所有内存都释放publicvoidtest(){DogdognewDog();// dog变量在栈// 方法结束栈中的dog变量消失// 但是堆中的Dog对象还在// 需要垃圾回收器来清理}六、一个完整示例学生管理系统publicclassStudentSystem{publicstaticvoidmain(String[]args){// 创建3个学生对象Students1newStudent();s1.name张三;s1.score90;Students2newStudent();s2.name李四;s2.score85;Students3newStudent();s3.name王五;s3.score95;// 打印所有学生printStudent(s1);printStudent(s2);printStudent(s3);// 最高分的学生StudenttopgetTopStudent(s1,s2,s3);System.out.println(最高分top.name);}staticvoidprintStudent(Students){// s指向堆中的某个Student对象System.out.println(s.name:s.score);}staticStudentgetTopStudent(Studenta,Studentb,Studentc){Studentmaxa;// max也指向堆中的对象if(b.scoremax.score)maxb;if(c.scoremax.score)maxc;returnmax;// 返回的是地址}}classStudent{Stringname;intscore;}内存布局栈内存main方法 堆内存 ┌─────┬───────┐ ┌─────────────┐ │ s1 │ 0x001 │─────────────→│ name:张三 │ ├─────┼───────┤ │ score:90 │ │ s2 │ 0x002 │─────┐ └─────────────┘ ├─────┼───────┤ │ ┌─────────────┐ │ s3 │ 0x003 │──┐ └───────→│ name:李四 │ ├─────┼───────┤ │ │ score:85 │ │ top │ 0x003 │←─┘ └─────────────┘ └─────┴───────┘ ┌─────────────┐ │ name:王五 │ │ score:95 │ └─────────────┘七、记忆口诀基础类型存值引用类型存址int、double、boolean值直接放变量里类、数组、接口变量里是地址栈管运行堆放对象栈方法调用、局部变量堆new出来的东西赋值等于复制基础类型复制值独立引用类型复制地址共享方法参数传递基础类型传值的副本改不了原变量引用类型传地址的副本能改对象内容八、练习题检验理解// 说出下面代码的输出结果publicclassTest{publicstaticvoidmain(String[]args){intx10;intyx;y20;System.out.println(x);// 输出Personp1newPerson();p1.age30;Personp2p1;p2.age40;System.out.println(p1.age);// 输出change(p1);System.out.println(p1.age);// 输出}staticvoidchange(Personp){p.age50;}}classPerson{intage;}// 答案10, 40, 50掌握了这些你就理解了Java内存最核心的部分。记住变量要么存值要么存地址不会存对象本身