如何删除JavaScript画布渲染上下文的状态堆栈?

时间:2016-05-01 07:30:13

标签: javascript canvas memory-management memory-leaks html5-canvas

我最近在JavaScript中使用<canvas>,并发现了创建一个非常糟糕的“内存泄漏”的可能性(更像是内存爆炸)。使用画布上下文时,您可以context.save()将绘图样式添加到“状态堆栈”,并context.restore()删除它。 (见the documentation for the rendering context on MDN。)

当您碰巧在没有恢复的情况下不断保存到状态堆栈时,会出现问题。在Chrome v50和Firefox v45中,这似乎占用了越来越多的私有内存,最终导致浏览器选项卡崩溃。 (顺便提一下,JavaScript内存在Chrome中不受影响,因此使用分析器/时间线工具进行调试非常困难。)

我的问题:如何清除或删除画布上下文的状态堆栈?使用普通数组,您可以检查length,修剪它splice或者只是重置为空[],但我还没有看到使用状态堆栈执行此操作的方法。

1 个答案:

答案 0 :(得分:0)

  

[我]发现了创造一个非常糟糕的“内存泄漏”的可能性

这在技术上不是内存泄漏。泄漏将是分配内存并松开指向它的指针,因此无法释放它。在这种情况下,跟踪指针但未释放内存。

  

当您碰巧在没有恢复的情况下不断保存到状态堆栈时会出现问题。

这是可以预料的。分配内存而不释放它将累积分配的内存块。

  

如何清除或删除画布上下文的状态堆栈?

唯一的方法是恢复所有已保存的状态,或通过将某个大小设置为canvas元素(即canvas.width = canvas.width)来重置上下文。

调用restore()save()更多次也是安全的(在这种情况下它只是在没有做任何事情的情况下返回)所以你理论上可以通过n个数的循环运行它迭代。后者在不良练习类别中会更多。

但话虽如此:如果保存和恢复的数量在假设相等时不匹配,通常表示代码中的其他地方存在问题。通过重置或在后期运行多个恢复来解决问题可能只会有助于掩盖实际问题。

以下是如何跟踪保存/恢复呼叫计数的示例 -

// NOTE: this code needs to run before a canvas context is created
CanvasRenderingContext2D.prototype.__save = CanvasRenderingContext2D.prototype.save;
CanvasRenderingContext2D.prototype.__restore = CanvasRenderingContext2D.prototype.restore;

// Our patch vectors
CanvasRenderingContext2D.prototype.__tracker = 0;
CanvasRenderingContext2D.prototype.save = function() {
  this.__tracker++;
  console.log("Track save:", this.__tracker);
  this.__save() 
}

CanvasRenderingContext2D.prototype.restore = function() {
  this.__tracker--;
  console.log("Track restore:", this.__tracker);
  this.__restore() 
}

// custom method to dump status
CanvasRenderingContext2D.prototype.trackstat = function() {
  if (this.__tracker)
    console.warn("Track stat:", this.__tracker);
  else
    console.log("Track stat: OK");
}

var ctx = document.createElement("canvas").getContext("2d");
ctx.save();                     // do a couple of save()s
ctx.save();
ctx.restore();                  // single restore()
ctx.trackstat();                // should report mismatch of 1
ctx.restore();                  // last restore()
ctx.trackstat();                // should report OK