热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

[转]UnderstandingandSolvingInternetExplorerLeakPatterns

转自:http:msdn.microsoft.comen-uslibrarybb250448.aspxTheEvolutionoftheWebDeveloperInt

转自:http://msdn.microsoft.com/en-us/library/bb250448.aspx

The Evolution of the Web Developer

In the past, memory leaks haven't posed huge problems for Web developers. Pages were kept relatively simple and navigation between different locations within a site was a great way to clean up any loose memory. If there was a leak, it was most likely small enough to go unnoticed.

New Web applications live up to higher standards. A page might run for hours without being navigated and retrieve updated information dynamically through Web services. Language features are pushed to the breaking point by combining complex event schemes, object-oriented JScript, and closures to produce entire applications. With these and other changes, certain memory leak patterns are becoming more prominent, especially those previously hidden by navigation.

The good news is that memory leak patterns can be easily spotted if you know what to look for. Most of the troublesome patterns you might face have known workarounds requiring only a small amount of extra work on your behalf. While some pages might still fall prey to small memory leaks, the most noticeable ones can be easily removed.

Leak Patterns

The following sections will discuss patterns of memory leaks and point out some common examples of each pattern. One great example of a pattern is the closure feature of JScript, while another example is the use of closures in hooking events. If you're familiar with the event hooking example, you might be able to find and fix many of your memory leaks, but other closure-related issues might go unnoticed.

Now, let's look at the following patterns:

  1. Circular References—When mutual references are counted between Internet Explorer's COM infrastructure and any scripting engine, objects can leak memory. This is the broadest pattern.

  2. Closures—Closures are a specific form of circular reference that pose the largest pattern to existing Web application architectures. Closures are easy to spot because they rely on a specific language keyword and can be searched for generically.

  3. Cross-Page Leaks—Cross-page leaks are often very small leaks of internal book-keeping objects as you move from site to site. We'll examine the DOM Insertion Order issue, along with a workaround that shows how small changes to your code can prevent the creation of these book-keeping objects.

  4. Pseudo-Leaks—These aren't really leaks, but can be extremely annoying if you don't understand where your memory is going. We'll examine the script element rewriting and how it appears to leak quite a bit of memory, when it is really performing as required.

Circular References

Circular references are the root of nearly every leak. Normally, script engines handle circular references through their garbage collectors, but certain unknowns can prevent their heuristics from working properly. The unknown in the case of IE would be the status of any DOM elements that a portion of script has access to. The basic principle would be as follows:

Figure 1 Basic Circular Reference Pattern

Figure 1. Basic Circular Reference Pattern

The cause of the leak in this pattern is based on COM reference counting. The script engine objects will hold a reference to the DOM element and will be waiting for any outstanding references to be removed before cleaning up and releasing the DOM element pointer. In our case we have two references on the script engine object: the script engine scope, and the DOM element expando property. While terminating the script engine will release the first reference, the DOM element reference will never be released because it is waiting on the script engine object to release it! You might think it would be easy to detect this scenario and fix the problem, but in practice the basic case presented is only the tip of the iceberg. You could have circular references at the end of a 30 object chain and those would be much harder to detect.

If you are wondering what this pattern looks like in HTML, you can cause a leak by using a global script engine variable and a DOM element as shown.









To break the leak pattern you can make use of explicit null assignments. By assigning null before the document unloads you are telling the script engine there is no longer an association between the element and the object inside the engine. It can now properly clean up references and will release the DOM element. In this case, you as the Web developer know more about the relationships between your objects than the script engine does.

While that is the basic pattern, it can be difficult to spot more complex scenarios. A common usage of object-oriented JScript is to extend DOM elements by encapsulating them inside of a JScript object. During the construction process, you generally pass in the DOM element you want to attach to and then store a reference to the DOM element on the newly constructed object while at the same time storing an instance of the newly constructed object on the DOM element. That way your application model always has access to everything it needs. The problem is this is a very explicit circular reference, but because it uses different language aspects it might go unnoticed. Breaking up this kind of pattern can become more complex, and you can use the same simple methods discussed earlier.










