🔡内存管理碰撞课程


作者:Lin Clark
译者:Cody Chan
原帖链接:A crash course in memory management

这是图解 SharedArrayBuffers 系列的第一篇:

  1. 内存管理碰撞课程
  2. 图解 ArrayBuffers 和 SharedArrayBuffers
  3. 用 Atomics 避免 SharedArrayBuffers 竞争条件

为了更好地理解 JavaScript 里的 ArrayBuffer 和 SharedArrayBuffer,首先应该了解点内存管理知识

你可以把一个机器的内存想成一个一个的盒子,我把它们当作办公室的邮箱或者幼儿园里放小孩东西的格子

如果你想把某个东西留给某个小孩,你就把东西放到格子里

每个盒子旁边都有一个编号,这就是内存地址,用于告诉别人如何去找到你留的东西

每个盒子大小都一样,可以存放特定容量东西。盒子的具体容量取决于机器,这个大小我们称为字长,一般为 32 位或者 64 位。不过,为了展示方便,我这里字长用 8 位

如果你想把数字 2 放到盒子里,这很容易做到,因为数字很容易用二进制表示

但是如果你想放一个非数字类型的呢?比如字母 H?

我们需要一种方式可以用数字来表示它,这需要借助编码技术,类似 UTF-8 。首先,我们需要一个东西可以把字母转为数字……类似一个编码环,接下来就可以存储了

当我们从盒子里取回的时候,我们需要把取出的数字放到解码环里翻译回字母 H

自动内存管理

如果你是写 JavaScript 的,实际上你根本不需要关心内存,它对用户是透明的,这意味着你无法直接操纵内存

与此相对的, JS 引擎扮演着中间人角色,它为你管理好内存

我们用 React 举个例子,比如创建一个变量

JS 引擎通过一个编码器得到这个变量的二进制表示

然后,它会找到内存中可以放这个值的位置,这个过程称为内存分配

紧接着,引擎会持续追踪这个变量,看其是否还在程序中被使用。如果这个变量已经没有引用了,这块内存会被回收,JS 引擎就又可以在这里放新的值了

追踪内存中的变量(字符串、对象以及其它类型)并且在没有引用的时候清除它们的过程我们称作垃圾回收

类似 JavaScript 这种代码无法直接处理内存的语言叫做自动内存管理(memory-managed)语言

内存自动管理确实让开发者省了不少心,但是这也会带来额外的开销,这些额外开销会让程序的性能无法评估

手动内存管理

手动内存管理的语言不太一样,继续上面例子,看 React 用 C 写的话(借助 WebAssembly 是 可能的)会如何处理内存

不像 JavaScript,C 没有单独的内存管理抽象层,相反,你得直接与内存打交道。你可以从内存中直接拿东西,也可以直接往内存里存东西

当你把 C 或者其它语言编译为 WebAssembly 时,编译工具会在 WebAssembly 里增加一些辅助代码。比如,它会加入编解码的代码,这些代码我们称为运行时环境,运行时环境会帮助处理一些类似 JS 引擎帮 JS 做的事

但是,对于手动管理内存的语言,运行时不会包括垃圾回收

这并不意味着你需要完全由自己处理内存,即使在这类语言里,运行时你依然会得到一些辅助。比如,C 语言运行时会在一个 free list 里追踪可以使用的内存地址

你可以使用 malloc 函数(内存分配的简称)向运行时发出可以满足你数据存储需要的内存请求,这些内存会被从 free list 拿下来。当你用完了,需要调用 free 去释放内存,这些内存会被加回 free list

你必须清楚知道什么时候去调用这些函数,这就是为什么我们叫手动内存管理

作为一个开发者,知道什么时候去释放什么地方的内存是一件困难的事。如果你在错误时机处理了,这很可能会导致 BUG,甚至安全漏洞;如果你不处理,就会导致内存泄露

这就是为什么很多语言选择自动内存管理去避免人为错误,但是这牺牲了性能,下篇文章我还会谈到这些