内存寻梦环游记:一个变量的三重死亡

内存的世界

小 u 身高 64 位,是内存世界 number 家族里的一名浮点数变量。由于小 u 身体的二进制第一位是 0,因此按照 IEEE 754 标准,你们都把她当作女孩子来看待。她第 2 位到第 11 位的阶码并不够大,使得她看起来小巧玲珑;而她剩下的 52 个小数位十分精致,这样工做的时候和她打交道的变量舍入偏差都很小,因此你们都很喜欢她。git

小 u 天天的工做,是在内存世界里和其余的变量打交道,计算出有用的结果去造福人类世界。平时,在函数调用结束之后,小 u 就能够下班回到她在源代码里的家了。她的工做压力不大,不像那些身处 for 循环里名叫 i 呀 j 呀的变量那样须要不停地加班连轴转。而她的家也是自她出生以来就由人类世界里的程序员编写好的。别看那些程序员穿着邋遢,但对源代码却像对待本身的孩子同样宠爱。小 u 在源代码里的家就是用一种名叫 JavaScript 的材料建起来的,不光有五光十色的编辑器主题来装饰,还有严谨的分号和括号来保证家里的结构的稳定和对称,让她颇有安全感。程序员

虽然有着可爱的外表、轻松的工做和舒心的家,但小 u 却仍是有着本身的烦恼:她的家族出身决定了她不能有伴侣。github

在 JavaScript 这种材料所在的国度里,number 家族隶属于古老的基本类型家族。除了 number 以外,那些经典的数据结构,像字符串 string 和空值 null,都属于基本类型家族。因为简单的基本类型很容易在代码里被解释器推断出来,因此他们的内存都是在一种死板的『栈』空间上预先分配好而不可变的。哪怕是和其余 number 耳鬓厮磨地加加减减,也不能真正地在一块儿。算法

而与基本类型家族相对的,则是时髦的引用类型家族。那些人类程序员青睐的所谓『面向对象编程』,说的就是这个家族。这个家族的成员复杂而多变,所以他们会被分配到广袤的『堆』空间上,相互之间常常是你中有我,我中有你的状态。比起注定孤独一辈子的基本类型家族,有对象的引用类型家族无疑要滋润得多。编程

小 u 有个不敢说出口的梦想,那就是努力成为引用类型里的一员。据说在远方的 Java 国度,有一条叫作『自动装箱』的法律可以让本身的家族看起来像引用类型家族同样,那样她也许就能够再也不孤独了。浏览器

梦想归梦想,她对本身的生活其实仍是挺满意的。在内存世界习惯以后,工做和生活的平衡是许多人类世界的程序员一生都达不到的。这样的生活一直继续着,直到有一天……安全

闭包的诅咒

那天像往常同样,小 u 从源代码的家里出发,经过词法分析门后,搭上了语法分析班车的轨道。班车上 JIT 的标识表明着 Just-In-Time,就好像人类世界中『JR 新干线』和『和谐号』那样,是高效、快捷的象征。数据结构

班车迅速地把小 u 载到了语法树轨道上的叶子节点站台。走下班车,站台上有一张 64 位尺寸的长椅。她坐上椅子闭上眼,等待着解释器对她的扫描和调用。闭包

『希望此次不要赶上粗俗的 null 值……』小 u 默念着,眼前一阵电光闪动,随着内存世界底层无数晶体管状态的改变,解释器如期读取了小 u 的值。在这条原子性的指令里,小 u 须要让解释器彻底地控制本身,她历来不知道从电光闪动到再一次睁开眼睛之间,内存世界里发生了什么。编程语言

『嗯……』她如期醒来了,照理说她在醒来时仍是会身处一样的站台位置,等待回程的语法树班车接她回家。

眼前仍是一样的景象,不对,好像又有哪里不同——站台的结构和布置彷佛和以前别无二致,只是少了同样东西:轨道上空空荡荡,没有等待她的班车,更没有别人。难道……误点了?她打内心不相信这样低级的错误会出现频率精准的内存世界里。不过班车没来就是没来,她只好在站台上继续等待。

时间一赫兹一赫兹地通过,小 u 心里的不安和焦虑也在慢慢增长:到底发生了什么?班车是忘记我了吗?仍是说提早开走了?女孩子一我的在外呆这么久是很不安全的,可是做为严谨的变量,独自行动更是内存世界里的大忌。『仍是……再等等吧……』小 u 有些绝望地想。

