内存Memory
内存绘制
我们需要知道在碎片化的代码下,程序是如何一步步走出来,以及如何找到资源的
在线绘制
draw.io
内存模板文件
纸质绘制
内存图纸
数值传输
发生地点
赋值运算
1 2 3 4 5 public class Cla { public void run() { int a = 3; }}
传参
1 2 3 4 5 6 7 8 9 public class Cla { public void run() { f(3); } public void f(int a) { ... }}
返回值
1 2 3 4 5 6 7 8 9 public class Cla { public void run() { Console.println(f() + 1); } public int f() { return 3; }}
值类型 与 引用类型
编程的 两大 内存分配和管理规则
值类型 Value Type
引用类型 Reference Type
原则
在 Java 中,传输的是数据格里的数据
值类型Value Type
值类型
规则
采用值类型的内存分配方式,变量里存储的是数据本身
例子
1 2 3 4 Point p1 = Point(3, 4);Point p2 = Point(5, 6);p1 = p2;p1.x = 7;
时间线
1 2 Point p1 = Point(3, 4);Point p2 = Point(5, 6);

1 2 3+Point p1 = Point(3, 4);Point p2 = Point(5, 6);p1 = p2;

1 2 3 4+Point p1 = Point(3, 4);Point p2 = Point(5, 6);p1 = p2;p1.x = 7;

值类型 与 语言
Java 中的 对象 没有采用这种内存管理机制
C++ 中的 struct 可以采用这种内存机制管理内存
Java 中的 8大基础类型 采用了这种内存管理机制
Java 中的 基础类型
1 2 3 4 int a = 3;int b = 4;a = b;a = 5;
时间线
1 2 int a = 3;int b = 4;

1 2 3+int a = 3;int b = 4;a = b;

1 2 3 4+int a = 3;int b = 4;a = b;a = 5;

引用类型Reference Type
变量开辟
任何变量 都有 四个重要的信息
1 int a = 3;

地址
变量名
数据格
值
指针Pointer
一个变量里的存的值,正好是另外一个变量的地址
1 2 int a = 3;int* p = &a;

引用类型
例子
1 2 3 4 Point p1 = new Point(3, 4);Point p2 = new Point(5, 6);p1 = p2;p1.x = 7;
时间线
内存被分为 堆 和 栈 两大区域。

1 Point p1 = new Point(3, 4);
对象属性 的 内存 开辟在 堆里。

1 Point p1 = 201;
局部变量、参数 的 内存 开辟在 栈里。
采用引用类型的内存分配方式,对象变量里 存的 是 对象在 堆里的 地址。

1 2+Point p1 = new Point(3, 4);Point p2 = new Point(5, 6);

1 2 3+Point p1 = new Point(3, 4);Point p2 = new Point(5, 6);p1 = p2;

1 2 3 4+Point p1 = new Point(3, 4);Point p2 = new Point(5, 6);p1 = p2;p1.x = 7;
. 运算符 会产生地址跳跃

对象 与 对象变量
对象变量里存的是对象的引用
对象变量赋值, 赋值的是对象变量里的内容
联动效应
网红的故事
网红 是 对象,是 对象在堆里开辟的空间
网红的手机号 是 对象在堆里的地址
小纸条 是 对象变量,是 指针,存储着 网红的手机号
引用类型 与 语言
Java 中的 对象类型(非基础类型),采用这种内存管理方式
绝大多数编程语言 都支持 这种内存管理方式
Java 中不支持获得 栈空间内变量的地址,所以我们画图时可以省略

传值传引用
1 Point p = new Point();
如果我们说 传输 p 的 引用。是有歧义的。
到底是值 p 里的 对象的地址
还是 p 作为变量,本身的地址
垃圾回收

针对图中 201 的内存地址,已经没有任何变量存储它,也就不再有任何变量需要它
Java 会在运行时 发现它,并把它杀掉
对象 与 内存
对象变量作为别名
对象变量里存的是对象的引用
对象变量赋值, 赋值的是对象变量里的内容
1 2 3 Point p1 = new Point(3, 4);Point p2 = new Point(5, 6);Point p3 = p2;
内存

