我有一个方法,每2秒运行一次,将视频流捕获到画布并将其写入文件:
function capture(streamName, callback) {
var buffer,
dataURL,
dataSplit,
_ctx;
_ctx = _canvas[streamName].getContext('2d');
_ctx.drawImage(_video[streamName], 0, 0);
dataURL = _canvas[streamName].toDataURL('image/png');
dataSplit = dataURL.split(",")[1];
buffer = new Buffer(dataSplit, 'base64');
fs.writeFileSync(directory + streamName + '.png', buffer);
}
setInterval(function() {
// Called from here
captureState.capture(activeScreens[currentScreenIndex]);
gameState.pollForState(processId, activeScreens[currentScreenIndex], function() {
// do things...
});
}, 2000);
假设_video[streamName]
作为正在运行的<video>
存在而_canvas[streamName]
作为<canvas>
存在。该方法有效,它只会导致内存泄漏。
垃圾收集无法跟上方法使用的内存量,随之发生内存泄漏。
我已将其缩小到这一行:
buffer = new Buffer(dataSplit, 'base64');
如果我发表评论,会有一些内存累积(~100MB),但每30秒左右会下降一次。
有些帖子建议buffer = null;
删除引用并标记垃圾回收,但这并未改变任何内容。
有什么建议吗?
时间轴: https://i.imgur.com/wH7yFjI.png https://i.imgur.com/ozFwuxY.png
分配资料: https://www.dropbox.com/s/zfezp46um6kin7g/Heap-20160929T140250.heaptimeline?dl=0
只是为了量化。运行约30分钟后,它使用2 GB内存。这是一个电子(铬/桌面)应用程序。
解决
预先分配缓冲区就是修复它的原因。这意味着除了在函数外部使用buffer
作用域之外,还需要使用buffer.write
重用创建的缓冲区。为了保持正确的标头,请确保使用encoded
的{{1}}参数。
答案 0 :(得分:2)
Matt,我不确定什么没有使用预先分配的缓冲区,所以我发布了一个如何使用这种预分配缓冲区的算法。这里的关键是缓冲区只分配一次,因为不应该有任何内存泄漏。
var buffers = [];
var bsize = 10000;
// allocate buffer pool
for(var i = 0; i < 10; i++ ){
buffers.push({free:true, buf: new Buffer(bsize)});
}
// sample method that picks one of the buffers into use
function useOneBuffer(data){
// find a free buffer
var theBuf;
var i = 10;
while((typeof theBuf==='undefined')&& i < 10){
if(buffers[i].free){
theBuf = buffers[i];
}
i++;
}
theBuf.free = false;
// start doing whatever you need with the buffer, write data in needed format to it first
// BUT do not allocate
// also, you may want to clear-write the existing data int he buffer, just in case before reuse or after the use.
if(typeof theBuf==='undefined'){
// return or throw... no free buffers left for now
return;
}
theBuf.buf.write(data);
// .... continue using
// dont forget to pass the reference to the buffers member along because
// when you are done, toy have to mark it as free, so that it could be used again
// theBuf.free = true;
}
你试过这样的事吗?它在哪里失败了?
答案 1 :(得分:0)
代码中没有缓冲区对象泄漏。
您不再在代码中保留引用的任何Buffer对象将立即可用于垃圾回收。
回调引起的问题以及如何将其用于捕获函数。 请注意,只要回调正在运行,GC就无法清除缓冲区或任何其他变量。
答案 2 :(得分:0)
最近我遇到了类似的问题,一个软件应用程序使用了arrayBuffer形式的~500MB数据。我以为我有内存泄漏,但事实证明Chrome正在尝试对一组大型ArrayBuffer和相应的操作进行优化(每个缓冲区大小约为60mb,一些稍大的对象)。 CPU使用率似乎永远不允许GC运行,或者至少它是如何运行的。我必须做两件事来解决我的问题。我没有阅读任何关于何时GC被安排证明或反驳的具体规范。我必须做的事情:
在应用这两个步骤之后,事情就为我而动,并且被垃圾收集了。不幸的是,当将这两个东西彼此独立地应用时,我的应用程序一直在崩溃(在这之前爆炸成GB的内存)。以下是我对您的代码尝试的看法。
垃圾收集器的问题是你无法强制它运行。因此,您可以拥有准备进行malloced的对象,但无论出于何种原因,浏览器都无法为垃圾收集器提供机会。 buffer = null
的另一种方法是用delete
operator明确地打破引用 - 这就是我所做的,但理论上... = null
是等价的。请注意,delete
无法在var
运算符创建的任何变量上运行,这一点非常重要。因此,我的建议如下:
function capture(streamName, callback) {
this._ctx = _canvas[streamName].getContext('2d');
this._ctx.drawImage(_video[streamName], 0, 0);
this.dataURL = _canvas[streamName].toDataURL('image/png');
this.dataSplit = dataURL.split(",")[1];
this.buffer = new Buffer(dataSplit, 'base64');
fs.writeFileSync(directory + streamName + '.png', this.buffer);
delete this._ctx;//because the context with the image used still exists
delete this.dataURL;//because the data used in dataSplit exists here
delete this.dataSplit;//because the data used in buffer exists here
delete this.buffer;
//again ... = null likely would work as well, I used delete
}
第二,小休息。因此,您似乎已经进行了一些密集的流程,系统无法跟上。它实际上没有达到2s保存标记,因为每次保存需要超过2秒。队列上总有一个函数用于执行captureState.capture(...)
方法,它从来没有时间进行垃圾收集。调度程序上的一些有用的帖子以及setInterval和setTimeout之间的差异:
http://javascript.info/tutorial/settimeout-setinterval
http://ejohn.org/blog/how-javascript-timers-work/
如果确实如此,为什么不使用setTimeout
并简单检查大约2秒(或更长)的时间已经过去并执行。在执行该检查时,始终强制您的代码在保存之间等待一段时间。给浏览器时间来安排/运行GC - 类似于以下内容(pollForState中的100 ms setTimeout):
var MINIMUM_DELAY_BETWEEN_SAVES = 100;
var POLLING_DELAY = 100;
//get the time in ms
var ts = Date.now();
function interValCheck(){
//check if 2000 ms have passed
if(Date.now()-ts > 2000){
//reset the timestamp of the last time save was run
ts = Date.now();
// Called from here
captureState.capture(activeScreens[currentScreenIndex]);
//upon callback, force the system to take a break.
setTimeout(function(){
gameState.pollForState(processId, activeScreens[currentScreenIndex], function() {
// do things...
//and then schedule the interValCheck again, but give it some time
//to potentially garbage collect.
setTimeout(intervalCheck,MINIMUM_DELAY_BETWEEN_SAVES);
});
}
}else{
//reschedule check back in 1/10th of a second.
//or after whatever may be executing next.
setTimeout(intervalCheck,POLLING_DELAY);
}
}
这意味着每2秒捕获的次数不会超过一次,但在某种意义上也会诱使浏览器有时间使用GC并删除剩下的任何数据。
最后的想法,娱乐更传统的内存泄漏定义,基于我在代码中看到的内存泄漏的候选者将出现activeScreens
,_canvas
或_video
成为某种对象?如果上述问题无法解决您的问题(无法根据当前共享的内容进行任何评估),那么探讨这些问题可能是值得的。
希望有所帮助!
答案 3 :(得分:0)
我已将其缩小到这一行:
buffer = new Buffer(dataSplit, 'base64');
简短解决方案不是使用Buffer
,因为没有必要将文件写入文件系统,其中base64
data URI
部分存在文件引用。 setInterval
似乎没有被清除。您可以为setInterval
定义参考,然后在clearInterval()
<video>
事件中致电ended
。
您可以在不声明任何变量的情况下执行功能。按照NodeJS: Saving a base64-encoded image to disk,Answer所述,删除data
返回的MIME
,base64
类型和data URI
部分HTMLCanvasElement.prototype.toDataURL()
部分NodeJS write base64 image-file
function capture(streamName, callback) {
_canvas[streamName].getContext("2d")
.drawImage(_video[streamName], 0, 0);
fs.writeFileSync(directory + streamName + ".png"
, _canvas[streamName].toDataURL("image/png").split(",")[1], "base64");
}
var interval = setInterval(function() {
// Called from here
captureState.capture(activeScreens[currentScreenIndex]);
gameState.pollForState(processId, activeScreens[currentScreenIndex]
, function() {
// do things...
});
}, 2000);
video[/* streamName */].addEventListener("ended", function(e) {
clearInterval(interval);
});