写点什么

Flash 务实主义(五)——AS3 的垃圾回收

  • 2011-05-05
  • 本文字数:4070 字

    阅读完需:约 13 分钟

GC 和内存泄露无关

垃圾回收,这次是一个被无数人讨论过的传统话题。

Action Script 使用的是和 Java 相似的内存管理机制,并不会即时回收废弃对象的内存,而是在特定时间统一执行一次 GC(Gabage Collection)操作来释放废弃对象的内存,避免了重复判断是否需要回收产生的性能问题。

但要注意,这只是决定回收的时机,而不是回收的内容。这个延迟执行内存回收也就是个表面的现象,不管什么时候执行 GC,能够回收的内存最终都能回收,不能回收的肯定不能回收。唯一的影响是,因为回收是延迟执行的,你在查看内存的时候不能直观地看到因为一个对象被废弃而回收内存的过程,会产生迷惑。

但这对于解决内存泄露是无关紧要的。

内存泄露指的就是当你销毁了一个对象的时候,它占用的内存却无法被回收,这会导致可用内存越来越小最终溢出,在内存紧张的环境中将会造成系统崩溃。其原因多种多样,但一般都是开发者的疏忽所致,没有提供给系统足够的可以销毁对象的依据。

执行 GC 虽然和内存泄露没有关系,但是如果不在测试前执行 GC,你将看不到当时实际的不可回收内存的量,而内存泄露就是指不可回收内存的数量的增加。因此,测试内存回收将离不开 GC 方法。没有使用 GC 方法的测试用例是没有意义的,因为这其中掺杂了偶然性(什么时候执行 GC)。不少荒谬的测试结果都是因为没有在正确的位置执行 GC 导致的。

Flash Player 虽然没有开放发布状态的手动 gc,但调试版本是可以使用的,正好可以让我们测试。此外下面的 HACK 代码也可以在发布阶段触发 GC。

复制代码
try {
new LocalConnection ().connect ( "gc" );
new LocalConnection ().connect ( "gc" );
} catch ( e:Error ) {}

但我再次强调,调用 GC 仅仅是用于测试。实际产品中调用 GC 基本没有意义(除了用于控制 GC 时机),总之如果你的程序出现了内存泄露,那一定和 GC 没有关系,请不要再在这种地方浪费宝贵的时间与精力。

只有在申请内存时才会触发自动 GC

AVM2 的 GC 是在每次申请内存时,根据当前内存占用来触发的。申请内存是一个必要因素。所以,如果你一直不进行申请内存的操作,就算内存达到了一个高值,它也不会进行 GC。

这确实是个不合理的地方。但是,在实际环境中,一直不请求内存的情况是很少见的,就算出现,当时也未必处于内存的高值。这种情况主要出现在测试环境中,导致一些人会怀疑自动 GC 的功能是否正常。实际上这也是没有必要的。

Flash 中垃圾回收的条件

在 AVM2 中,除去特殊的 BitmapData 必须调用 dispose 才能回收内存外,其他的部分都是用引用计数法和标记清除法作为判断是否应该回收内存的手段,而且并没有提供主动回收的 API,详细部分请看这篇日志,我就不重复了。

http://www.cnblogs.com/cos2004/archive/2010/11/07/1870980.html

因此,你要回收一个对象,只要保证没有任何对象引用它,而且他的方法没有被当做事件函数——或者说,他和程序的其他部分已经没有任何联系,它就满足了引用计数法的标准,就一定会被回收。做到这一点的方法就是一般说的“执行 removeChild,removeEventListener,将对他的引用设置为 null”。

但是,实际上回收一个对象的要求并没有那样严格,就在于 FP 除了引用计数法,还包括标记清除法。标记清除法是从程序的根对象开始(stage, 静态属性, 活动的定时器和加载器,ExternalInface.callBack)一级一级遍历对象,只要遍历不到,即使不满足引用计数法的条件也可以回收。比如两个对象互相引用,但是和外界都没有关系,形成了孤岛,它们就可以被回收,尽管它们因为互相引用使得引用数不为 0。比起单纯的引用计数,这种办法能确实能找到已经无法再访问到的实际上的闲置对象。所以,可以看到很多人的代码实际上并没有设置 null,甚至没有 removeEventListener,它一样可以被正常回收,少写这些代码可以使得程序更简洁,要全部符合标记清除法的条件,会很累。

“无法被根访问”,这种说法很暧昧,基本不能当做判断依据。所以我下面会举几个具体例子,来说明什么样的情况是符合标记清除法的要求的。

