var theThing = null;
var replaceThing = function () {
var originalThing = theThing;
var unused = function () {
if (originalThing)
console.log("hi");
};
theThing = {
longStr: new Array(1000000).join('*'),
someMethod: function () {
console.log(someMessage);
}
};
};
setInterval(replaceThing, 1000);
snapshot(v8)
既然要讲v8的snapshot那就得先看看snapshot的表示形式,整个snapshot里的对象是以graph的形式展示的,节点是对象(会以对象的构造函数的形式展示),边是属性值, 例如
此图是snapshot的json格式图
此图中的索引都是从零开始的
再具体的解释如下:
那么接下来就要说一下snapshot里面是有哪些类型即节点的类型(当然这些类型都来自v8)完整的数据类型,而我们在这里要讲则是在snapshot里面定义的类型(来自v8), 这里我们要区分primitive(包扩三类number, string, symbol,js会替我们auto-boxing)和Object(不要问什么)
chrome devtools 里的概念
shallow size:
是指对象自己本身占用的大小, 不包含引用对象内容的大小
retained size:
是指不但包含对象自身,还包含该对象所能引用的或者间接引用(parent.child, parent.child.child)
GC roots
GC roots的概念来自与垃圾回收算法,js的垃圾回收算法是基于根不可达来回收不使用的内存的,就选取某个对象作为初始点,沿着这个对象的引用链往下走,凡是通过这个对象无法访问到(是指通过引用可以获取到对象)的对象就认为是可以被回收的。
如上图5,6,7就无法被访问到,此时就可以认为5,6,7可以被回收了
从上面我们可以看出来,所有的对象是以树的形式展示的,devtools中如何显示对象树的? 对象的保留树 就像我们前面所说的,堆就是由相互连接的对象构成的网络。在数学的世界中,这种结构称作图或者内存图。一个图是由节点和边构成的,而节点又是由边连接起来的,其中节点和边都有相应的标签。
Object count 挡在summary视图模式下查看时,会有这个,按照上述来说对象树的节点是constructor, 属性是边,那么object count 就是通过这个constructor 构造出来的对象实例数量
巧了还有一个我们可以在devtools里经常看到的就是有些对象是黄颜色标识的有些是红色标识的,见图, 图中很明显标识红色和黄色的原因
在summary视图下第一栏是从constructor而这一栏是分两类的
另一类带括号的又有如下区分
见上图,他管()的行为叫tag,那就很明显了,在括号()下面的对象就是全部的这种对象了。
看一次对其中两个对象的识别及分析
代码如下,num2是一个Number对象,不知道为啥,我以字面量分配的num1,我没找到。。。(有待继续)
接下来的图示顺序分析的
常见的内存泄露种类
全局变量
这里bar没有生命就意味着他被global引用了,那么他就不会被回收
被遗忘的计时器或回调函数
此例说明:与节点或数据关联的计时器不再需要,node 对象可以删除,整个回调函数也不需要了。可是,计时器回调函数仍然没被回收(计时器停止才会被回收)。同时,someResource 如果存储了大量的数据,也是无法被回收的。
还有时间监听:
被引用的dom
有时,保存 DOM 节点内部数据结构很有用。假如你想快速更新表格的几行内容,把每一行 DOM 存成字典(JSON 键值对)或者数组很有意义。此时,同样的 DOM 元素存在两个引用:一个在 DOM 树中,另一个在字典中。将来你决定删除这些行时,需要把两个引用都清除。
此外还要考虑 DOM 树内部或子节点的引用问题。假如你的 JavaScript 代码中保存了表格某一个
闭包
这段代码被引用了无数次了来自meteor
代码片段做了一件事情:每次调用 replaceThing ,theThing 得到一个包含一个大数组和一个新闭包(someMethod)的新对象。同时,变量 unused 是一个引用 originalThing 的闭包(先前的 replaceThing 又调用了 theThing )。思绪混乱了吗?最重要的事情是,闭包的作用域一旦创建,它们有同样的父级作用域,作用域是共享的。someMethod 可以通过 theThing 使用,someMethod 与 unused 分享闭包作用域,尽管 unused 从未使用,它引用的 originalThing 迫使它保留在内存中(防止被回收)。当这段代码反复运行,就会看到内存占用不断上升,垃圾回收器(GC)并无法降低内存占用。本质上,闭包的链表已经创建,每一个闭包作用域携带一个指向大数组的间接的引用,造成严重的内存泄漏。
Meteor 的博文 解释了如何修复此种问题。在 replaceThing 的最后添加 originalThing = null 。
An object size
Shallow size
Retained
从obj1入手,上图中蓝色节点代表仅仅只有通过obj1才能直接或间接访问的对象。因为可以通过GC Roots访问,所以左图的obj3不是蓝色节点;而在右图却是蓝色,因为它已经被包含在retained集合内。
所以对于左图,obj1的retained size是obj1、obj2、obj4的shallow size总和;右图的retained size是obj1、obj2、obj3、obj4的shallow size总和。 对于obj2,它的retained size是:在左图中,是obj2和obj4的shallow size的和;在右图中,是obj2、obj3和obj4的shallow size的和。
那么V8的GC root都有哪些呢
还有一些全局变量
built-in object maps: 内建的对象
symbol table: 符号表(没搞明白这是个啥子鬼)
stacks of VM threads;(这个应该是指栈中的 变量)
compilation cache;(编译的缓存)
handle scopes;(v8中的属术语,v8中每个对象都是被封在handle中的,句柄的scope)
global handles;(全局的句柄)
那么哪些动作会导致新的分配动作呢
使用devtools进行profiling的tips
three snapshot method (https://docs.google.com/presentation/d/1wUVmf78gG-ra5aOxvTfYdiLkdGaR9OhXRnOlIcEmu2s/pub?start=false&loop=false&delayms=3000&slide=id.g31ec7af_0_58)
最后在snapshot3的summary下查看在1和2之间分配的对象
参考
v8类型(知乎上的一篇)
v8类型源码注释
v8-object-representation
js内部编码介绍
v8类型图
snapshot的格式头文件
生成snapshot的cc文件
heap profiling
snapshot格式
easy profiling
常见内存泄露copy于此处