内存泄漏
定义
程序的运行需要内存,对于持续运行的服务进程,必须及时释放不再用到的内存。不再用到的内存,没有及时释放,就叫做内存泄漏。
影响
内存如果没有及时释放,占用就会越来越高,轻则影响系统性能,重则导致进程崩溃。
如何规避
- 垃圾回收机制
- 规范代码(手动释放等)
- 内存检查
一、垃圾回收机制
定义
有些语言(比如 C 语言)必须手动释放内存,程序员负责内存管理。这很麻烦,所以大多数语言提供自动内存管理,减轻程序员的负担,这被称为"垃圾回收机制"。
垃圾回收器
垃圾回收器会定期扫描对象,并计算引用了每个对象的其他对象的数量。如果一个对象的引用数量为 0(没有其他对象引用过该对象),那么该对象的内存即可回收。
局限性
并不是说有了垃圾回收机制,就可以当甩手程序员了。你还是需要关注内存占用:
那些很占空间的值,一旦不再用到,你必须检查是否还存在对它们的引用。如果是的话,就必须手动解除引用。
二、如何避免内存泄漏
在编程过程中,有很多操作会导致内存泄漏,下面是常见的泄漏情景以及如何规避。
1、全局变量
全局作用域要等进程全部退出才会释放,此时将会导致引用的对象常驻内存。
-
要避免意外的全局变量(使用 严格模式 或 代码规范工具 ESlint)。
function fn() { a = "全局变量"; // 等同于 window.a = "全局变量" }
-
如果需要释放常驻内存的对象,可以将变量重新赋值让旧的对象脱离引用关系:
var arr = [1, 2, 3]; console.log('hello'); // 重新赋值 arr = null; // 或 undefined
2、闭包
闭包中的私有变量不会被释放。
了解更多闭包相关知识,可参考:【JavaScript编程】闭包
3、定时器
setInterval
或 setTimeout
设置之后如果没有清除,定时器的回调函数以及内部依赖的变量都不能被回收,造成内存泄漏。需要手动 clear
:
clearTimeout()
clearInterval()
4、DOM泄露
三、检查内存占用
1、浏览器
Chrome 浏览器查看内存占用,按照以下步骤操作:
1)打开开发者工具,选择 Performance 面板;
2)在顶部勾选 Memory;
3)点击左上角的录制按钮;
4)在页面上进行各种操作,模拟用户的使用情况;
5)一段时间后,点击对话框的 stop 按钮,面板上就会显示这段时间的内存占用情况。
2、命令行
使用 node 提供的 process.memoryUsage()
方法:
$ node
> process.memoryUsage()
{ rss: 21782528,
heapTotal: 7684096,
heapUsed: 4947864,
external: 16837 }
rss
(resident set size):所有内存占用,包括指令区和堆栈。
heapTotal
:"堆"占用的内存,包括用到的和没用到的。
heapUsed
:用到的堆的部分。(使用中的内存量)
external
: V8 引擎内部的 C++ 对象占用的内存。
实例: 封装一个显示内存占用的方法,如下:
var showMem = function() {
var mem = process.memoryUsage()
var bf = function(bytes) {
return (bytes / 1024 / 1024).toFixed(2) + 'MB'
}
console.log('Process: heapTotal:' + bf(mem.heapTotal) + ',heapUsed:' + bf(mem.heapUsed) + ',rss:' + bf(mem.rss))
console.log('-------------------------------------------------------------')
}
然后写一个不停分配内存但不释放内存的代码:
var useMem = function() {
var size = 20 * 1024 * 1024
var arr = new Array(size)
for (var i = 0; i < size; i++) {
arr[i] = 0
}
return arr
}
var total = []
for (var j = 0; j < 15; j++) {
showMem()
total.push(useMem())
}
showMem()
执行可以看到,每次调用useMem都导致了3个值得增长,最后内存溢出。如下:
SG-MAC:$ node index.js
Process: heapTotal:6.83MB,heapUsed:4.21MB,rss:20.16MB
-------------------------------------------------------------
Process: heapTotal:166.84MB,heapUsed:164.25MB,rss:181.57MB
-------------------------------------------------------------
Process: heapTotal:326.85MB,heapUsed:324.26MB,rss:341.63MB
-------------------------------------------------------------
Process: heapTotal:486.86MB,heapUsed:484.26MB,rss:501.69MB
-------------------------------------------------------------
Process: heapTotal:646.88MB,heapUsed:644.26MB,rss:661.74MB
-------------------------------------------------------------
Process: heapTotal:806.89MB,heapUsed:804.26MB,rss:821.77MB
-------------------------------------------------------------
Process: heapTotal:966.90MB,heapUsed:964.27MB,rss:981.78MB
-------------------------------------------------------------
Process: heapTotal:1126.91MB,heapUsed:1124.27MB,rss:1141.80MB
-------------------------------------------------------------
Process: heapTotal:1286.92MB,heapUsed:1284.27MB,rss:1301.82MB
-------------------------------------------------------------
<--- Last few GCs --->
[45451:0x103000000] 1382 ms: Mark-sweep 1284.0 (1290.9) -> 1283.9 (1290.9) MB, 136.8 / 0.0 ms allocation failure GC in old space requested
[45451:0x103000000] 1501 ms: Mark-sweep 1283.9 (1290.9) -> 1283.9 (1287.9) MB, 119.9 / 0.0 ms last resort GC in old space requested
[45451:0x103000000] 1625 ms: Mark-sweep 1283.9 (1287.9) -> 1283.9 (1287.9) MB, 124.1 / 0.0 ms last resort GC in old space requested