龙盟编程博客 | 无障碍搜索 | 云盘搜索神器
快速搜索
主页 > web编程 > Javascript编程 >

深入浅出JavaScript内存泄漏(1)(4)

时间:2013-03-06 14:58来源:未知 作者:admin 点击:
分享到:
Figure 3. DOM插入顺序泄漏模型 接下来,我们将给出一个躲避了大多数泄漏检测算法的泄漏示例。因为我们实际上没有泄漏任何可见的元素,并且由于被泄漏

Figure 3. DOM插入顺序泄漏模型

接下来,我们将给出一个躲避了大多数泄漏检测算法的泄漏示例。因为我们实际上没有泄漏任何可见的元素,并且由于被泄漏的对象太小从而你可能根本不会注意这 个问题。为了使我们的示例产生泄漏,在动态创建的元素结构中将不得不内联的包含一个脚本函数指针。在我们设置好这些元素间的相互隶属关系后这将会使我们泄 漏内部临时脚本对象。由于这个泄漏很小,我们不得不将示例执行成千上万次。

事实上,一个对象的泄漏只有很少的字节。在运行示例并将浏览器导航到一个空白页 面,你将会看到两个版本代码在内存使用上的区别。当我们使用第一种方法,将子元素加入其父元素再将构成的子树加入页面DOM,我们的内存使用量会有微小的 上升。这就是一个交叉导航泄漏,只有当我们重新启动IE进程这些泄漏的内存才会被释放。如果你使用第二种方法将父元素加入页面DOM再将子元素加入其父元 素中,同样运行若干次后,你的内存使用量将不会再上升,这时你会发现你已经修复了交叉导航泄漏的问题。

  1. <html> 
  2. <head> 
  3. <script language="JavaScript"> 
  4.  
  5. function LeakMemory()  
  6. {  
  7. var hostElement = document.getElementById("hostElement");  
  8.  
  9. // Do it a lot, look at Task Manager for memory response  
  10.  
  11. for(i = 0; i < 5000; i++)  
  12. {  
  13. var parentDiv =  
  14. document.createElement("<div onClick='foo()'>");  
  15. var childDiv =  
  16. document.createElement("<div onClick='foo()'>");  
  17.  
  18. // This will leak a temporary object  
  19. parentDiv.appendChild(childDiv);  
  20. hostElement.appendChild(parentDiv);  
  21. hostElement.removeChild(parentDiv);  
  22. parentDiv.removeChild(childDiv);  
  23. parentDiv = null;  
  24. childDiv = null;  
  25. }  
  26. hostElement = null;  
  27. }  
  28.  
  29. function CleanMemory()  
  30. {  
  31. var hostElement = document.getElementById("hostElement");  
  32.  
  33. // Do it a lot, look at Task Manager for memory response  
  34.  
  35. for(i = 0; i < 5000; i++)  
  36. {  
  37. var parentDiv =  
  38. document.createElement("<div onClick='foo()'>");  
  39. var childDiv =  
  40. document.createElement("<div onClick='foo()'>");  
  41.  
  42. // Changing the order is important, this won't leak  
  43. hostElement.appendChild(parentDiv);  
  44. parentDiv.appendChild(childDiv);  
  45. hostElement.removeChild(parentDiv);  
  46. parentDiv.removeChild(childDiv);  
  47. parentDiv = null;  
  48. childDiv = null;  
  49. }  
  50. hostElement = null;  
  51. }  
  52. </script> 
  53. </head> 
  54.  
  55. <body> 
  56. <button onclick="LeakMemory()">Memory Leaking Insert</button> 
  57. <button onclick="CleanMemory()">Clean Insert</button> 
  58. <div id="hostElement"></div> 
  59. </body> 
  60. </html> 

这类泄漏应该被澄清,因为这个解决方法有悖于我们在IE中的一些有益经验。创建带有脚本对象的DOM元素,以及它们已进行的相互关联是了解这个泄漏的关键 点。这实际上这对于泄漏来说是至关重要的,因为如果我们创建的DOM元素不包含任何的脚本对象,同时使用相同的方式将它们进行关联,我们是不会有任何泄漏 问题的。示例中给出的第二种技巧对于关联大的子树结构可能更有效(由于在那个示例中我们一共只有两个元素,所以建立一个和页面DOM不相关的树结构并不会 有什么效率问题)。

