昨天的文章通过 Performance 和 Memory 工具证明了打开 devtools 的时候 console.log 会有内存泄漏。
有 console.log 的时候,内存是这样的:
![5012a91f8dc7bebb02784f009b4243f4.png](https://i-blog.csdnimg.cn/blog_migrate/97e9201bffbe53244d2d590d78c2d40e.png)
去掉之后是这样的:
![8c372332e9a3c7af9cb74fb385818672.png](https://i-blog.csdnimg.cn/blog_migrate/265beca4709e4313b86d15baa039530f.png)
我们得出结论,console.log 会导致内存泄漏。
这点没错。
但很多同学会有疑问,是不是因为打开 devtools 才有内存泄漏,不打开就不会呢?
这个我测试了一下:点击几次按钮,这时候应该调用了 console.log 打印了,然后我过了 10 分钟,确保执行过 gc 了,再打开控制台,依然是可以看到那个对象的详情的。
![76689077eed33d6e7deeb83ffcf8c262.gif](https://i-blog.csdnimg.cn/blog_migrate/f246c266f7d18272a4ef7216cf8fe107.gif)
这说明打印的对象没有被 gc,不然怎么还可以看到详情呢?
于是我得出结论,不打开 devtools 也是有内存泄漏的。
但我今天换了种测试方法,貌似不打开 devtools 时 console.log 是没有内存泄漏的。
不打开 devtools 怎么确定内存泄漏问题呢?
看下内存大小不就知道了?
通过 performance.memory.totalJSHeapSize 是可以拿到堆内存大小的。
我们通过分析 console.log 的代码执行后的堆内存大小变化就行。
也就是这样:
<!DOCTYPE html>
<html lang="en">
<body>
<button id="btn">点我</button>
<div id="box"></div>
<script>
const btn = document.getElementById('btn');
const box = document.getElementById('box');
btn.addEventListener('click', function() {
const MB = 1024 * 1024;
log();
function log() {
const memory = performance.memory.totalJSHeapSize;
const usagedMemory = Math.floor(memory / MB);
box.insertAdjacentHTML('beforeend', `<span>${usagedMemory} </span>`);
const obj = {usagedMemory, str: 'g'.repeat(50 * MB)};
console.log(obj);
setTimeout(() => log(), 50);
}
});
</script>
</body>
</html>
按钮点击的时候,拿到当前堆内存的大小。然后打印一个大字符串和堆内存大小。
因为我们看不到控制台,所以也会加到 dom 中来显示。
通过定时器不断地执行这样的操作。
我们先打开 devtools 测试下:
![8410adca437fc5e58eb1bbbe61449254.gif](https://i-blog.csdnimg.cn/blog_migrate/32396016d27b7e6b79516f646eeb5a6b.gif)
可以看到每次打印后内存都在增长,并且在内存达到 4G 的时候就崩溃了。
说明 console.log 确实存在内存泄漏。
那我们再关掉 devtools 测试下:
![5e34733081f8c61c3fdde8036105ce98.gif](https://i-blog.csdnimg.cn/blog_migrate/58b2c8c55df0193d11b2feaca8482887.gif)
内存一直稳定不变,说明函数执行完之后,作用域销毁,打印的对象就被销毁了,没有内存泄漏。
我们过程中打开 devtools 测试下:
那如果我先打开 devtools,然后再关掉呢?
![56fe6a38a99485389188898825b6e862.gif](https://i-blog.csdnimg.cn/blog_migrate/f51ea98b6a53821eadc040df2e6c270e.gif)
可以看到,只要关闭了 devtools,内存就稳定了。但之前打印的对象依然被引用着,那部分内存不会被释放。
这样,我们就可以得出结论:不打开 devtools 的时候,console.log 不会内存泄漏。
(但我感觉我之前的测试方式也没错呀,不知道哪里有问题)
还有同学问,那如果直接打印字符串呢?
我们直接打印字符串试一下:
![170710caaf6bfc915b2059e85ab76af6.png](https://i-blog.csdnimg.cn/blog_migrate/f131398c54ee83eae8c3a99f27fbbe10.png)
![7f0be8eb3b6f00a05d956c8cd4531312.gif](https://i-blog.csdnimg.cn/blog_migrate/13fdd534bab45f842e6644add5a224d8.gif)
可以看到,内存也是平稳的。
为什么呢?字符串不也是对象、可以看到详情的吗?
这是因为字符串比较特殊,有个叫做常量池的东西。
录制一下内存快照:
![210af936a7103d5350081c9604e456b4.gif](https://i-blog.csdnimg.cn/blog_migrate/03a9578079db59c7b1ad2c56e49c7754.gif)
看一下字符串占用的内存:
![423671731c38bdd4a41a9311e5c7ba19.png](https://i-blog.csdnimg.cn/blog_migrate/5e029ae6d6c9273de8dc89302986a8c9.png)
是 @91 的地址。
我过了一段时间再录制了一次快照,依然只有一个字符串,地址是 @91。
这就是字符串常量池的作用,同样的字符串只会创建一次,减少了相同字符串的内存占用。
但 string 还有另一种创建方式:new String
这种方式就不一样了:
![dd24500b9bfad1a95a9aafc25ef89491.png](https://i-blog.csdnimg.cn/blog_migrate/a77632e7de03d3e056e2dcfa791f6ce0.png)
这时候创建的是一个堆中的对象,然后引用了常量池中的 string。
![3e587feda566fbb8e5cd15a13391ad96.png](https://i-blog.csdnimg.cn/blog_migrate/95b59fac6810ad2d51c018a46b579a63.png)
这也是为啥字符串字面量是 string,而 new String 是 object:
![cd6bde1cfb69a9ca865560f57a4c7ecb.png](https://i-blog.csdnimg.cn/blog_migrate/941c28be2b8828f8a9fc917e308eefb3.png)
因为会不断在堆中创建对象,所以这时候 console.log 的内存泄漏依然会使堆内存上升:
![4c40f39066a746d6be11f88ca4b4c609.png](https://i-blog.csdnimg.cn/blog_migrate/82d05fc74dd549c858c4754200f0d1e3.png)
那 node.js 的 console.log 有没有内存泄漏呢?
我们也用同样的方式测试下就好了,只是这时候拿到内存数据是用 progress.memoryUsage() 的 api:
const MB = 1024 * 1024;
log();
function log() {
const memory = process.memoryUsage().heapUsed
const usagedMemory = Math.floor(memory / MB);
const obj = { usagedMemory, obj: 'g'.repeat(50 * MB) };
console.log(obj);
setTimeout(() => log(), 50);
}
执行一下:
可以看到内存是稳定的,并不会内存泄漏。
这是因为 node 打印的是序列化以后的对象,并不是对象引用。
总结
console.log 在 devtools 打开的时候是有内存泄漏的,因为控制台打印的是对象引用。但是不打开 devtools 是不会有内存泄漏的。
我们通过打印内存占用大小的方式来证明了这一点。
string 因为常量池的存在,同样的字符串只会创建一次。new String 的话才会在堆中创建一个对象,然后指向常量池中的字符串字面量。
此外,nodejs 打印的是序列化以后的对象,所以是没有内存泄漏的。
所以,生产环境也是可以用 console.log 的,没有内存泄漏问题。
- END -
关于奇舞团
奇舞团是 360 集团最大的大前端团队,代表集团参与 W3C 和 ECMA 会员(TC39)工作。奇舞团非常重视人才培养,有工程师、讲师、翻译官、业务接口人、团队 Leader 等多种发展方向供员工选择,并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。