参考: http://slides.com/gruizdevilla/memory
内存是一张图
- 原始类型,只能作为叶子。原始类型不能引用其他类型
- 数字
- 字符串
- 布尔值
- 除了原始类型之外,其他类型都是对象,其实就是键值对
- 数组是一种特殊对象,它的键是连续的数字
内存从根开始
- 在浏览器中,根对象是window
- 在nodejs中,根对象是global
- 任何从根无法到达的对象,都会被GC回收,例如下图的节点9和10
- 根节点的GC是无法控制的
路径
- 从根节点开始到特定对象的路径,如下图的1-2-4-6-8
支配项
- 每个对象有且仅有一个支配项,支配项对对象可能不是直接引用
- 举例子
- 节点1支配节点2
- 节点2支持节点3、4、6
- 节点3支配节点5
- 节点6支配节点7
- 节点5支配节点8
- 上面的例子有个不好理解的是节点2为什么支配了节点6?如果节点A存在于从根节点到节点B的每一个路径中,那么A就是B的支配项。2存在于1-2-4-6,也存在于1-2-3-6,所以节点2支配节点6
V8 新生代与老生代
- v8内存分为新生代和老生代内存,两块内存使用不同的内存GC策略
- 相比而言,新生代GC很快,老生代则较慢
- 新生代的内存在某些条件下会被转到老生代内存区
- GC发生时,用可能应用会暂停
解除引用的一些错误
var a = {name: 'wdd'}
delete a.name // 这回让对象a变成慢对象
var a = {name: 'wdd'}
a.name = null // 这个则更好
关于slow Object
- V8 optimizing compiler makes assumptions on your code to make optimizations.
- It transparently creates hidden classes that represent your objects.
- Using this hidden classes, V8 works much faster. If you “delete” properties, these assumptions are no longer valid, and the code is de-optimized, slowing your code.
// Fast Object
function FastPurchase(units, price) {
this.units = units;
this.price = price;
this.total = 0;
this.x = 1;
}
var fast = new FastPurchase(3, 25);
// Slow Object
function SlowPurchase(units, price) {
this.units = units;
this.price = price;
this.total = 0;
this.x = 1;
}
var slow = new SlowPurchase(3, 25);
//x property is useless
//so I delete it
delete slow.x;
Timers内存泄露
//
var buggyObject = {
callAgain: function () {
var ref = this;
var val = setTimeout(function () {
console.log('Called again: '
+ new Date().toTimeString());
ref.callAgain();
}, 1000);
}
};
buggyObject.callAgain();
buggyObject = null;
闭包内存泄露
var a = function () {
var largeStr =
new Array(1000000).join('x');
return function () {
return largeStr;
};
}();
var a = function () {
var smallStr = 'x',
largeStr =
new Array(1000000).join('x');
return function (n) {
return smallStr;
};
}();
var a = function () {
var smallStr = 'x',
largeStr =
new Array(1000000).join('x');
return function (n) {
eval(''); //maintains reference to largeStr
return smallStr;
};
}();
DOM 内存泄露
#leaf maintains a reference to it’s parent (parentNode), and recursively up to #tree, so only when leafRef is nullified is the WHOLE tree under #tree candidate to be GC
var select = document.querySelector;
var treeRef = select("#tree");
var leafRef = select("#leaf");
var body = select("body");
body.removeChild(treeRef);
//#tree can't be GC yet due to treeRef
treeRef = null;
//#tree can't be GC yet, due to
//indirect reference from leafRef
leafRef = null;
//NOW can be #tree GC
守则
- Use appropiate scope
- Better than de-referencing, use local scopes.
- Unbind event listeners
- Unbind events that are no longer needed, specially if the related DOM objects are going to be removed.
- Manage local cache
- Be careful with storing large chunks of data that you are not going to use.
分析内存泄漏的工具
- 浏览器:
performance.memory
- devtool memory profile
关于闭包的提示
- 给闭包命名,这样在内存分析时,就可以按照函数名找到对应的函数