浏览器中的内存泄露(2)
作者:winter 来源:winter 博客 发布时间:2008-05-03 16:53:00
2.内存泄露的原因
作为一门垃圾回收的语言(注意不是垃圾语言),内存泄露的原因只有一个:引擎的bug(本小节用于休闲、调节气氛)
3.内存泄露的方式
目前发现的可能导致内存泄露的代码有三种:
循环引用
自动类型装箱转换
某些DOM操作
下面具体的来说说内存是如何泄露的
循环引用:这种方式存在于IE6和FF2中(FF3未做测试),当出现了一个含有DOM对象的循环引用时,就会发生内存泄露。
什么是循环引用?首先搞清楚什么是引用,一个对象A的属性被赋值为另一个对象B时,则可以称A引用了B。假如B也引用了A,那么A和B之间构成了循环引用。同样道理 如果能找到A引用B B引用C C又引用A这样一组饮用关系,那么这三个对象构成了循环引用。当一个对象引用自己时,它自己形成了循环引用。注意,在js中变量永远是对象的属性,它可以指向对象,但决不是对象本身。
循环引用很常见,而且通常是无害的,但如果循环引用中包含DOM对象或者ActiveX对象,那么就会发生内存泄露。例子:
var a=document.createElement("div");
var b=new Object();
a.b=b;
b.a=a;
很多情况下循环引用不是这样的明显,下面就是著名的闭包(closure)造成内存泄露的例子,每执行一次函数A()都会产生内存泄露。试试看,根据前面讲的scope对象的知识,能不能找出循环引用?
function A()...{
var a=document.createElement("div");
a.onclick=function()...{
alert("hi");
}
}
A();
OK, 让我们来看看。假设A()执行时创建的作用域对象叫做ScopeA 找到以下引用关系
ScopeA引用DOM对象document.createElement("div");
DOM对象document.createElement("div");引用函数function(){alert("hi")}
函数function(){alert("hi")}引用ScopeA
这样就很清楚了,所谓closure泄露,只不过是几个js特殊对象的循环引用而已。
自动类型装箱转换:这种泄露存在于ie6 ie7中。这是极其匪夷所思的一个bug,看下面代码
var s="lalalalala";
alert(s.length);
这段代码怎么了?看看吧,"lalalalala"已经泄露了。关键问题出在s.length上,我们知道js的类型中,string并非对象,但可以对它使用.运算符,为什么呢?因为js的默认类型转换机制,允许js在遇到.运算符时自动将string转换为object型中对应的String对象。而这个转换成的临时对象100%会泄露(汗一下)。
某些DOM操作也可能导致泄露 这些恶心的bug只存在于ie系列中。在ie7中 因为试图fix循环引用bug而让情况变得更糟,以至于我对写这一段种满了恐惧。
从ie6谈起,下面是微软的例子,
<html>
<head>
<script language="JScript">...
function LeakMemory()
...{
var hostElement = document.getElementById("hostElement");
// Do it a lot, look at Task Manager for memory response
for(i = 0; i < 5000; i++)
...{
var parentDiv =
document.createElement("<div onClick='foo()'>");
var childDiv =
document.createElement("<div onClick='foo()'>");
// This will leak a temporary object
parentDiv.appendChild(childDiv);
hostElement.appendChild(parentDiv);
hostElement.removeChild(parentDiv);
parentDiv.removeChild(childDiv);
parentDiv = null;
childDiv = null;
}
hostElement = null;
}
function CleanMemory()
...{
var hostElement = document.getElementById("hostElement");
// Do it a lot, look at Task Manager for memory response
for(i = 0; i < 5000; i++)
...{
var parentDiv =
document.createElement("<div onClick='foo()'>");
var childDiv =
document.createElement("<div onClick='foo()'>");
// Changing the order is important, this won't leak
hostElement.appendChild(parentDiv);
parentDiv.appendChild(childDiv);
hostElement.removeChild(parentDiv);
parentDiv.removeChild(childDiv);
parentDiv = null;
childDiv = null;
}
hostElement = null;
}
</script>
</head>
<body>
<button onclick="LeakMemory()">Memory Leaking Insert</button>
<button onclick="CleanMemory()">Clean Insert</button>
<div id="hostElement"></div>
</body>
</html>
看看结果吧,LeakMemory造成了内存泄露,而CleanMemory没有,循环引用了么?仔细看看没有。那么是什么问题呢?MS的解释是"插入顺序不对",必须先将父级元素appendChild。这听起来有些模糊,这里给出一个比较恰当的等价描述:永远不要使用DOM节点树之外元素的appendChild方法。
接下来是ie7和ie8 beta 1中运行这段程序,看到什么?没看错吧,2个都泄露了!别急,刷新一下页面就好了。为什么呢?ie7改变了DOM元素的回收方式:在离开页面时回收DOM树上的所有元素,所以ie7下的内存管理非常简单:在所有的页面中只要挂在DOM树上的元素,就不会泄露,没挂在DOM树上,肯定泄露。所以,ie7中记住一条原则:在离开页面之前把所有创建的DOM元素挂到DOM树上。
接下来谈谈ie7的这个设计吧,坦白的说,这种做法纯粹是偷懒的垃圾做法。动态垃圾回收不是保证所有内存都在离开页面时收回,而是要保证内存的充分利用,运行时不回收,等到离开时回收有什么用?这只是名义上的避免泄露,其实是完全的泄露。况且还没有回收DOM节点树之外的元素。
4.内存泄露的解决方案
内存泄露怎么办?真的以后不用闭包了么?没法封装控件了?这样做还不如要了js程序员的命,嘿嘿。
事实上,通过一些很简单的小技巧,可以巧妙的绕开这些危险的bug。
to be continued......
coming soon:
显式类型转换
避免事件导致的循环引用
不影响返回值地打破循环引用
延迟appendChild
代理DOM对象


