BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

Flash Player和Adobe AIR垃圾收集内幕

| 作者 Michelle Yaiser 关注 0 他的粉丝 发布于 2011年11月18日. 估计阅读时间: 13 分钟 | CNUTCon 了解国内外一线大厂50+智能运维最新实践案例。

目录

需求

预备知识

本文适用于中级和高级ActionScript开发人员。需要对面向对象的编程概念和ActionScript 3开发具有中等程度的理解。

用户水平

中级

需要的产品

所有应用程序都要管理内存。应用程序的内存管理包括用于确定何时分配内存,分配多少内存,何时将内容放入回收站,以及何时清空回收站的准则。MMgc是Flash Player用于几乎所有内存分配工作的通用内存管理器。理解MMgc如何管理内存是优化您的代码和您应用程序的性能的一个重要部分。

垃圾收集器自动回收的内存被视为“受管理的内存”。垃圾收集器确定内存何时不再被应用程序使用并回收它。本文分析Flash Player 11和AIR 3中的内存分配、垃圾收集流程和新的pauseForGCIfCollectionImminent()API。

内存分配

Flash Player使用一个页面分配程序(GCheap)来从OD分配大块(几MB)的内存。Gcheap然后将大内存块分解为较小的4K页面,并根据需要将这些页面提供给垃圾收集(GC)内存管理器。

图1. GCHeap从OS分配内存,将它分解为4K的页面,并将这些页面提供给GC。

GC然后使用这些4K页面为系统中不大于2K的对象提供内存。

图2. 4K页面由GC分配给小于2K的对象

对于大于2K的对象(位图、视频、文件等),GCHeap向一个大型的内存分配程序提供一组连续的4K内存块。

当一个大内存块中几乎所有4K页面都分配了时,Flash Player运行垃圾收集来回收未使用的内存,然后GCHeap尝试从OS分配更多内存。换句话说,垃圾收集仅由内存分配触发。这一事实很重要,在测试和分析期间一定要记住,因为它意味着空闲应用程序的内存使用从不会改变。

堆和堆栈

堆是分配给在运行时创建或初始化的任何对象的内存。堆上的对象会一直存在到它们被垃圾收集。

图3. 对象A存在于堆上。它由堆栈上的局部变量o引用。(图字:堆栈内存 堆内存)

堆栈是存储在编译时定义的所有变量的内存。堆栈内存以一种顺序方式使用和重用。推送操作将一些内容添加到堆栈顶部。弹出操作从堆栈顶部删除一些内容。访问堆栈中间的内容的唯一方式是删除它上方的所有内容。

局部方法变量、参数和关于在一个方法完成时返回到何处的信息,在方法运行时被推送到堆栈上。对堆栈的更改发生得非常快。对象的堆栈引用可能非常短暂。这些对象引用可能存在于堆栈上,但分配给这些对象的内存来自堆。

图4. 局部变量在定义时被推送到堆栈上。关于在一个方法完成时返回何处的信息也推送到堆栈上。

Flash运行时垃圾收集实现

Flash Player和AIR结合使用延迟的引用计数和保守的标记并清除(mark-and-sweep)方法。

延迟的引用计数

在延迟的引用计数中,堆和堆栈引用之间存在区别。因为堆栈变化很快,并可能包含非常短暂的引用,所以引用计数不会在堆栈引用上执行。而在堆上为引用维护引用计数。

图5. 对象会跟踪它们拥有多少个引用。

堆上的每个对象会跟踪指向它的信息数量。每次您创建一个对象的引用,该对象的引用计数就会递增。当您删除一个引用时,该对象的引用计数会递减。如果对象的引用计数为0(没有任何信息指向它),它会被添加到零计数表(Zero Count Table,ZCT)中。当ZCT填满后,就会扫描堆栈以查找任何从堆栈到ZCT上的对象的引用。ZCT上任何没有堆栈引用的对象都会被删除。

延迟引用计数的一个问题是循环引用。如果ObjectA和ObjectB彼此引用,而系统中没有其他对象指向它们,它们将从不会拥有一个零引用计数,因此从不满足使用引用计数进行垃圾收集的资格。这时可以使用“标记并清除”的垃圾收集方法。

图6. Object A和Object B彼此引用,但没有其他引用。

标记/清除

在Flash Player或AIR中运行的应用程序具有多个GCRoot。您可以将GCRoot视为一个树的一部分,它将应用程序的对象当作树枝。舞台是一个GCRoot。加载程序是GCRoot。某些菜单是GCRoot。让在供应用程序使用的每个对象可从应用程序内的一个GCRoot访问。GCRoot从不会被垃圾收集。

应用程序中的每个对象有一个“标记位”。当垃圾收集的标记阶段开始时,所有这些标记位会被清除。MMgc会跟踪应用程序中的所有GCRoot。垃圾收集器首先从这些根开始,跟踪每个对象并为它到达的每个对象设置标记位。任何不再能够从任何根到达的对象也不再能够从应用程序的任何地方到达——它的标记位不会在标记阶段设置。收集器完成对它找到的所有对象进行标记之后,就会开始清除阶段。任何没有设置标记位的对象都会被销毁,它的内存会被回收。

图7. 一个循环引用中的对象没有被标记。

图7显示,每个可从Gcroot到达的对象都设置了自己的标记位(蓝色)。一个循环引用中的两个对象(ObjectA和ObjectB)不可从GCRoot到达。它们的标记位将不会设置。因此,即使它们没有零引用计数,这两个对象也会被垃圾收集。

