优秀的编程知识分享平台

网站首页 > 技术文章 正文

如何在javascript中检测一个对象是否已经被垃圾回收了

nanyue 2024-10-24 11:44:05 技术文章 2 ℃

原文链接: http://stevehanov.ca/blog/?id=148

原作者:steve hanov

如果你在使用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上使用的时候,它会被框架反复的显示/隐藏,这时候资源完全释放就至关重要了。

如何使用

Tags:

最近发表
标签列表