如何在javascript中检测一个对象是否已经被垃圾回收了
如何在javascript中检测一个对象是否已经被垃圾回收了
随着前端应用越来越复杂,内存泄露也同样影响了javascript应用。关键对象是否被正常回收,应该怎么跟踪。
原文 http://stevehanov.ca/blog/?id=148
如果你在使用javascript写一个应用程序,迟早你会担心内存泄露。不过知道是否存在内存泄露都挺困难的,下面就是个有用技巧。
WeakMap
一开始,你可能会想使用WeakMap。WeakMap/WeakSet 能够持有对象,不过不会阻止对象被垃圾回收。一个对象的实例被垃圾回收了,它就会从 WeakMap/WeakSet中移出。
所以,一个明显的解决方案是检查一个对象是否还在WeakMap中。如果找不到,那么它就被回收了。
可惜这个方法行不通。
问题在于WeakMap和WeakSet的设计使你知道对象在那里,才能查找对象。这是因为,为了查找一个对象,你需要已经持有这些对象的引用。这些集合甚至没有一个length
的方法。
为了检查一个对象是否在WeakMap中,你必须已经持有一个对它的引用,所以你也阻止了这个对象被垃圾回收。
所以,它们有什么用?WeakMap 最好是用来把对象联系到一起。比如,如果你有一堆<img> 元素,而且你想把一些数据和他们联系起来,你可能简单的使用 img.myextraproperty="blah"
.不过你的IDE可能会警告你,因为HTMLImageElement 没有这个属性。换个方法,你可以用WeakMap。如果额外的属性是一个值 true
,那么可以用WeakSet。
实际的解决方案
有些浏览器,包括Chrome 不过不包含Firefox,有能力检测javascript使用的内存大小。所以检测一个对象是否存在的方法,是让它足够大到可以显著的影响内存占用的大小。
在下面的代码中,我用WeakMap把你传入的任意对象关联了一个1G大的对象。当这个对象没有引用了,垃圾回收开始执行,你可以预期至少1GB的内存会被回收。这就是这个代码要的检查的东西。这个过程至少会用掉10秒钟,因为看起来Chrome每10秒运行一次垃圾回收。
/** 检测一个对象是否被回收
@param obj 检测对象
@param freeFn 释放对象的方法
@returns 返回一个promise {freed: boolean, memoryDiff:number}
@author Steve Hanov <steve.hanov@gmail.com>
*/
function isObjectFreed(obj, freeFn) {
return new Promise( (resolve) => {
if (!performance.memory) {
throw new Error("Browser not supported.");
}
// When obj is GC'd, the large array will also be GCd and the impact will
// be noticeable.
const allocSize = 1024*1024*1024;
const wm = new WeakMap([[obj, new Uint8Array(allocSize)]]);
// wait for memory counter to update
setTimeout( () => {
const before = performance.memory.usedJSHeapSize;
// Free the memory
freeFn();
// wait for GC to run, at least 10 seconds
setTimeout( () => {
const diff = before - performance.memory.usedJSHeapSize;
resolve({
freed: diff >= allocSize,
memoryDiff: diff - allocSize
});
}, 10000);
}, 100);
});
}
let foo = {bar:1};
isObjectFreed(foo, () => foo = null).then( (result) => {
document.write(`Object GCd:${result.freed}, ${result.memoryDiff} bytes freed`)
}, (error) => {
document.write(`Error: ${error.message}`)
})
如何使用
这个方法是我的javascript绘画应用 Zwibbler 项目的测试程序的一部分。它有个destory()方法来移除所有资源。不过有时,我会忘记移除一些事件监听器,这会保持对整个应用的引用。所以当在一些 React或者Angular上使用的时候,它会被框架反复的显示/隐藏,这时候资源完全释放就至关重要了。
- 本文标签: javascript
-
声明&链接
本站原创文章,于2020年07月19日由51soupan.cn发布,转载请注明出处