首先明确一点,标记清除法是只以能否能被根访问作为唯一依据的,并不需要关注被引用的次数,请不要混淆。

  • 属性的相互引用是很明确的,一般都是一个对象包含着若干属性,那么这个对象自然可以维持它的属性的引用。如果这个类不会被回收 (能够被根访问),他的所有属性也都不会被回收。同样的,如果这个类可以被回收的话(不能被根访问),也就不会妨碍属性的回收。所以你并不需要将所有属性设置为 null,除非你希望在对象存在时候就回收其属性的内存,这种需求基本不存在。
  • 静态属性是一个特殊的情况。静态属性本身就是根,所以你必须将其设置 null 才有可能被回收,没有别的办法。
  • 至于在显示列表中的对象。既然根 (stage) 可以用 getChildAt 访问到自己的所有子对象,那么只要你在显示列表中,就肯定不会被回收。然而,如果显示对象的父层对象已经不再显示列表内,它的子对象就算还在父层对象之中也没有关系,因为它已经不能被 stage 访问到了。所以你不需要 removeChild 各层的全部对象,而只需要 removeChild 最高一层的父对象即可。
  • A.addEventListener(“event”,B.handler),像这样添加过事件后,你可以认为 B.handler 成为了 A 的一个属性(因为 A 在需要的时候要能调用 B.handler),这里也符合属性相互引用的原则。但是事件判断起来的确要比属性麻烦,因为相互引用的情况很多。在这里可以分为三种情况:
    1. 对自己监听自己的事件,这相当于用自己的属性保存自己引用,任何情况都不会阻碍自己被回收。
    2. 对自己的子对象(属性或者 child)监听自己的事件。因为子对象本来就是自己在维持它的引用,那么即使它们会维持你的引用,也只会形成一个循环。一旦你和 stage 脱离了联系,子对象同样也会脱离联系,当然也无法妨碍你自己被回收了。除非子对象因为一些原因可以单独维持引用(诸如被保存在静态属性中),但这种情况很少见。
    3. 对自己的父对象 (parent 或者 stage) 监听自己的事件。因为这使得你成为了父对象的一个属性,只要 parent 或者 stage 不被回收,那么自己就不会被回收。尤其是 stage,它肯定不会被回收。这种情况一般都会导致自己无法回收,是必须 removeEventListener 的。

总得来说,就是务必注意对 stage,parent 的事件监听,其他情况一般都是不会妨碍回收的。而对 stage,parent 的监听大多都是各种鼠标,键盘事件。数量并不多,专门注意这里可以杜绝大部分因为事件造成的内存泄露。

其实,内存泄露并不容易出现。按照普通的编程习惯,只有监听 stage 事件这种做法会造成意料之外的泄露,一般都是可以顺利回收的。这比每次都要手工回收内存要方便多了。

这里只有 BitmapData 是例外。除了遵从上面的规则外,要回收它的内存,必须手动调用 dispose 方法,习惯自动回收的人会很累。务必注意,Bitmap 对象的 bitmapData 属性是需要手动销毁的,Loader 加载的位图是需要手动销毁的,当你用一个生成的位图作为位图填充绘制平铺的图像后,在销毁这个图像后也必须销毁这个位图(所以你必须一直保存位图的引用)。BitmapData 是 32 位的未经任何压缩的图像,随便一个体积都会非常大,不处理好它们的回收,一个 BitmapData 泄露就可以顶你数万个复杂对象的泄露。

如果出现非常明显的内存泄露,大部分时候都是位图泄露。所以在研究上面的引用计数法和标记清除法以及 GC 之前,请先保证位图部分不出问题。

弱引用时的例外

弱引用会改变垃圾回收的规则。如果使用了弱引用,addEventListener 将不会影响对象回收,即使对 stage 添加监听,也不会导致自己被回收。但是这同时也是缺点,因为有的时候你就是希望用引用限制住对象的回收,使用弱引用会使得这个对象有时回收有时不回收。虽然极少出现,但一旦出现,这种不容易重现的错误是很难查出来的。因此我并不推荐使用弱引用。

弱引用在 AVM2 中只有两处:

  • 一处是 addEventListener 的第 5 个属性,名为 userWeakReference,设置为 true,监听事件将不会影响对象回收。
  • 一处是 Dictionary 的构造函数参数,名为 weakKeys,设置为 true,当键为复杂对象时,即使 Dictionary 存在,键依然可以被回收。注意,这里说的是键,不是值,值是不享受弱引用待遇的。这个属性也写得也很明白,是 weakKeys。

内存泄露的查找方法

Flash Builder 提供了一个概要分析工具,可以帮助我们查找内存泄露。大多数情况都可以帮助我们解决问题。可以查看下面的文章:

http://blog.csdn.net/bbmjfpig/archive/2010/12/30/6107347.aspx

关键点在于,检测内存泄漏应该是“创建,取样,销毁,再创建,取样”,然后以两次取样的对比数据来观察泄露。因为对象在第一次创建时会有一些缓存数据,它们在设计上就不会随着对象销毁而回收的,比如类定义的缓存,比如皮肤。它们只会创建一次,和我们看到的泄露并不是一回事。

必要时可以执行强制 GC

