常用的两种算法:
引用计数(新版浏览器已弃用, 弃用原因: 会出现循环引用的情况, 无法进行垃圾回收, 导致内存泄漏)
标记清除
引用计数法
引用计数, 顾名思义一个对象是否有指向它的引用, 即看栈中是否有指向要释放的该块堆内存中的地址, 如果没有, 则该块内存是不需要的, 可以进行释放, 即垃圾回收
下面引用大佬的一个简短例子来说明情况
- // 创建一个对象 person, 他有两个指向属性 age 和 name 的引用
- var person = {
- age: 12,
- name: 'aaaa'
- };
- person.name = null; // 虽然 name 设置为 null, 但因为 person 对象还有指向 name 的引用, 因此 name 不会回收
- var p = person;
- person = 1; // 原来的 person 对象被赋值为 1, 但因为有新引用 p 指向原 person 对象, 因此它不会被回收
- p = null; // 原 person 对象已经没有引用, 很快会被回收
缺点: 引用计数有一个致命的问题, 那就是循环引用
当两个对象相互引用, 尽管他们已不再使用, 但是垃圾回收器不会进行回收, 最终可能会导致内存泄露.
- function cycle() {
- var o1 = {};//1
- var o2 = {};//1
- o1.a = o2;//2
- o2.a = o1; //2
- return "cycle reference!"
- }
- cycle();
cycle 函数执行完成之后, 对象 o1 和 o2 实际上已经不再需要了, 但根据引用计数的原则, 他们之间的相互引用依然存在, 因此这部分内存不会被回收. 所以现代浏览器不再使用这个算法.
但是 IE 依旧使用.
- var div = document.createElement("div");
- div.onclick = function() {
- console.log("click");
- };
上面的写法很常见, 但是上面的例子就是一个循环引用.
变量 div 有事件处理函数的引用, 同时事件处理函数也有 div 的引用, 因为 div 变量可在函数内被访问, 所以循环引用就出现了.
标记清除(常用)
文章里写的是: 标记清除算法将 "不再使用的对象" 定义为 "无法到达的对象". 即从根部 (在 JS 中就是全局对象) 出发定时扫描内存中的对象, 凡是能从根部到达的对象, 保留. 那些从根部出发无法触及到的对象被标记为不再使用, 稍后进行回收.
我这里个人理解: 不在原型链上的, 不能从全局对象链找到的对象, 会被认为是无法到达的对象(也可能我自己理解有误, 忘读者指出), 比如说下面这个例子
- var a = {} // 这里的 a 是挂在全局对象上的
- a = null // 这里 a 之前存放指向 {} 的地址变成了 null
- // 此时 {} 是无法找到的, 通过全局对象找到 a 也无法到达 {}, 因此{} 会被垃圾回收
无法触及的对象包含了没有引用的对象这个概念, 但反之未必成立.
所以上面的例子就可以正确被垃圾回收处理了.
所以现在对于主流浏览器来说, 只需要切断需要回收的对象与根部的联系, 就能进行垃圾回收
下面还是引用大佬的例子
最常见的内存泄露一般都与 DOM 元素绑定有关:
- email.message = document.createElement("div");
- displayList.appendChild(email.message);
- // 稍后从 displayList 中清除 DOM 元素
- displayList.removeAllChildren();
上面代码中, div 元素已经从 DOM 树中清除, 但是该 div 元素还绑定在 email 对象中, 所以如果 email 对象存在, 那么该 div 元素就会一直保存在内存中
参考文章:(https://www.muyiy.cn/blog/1/1.4.html# 垃圾回收算法)
来源: https://www.cnblogs.com/liuarui/p/11387345.html