More complex solutions to this problem involve registration schemes to note which elements/properties need to be unhooked, having the peer element hook events so that it can clean up before the document unloads, but often you can run into additional leak patterns without actually fixing the problem.

Closures

Closures are very often responsible for leaks because they create circular references without the programmer being fully aware. It isn't immediately obvious that parent function parameters and local variables will be frozen in time, referenced, and held until the closure itself is released. In fact this has become such a common programming tactic, and users have run into issues so often, there are quite a few resources already available. Because they detail some of the history behind closures as well as some of the specific instances of closure leaks we'll check those out after applying the closure model to our circular reference diagram and figuring out where these extra references are coming from.

Figure 2 Circular References with Closures

Figure 2. Circular References with Closures

With normal circular references there were two solid objects holding references to each other, but closures are different. Rather than make the references directly, they are made instead by importing information from their parent function's scope. Normally, a function's local variables and the parameters used when calling a function only exist for the lifetime of the function itself. With closures, these variables and parameters continue to have an outstanding reference as long as the closure is alive, and since closures can live beyond the lifetime of their parent function so can any of the locals and parameters in that function. In the example, Parameter 1 would normally be released as soon as the function call was over. Because we've added a closure, a second reference is made, and that second reference won't be released until the closure is also released. If you happened to attach the closure to an event, then you would have to detach it from that event. If you happened to attach the closure to an expando then you would need to null that expando.

Closures are also created per call, so calling this function twice will create two individual closures, each holding references to the parameters passed in each time. Because of this transparent nature it is really easy to leak closures. The following example provides the most basic of leaks using closures:









If you are wondering how to break this leak, it won't be as easy as a normal circular reference. The "closure" can be viewed as a temporary object that exists in the function scope. Once the function exits, you lose reference to the closure itself, so what would you end up calling detachEvent with? One of the most interesting approaches to this problem was demonstrated on MSN spaces thanks to Scott Isaacs. The approach uses a second closure to additionally hook the window's onUnload event, and because this closure has the same "scoped" objects it is able to detach the event, detach itself, and finish the clean up process. To make everything easily fit with our model we can also store the closure on an expando, detach it, and then null the expando, as in the following example.









In a Knowledge Base article, we actually recommend that you try not to use closures unless they are necessary. In the example, I've given we don't need to use a closure as the event handler, instead we can move the closure to a global scope. When the closure becomes a function, it no longer inherits the parameters or local variables from its parent function so we don't have to worry about closure-based circular references at all. Most code can be fixed by creating an architecture that doesn't rely on closures where they aren't necessary.

Finally, Eric Lippert, one of the developers of the scripting engines, has a great post on closures in general. His final recommendations are also along the lines of only using closures when truly necessary. While his article doesn't mention any of the workarounds for the closure pattern, hopefully we've covered enough examples here to get you started.

Cross-Page Leaks

Leaks that are based on order of insertion are almost always caused by the creation of intermediate objects that don't get cleaned up properly. That is exactly the case when creating dynamic elements and then attaching them to the DOM. The basic pattern is attaching two dynamically created objects together temporarily which creates a scope from the child to the parent element. Later, when you attach this two-element tree to the primary tree, they both inherit the scope of the document and a temporary object is leaked. The following diagram shows two methods for attaching dynamically created elements to the tree. In the first model, attach each child element to its parent, and finally attach the entire subtree to the primary tree. This method can cause leaks through temporary objects if other conditions are met. In the second model, we attach elements into the primary tree working our way from top-level dynamically created element down through all of the children. Because each attachment inherits the scope of the primary document we never generate temporary scopes. This method is much better at avoiding potential memory leaks.

Figure 3 DOM Insertion Order Leak Model

Figure 3. DOM Insertion Order Leak Model