猜你喜欢
- 引言最常见的闭包 (Closure) 范式大家都很熟悉了:(function() {// ...})(); 很
- 您想更改网站博客页面上 WordPress 文章的顺序吗?那么您就在正确的地方学习此功能并更改文章的顺序。因此,在本文中,我将向您展示如何通
- 本文实例为大家分享了微信小程序实现通讯录功能的具体代码,供大家参考,具体内容如下微信小程序模仿通讯录功能需要用到scroll-view标签思
- 1.数组和切片有什么区别Go语言中数组是固定长度的,不能动态扩容,在编译期就会确定大小,声明方式如下:var buffer [255]int
- python写文件时覆盖原来写的方法:使用“open('文件名','w')”语句,以写模式打开文件,然后使用
- 进程概述? 进程(Process)是计算机中已运行程序的实体。进程与程序不同,程序本身只是指令、数据及器组织形式的描述,进程才是程序(那些指
- 如果遇到下述错误,表示当启动mysqld时或重新加载授权表时,在用户表中发现具有非法密码的账户。发现用户'some_user'
- 引言在Python中但凡提到的赋值运算符其实讲的就是等号=,在编程语言中的等号含义再也不是数学中的1+1=2的这种等号,真实含义是将=右侧的
- 变量(Variable)可以看成一个小箱子,专门用来“盛装”程序中的数据。每个变量都拥有独一无二的名字,通过变量的名字就能找到变量中的数据。
- 一、表单控件绑定v-modelv-model 双向数据绑定;一般用于表单元素,会忽略表单元素的value、checked、selected的
- 目前可以实现简单的计算。计算前请重置,设计的时候默认数字是0,学了半天就做出来个这么个结果,bug不少。 python3.5 + PyQt5
- 当今互联网时代,数据处理已经成为了一个非常重要的任务。而Redis作为一款高性能的NoSQL数据库,越来越受到了广大开发者的喜爱。本篇博客将
- 背景随着Web技术的发展和移动互联网的发展,Hybrid技术已经成为一种前端开发的主流技术方案。那什么是Hybrid App呢?Hybrid
- Tensorboard详解该类在存放在keras.callbacks模块中。拥有许多参数,主要的参数如下:1、log_dir: 用来保存Te
- 这篇文章主要介绍了python中删除某个元素的方法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋
- 前言在使用传统物理机或云服务器上部署项目都会存在一些痛点比如:项目部署速度慢、资源浪费、迁移难且扩展低而使用 Docker 部署项目的优势包
- 索引的概念MySQL索引是一种用于加速数据库查询的数据结构,它类似于书籍的目录,能够快速指导我们找到需要的信息。MySQL索引可以根据一定的
- 关于Python的文件遍历,大概有两种方法,一种是较为便利的os.walk(),还有一种是利用os.listdir()递归遍历。方法一:利用
- 背景在程序员的职业生涯中,总会遇到数据库表被锁的情况,前些天就又撞见一次。由于业务突发需求,各个部门都在批量操作、导出数据,而数据库又未做读
- CBV添加装饰器给CBV添加装饰器有三种方法,三种方法都需要导入模块:from django.utils.decorators import