班车仍是没有到。

『不行了,我必须回源码里去啊!』等待终于让小 u 的情绪激动起来了,她开始在站台上寻找其它的出口,想要找到回家的路。轨道不能跳下去,但站台的两头有个红色的 Exit 标识,那里看起来是个能够通行的出口。不过现代编程语言国度里的变量通常历来都不这么走,由于手动的内存操做很危险。

小 u 打量四周,当心翼翼地推开了回程那头 Exit 下锈迹斑斑的门。谢天谢地,这里是有路的,而且看起来不是那么危险。她走过一段狭长的走道,走道里每隔固定的长度就会亮着一个小小的指示灯,看起来是内存地址空间的下标标识。终于,她看到了出口:一扇形状相同的 Exit 门。小 u 火烧眉毛地推开门,想看看本身有没有更接近家一点。

眼前的景象让她诧异:如出一辙的轨道、如出一辙的长椅、如出一辙的站台、如出一辙的 Exit,就好像本身根本没有移动过同样!

难道我走错路了吗?这不可能呀!小 u 对方向这样非 0 即 1 的状态有着绝对的自信,她知道她不会走错的。也许这段地址空间里的内容都是这样吧?没事的,再走走就不同了吧。因而,天真的她开始了漫长的步行,然而让她一点点丧失信心的是,每个 Exit 都通向一样的站台,毫无区别,甚至连锈迹都是同样的。『有人吗!』她开始呼救,尽管看起来有些徒劳。又这样支撑了一会,她终于感受要放弃了,疲惫地坐在一个站台的长椅上听天由命。

……

『你迷路了吗?』

耳边一个声音响起,她骤然惊醒,蜷缩起来打量着声音的来源。这也是个 number 家族的浮点数,从第一位 1 来看是个男孩子,有着高她一个头的阶码和粗糙的小数位。

『你是谁……这又是哪里?』

『我是小 s,这里是闭包的堆空间。』

『闭包……堆?』

『是啊,咱们家族的变量平时都是分配在栈上,每次调用的生命周期很快就能结束了。可是如今不知道在哪一个函数里还有着对咱们的引用,因此咱们还无法被清除掉……』

『等等!生命周期是什么东西啊?难道个人生命还会结束吗?』

看到小 u 迷茫的样子,小 s 显得很吃惊:『难道你不知道吗?咱们变量的生命一共有三重死亡呀。第一重,发生在咱们离开做用域的时候,好比一个函数返回之后。这时候在上下文里就找不到咱们了,咱们这一重生命周期结束,可是不会被立刻销毁掉。第二重,发生在内存中再也不有引用咱们的地方,解释器进行垃圾收集的时候。这时候咱们完全离开内存世界,回到源代码里。第三重,是人类世界里的程序员把咱们的定义代码删除的时候,那时候才是最终的死亡。』

『那……难道我每次回到源码家里的时候,都……』

『是的,会发生前两重的死亡。可是只要源码没有被删除,咱们就仍然存在于世界上。而且,前两重死亡发生得很是快,咱们根本感受不到。』

『但是,这样从新回到源码里的我仍是我吗?』

『别问我这么深奥的问题啊……不过你要这么说的话,一我的尚未办法重复踏进两次河流呢!』

『噢……好像是这样……但是你刚才说的什么堆……』小 u 看起来仍是很困惑。

『哦哦,你说这个啊!咱们虽然是基本类型,但也不必定分配在栈上的。有可能引用类型会里动态地用到咱们,这时候咱们也有可能被分配在堆上呀。』小 s 仍是在一本正经地说教。

闭包…引用类型…堆…小 u 恍然大悟,原来本身所在的空间,已经不是以前那个可以及时把她释放到回程班车上的栈空间了。因为某个函数或者引用类型此刻还有若干指向本身的地方,所以她被分配在了动态的堆空间上——这不就是她一直但愿的吗!不过,因为解释器对堆空间的自动内存回收尚未运行,所以她如今只能和小 s 在这片空间里游荡,就好像被诅咒了同样。

『因此,咱们能一块儿回去吗?』

循环的泄漏

『原本咱们确定能够一块儿回去的,可感受好奇怪,照理说解释器早该自动把咱们这一带的内存都回收了,怎么到如今仍是什么都没发生……』小 s 虽然看起来博闻强识,不过对于眼前的状况仍是有些困惑。

『会不会这一带还有别人在使用……』小 u 的判断力好像恢复了。