第二个技巧是在创建元素的开始不关联任何的脚本对象,所以你可以安全的创建子树。当你把你的子树关联到页面DOM上后,再继续处理你需 要的脚本事件。牢记并遵守关于循环引用和闭包函数的使用规则,你不会再在挂接事件时在你的代码中遇到不同的泄漏。

我真的要指出这个问题,因为我们可以看出不是所有的内存泄漏都是可以很容易发现的。它们可能都是些微不足道的问题,但往往需要成千上万次的执行一个更小的 泄漏场景才能使问题显现出来,就像DOM元素插入顺序引起的问题那样。如果你觉得使用所谓的"最佳"经验来编程,那么你就可以高枕无忧,但是这个示例让我 们看到,即使是"最佳"经验似乎也可能带来泄漏。我们这里的解决方案希望能提高这些已有的好经验,或者介绍一些新经验使我们避免泄漏发生的可能。

貌似泄漏(Pseudo-Leaks)

在大多数时候,一些APIs的实际的行为和它们预期的行为可能会导致你错误的判断内存泄漏。貌似泄漏大多数时候总是出现在同一个页面的动态脚本操作中,而 在从一个页面跳转到空白页面的时候发生是非常少见的。那你怎么能象排除页面间泄漏那样来排除这个问题,并且在新任务运行中的内存使用量是否是你所期望的。 我们将使用脚本文本的重写来作为一个貌似泄漏的示例。

象DOM插入顺序问题那样,这个问题也需要依赖创建临时对象来产生"泄漏"。对一个脚本元素对象内部的脚本文本一而再再而三的反复重写,慢慢地你将开始泄 漏各种已关联到被覆盖内容中的脚本引擎对象。特别地,和脚本调试有关的对象被作为完全的代码对象形式保留了下来。

  1. <html> 
  2. <head> 
  3. <script language="JavaScript"> 
  4. function LeakMemory()  
  5. {  
  6. // Do it a lot, look at Task Manager for memory response  
  7. for(i = 0; i < 5000; i++)  
  8. {  
  9. hostElement.text = "function foo() { }";  
  10. }  
  11. }  
  12. </script> 
  13. </head> 
  14. <body> 
  15. <button onclick="LeakMemory()">Memory Leaking Insert</button> 
  16. <script id="hostElement">function foo() { }</script> 
  17. </body> 
  18. </html> 

如果你运行上面的示例代码并使用任务管理器查看,当从"泄漏"页面跳转到空白页面时,你并不会注意到任何脚本泄漏。因为这种脚本泄漏完全发生在页面内部, 而且当你离开该页面时被使用的内存就会回收。对于我们原本所期望的行为来说这样的情况是糟糕的。

你希望当重写了脚本内容后,原来的脚本对象就应该彻底的从 页面中消失。但事实上,由于被覆盖的脚本对象可能已用作事件处理函数,并且还可能有一些未被清除的引用计数。正如你所看到的,这就是貌似泄漏。在表面上内存消耗量可能看起来非常的糟糕,但是这个原因是完全可以接受的。

总结

每一位Web开发员可能都整理有一份自己的代码示例列表,当他们在代码中看到如列表中的代码时,他们会意识到泄漏的存在并会使用一些开发技巧来避免这些问 题。这样的方法虽然简单便捷,但这也是今天Web页面内存泄漏普遍存在的原因。考虑我们所讨论的泄漏情景而不是关注独立的代码示例,你将会使用更加有效的策略来解决泄漏问题。这样的观念将使你在设计阶段就把问题估计到,并且确保你有计划来处理潜在的泄漏问题。

使用编写加固代码(译者注:就是异常处理或清理对象等的代码)的习惯并且采取清理所有自己占用内存的方法。虽然对这个问题来说可能太夸张了,你也可能几乎从没有见到编写脚本却需要自己清理自己占用的内 存的情况;使这个问题变得越来越显著的是,脚本变量和expando属性间存在的潜在泄漏可能。

精彩图集

赞助商链接