我很难理解为什么这段代码会产生内存泄漏,或者至少会出现内存泄漏。
我试图将一组测验对象写入indexedDB数据库。在这个阶段,我只是试图测试编写不同大小的不同大小的测验的速度。
包含的只是写入数据库的代码部分。 port.quiz []对象是全局定义的。代码设置为在成功编写每个测验时显示消息,并在完成完整事务时显示最终消息。
我无法理解的部分是为什么语句"删除port.quiz [code [c]]"在request.onsuccess块中,需要store_data函数来避免内存泄漏。如果它被注释掉,内存使用量会随着每个测验的编写而增加,并且在事务结束时不会被释放。
如果包含删除语句,则内存使用量会增加四到五个测验存储然后下降;但是,每下降后它上升的最大量就会增加。在事务结束时,内存使用量返回到调用write_quizzes函数之前的状态。
测试有点荒谬,因为我编写了100个测验,每个测验包含500个问题,每个问题的组成部分都填充了比任何人都使用的文本更多的文本。但是我试图测试超过合理的预期最大限制。除了内存泄漏,它运行得相当好。
如果在Windows任务管理器中查看内存使用情况,则如果未包含delete port.quiz语句,则启动时大约为2MB,并在第100次测验时上升到2GB。如果包含该语句,则内存使用量上升至5-6MB然后下降至4MB,然后上升至7-8MB然后降至5MB,依此类推,直至最大值大约为1.1-1.2GB然后再降至交易结束时2MB。
我想也许问题可能就像这两个链接所描述的那样,但我还没能弄明白。
https://auth0.com/blog/four-types-of-leaks-in-your-javascript-code-and-how-to-get-rid-of-them/
https://blog.meteor.com/an-interesting-kind-of-javascript-memory-leak-8b47d2e7f156
我尝试在write_quizzes函数之外移动store_data函数,以防问题与共享范围有关;它似乎减少了达到的最大内存使用量,但总体上存在相同的行为。
我还遇到了一个针对Edge浏览器的问题,该问题涉及indexedDB生成内存泄漏;但是我一直只在Firefox上测试,无论如何都没有在链接中提供解决方案。
https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7122372/
即使使用delete语句,内存使用率也太高。包含100个测验的对象大约是2MB。那么,为什么随着每个单独测验的写作,内存使用量增加如此之多,让我感到困惑。也许,我只是忽略了一些明显和根本的东西。
感谢您提供任何指导或建议。
function write_quizzes() {
// Separating the transaction from the objectStore allows for
// separate onsuccess and onerror events.
var qz_tran = db.transaction( ["quiz data"], "readwrite" ),
store = qz_tran.objectStore("quiz data"),
code = new Array(),
i = 0, q, l,
request;
// Do something when all the data is added to the database.
qz_tran.oncomplete = function(event) {
$("#msg").text('Finished writing the ' + i + ' quizzes to database!');
}; // close oncomplete
qz_tran.onerror = function(event) {
// Don't forget to handle errors!
alert("Error writing data " + event.target.errorCode + " .");
}; // close onerror
// Writing quiz object references to an array in order to write recursively.
for ( q in port.quiz ) {
code[++i] = q;
}; // next q
l = code.length;
store_data(1);
function store_data(c) {
request = store.add( port.quiz[ code[c] ], code[c] );
request.onsuccess = function( event ) {
$('#msg').text('Writing quiz ' + code[c]);
delete port.quiz[ code[c] ];
// Why does this need to be deleted and how is it leaking memory?
if ( c + 1 < l ) { store_data(++c); };
}; // close onsuccess
request.onerror = function( event ) {
alert('write error : ' + event.target.errorCode );
}; // close onerror
} // close store_data
使用示例编辑:
下面是带有脚本的html代码,用于演示遇到的问题。如果点击“构建产品组合”&#39;按钮,它将构建垃圾测验数据的示例组合,并在测验级别打开一个具有一个对象存储的数据库。在消息显示数据库已打开后,每次点击“写测验”即可。按钮会将五十个测验对象中的一个从项目组合中写入对象库。如果观察内存使用情况,即使在Windows任务管理器中,在消息读取“完成向数据库写入测验”后,每次点击都会增加,并且在重新加载页面时不会释放。
我试验了几个小时,在一次试验中,我注意到现有测验密钥的store.put会增加内存使用量一秒左右,然后返回到点击前级别;但对于一个新密钥,put会占用内存并保持它就像store.add在这里一样。
我几乎是indexedDB的新手,但只能得出结论问题是使用store.add或者因为投资组合对象是全局的。我承认我不了解MDN网络文档中有关store.add创建结构化克隆和值序列化的信息,但想知道该步骤中的某些内容是否不会释放内存。
https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/add
https://html.spec.whatwg.org/multipage/structured-data.html#structured-clone
这两个链接的报告日期为2015年,但最近的更新时间为29天;他们描述的问题,特别是它出现在较大的对象而不是较小的对象上,似乎与这种情况类似,因为我试图测试多个大型测验对象的编写。或者,我的测试用例对于单个对象来说有点不切实际,而且测试大小太大了。
竞争条件可能导致冗余的IDBFS存储同步操作。 https://github.com/kripken/emscripten/issues/3908
不稳定的IndexedDB大文件存储操作导致IndexedDB消耗大量未释放的内存。 https://bugzilla.mozilla.org/show_bug.cgi?id=1223782
也是相关的,也是最近的,还有待回答..
Indexeddb seems to not free memory
有趣的是,如果gen_port函数中的k循环被完全注释掉,那么answer_choice对象的文本属性(变量t = 5000个问号)增加了26倍,成为130,000个问号并写入作为每个问题对象的唯一数据元素,使得每个问题对象仍然包含与在k循环中生成的26个单独的答案选择对象的集合时相同数量的数据,不发生内存泄漏。 (请参阅下面的代码示例中的gen_port_2。)仅当存在答案选择对象级别时才会发生:portfolio.quiz []。question [] .ansch_choice [] .text。在将每个测验写入数据库时,内存使用量会跳跃,但完成后会返回到预写级别。
这让我怀疑这个问题与上面名为&#39; Erratic IndexedDB大文件存储操作的bugzilla链接非常相关,导致IndexedDB消耗大量未释放的内存&#39。也许,当blob只是一大块数据而没有需要在数据库中进行序列化的嵌套对象级别时,修复blob问题并不能解决这个问题。在firefox.exe的Windows任务管理器中观察到内存泄漏,这是三年前首次描述的链接。
正如我之前所写,我在这方面非常新手,可能会非常困惑。我也不熟悉bugzilla以及我是否可以将其添加到他们的小组但是会进行调查。也许,我的例子是滥用indexedDB打算提供的内容;但也许不是。
出于我的特定目的,我想我可以在写入数据库之前对测验级对象进行字符串化。我只想将测验存储和检索为块,并且不需要序列化步骤来通过测验对象中的各个数据元素搜索或查询数据库。所以,我认为我能够在不泄漏记忆的情况下完成项目所需的工作,但这对其他人来说仍然是一个问题。
更新
不幸的是,使用JSON.stringify并不起作用,因为正如我所料,序列化步骤似乎分配RAM而不是在完成时释放它。当测验对象转换为字符串而不是写入数据库时,内存使用量会增加。因此,所有这些尝试都在改变内存问题的发生。如果在不使用JSON.stringify的情况下将测验对象转换为字符串,则在转换期间内存使用率保持不变,但在将字符串写入数据库时会增加。在这两种情况下,内存几乎总是被释放,并且仅在delete port.quiz [c]语句未被注释掉时才会发生;但是,当测验对象被写入数据库或者当它们被转换为字符串时,释放发生的速度很慢并且内存使用率达到1GB左右的高水平以完成任务。并且,在没有释放内存的情况下,刷新不会释放它,只关闭页面。如果删除了删除port.quiz [c]语句,则内存使用量会增加,直到它崩溃我的只有4GB RAM的计算机。
我可以在没有内存使用问题的情况下向数据库编写大量测验组合的唯一方法是为每个测验创建一个单独的对象存储,然后将每个测验的问题写入该存储。只要delete port.quiz [c]语句处于活动状态,较小的对象就不会引起内存使用问题。因此,我可以编写更大量的数据,而不会达到前一种情况的一半内存使用量。
这个问题似乎没什么兴趣;但如果你理解为什么会发生这种情况和/或如何纠正它,请解释一下。谢谢。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<script src="jquery-3.3.1.js"></script>
<style>
html { background-color: rgb(73,110,147); }
</style>
</head>
<header>
</header>
<body>
<div style='width: 750px; min-height: 100px; margin: 0 auto; border: 1px solid black; background-color: white;'>
<p id="msg" style="margin: 25px auto; text-align: center;">IndexedDB Testing</p>
<button id="build_port">Build Portfolio</button>
<button id="write_quiz">Write Quiz</button>
</div>
</body>
<script>
"use strict";
$(document).ready( function() {
var db, c = 0, port;
$("#build_port").click( load_port );
$("#write_quiz").click( function() { add_quiz(++c); } );
function load_port() {
$("#msg").text('Generating quiz portfolio');
setTimeout( function() { port = gen_port(50); open_db(); }, 10 );
} // close load_port
function add_quiz(c) {
var qz_tran = db.transaction( ["quiz data"], "readwrite" ),
store = qz_tran.objectStore("quiz data"),
request;
qz_tran.oncomplete = function(event) {
$("#msg").text('Done writing quiz ' + c + ' to the database!');
};
qz_tran.onerror = function(event) {
alert("Error Writing data " + event.target.errorCode + " .");
};
request = store.add( port.quiz[ c ], c );
request.onsuccess = function( event ) {
$('#msg').text( 'Writing quiz ' + c );
//delete port.quiz[ c ];
};
request.onerror = function( event ) { alert('write error : ' + event.target.errorCode ); };
} // close add_quiz
function open_db() {
if ( !window.indexedDB ) {
alert("Your browser doesn't support a stable version of IndexedDB. Such and such feature will not be available.");
}; // end if
// Temporarily deleting each time for testing purposes.
// Will later need to test for an existing databases just like did for localStorage.
indexedDB.deleteDatabase("quizDB");
// Attempt to open a database.
var status = 'Existing',
req = window.indexedDB.open( "quizDB", 1 );
req.onerror = function(event) {
alert('Attempt to open the portfolio database failed. Error code : ' + event.target.errorCode + '.' );
}; // close onerror
req.onsuccess = function(event) {
db = this.result;
db.onerror = function(event) {
// Generic error handler for all errors targeted at this database's requests!
alert( 'Database error: ' + event.target.errorCode + '.' );
}; // close onerror
$("#msg").text('opened/created database as : ' + status );
}; // close onsuccess
req.onupgradeneeded = function(event) {
var db = event.target.result;
status = 'New';
db.createObjectStore( "quiz data" );
}; // close onupgradeneeded
} // close open_db
function gen_port(n) {
var i, j, k, port = new Object(),
t = new Array(5000).join('?');
port.quiz = new Object();
for ( i = 1; i <= n; i++ ) {
port.quiz[i] = new Object();
port.quiz[i].name = 'Quiz ' + i;
port.quiz[i].question = new Object();
for ( j = 1; j <= 500; j++ ) {
port.quiz[i].question[j] = new Object();
for ( k = 1; k <= 26; k++ ) {
port.quiz[i].question[j]['answer_choice_' + k] = new Object();
port.quiz[i].question[j]['answer_choice_' + k].text = 'Quiz ' + j + ', Question ' + j + ' AC ' + k + ' : ' + t;
port.quiz[i].question[j]['answer_choice_' + k].checked = true;
port.quiz[i].question[j]['answer_choice_' + k].pos = k;
}; // next k
}; // next j question
}; // next i quiz
$("#msg").text('Done generationg quiz portfolio');
return port;
} // close gen_port
function gen_port_2(n) {
var i, j, k, port = new Object(),
t = new Array(130000).join('?');
port.quiz = new Object();
for ( i = 1; i <= n; i++ ) {
port.quiz[i] = new Object();
port.quiz[i].name = 'Quiz ' + i;
port.quiz[i].question = new Object();
for ( j = 1; j <= 500; j++ ) {
port.quiz[i].question[j] = new Object();
port.quiz[i].question[j] = t;
}; // next j question
}; // next i quiz
$("#msg").text('Done generationg quiz portfolio');
return port;
} // close gen_port
}); // close document ready
</script>
</html>