前言
javascript自身带有垃圾回收机制, 所以不需要我们自己手动的回收内存。 垃圾回收机制会按照一定周期运行。 垃圾回收具体有那些方式呢? 又是怎做的呢?
垃圾回收机制
目前javascript引擎中的垃圾回收机制, 主要是两种:
1. 标记清除
2. 引用计数
标记清除
从名字可以看出, 这里有两个动作:标记出需要清除的、具体执行清除动作。
工作原理: 当变量进入执行环境时, 将这个变量标记为进入环境。 变量离开环境时, 则将其标记为离开环境。 标记离开环境的就会被收回。
工作流程:
1. 垃圾回收器, 在运行的时候会给存储在内存中的所有变量都添加上标记。
2. 去掉环境中的变量一级被环境中的变量应用的变量的标记
3. 再被加上标记的会被视为准备删除的变量
4. 垃圾回收器完成内存清除工作, 销毁那些带标记的值, 并收回他们所占用的内存空间。
大白话: 在标记的时候其实会做两次操作, 第一次是为了给所有变量都添加上标记, 第二次就是为了去掉不需要被标记的内存。最后再具体被清除。
缺点
该方法回收的效率比较低。 并且会造成较多的碎片空间。 所有其实还有一个碎片空间整理的过程。
具体算法
先说几个概念:
1. mutator和collector
这两个名词经常出现在垃圾回收算法中。
mutator的职责一般是分配内存, 从内存中读取内容, 将内容写入内存。
collector则是回收不在使用的内存来供mutator进行new操作的使用。
2. mutator roots(mutator根对象)
mutator根对象一般指的是分配在堆内存之外, 可以自备mutator直接访问到的对象, 一般是指静态/全局变量。
3. 可达对象
从mutator根对象开始遍历, 可以被访问到的对象都称为是可达对象。 这些变量也是mutator正在使用的对象
算法原理
在标记阶段, collector从mutator跟对象开始遍历, 对从mutator跟对象可以访问到的对象都打上标记,
在清除阶段, collector对堆内存从头到尾进行线性的遍历, 如果发现某个对象没有标记为可达对象, 就会将其回收。
但是 collector在进行标记和清除阶段会将整个应用程序暂停, 等待标记清除结束后才会恢复应用程序的运行。
引用计数
该方法的原理就是跟踪记录每个值被引用的次数。 如果被引用的次数变为0, 就会代表为被清除。
具体操作:
1. 将一个引用类型的值赋值给一个变量的时候, 该值的引用次数会加一,
2. 将包含这个引用的变量又赋值成其他值, 该值的应用次数就会减一
3. 当变为0时, 这个值就没有办法访问了。
4. 当垃圾收集下次运行时, 它就会释放引用次数为o的值所占内存
问题
如果出现循环引用, 引用次数永远不会变为0, 就会出现内存泄漏。这个时候我们需要手动的解开相互之间的应用。
什么情况下回引起内存泄漏?
虽然有垃圾回收机制但是我们编写代码操作不当还是会造成内存泄漏
意外的全局变量引起的内存泄漏
原因: 全局变量, 不会被回收 解决: 使用严格模式
闭包引起的内存泄漏
原因: 闭包可以维持函数内部变量, 使其得不到释放。 解决: 将事件处理函数定义在外部, 解除闭包, 或者定义水岸处理函数的外部函数中,删除对dom的引用。