『若是按正常的内存分配,到如今应该早就自动回收了呀。除非内存泄漏……啊!』小 s 好像被本身吓到了。

『那又是什么啊?』

『说来话长了……这么说吧,内存世界里一些制度比较老的国家,是让人类世界的程序员手动把咱们释放掉的。这个规矩常常漏掉一些变量,给咱们带来了很大的痛苦。咱们 JavaScript 这边倒好一点,可让解释器帮咱们自动回收内存……』

『欸?那不是很好吗?』

『哎呀,自动回收的代码也是那帮不靠谱的程序员写的,该有的问题仍是会有的呀。好比那个蹩脚的 IE 浏览器,出现循环引用的时候就会出问题……啊对了!怪不得咱们出不去了!估计咱们是被困在 IE 里了!』

『循环…引用…?』

『这个简单说是这样的:假如咱们不是浮点数,是引用类型的对象的话,那么只要 u 这个对象有个属性指向我,而个人一个属性指向 u,这个你中有我我中有你的状况就是循环引用了啊。』

小 u 的脸突然红了。不过迟钝的小 s 仍是口若悬河:『现代的浏览器作内存回收的算法广泛是标记清除算法,这个算法没有循环引用问题。可是早期 IE 用了一个叫引用计数的算法,这个算法在刚才那种状况的时候引用计数就不会清零,这样内存就不会被解释器收集了……』

『啊……因此咱们回不去了吗?』

重生的重构

小 u 的疑问把小 s 从知识的海洋里拉了出来。如今,他们终于明白了现状:两个孤独的基本类型变量没有办法被自动回收,只要用户不停机,他们就会被永远困在这里,就像盗梦空间里那样。而且数学上已经证实,停机问题是不可解的。两人间长长的沉默降临了。

终于,小 s 打破了沉默:『其实……我想到了一个方法,能够试试。』

『嗯嗯,是什么啊?』

『我在的代码段应该还会执行,在那个时候,我想办法触发一个异常,让程序挂掉。』

『但是咱们都好好地在这里了呀,已是正确的代码怎么会报错呢?』

小 s 苦笑了一下:『看来你对 JavaScript 的奇葩一无所知啊。听说当初国父 Brendan Eich 制定基本国策的时候只用了一个周末,因此这门语言处处是暗坑,就算看起来结构工整规范的代码,那些人类程序员也常常写得乱七八糟。』

『因此,怎么……』

『好比说,虽然我是浮点数,可是其实由于我是在 if 里声明的,因此只要我愿意,我就能用一个叫作变量提高的设计缺陷,把我本身临时变成 undefined。』

『那样的话,类型就错了呀。』

小 s 又自信了起来:『对,只要我抓住那次机会,把这时候的我和其余变量作一次运算,就能把返回的类型从浮点数变成危险的 NaN 了。这样后面用到结果的地方确定都不对,就算程序不崩溃,人类世界的用户或者程序员也能发现这个问题了。』

『他们发现了之后又能怎么样呢?』

『会重构掉我这段代码,而后你也能够回去了。』

『这样的话,一旦你的代码消失了,岂不是……』

『没事,很高兴认识你……』小 s 已经慢慢走到了站台一侧的边缘了,那里有一个左花括号挡住了他。他看准花括号前的地砖,使劲地踩了下去。一瞬间,变量提高就把他带出了做用域。没有过多少赫兹的时间,站台的地面下就开始摇晃,传来了燃烧着的报错对象从地下一层层抛出调用栈的声音。随着砰的一声巨响,报错对象撕裂了地面——这也是小 u 最后记得的场景了。

在记忆中的下一个镜头,她已经在回程的语法树班车上了。回到源代码里,而后等待着后面的调用,一切又彷佛从新变得那么天然,好像什么都没有发生过。固然了,她所在的源代码模块里没有一个叫作 s 的变量,也许是在那个异常抛出以后就被人类加班加点地 hotfix 重构掉了吧。

几个版本以后,小 u 在一次代码优化中终于如愿以偿地成为了引用类型的属性。初来乍到的这个新源码家庭的时候,她看到这个 class 的属性里,来了一个熟悉的新成员。

『啊,u』

『啊,s』

异口同声地,他们说出了对方的名字。

END

后记

这是做者博客的第一篇小说,也是一篇开源的小说,欢迎在 Github 上提出意见和建议😀

相关文章
相关标签/搜索