Next, we are going to cover an example of a leak that is transparent to most leak-detection algorithms. Because we don't leak any publicly visible elements and the objects we leak are very small you might never notice this problem. For our example to work, the dynamically created elements will have to contain a script pointer in the form of an inline function. This will allow us to leak an internal script object that is created temporarily as we attach elements together. Because the leak is small, we'll have to run thousands of samples. In fact, the objects leaked are only a few bytes. By running the sample and navigating to an empty page, you can see the difference in memory consumption between the two versions. When we use the first DOM model of attaching child to parent, then parent to the primary tree, our memory usage goes up a bit. This is a cross-navigation leak and the memory isn't reclaimed until you restart the IE process. If you run the sample a few more times, using the second DOM model of attaching the parent to the primary tree and then the child to the parent, your memory won't continue to climb and you'll find that you've fixed the cross-page navigation leak.











This leak deserves clarification, because our workaround goes against some best practices in IE. The key points to understand about the leak are that DOM elements are being created with scripts already attached. This is actually crucial to the leak, because if we create DOM elements that don't contain any script and attach them together in the same manner we don't have a leak problem. This gives rise to a second workaround that might be even better for larger subtrees (in the example we only have two elements, so building the tree off the primary DOM isn't a performance hit). The second workaround would be to create your elements with no scripts attached initially so that you can safely build your subtree. After you've attached your subtree to the primary DOM, go back and wire up any script events at that point. Remember to follow the principles for circular references and closures so you don't cause a different leak in your code as you hook up your events.

I really wanted to point out this issue because it shows that not all memory leaks are easy to find. It could take thousands of iterations of a smaller pattern to become visible, and it might be something slight, like the order of insertion of DOM elements that causes the problem to arise. If you tend to program using only best practices, then you think you are safe, but this leak shows that even best practices can exhibit leaks. Our solution here was to improve upon the best practice or even introduce a new best practice in order to remove the leaking condition.

Pseudo-Leaks

Often times the actual behavior and expected behavior of some APIs can lead you to misdiagnose memory leaks. Pseudo-leaks almost always appear on the same page during dynamic scripting operations and should rarely be visible after navigation away from the page to a blank page. That is how you can eliminate the issue as a cross-page leak and then start to work on whether the memory consumption is expected. We'll use script text rewriting as our example of a pseudo-leak.

Like the DOM Insertion Order issue, this issue also relies on the creation of temporary objects in order to "leak" memory. By rewriting the script text inside of a script element over and over again, slowly you'll begin to leak various script engine objects that were attached to the previous contents. In particular, objects related to debugging script are left behind as are fully formed code elements.










If you run the above code and use the Task Manager trick again, while navigating between the "leaking" page and a blank page, you won't notice a script leak. This script leak is entirely within a page and when you navigate away then you get your memory back. The reason this one is bad is due to expected behavior. You expect that after rewriting some script that the original script won't stay around. But it really has to, because it might have been used already for event attachments and there might be outstanding reference counts. As you can see, this is a pseudo-leak. On the surface the amount of memory consumption looks really bad, but there is a completely valid reason.

Conclusion

Every Web developer builds a personal list of code examples that they know leak and learns to work around those leaks when they see them in code. This is extremely handy and is the reason the Web is relatively leak-free today. Thinking about the leaks in terms of patterns instead of individual code examples, you can start to develop even better strategies for dealing with them. The idea is to take them into account during the design phase and make sure you have plans for any potential leaks. Use defensive coding practices and assume that you'll need to clean up all your own memory. While this is an overstatement of the problem, you very rarely need to clean up your own memory; it becomes obvious which variables and expando properties have the potential for leaking.

In the interest of patterns and design I highly recommend Scott's short blog entry because it demonstrates a general purpose example of removing all closure-based leaks. It does require a bit more code, but the practice is sound and the improved pattern is easy to spot in code and to debug. Similar registration schemes can be used for expando-based circular references as long as care is taken that the registration method itself isn't riddled with leaks (especially where closures are used)!

About the author

Justin Rogers recently joined the Internet Explorer team as an Object Model developer working on extensibility and previously worked on such notable projects as the .NET QuickStart Tutorials, .NET Terrarium, and SQL Reporting Services Management Studio in SQL Server 2005.

 

 


转载于:https://www.cnblogs.com/napoleon_liu/archive/2010/11/10/1873210.html


推荐阅读
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • 本文介绍了在rhel5.5操作系统下搭建网关+LAMP+postfix+dhcp的步骤和配置方法。通过配置dhcp自动分配ip、实现外网访问公司网站、内网收发邮件、内网上网以及SNAT转换等功能。详细介绍了安装dhcp和配置相关文件的步骤,并提供了相关的命令和配置示例。 ... [详细]
  • IhaveconfiguredanactionforaremotenotificationwhenitarrivestomyiOsapp.Iwanttwodiff ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • 使用在线工具jsonschema2pojo根据json生成java对象
    本文介绍了使用在线工具jsonschema2pojo根据json生成java对象的方法。通过该工具,用户只需将json字符串复制到输入框中,即可自动将其转换成java对象。该工具还能解析列表式的json数据,并将嵌套在内层的对象也解析出来。本文以请求github的api为例,展示了使用该工具的步骤和效果。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 李逍遥寻找仙药的迷阵之旅
    本文讲述了少年李逍遥为了救治婶婶的病情,前往仙灵岛寻找仙药的故事。他需要穿越一个由M×N个方格组成的迷阵,有些方格内有怪物,有些方格是安全的。李逍遥需要避开有怪物的方格,并经过最少的方格,找到仙药。在寻找的过程中,他还会遇到神秘人物。本文提供了一个迷阵样例及李逍遥找到仙药的路线。 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 本文介绍了绕过WAF的XSS检测机制的方法,包括确定payload结构、测试和混淆。同时提出了一种构建XSS payload的方法,该payload与安全机制使用的正则表达式不匹配。通过清理用户输入、转义输出、使用文档对象模型(DOM)接收器和源、实施适当的跨域资源共享(CORS)策略和其他安全策略,可以有效阻止XSS漏洞。但是,WAF或自定义过滤器仍然被广泛使用来增加安全性。本文的方法可以绕过这种安全机制,构建与正则表达式不匹配的XSS payload。 ... [详细]
  • 合并列值-合并为一列问题需求:createtabletab(Aint,Bint,Cint)inserttabselect1,2,3unionallsel ... [详细]
  • 本文介绍了Codeforces Round #321 (Div. 2)比赛中的问题Kefa and Dishes,通过状压和spfa算法解决了这个问题。给定一个有向图,求在不超过m步的情况下,能获得的最大权值和。点不能重复走。文章详细介绍了问题的题意、解题思路和代码实现。 ... [详细]
  • Iamtryingtocreateanarrayofstructinstanceslikethis:我试图创建一个这样的struct实例数组:letinstallers: ... [详细]
  • Ihaveaworkfolderdirectory.我有一个工作文件夹目录。holderDir.glob(*)>holder[ProjectOne, ... [详细]
  • 本文介绍了一道经典的状态压缩题目——关灯问题2,并提供了解决该问题的算法思路。通过使用二进制表示灯的状态,并枚举所有可能的状态,可以求解出最少按按钮的次数,从而将所有灯关掉。本文还对状压和位运算进行了解释,并指出了该方法的适用性和局限性。 ... [详细]
  • 深入理解Java虚拟机的并发编程与性能优化
    本文主要介绍了Java内存模型与线程的相关概念,探讨了并发编程在服务端应用中的重要性。同时,介绍了Java语言和虚拟机提供的工具,帮助开发人员处理并发方面的问题,提高程序的并发能力和性能优化。文章指出,充分利用计算机处理器的能力和协调线程之间的并发操作是提高服务端程序性能的关键。 ... [详细]
author-avatar
手机用户2602901471
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有