问题
有几个对象,几个对象变量
意义
p3 是 p2 所指向对象的昵称
另外一个案例
1 2 3 Player white = new Player();Player black = new Player();Player winner = white;
对象比较
案例
1 2 3 4 5 Point p1 = ...;Point p2 = ...;if (p1 == p2) { ...}
比较两个对象是不是一样?
一个对象? 值一样的两个对象?
分析
如果 p1 == p2
证明 p1
p1
指向的是一个对象, 必然相等
如果 p1 != p2
也不能代表 p1
和 p2
的内容是不一样的
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Point { ... public boolean equalsTo(Point point) { if (point == null) { return false; } if (point == this) { return true; } return x == point.x && y == point.y; } ...}
对象变量作为属性
案例
1 2 3 4 5 6 7 8 9 10 Player player = new Player();player.setAtk(10);player.setDef(10);player.setHp(100); Weapon weapon = new Weapon();weapon.setAtk(2);weapon.setDef(2); player.setWeapon(weapon);
时间线
1 2 3 4 Player player = new Player();player.setAtk(10);player.setDef(10);player.setHp(100);

1 2 3 4 5 6+7+8+Player player = new Player();player.setAtk(10);player.setDef(10);player.setHp(100); Weapon weapon = new Weapon();weapon.setAtk(2);weapon.setDef(2);

1 2 3 4 5 6 7 8 9 10+Player player = new Player();player.setAtk(10);player.setDef(10);player.setHp(100); Weapon weapon = new Weapon();weapon.setAtk(2);weapon.setDef(2); player.setWeapon(weapon);

数组 与 内存
数组是对象
数组是对象, 数组变量 是数组的对象变量
1 2 3 int[] numbers1 = new int[3];int[] numbers2 = numbers1;numbers2[0] = 3;

对象数组
数组是 一堆数据格
对象类型的数组 是一堆 对象的数据格, 没有对象
对象数组可以用于存储对象
1 2 3 4 5 int[] numbers = new int[3];numbers[0] = 2; Point[] points = new Point[3];points[0] = new Point(3, 4);

二维数组
数组是对象
二维数组包含着一维数组,其实包含着的是 一维数组的对象变量
1 2 3 4 int[][] matrix = { {6, 7, 3}, {8, 4}};

高级人工内存
流程执行
案例
1 2 3 4 public class Account { private int balance = 1; ...}
1 2 3 4 5 6 7 8 public class Main { ... public void run() { Account account = new Account(); int balance = 3; Console.println(balance); }}
准备
堆栈结构
同时 栈区域 记录当前 函数 以及 当前所执行的代码
变量开辟
局部变量 / 参数 在栈里
属性 在 堆里
变量缩合
所有表达式的变量,均需从栈空间内寻找变量
方法调用规则
案例
1 2 3 4 5 6 7 8 9 10 11 public class Account { private int balance = 1; public Account merge(Account account) { int sum = this.balance + account.balance; Account merged = new Account(sum); balance = 0; account.balance = 0; return merged; } ...}
1 2 3 4 5 6 7 8 public class Main { ... public void run() { Account account1 = new Account(10); Account account2 = new Account(2); Account account3 = account1.merge(account2); }}
调用方法前的准备
对象变量 和 所有的参数都需要缩合
对象变量 会 缩合成 所存储的对象的地址
调用方法
增加 隔板
每个函数都有自己的变量空间
调用函数,会为函数开辟新的内存区域,在栈里
传输 this
为 被调用函数 创建一个 this 变量,在栈里
谁点函数,this 就是谁
传输 参数
为 被调用函数 创建参数变量,在栈里
传参的值 会 过去
变量缩合
所有表达式的变量,均需从栈空间内寻找变量。
且 不能约过挡板
this 补充属性
如果在 占空间 无法找到变量,则 在前面 补充 this.
忽略
如果构造方法 或者 Getter Setter 只是简单的赋值。
则可以忽略进入这个方法
方法返回前的准备
return 的值 需要表达式缩合
方法返回
记住 返回值
删除 栈空间内 挡板下 所有的变量
删除 挡板
原 函数 缩合为 刚才的 返回值
方法的连续调用规则
案例
1 2 3 4 5 6 7 8 9 10 11 12 public class Account { private int balance = 1; public void transferTo(Account account) { account.balance += this.balance; clear(); } public void clear() { this.balance = 0; } ...}
1 2 3 4 5 6 7 8 public class Main { ... public void run() { Account account1 = new Account(10); Account account2 = new Account(2); account1.transferTo(account2); }}
this 补充方法
调用方法时,如果前方没有 对象变量 引导,则补充 this.
持续进入
调用方法时,如果进入新的方法,则会持续增加隔板
逐级退出
每个方法调用完了,都会销毁它自己的资源和隔板
高级人工内存 实战
练习 1
下面的代码运行后,你会看到什么
1 2 3 4 public class Point { private int x; private int y;}
1 2 3 4 5 6 7 8 9 10 public class Rectangle { private Point origin; private int width; private int height; public boolean hitTest(Point target) { boolean hTest = target.getX() >= origin.getX() && target.getX() <= origin.getX() + width; boolean vTest = target.getY() >= origin.getY() && target.getY() <= origin.getY() + height; return hTest && vTest; }}
1 2 3 4 5 6 7 8 9 public class Main { ... public void run() { Point p4 = new Point(12, 4); Rectangle r = new Rectangle(p4, 10, 4); // origin, width, height Point p5 = new Point(14, 6); Console.println(r.hitTest(p5)); }}
练习 2
下面的代码运行后,你会看到什么
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Point { private int x; private int y; public double distanceTo(Point target) { return distanceTo(target.x, target.y); } public double distanceTo(int x, int y) { int dx = this.x - x; int dy = this.y - y; return Math.sqrt(dx * dx + dy * dy); } public Point added(Point delta) { return new Point(x + delta.x, y + delta.y); }}
1 2 3 4 5 6 7 8 9 public class Circle { private Point origin; private int radius; public boolean hitTest(Point target) { Point delta = new Point(radius, radius); Point center = origin.added(delta); return center.distanceTo(target) <= radius; }}
1 2 3 4 5 6 7 8 9 public class Main { ... public void run() { Point p1 = new Point(3, 4); Circle c = new Circle(p1, 2); Point p2 = new Point(6, 5); Console.println(c.hitTest(p2)); }}
高级内存监控
代码
Point.java
Circle.java
Rectangle.java
Main.java
工具
左侧显示的是栈里的变量空间
右侧显示的是栈里的变量的值
小三角打开后,是堆中的值
引用类型 值类型化
值类型与引用类型 在语言中的支持性
只支持引用类型
现在流行的语言,大多对复杂的数据结构,只支持 引用类型
比如 Java / Python / JavaScript
都支持
一些语言 对复杂的数据结构 支持 引用类型 以及 值类型
比如 C++ / Swift / Go
值类型与引用类型 的 需求
引用类型带来联动
1 2 3 4 5 6 7 public class Main { public void run() { Account account1 = ...; Account account2 = ..; account1.transferTo(account2); }}
1 2 3 4 5 6 7 public class Account { private int balance = 100; public void transferTo(Account account) { account.balance += this.balance; this.balance = 0; }}
值类型带来拷贝
1 2 3 4 5 6 public class Main { public void run() { int[] numbers = {6, 7, 3, 8, 4}; int[] unitized = new Linear().unitize(numbers); }}
1 2 3 4 5 6 7 8 9 10 11 12 public class Linear { public int[] unitize(int[] numbers) { for (int i = 0; i < numbers.length; i++) { if (numbers[i] > 0) { numbers[i] = 1; } else if (numbers[i] < 0){ numbers[i] = -1; } } return numbers; }}
结论
对于 不支持 值类型的 语言
我们需要机制 让 引用类型 模仿 值类型 的工作模式,也就是取消同步效果
提供拷贝方法
思路
Copy when assigning 在赋值时拷贝
每当将对象传给另外一个变量时,或者每当需要时,都创造一个新的对象,从而解除跟之前对象的关联
一般拷贝方法
1 2 3 4 5 6 7 public class Point { private int x; private int y; public Point copy() { return new Point(x, y); }}
1 2 3 4 5 6 7 8 public class Main { public void run() { Point p1 = new Point(3, 4); Point p2 = p1.copy(); Point p3 = p2.copy(); p3.setX(7); }}
拷贝构造
1 2 3 4 5 6 7 8 public class Point { private int x; private int y; public Point(Point point) { this.x = point.x; this.y = point.y; }}
1 2 3 4 5 6 7 8 public class Main { public void run() { Point p1 = new Point(3, 4); Point p2 = new Point(p1); Point p3 = new Point(p2); p2.setX(7); }}
时间线



不变对象Immutable Object
思路
Copy when changing 在改变时拷贝
允许多个变量都指向同一个变量,但是对象的数值一旦设定,不可改变。
每当改变时,都会创造新的对象
案例
1 2 3 4 5 6 7 public class Point { private int x; private int y; public Point setX(int x) { return new Point(this.x + x, y); }}
1 2 3 4 5 6 7 8 public class Main { public void run() { Point p1 = new Point(3, 4); Point p2 = p1; Point p3 = p2; p3 = p3.setX(7); }}
时间线



String
Java 中的 String 就是不变对象
所有更改 String 的操作,都会导致一个新的 String 对象的产生
1 2 3 String name = "zzax";name = name.substring(1, 3);Console.println(name);
拷贝层级
案例
代码
1 2 3 4 public class Point { private int x; private int y;}
1 2 3 4 public class Circle { private Point center; private int radius;}
1 2 3 4 5 6 public class Main { public void run() { Circle c1 = new Circle(new Point(3, 4), 5); Circle c2 = ...; }}
图

0 级拷贝
代码
1 2 3 4+5 6 public class Main { public void run() { Circle c1 = new Circle(new Point(3, 4), 5); Circle c2 = c1; }}
图

1 级拷贝
代码
1 2 3 4*5 6 public class Main { public void run() { Circle c1 = new Circle(new Point(3, 4), 5); Circle c2 = c1.copy(); }}
1 2 3 4+5+6+7 public class Circle { private Point center; private int radius; public Circle copy() { return new Circle(center, radius); }}
图

2 级拷贝
代码
1 2 3 4 5*6 7 public class Circle { private Point center; private int radius; public Circle copy() { return new Circle(center.copy(), radius); }}
1 2 3 4+5+6+7 public class Point { private int x; private int y; public Point copy() { return new Point(x, y); }}
图

深拷贝 与 浅拷贝Deep Copy vs Shallow Copy
是什么
一个学术词汇,关注的是拷贝的时候 层级的差别
数组的浅拷贝
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Main { ... public void run() { Point[] points = { new Point(0, 0), new Point(3, 0), new Point(3, 4) } // shallow copy Point[] copiedPoints = new Point[points.length]; for(int i = 0; i < points.length; i++) { copiedPoints[i] = points[i]; } }}
内存图

特性
之后 更改 copiedPoints 数组 不会影响到 之前的 points 数组
但 修改 copiedPoints 数组里的对象 会影响到 points 数组里的对象
数组的深拷贝
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Main { ... public void run() { Point[] points = { new Point(0, 0), new Point(3, 0), new Point(3, 4) } // shallow copy Point[] copiedPoints = new Point[points.length]; for(int i = 0; i < points.length; i++) { copiedPoints[i] = points[i].copy(); } }}
内存图

特性
之后 更改 copiedPoints 数组 不会影响到 之前的 points 数组
修改 copiedPoints 数组里的对象 也不会 影响到 points 数组里的对象