内存泄露优化
内存分析与内存泄漏定位
无论是分布式计算系统、服务端应用程序还是
类似于malloc()
与 free()
这样的底层内存管理原子操作,开发者需要显式手动地进行内存的申请与释放;而
内存分配与回收
笔者在

其中队列指的是消息队列、栈就是函数执行栈,其基本结构如下所示:

而主要的用户创建的对象就存放在堆中,这也是我们内存分析与内存泄漏定位所需要关注的主要的区域。所谓内存,从硬件的角度来看,就是无数触发器的组合;每个触发器能够存放
var n = 374; // allocates memory for a number
var s = "sessionstack"; // allocates memory for a string
var o = {
a: 1,
b: null,
}; // allocates memory for an object and its contained values
var a = [1, null, "str"]; // (like object) allocates memory for the
// array and its contained values
function f(a) {
return a + 3;
} // allocates a function (which is a callable object)
// function expressions also allocate an object
someElement.addEventListener(
"click",
function () {
someElement.style.backgroundColor = "blue";
},
false
);
某个对象的内存生命周期分为了内存分配、内存使用与内存回收这三个步骤,当某个对象不再被需要时,它就应该被清除回收;所谓的垃圾回收器,
var o1 = {
o2: {
x: 1,
},
};
// 2 objects are created.
// 'o2' is referenced by 'o1' object as one of its properties.
// None can be garbage-collected
var o3 = o1; // the 'o3' variable is the second thing that
// has a reference to the object pointed by 'o1'.
o1 = 1; // now, the object that was originally in 'o1' has a
// single reference, embodied by the 'o3' variable
var o4 = o3.o2; // reference to 'o2' property of the object.
// This object has now 2 references: one as
// a property.
// The other as the 'o4' variable
o3 = "374"; // The object that was originally in 'o1' has now zero
// references to it.
// It can be garbage-collected.
// However, what was its 'o2' property is still
// referenced by the 'o4' variable, so it cannot be
// freed.
o4 = null; // what was the 'o2' property of the object originally in
// 'o1' has zero references to it.
// It can be garbage collected.
不过这种算法往往受制于循环引用问题,即两个无用的对象相互引用:
function f() {
var o1 = {};
var o2 = {};
o1.p = o2; // o1 references o2
o2.p = o1; // o2 references o1. This creates a cycle.
}
f();
稍为复杂的算法即是所谓的标记
内存泄漏
所谓的内存泄漏,即是指某个对象被无意间添加了某条引用,导致虽然实际上并不需要了,但还是能一直被遍历可达,以致其内存始终无法回收。本部分我们简要讨论下
上图中各项指标的含义为:
CPU usage - 当前站点的 CPU 使用量;JS heap size - 应用的内存占用量; DOM Nodes - 内存中 DOM 节点数目;JS event listeners- 当前页面上注册的 JavaScript 时间监听器数目; Documents - 当前页面中使用的样式或者脚本文件数目; Frames - 当前页面上的 Frames 数目,包括iframe 与workers ;Layouts / sec - 每秒的 DOM 重布局数目;Style recalcs / sec - 浏览器需要重新计算样式的频次;
当发现某个时间点可能存在内存泄漏时,我们可以使用


全局变量
function foo(arg) {
bar = "some text";
}
// 等价于
function foo(arg) {
window.bar = "some text";
}
另一种常见的创建全局变量的方式就是误用 this
指针:
function foo() {
this.var1 = "potential accidental global";
}
// Foo called on its own, this points to the global object (window)
// rather than being undefined.
foo();
一旦某个变量被挂载到了use strict
或者进行模块化编码
function scan(o) {
Object.keys(o).forEach(function (key) {
var val = o[key];
// Stop if object was created in another window
if (
typeof val !== "string" &&
typeof val !== "number" &&
typeof val !== "boolean" &&
!(val instanceof Object)
) {
debugger;
console.log(key);
}
// Traverse the nested object hierarchy
});
}
定时器与闭包
我们经常会使用 setInterval
来执行定时任务,很多的框架也提供了基于回调的异步执行机制;这可能会导致回调中声明了对于某个变量的依赖,譬如:
var serverData = loadData();
setInterval(function () {
var renderer = document.getElementById("renderer");
if (renderer) {
renderer.innerHTML = JSON.stringify(serverData);
}
}, 5000); //This will be executed every ~5 seconds.
定时器保有对于
var theThing = null;
var replaceThing = function () {
var originalThing = theThing;
var unused = function () {
if (originalThing)
// a reference to 'originalThing'
console.log("hi");
};
theThing = {
longStr: new Array(1000000).join("*"),
someMethod: function () {
console.log("message");
},
};
};
setInterval(replaceThing, 1000);
function sayHi() {
var allNames = [];
return (name) => {
allNames.push(name);
return "👋 " + name;
};
}
var hello = sayHi();
for (var i = 0; i < 1000000; i++) {
hello("Gandhi");
}
上述代码中
DOM 引用与监听器
有时候我们可能会将
var elements = {
button: document.getElementById("button"),
image: document.getElementById("image"),
};
function doStuff() {
elements.image.src = "http://example.com/image_name.png";
}
function removeImage() {
// The image is a direct child of the body element.
document.body.removeChild(document.getElementById("image"));
// At this point, we still have a reference to #button in the
//global elements object. In other words, the button element is
//still in memory and cannot be collected by the GC.
}
此时我们就需要将
var element = document.getElementById("launch-button");
var counter = 0;
function onClick(event) {
counter++;
element.innerHtml = "text " + counter;
}
element.addEventListener("click", onClick);
// Do stuff
element.removeEventListener("click", onClick);
element.parentNode.removeChild(element);
// Now when element goes out of scope,
// both element and onClick will be collected even in old browsers // that don't handle cycles well.
现代浏览器使用的现代垃圾回收器则会帮我们自动地检测这种循环依赖,并且予以清除;
iframe
// 子页面内
window.top.innerObject = someInsideObject
window.top.document.addEventLister(‘click’, function() { … });
// 外部页面
innerObject = iframeEl.contentWindow.someInsideObject
就有可能导致
<a href="#">Remove</a>
<iframe src="url" />
$('a').click(function(){
$('iframe')[0].contentWindow.location.reload();
// 线上环境实测重置 src 效果会更好
// $('iframe')[0].src = "javascript:false";
setTimeout(function(){
$('iframe').remove();
}, 1000);
});
或者手动地执行页面清除操作:
window.onbeforeunload = function(){
$(document).unbind().die(); //remove listeners on document
$(document).find('*').unbind().die(); //remove listeners on all nodes
//clean up cookies
/remove items from localStorage
}
Web Worker
现代浏览器中我们经常使用
function send() {
setInterval(function () {
const data = {
array1: get100Arrays(),
array2: get500Arrays(),
};
let json = JSON.stringify(data);
let arbfr = str2ab(json);
worker.postMessage(arbfr, [arbfr]);
}, 10);
}
function str2ab(str) {
var buf = new ArrayBuffer(str.length * 2); // 2 bytes for each char
var bufView = new Uint16Array(buf);
for (var i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
在实际的代码中我们应该检测
let ab = new ArrayBuffer(1);
try {
worker.postMessage(ab, [ab]);
if (ab.byteLength) {
console.log("TRANSFERABLE OBJECTS are not supported in your browser!");
} else {
console.log("USING TRANSFERABLE OBJECTS");
}
} catch (e) {
console.log("TRANSFERABLE OBJECTS are not supported in your browser!");
}