本篇主要聊一聊JavaScript垃圾回收:Garbage Collecation,在理解垃圾回收机制前,先理解一下JavaScript变量的生命周期:全局变量–全局销毁结束(退出浏览器);局部变量–当前函数执行完毕结束(闭包例外);
javascript的垃圾回收机制是运行环境自动收集机制,也就是说执行环境负责管理代码在执行过程中使用的内存,不用人工操作,这让我们更专注于编辑代码上。
不管是高级语言,还是低级语言。内存的管理都是:
- 分配内存
- 使用内存(读或写)
- 释放内存
前两步,大家都没有太大异议。关键是释放内存这一步,各种语言都有自己的垃圾回收(garbage collection, 简称GC)机制。做GC的第一步是判断堆中存的是数据还是指针,是指针的话,说明它被指向活跃的对象。有3种判断方法:
-Conservative:如果存储格式是地址,就认为是。C/C++有用到这种算法。
-Compiler hints:对于静态语言,比如Java,编译器是知道它是不是指针的,所以可以用这种。
-Tagged pointers:JavaScript用的是这种,在字末位进行标识,1为指针。
对于JavaScript而言,最初的垃圾回收机制,是基于引用计次来做的。后来升级为标记清除。
一、引用计次(reference counting)
当对象被引用次数为0时,就被回收。潜在的一个问题是:循环引用时,两个对象都至少被引用了一次,将不能自动被回收。所以导致,我们常讲的内存泄露。
在低版本IE中经常会出现内存泄露,很多时候就是因为其采用引用计数方式进行垃圾回收。引用计数的策略是跟踪记录每个值被使用的次数,当声明了一个变量并将一个引用类型赋值给该变量的时候这个值的引用次数就加1,如果该变量的值变成了另外一个,则这个值得引用次数减1,当这个值的引用次数变为0的时候,说明没有变量在使用,这个值没法被访问了,因此可以将其占用的空间回收,这样垃圾回收器会在运行的时候清理掉引用次数为0的值占用的空间。
看起来也不错的方式,为什么很少有浏览器采用,还会带来内存泄露问题呢?主要是因为这种方式没办法解决循环引用问题。比如对象A有一个属性指向对象B,而对象B也有有一个属性指向对象A,这样相互引用
1 | // 引用计次 |
二、标记清除(mark and sweep)
这是JavaScript最常见的垃圾回收方式,当变量进入执行环境的时候,比如函数中声明一个变量,垃圾回收器将其标记为“进入环境”,当变量离开环境的时候(函数执行结束)将其标记为“离开环境”。至于怎么标记有很多种方式,比如特殊位的反转、维护一个列表等,这些并不重要,重要的是使用什么策略,原则上讲不能够释放进入环境的变量所占的内存,它们随时可能会被调用的到。
垃圾回收器会在运行的时候给存储在内存中的所有变量加上标记,然后去掉环境中的变量以及被环境中变量所引用的变量(闭包),在这些完成之后仍存在标记的就是要删除的变量了,因为环境中的变量已经无法访问到这些变量了,然后垃圾回收器相会这些带有标记的变量机器所占空间。
大部分浏览器都是使用这种方式进行垃圾回收,区别在于如何标记及垃圾回收间隔而已,只有低版本IE,不出所料,又是IE。。。