首页 > 解决方案 > store.add 中 indexedDB 中的内存泄漏示例(请参阅编辑中的示例)

问题描述

我很难理解为什么这段代码会产生内存泄漏,或者至少看起来是内存泄漏。

我正在尝试将一组测验对象写入 indexedDB 数据库。在这个阶段,我只是想测试编写不同数量不同大小的测验的速度。

仅包括写入数据库的代码部分。port.quiz[] 对象是全局定义的。代码设置为在每个测验成功编写时显示消息,并在完整事务完成时显示最终消息。

我一直无法理解的部分是为什么需要 store_data 函数的 request.onsuccess 块中的语句“delete port.quiz[code[c]]”来避免内存泄漏。如果将其注释掉,则内存使用量会随着每个测验的写入而增长,并且不会在事务结束时释放。

如果包含 delete 语句,则内存使用量会在四五个测验存储中上升,然后下降;但是,它上升到的最大量在每次下降后都会增加。在事务结束时,内存使用返回到调用 write_quizzes 函数之前的状态。

测试有点荒谬,因为我正在编写 100 个测验,每个测验包含 500 个问题,并且每个问题的组成部分都充满了比任何人都使用的更多的文本。但我正在尝试测试一个超出合理预期的最大限制。除了内存泄漏,它工作得相当好。

如果在 Windows 任务管理器中查看内存使用情况,它从大约 2MB 开始,如果不包括 delete port.quiz 语句,则到第 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

我尝试将 store_data 函数移到 write_quizzes 函数之外,以防问题与共享范围有关;并且它似乎减少了达到的最大内存使用量,但总体上存在相同的行为。

我还遇到了 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 代码,演示了遇到的问题。如果单击“构建组合”按钮,它将构建一个包含垃圾测验数据的示例组合,并在测验级别打开一个包含一个对象存储的数据库。在消息读取数据库已打开后,每次单击“写入测验”按钮都会将组合中的五十个测验对象之一写入对象存储。如果观察内存使用情况,即使在windows任务管理器中,每次点击后都会出现“Done writing quiz n to the database”的消息,并且不会在重新加载页面时释放。

我试验了几个小时,在一次试验中,我注意到现有测验键的 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 似乎没有释放内存

有趣的是,如果 gen_port 函数中的 k 循环被完全注释掉,并且 answer_choice 对象的文本属性(变量 t = 5000 个问号)增加了 26 倍为 130,000 个问号,并写为唯一每个问题对象的数据元素,这样每个问题对象仍然包含与在 k 循环中生成 26 个单独的答案选择对象的集合时所包含的数据量大致相同的数据,不会发生内存泄漏。(请参见下面代码示例中的 gen_port_2。)它仅在存在答案选择对象级别时发生:portfolio.quiz[].question[].answer_choice[].text。将每个测验写入数据库时​​内存使用量会上升,但完成后会返回到预写入级别。

这使我怀疑该问题与上面标题为“不稳定的IndexedDB 大文件存储操作导致 IndexedDB 消耗大量未释放的内存”的 bugzilla 链接的问题非常相关。当 blob 只是一大块数据而没有需要在数据库中序列化的嵌套对象级别时,修复 blob 问题可能并不能解决此问题。如三年前首次描述的链接,在 firefox.exe 的 Windows 任务管理器中观察到内存泄漏。

正如我之前所写的,我在这方面非常新手,可能会很困惑。我也不熟悉 bugzilla 以及是否可以将其添加到他们的组中,但会进行调查。也许,我的例子是对 indexedDB 提供的内容的误用;但也许不是。

对于我的特定目的,我认为我可以在写入数据库之前对测验级对象进行字符串化。我只想将测验作为一个块存储和检索,并且不需要序列化步骤来通过测验对象中的各个数据元素搜索或查询数据库。所以,我认为我的答案是能够在不泄漏内存的情况下完成我的项目所需,但这对其他人来说仍然是一个问题。

更新

不幸的是,使用 JSON.stringify 也不起作用,因为正如我所料,序列化步骤似乎分配了 RAM,而不是在完成时释放它。随着测验对象转换为字符串而不是写入数据库时​​,内存使用量会增加。因此,所有这些尝试都在改变发生内存问题的时间点。如果在不使用 JSON.stringify 的情况下将测验对象转换为字符串,则内存使用量在转换期间保持不变,但在将字符串写入数据库时​​会增加。在这两种情况下,内存几乎总是被释放,并且只有在 delete port.quiz[c] 语句没有被注释掉时才会发生;但是释放速度很慢,并且内存使用量达到了 1GB 左右的高水平,以完成任务,然后再删除,在将测验对象写入数据库或将它们转换为字符串时。而且,在没有释放内存的情况下,刷新不会释放它,只有关闭页面才会释放。如果删除 port.quiz[c] 语句被注释掉,内存使用量会增加,直到它崩溃我只有 4GB 内存的机器。

我可以将大量测验组合写入数据库而不会出现内存使用问题的唯一方法是为每个测验创建一个单独的对象存储,然后将每个测验的问题写入该存储。只要 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>

标签: memory-leaksindexeddb

解决方案


推荐阅读