内存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.
1 2
Point p1 = Point(3, 4);Point p2 = Point(5, 6);
2.
1 2 3+
Point p1 = Point(3, 4);Point p2 = Point(5, 6);p1 = p2;
3.
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.
1 2
int a = 3;int b = 4;
2.
1 2 3+
int a = 3;int b = 4;a = b;
3.
1 2 3 4+
int a = 3;int b = 4;a = b;a = 5;

引用类型Reference Type

变量开辟

任何变量 都有 四个重要的信息

1
int a = 3;
1.

地址

2.

变量名

3.

数据格

4.

指针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.

内存被分为 堆 和 栈 两大区域。

2.
1
Point p1 = new Point(3, 4);

对象属性 的 内存 开辟在 堆里。

3.
1
Point p1 = 201;

局部变量、参数 的 内存 开辟在 栈里。

采用引用类型的内存分配方式,对象变量里 存的 是 对象在 堆里的 地址。

4.
1 2+
Point p1 = new Point(3, 4);Point p2 = new Point(5, 6);
5.
1 2 3+
Point p1 = new Point(3, 4);Point p2 = new Point(5, 6);p1 = p2;
6.
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 也不能代表 p1p2 的内容是不一样的

代码
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.
1 2 3 4
Player player = new Player();player.setAtk(10);player.setDef(10);player.setHp(100);
2.
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);
3.
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); }}
调用方法前的准备

对象变量 和 所有的参数都需要缩合

对象变量 会 缩合成 所存储的对象的地址

调用方法
1.

增加 隔板

每个函数都有自己的变量空间

调用函数,会为函数开辟新的内存区域,在栈里

2.

传输 this

为 被调用函数 创建一个 this 变量,在栈里

谁点函数,this 就是谁

3.

传输 参数

为 被调用函数 创建参数变量,在栈里

传参的值 会 过去

变量缩合

所有表达式的变量,均需从栈空间内寻找变量。

且 不能约过挡板

this 补充属性

如果在 占空间 无法找到变量,则 在前面 补充 this.

忽略

如果构造方法 或者 Getter Setter 只是简单的赋值。

则可以忽略进入这个方法

方法返回前的准备

return 的值 需要表达式缩合

方法返回
1.

记住 返回值

2.

删除 栈空间内 挡板下 所有的变量

3.

删除 挡板

4.

原 函数 缩合为 刚才的 返回值

方法的连续调用规则

案例
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); }}
时间线
1.
2.
3.

不变对象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); }}
时间线
1.
2.
3.
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 数组里的对象

ZZAX 微信公众

文档一更新,立刻告诉你