弱引用

Flash Player也可以维持对某些类型的对象的“弱引用”。弱引用是一种对垃圾收集器的正常跟踪过程(跟随所有根来查找可到达的对象的过程)不可见的引用。

当您实例化一个新字典时,可以表明您希望它与字典的键建立较弱的关联。

var d:Dictionary = new Dictionary( true );
d[ someObject ] = someValue;

您也可以在添加事件监听器时,将addEventListener()的函数useWeakReference参数设置为true。

obj.addEventListener( "type", handler, false, 0, true );

在这两种情况下,您都会要求Flash Player在两个对象之间建立引用,但以一种较弱的方式保持该引用。具体来讲,这意味着这个具体的引用在标记期间不会被跟随。

图8. 若引用在标记期间不会被跟踪。

在这种情况下,到Object B的唯一路径是弱的。在跟踪期间将不会经过它,因此Object B不会被标记,并会被收集。但是,如果还有另一个到Object B的强路径,Object B将被标记并被持久化。

图9. 具有强引用的对象将在跟踪期间被找到并标记。

您应该始终清理未使用的引用,从字典删除未使用的项,以及使用removeEventListener()。但是,有时清理未使用的引用不切实际或无法做到。比如在您的类在您不知情的情况下实例化和销毁时——项渲染器就是通过这种方式使用的。在这些情况下,维持对象的若引用将允许Flash Player最终删除它们并回收内存。

保守收集

MMgc被视为一种保守的标记/清除收集器。MMgc无法确定内存中的某些值是对象指针(内存地址)还是数字值。为了避免意外地收集值可能指向的对象,MMgc假设每个值都可以是一个指针。因此,一些没有实际被指向的对象将从不被收集,将被视为一种内存泄漏。尽管您希望最小化内存泄漏以优化性能,但由保守的GC所导致的偶然泄漏可能是随机的,不会随时间增长,并且对应用程序性能的影响比开发人员导致的泄漏小得多。

增量收集

不幸的是,垃圾收集可导致Flash Player在收集过程完成时定期暂停。这种暂停与应用程序当前运行的内存量成正比。它可能比希望的时间更长,在一些程序中可以察觉到。

标记阶段是垃圾收集过程中最消耗时间的部分。由于此事实,标记过程使用一个动作队列和一个3色算法增量化了。该队列在标记增量之间维护标记状态。

表1. 3色算法

clip_image013[4]

黑色对象已标记,不再位于队列中。

clip_image015

灰色对象位于队列中,还未被标记。

clip_image017

白色对象既未标记也不在队列中。

在标记阶段的开始,所以GCRoot被推送到队列中并变为灰色。

图10. GCRoot在推送到工作队列中时变成灰色。

随着标记过程的继续,标记的对象变为灰色,并从工作队列删除。

图11. 标记的对象是黑色的,不再在工作队列中。

此过程会正常继续进行,直到将一个新对象(白色)添加到一个黑色对象上。当发生此情况时,白色对象从不会设置它的标记位,因为它们的GCRoot已标记。不设置它们的标记位,它们将在清除阶段被垃圾收集。

图12. 新对象被添加到以前标记的对象上。

要预防此问题,可以在MMgc中使用一个白色边界来强制将任何添加到黑色对象上的白色对象立即添加到工作队列中。

图13. 添加到以前标记的对象中的新对象被立即添加到工作队列中。

通过使用工作队列和3色算法,可开始和停止标记阶段来帮助避免长时间、意外的垃圾收集暂停。

迫近度

标记阶段可能是垃圾收集中最耗时的部分,但实际上清空回收站(重新分配空闲内存)也比较耗时。重新分配还可能导致应用程序暂停。垃圾收集器离标记阶段的完成和清除(重新分配)阶段的开始的时间称为“迫近度(imminence)”

图14. 迫近度(图字:标记 暂停、迫近度增长)

public static function pauseForGCIfCollectionImminent(imminence:Number = 0.75):void是Flash Player 11和AIR 3中的一个新方法,允许您通知垃圾收集器这是完成标记和执行收集的好时机(ActionScript参考文档中的API项)。计划在用户不会注意到时发生可能的暂停,这会带来更好的用户体验。例如,一个游戏可能在游戏中一个级别完成时调用此函数,进而减少在玩游戏期间发生暂停的机会。

您传递给此方法的迫近度值用于与垃圾收集器处于标记阶段中的位置进行比较。如果您传递给它的值比垃圾收集器的迫近度值小,标记和清除将同步完成并导致应用程序暂停。垃圾收集器必须处于该过程的25%以上,才能响应这个暂停以进行收集的请求。传递一个较小的值(但大于0.25)很可能会强制执行收集,导致应用程序暂停。传递一个较大的值将告诉垃圾收集器只有在即将暂停时完成收集。

延伸阅读

理解内存管理和垃圾收集在Flash Player和AIR中的工作原理,将有助于您优化您的代码并开发更高性能的应用程序。请查阅Michael Labriola介绍垃圾收集的演示Talking Trash。阅读Christian Cantrell的Providing Hints to the Garbage Collector in AIR 3。您也可以阅读详细的MMgc讨论,其中包含对底层C++代码的描述。

查看原文:Garbage collection internals for Flash Player and Adobe AIR

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我
社区评论

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

讨论

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT