新生代的 Scavenge 回收算法、老生代的 Mark-Sweep & Mark-Compact 算法相关的文章已经很多 , 这里就不赘述了 , 例如这篇文章讲的不错 Node.js 内存管理和 V8 垃圾回收机制 。
内存泄漏由于不当的代码 , 有时候难免会发生内存泄漏 , 常见的有四个场景:
- 全局变量
- 闭包引用
- 事件绑定
- 缓存爆炸
全局变量没有使用 var/let/const 声明的变量会直接绑定在 Global 对象上(Node.js 中)或者 windows 对象上(浏览器中) , 哪怕不再使用 , 仍不会被自动回收:
function test() { x = new Array(100000);}test();console.log(x);
这段代码的输出为 [ <100000 empty items> ] , 可以看到 test 函数运行完后 , 数组 x 仍未被释放 。闭包引用闭包引发的内存泄漏往往非常隐蔽 , 例如下面这段代码你能看出来是哪儿里有问题吗?
let theThing = null;let replaceThing = function() { const newThing = theThing; const unused = function() { if (newThing) console.log("hi"); }; // 不断修改引用 theThing = { longStr: new Array(1e8).join("*"), someMethod: function() { console.log("a"); }, }; // 每次输出的值会越来越大 console.log(process.memoryUsage().heapUsed);};setInterval(replaceThing, 100);
运行这段代码可以看到输出的已使用堆内存越来越大 , 而其中的关键就是因为 在目前的 V8 实现当中 , 闭包对象是当前作用域中的所有内部函数作用域共享的 , 也就是说 theThing.someMethod 和 unUsed 共享同一个闭包的 context , 导致 theThing.someMethod 隐式的持有了对之前的 newThing 的引用 , 所以会形成 theThing -> someMethod -> newThing -> 上一次 theThing ->... 的循环引用 , 从而导致每一次执行 replaceThing 这个函数的时候 , 都会执行一次 longStr: new Array(1e8).join("*") , 而且其不会被自动回收 , 导致占用的内存越来越大 , 最终内存泄漏 。对于上面这个问题有一个很巧妙的解决方法:通过引入新的块级作用域 , 将 newThing 的声明、使用与外部隔离开 , 从而打破共享 , 阻止循环引用 。
let theThing = null;let replaceThing = function() { { const newThing = theThing; const unused = function() { if (newThing) console.log("hi"); }; } // 不断修改引用 theThing = { longStr: new Array(1e8).join("*"), someMethod: function() { console.log("a"); }, }; console.log(process.memoryUsage().heapUsed);};setInterval(replaceThing, 100);
这里通过 { ... } 形成了单独的块级作用域 , 而且在外部没有引用 , 从而 newThing 在 GC 的时候会被自动回收 , 例如在我的电脑运行这段代码输出如下:209712824501042454240...266108026652002086736 // 此时进行垃圾回收释放了内存2093240
事件绑定事件绑定导致的内存泄漏在浏览器中非常常见 , 一般是由于事件响应函数未及时移除 , 导致重复绑定或者 DOM 元素已移除后未处理事件响应函数造成的 , 例如下面这段 React 代码:class Test extends React.Component {componentDidMount() {window.addEventListener('resize', function() {// 相关操作});}render() {return <div>test component</div>;}}
<Test /> 组件在挂载的时候监听了 resize 事件 , 但是在组件移除的时候没有处理相应函数 , 假如 <Test /> 的挂载和移除非常频繁 , 那么就会在 window 上绑定很多无用的事件监听函数 , 最终导致内存泄漏 。可以通过如下的方式避免这个问题:class Test extends React.Component {componentDidMount() {window.addEventListener('resize', this.handleResize);}handleResize() { ... }componentWillUnmount() {window.removeEventListener('resize', this.handleResize);}render() {return <div>test component</div>;}}
推荐阅读
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 曹真厉害还是曹休厉害
- Excel合并单元格的常见操作,序号填充、求和一步搞定,这些都是干货
- CSS3绘制各种形状:弧形、心形、星星、箭头,通通不在话下
- linux编程yum 命令详解
- 维生素b可以减肥吗?
- 马蹄汤的做法
- 胡萝卜土豆排骨汤
- 肉杂拌汤
- 女生唯美的二字昵称有哪些?
- 银元|家里面找出很多1分、2分、5分硬币,收藏价值怎么样呢?