因为每次 GC 都需要消耗性能,对象越多,GC 越慢。我理解 Flash Player 禁用发布版本的 System.gc() 是为了避免开发者滥用这个方法,但有些时候我们的确需要手动控制 GC 时机,因为 GC 过程如果遇到大量可回收对象会让 Flash Player 卡住。

比如,我们需要在切换屏幕时回收一次内存,这时候卡是看不出来的,而不是切换完后播放动画时回收然后让动画顿住。或者,我们会定期在必要的时候执行一次 GC,将 GC 需要的时间分担开。所以这时候用 HACK 方法强制执行一次 GC 也不失为一个选择。当然,这和内存泄露半点关系都没有。

Flash Player 这个地方的设计特别的不好。它自己又不支持分步 GC,一旦 GC 的时候没有办法避免卡的问题。结果 GC 的时机还不给控制……

微量剩余内存

测试中 FLASH 的确存在微量内存无限增加的问题,原因未知。我将 50 万个对象扔在一个数组中,销毁后确实会多出 1M 的内存占用(如果没扔在数组中不会),但这个数量很小,但达到能看得出来的 100M 内存需要 5000 万个对象,这个数额在通常情况下很难达到。

不过也有人说这只是对象销毁而内存并未全部释放的表现,实际上最后还是能完全释放的。或者是因为 totalMemory 的不精确所造成的。这个我就不清楚了。

不过就算这个的确是 FlashPlayer 的 BUG,也无伤大雅吧。

2011-05-05 20:516083

评论

发布
暂无评论
发现更多内容

软件测试/测试开发丨应用打包还是测试团队老大难问题?

测试人

软件测试 自动化测试 测试开发

敏捷看板管理工具哪个好?

爱吃小舅的鱼

项目管理 Kanban管理

海尔牵头!又一国家重点研发计划启动!

Openlab_cosmoplat

人工智能 开源项目 开源社区

小程序SDK的发展趋势与未来展望

FinFish

小程序 APP开发 小程序容器 超级app

Themis Pro版将正式推出,3次迭代到底在酝酿什么?

股市老人

MySQL多版本并发控制MVCC实现原理

做梦都在改BUG

Java MySQL 数据库 MVCC

2023年“开放原子校源行”项目正式启动,腾讯大力支持开源人才培养

科技热闻

三次迭代终放“大招”,Themis Pro版即将问世

小哈区块

三次迭代终放“大招”,Themis Pro版即将问世

BlockChain先知

Service初涉

梦笔生花

android service

IT采购,不再默默扛下“背刺”

白洞计划

AI 联想

Higress 0.7.0 版本发布:GA 进入倒计时

阿里巴巴云原生

阿里云 云原生 Higress

龙蜥社区 3 月度运营大事件回顾

OpenAnolis小助手

活动 生态 龙蜥社区 运营月报 重要事件

HTTP与HTTPS的区别

测吧(北京)科技有限公司

测试

Flutter 使用 CustomPaint 绘制基本图形

岛上码农

flutter ios 安卓 移动端开发 跨平台开发

博睿数据中海油多云资源监控与治理案例荣膺云数大会年度优秀实践案例

博睿数据

可观测性 智能运维 博睿数据 精选案例

模块八作业 - 消息队列存储消息数据的 MySQL 表格

🐢先生

架构实战营

面对ChatGPT,中国AI可以不疾不徐不焦虑

脑极体

AI

愿我们心中都有信念,眼里都有光芒

禅道项目管理

团队管理 项目管理 敏捷开发

三次迭代终放“大招”,Themis Pro版即将问世

西柚子

产品愿景设计:解锁团队潜能,引领市场竞争优势

L3C老司机

产品设计 数字化转型 设计思维 产品设计与思考 产品愿景

FastAPI 的路由介绍与使用

宇宙之一粟

Python FastApi 路由

AI自然语言处理的过去和未来

鲸品堂

自然语言处理 ChatGPT 企业号 4 月 PK 榜

新思科技:车联网产业的起点是安全

InfoQ_434670063458

车联网 新思科技 汽车安全

使用 Lambda Web Adapter 在 Lambda 上 构建 web 应用

亚马逊云科技 (Amazon Web Services)

Amazon

Themis Pro版将正式推出,3次迭代到底在酝酿什么?

鳄鱼视界

GitHub开源大厂缓存架构Redis优化的文档,900页全是干货

做梦都在改BUG

Java 数据库 redis 缓存

快速玩转 CNStack 2.0 流量防护

阿里巴巴云原生

阿里云 云原生 CNStack

Selenium Grid作用是什么?Selenium Grid的使用过程?

测吧(北京)科技有限公司

测试

OceanBase入选啦!金融信创优秀解决方案(第二期)

OceanBase 数据库

数据库 oceanbase

Flash务实主义(五)——AS3的垃圾回收_Java_flashyiyi_InfoQ精选文章