[JavaScript 随笔] 垃圾回收
在 JavaScript 中,由于垃圾回收是自动进行的,所以人们在编码时可能不太会注意这方面。但事实是,一些 webapp 在使用一段时间后,会出现卡顿的现象,特别是那些单页应用,包括 WebView 方式的手机 app 。这个现象在传统的“单击 - 刷新”类型的页面中并不明显,因为页面刷新之后,所有没有被回收的垃圾对象也会被清除,但是在单页应用中,如果没有手动去点浏览器的刷新按钮,那么就算是很小的内存泄露,随着页面停留时间的增长,累积的泄露会越来越多,在手机上的感觉就更明显了。
所以这里想讨论一下内存泄露是如何发生的,以及如何去避免。
开门见山,一般有两种方式的垃圾回收机制,一个是“引用计数”,当一个对象被引用的次数为 0 时,该对象就可以被回收;另一个是“标记清除”,当一个对象不能再被访问到时,对该对象进行标记,等下一轮 GC 事件发生时,这些对象就会被清除。从 2012 年起,所有的现代浏览器都是基于“标记清除”的回收算法,所以,如果需要兼容更早的浏览器,可能需要做更多的事。GC 的时机由 JS 引擎决定,需要知道的事,当 GC 进行时,主线程会被阻塞,这个时间可以通过 Chrome 的 Timeline 工具看到,最少也会超过 10 ms 吧。
Chrome DevTools - Timeline
在 Chrome 中可以很直观方便地看到垃圾回收事件的执行。打开 Chrome 的 Timeline,只需要勾选“Memory”就可以了,并且在左边的 View 中选中第二个。
然后单击放大镜下面的圆点,这时候 Chrome 会开始记录内存分配、绘制等事件,等你打开一张页面,比如百度吧,再单击这个圆点(现在应该是红色的了),就会看到一条蓝色的折线。不同页面不一样,但几乎都会有一个突然下降的地方,比如下图中 1200 ms 左边的地方,单击它,就能在下方显示 GC 事件所用的时间,以及它回收了多少内存。
如果你看到自己网站的这条蓝色折线是呈上升趋势,在不断的 GC 后,内存还是在上升,就极有可能是发生了内存泄露,需要排查一下代码。
引用计数
这里的问题在于“循环引用”,如果对象 a 的属性引用了 b,而 b 的属性引用了 a,由于引擎只有在变量的引用次数为 0 的情况下才会回收,这里 a 和 b 的引用次数至少有 1,所以就算它们所在的函数执行完了,这两个变量还是无法被回收掉。
function foo() {
var a = {},
b = {};
a.attr = b;
b.attr = a;
}
foo();
当 foo 函数执行很多次之后,就会有很多个无法被回收的 a 和 b 存在。
实际情况可能是这样的:
function foo() {
var text = document.getElementById('input-text');
text.onfocus = function() {
text.value = '';
}
}
foo();
意思是,当光标移到输入框时,清空原有的内容。考虑 text 变量和 foo 里面的匿名函数,text 的 onfocus 属性引用了匿名函数,而该匿名函数引用了 text 变量(循环了),所以当 foo 执行结束后,这两个对象由于引用次数大于 0 而无法被回收。
对于这种情况,只需要在 foo 的末尾对 text 变量置空就可以了。
text = null;
如果你用 Chrome 运行这个例子的话,会看到蓝线还是降到初始的高度了,因为 Chrome 是基于“标记清除”的算法来回收内存的,所以不会有“循环引用”的问题。
标记清除
对于标记清除,心中要想象一个树,每个页面都存在一个根,每当一个函数执行,就会生成一个节点。自然,嵌套的函数调用就会有子节点。一般情况下(没有闭包),当函数执行完时,内部的变量都是无法被其他代码访问的,所以它就被标记为“无法被访问”。GC 时,JS 引擎统一对所有这些状态的对象进行回收。
介绍两个概念。Shallow Size,表示该对象本身占用的内存。Retained Size,表示释放该对象后能得到的内存大小。什么意思?比如上图绿色的 #3,这个绿色的面积就是 Shallow Size。释放 #3 后,#4 和 #5 也会被释放,所以 Retained Size 就是 #3、#4、#5 的总大小。
在“标记清除”算法中,难点是如何判断一个对象已经是“无法被访问”了。
DOM 片段
如果用树去分析垃圾回收,会发现其实我们需要做的事情很少,因为当一个函数执行完之后,它连带的对象都会被清除。就算有闭包,当引用该闭包的函数执行完时,这些闭包也同样会被标记。
那么在哪里会发生内存泄露呢?看这个例子。
var btn = document.getElementById('btn');
btn.onclick = function() {
var fragment = document.createElement('div');
}
它表示每单击一次按钮,就创建一个 <div>,它没有引用任何对象,但是回调结束之后,这个空的 <div> 是不会被回收的。
DOM 事件
var content = document.getElementById('content');
content.innerHTML = '<button id="button">click</button>';
var button = document.getElementById('button');
button.addEventListener('click', function() {});
content.innerHTML = '';
这段代码过后,虽然 <button> 从 DOM 中移除了,由于它的监听器还在,所以无法被 GC 回收。
要避免这种情况就是通过 removeEventListener 将回调函数去掉。
定时器
如果使用 setInterval,那么它引用到的变量的上下文会保留下来。
function foo() {
var name = 'tom',
title = 'Hero';
window.setInterval(function() {
alert(name);
}, 1000);
}
foo();
这里的情况时,每隔 1 秒弹框一次。第一,虽然只用到了 name,但 name 所在的上下文都无法被释放,包括 title 。第二,由于定时器一直在执行,所以这个上下文是不会被释放的。当然,有时候这是业务要求,也谈不上内存泄露了,只不过要注意的是,如果真的有没必要的定时器,请调用 clearInterval 把它去掉吧。
另一方面,你不用为了仅仅避免内存泄露对 setTimeout 调用 clearTimeout 。它是不会造成内存泄露的,除非是别的什么原因,比如说,在 setTimeout 中递归调用了当前定时器,这相当于模拟 setInterval,可以与 setInterval 做类似处理。
小结
在平时的一些开发过程中,我发现虽然在 Chrome 中发生了 GC 事件,并且内存也降得很低,如果用 Profile 工具 Take Heap Snapshot 的话,也不会觉得有内存泄露发生。但在手机上(WebView)的确会存在越用越卡的现象,这点可能要根据不同的环境来分析,但文中提到的关键两个地方就是:解除引用,以及解除监听的事件。
如果自己的代码中能做到这两点的话,可能卡顿是由别的问题引起的,而不是内存泄露。
[JavaScript 随笔] 垃圾回收的更多相关文章
- JavaScript 之垃圾回收和内存管理
JavaScript 具有自动垃圾收集机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存.而在 C 和 C++ 之类的语言中,开发人员的一项基本 ...
- 160930、Javascript的垃圾回收机制与内存管理
一.垃圾回收机制-GC Javascript具有自动垃圾回收机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存. 原理:垃圾收集器会定期(周期性 ...
- JavaScript的垃圾回收
来自MDN,Memory Management 简介 在底层语言中,比如C,有专门的内存管理机制,比如malloc() 和 free().而Javascript是有垃圾回收(garbage colle ...
- JavaScript的垃圾回收机制
JavaScript语言是一门优秀的脚本语言.其中包含脚本语言的灵活性外还拥有许多高级语言的特性.例如充许构建和实例化一个对象,垃圾回收机制(GC:Garbage Collecation).通常我们使 ...
- 浅析JavaScript的垃圾回收机制
JavaScript语言是一门优秀的脚本语言.其中包含脚本语言的灵活性外还拥有许多高级语言的特性.例如充许构建和实例化一个对象,垃圾回收机制(GC:Garbage Collecation).通常我们使 ...
- Nodejs通过Thrift操作hbase卡住原因分析及与javascript的垃圾回收机制的关系
在最近使用Nodejs通过Thrift操作hbase的时候写了个脚本,不断发送http请求,从而取得hbase下所需的数据,但是在run的过程中for循环并没有执行完全,在执行一部分后会卡住,就再也进 ...
- JavaScript 的垃圾回收与内存泄露
JavaScript采用垃圾自动回收机制,运行时环境会自动清理不再使用的内存,因此javascript无需像C++等语言一样手动释放无用内存. 在这之前先说一下垃圾回收的两种方式:引用计数与标记清除. ...
- javascript的垃圾回收机制和内存管理
垃圾回收 javascript不同于c.c++的一个特点是:具有自动的垃圾回收机制,这就意味着,开发人员可以专注于业务,而不必把过多精力放在内存的管理上,提高开发效率. 所谓的垃圾回收就是找出那些不再 ...
- javascript的垃圾回收机制与内存管理
一.垃圾回收机制—GC Javascript具有自动垃圾回收机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存. 原理:垃圾收集器会定期(周期性 ...
随机推荐
- eclipse如何导入项目
链接来自百度经验: 点击打开链接
- Java基础小总结
1,Java事件处理机制 (1)三部分:事件源.事件(处理)对象.实现事件监听器: (2)事件处理程序:可以通过ActionEvent e,e.getSource确定是哪个事件触发了,然后通过类似JB ...
- Unity3D 发布无边框exe
关于:Unity3D 发布无边框exe,Unity3D Build exe无边框 Unity发布windows版本 总是带着边框,很想给它去掉,笔者在网上查了一番,常见的有3中. 1:通过unity3 ...
- C++嵌入Python,以及两者混用
以前项目中是C++嵌入Python,开发起来很便利,逻辑业务可以放到python中进行开发,容易修改,以及功能扩展.不过自己没有详细的研究过C++嵌入python的细节,这次详细的研究一下.首先我们简 ...
- 数论 UVA 11076
这道题目的意思简单易懂说的是给你n个数(可能有重复相同的数字),列出他们所有排列的情况,再逐位相加,求出和,例如:给你1,2,3,则排列的情况为<123>, <132>, &l ...
- python知识点记录(一):
1.如何使print输出不换行: 在print语句末尾加上一个英文逗号. 2.安装第三方模块时,用pip和easy_install是一样的.下载一个setuptools.exe安装好就有easy_in ...
- 外观模式(Facade Pattern)
一.引言 在软件开发过程中,客户端程序经常会与复杂系统的内部子系统进行耦合,从而导致客户端程序随着子系统的变化而变化,然而为了将复杂系统的内部子系统与客户端之间的依赖解耦,从而就有了外观模式,也称作 ...
- 黑马程序员——JAVA基础之装饰设计模式
------- android培训.java培训.期待与您交流! ---------- 装饰设计模式: 当想要对已有的对象进行功能增强时,可以定义类,将已有对象传入,基于已有的功能,并提供加强功能. ...
- 20151215单选按钮列表,复选框列表:CheckBoxList
单选框:RadioButton GroupName:组名,如果要实现单选效果每个单选按钮的组名必须一样 是否被选中 RadioButton.checked 单选按钮列表:RadioButtonList ...
- yii2 使用twig 模板引擎
yii2 默认使用PHP 和html 混合的方式来写视图层,但我个人还是喜欢纯模板语言的方式.而且已经非常习惯使用twig的语法,最近想使用yii2进行开发,所以还是选择使用twig视图引擎. git ...