现在的位置: 首页 > 综合 > 正文

使用Flex Builder 3.x 分析工具

2013年05月26日 ⁄ 综合 ⁄ 共 6944字 ⁄ 字号 评论关闭

使用Flex Builder 3.x 分析工具

Contents

[hide]

前言

关于分析工具

(原文见Flex Builder 3.x 帮助文档 About Profiling

Adobe Flex 分析工具(Profiler)能够帮助我们诊断应用程序中的性能瓶颈和内存泄漏。我们从Flex Builder中启动分析工具之后,在我们同应用程序的交互过程中,分析工具将记录应用程序的各种状态。例如,对象的数量及这些对象的大小,被调用的方法的数量以及调用这些方法所消耗的时间。

分析应用程序能够帮助我们确定以下问题:

  • 调用频率 有时,我们会多次调用一些计算代价昂贵(耗时)的方法,而这些调用是不必要的。通过识别那些经常被调用的方法,我们能够在调节性能的过程中,专注于应用程序中对性能影响最大的地方。
  • 方法耗时 分析工具能够告诉我们一个调用特定方法所消耗的时间。如果这个方法被调用了多次,分析工具将告诉我们,在与应用程序交互的这段时间里,调用这个方法所消耗的平均时间。如果其中的一些方法造成了性能瓶颈,我们可以想办法优化一下它们。
  • 调用堆栈 通过追踪某一方法的调用堆栈,我们可以看到应用程序调用该方法的完整路径。
  • 实例数量(对象分配) 有时,我们会发现同一对象被创建了太多次,而我们只需要这一对象的几个实例。在这些情况下,如果只需要一个实例,我们可能考虑使用单件模式;如果需要多个,则应用其他技术来减少对象分配。如果确实需要很多该对象的实例,我们得考虑优化对象本身来降低资源总数以及内存占用量。
  • 对象大小 如果观察到某些对象大小异常,我们可以尝试优化它们以减少其内存占用量。程序中某些对象被多次创建时尤为有效。
  • 垃圾回收 比较性能快照时,我们可能发现一些不再被程序使用的对象仍然在“loitering”,或者存储在内存中。为了避免内存泄漏,我们可以添加一些逻辑,来移除这些对象身上的“残余”引用。

我们不应当把性能分析看成一个与应用程序开发毫无关联,相对独立的阶段。相反,性能分析应当或多或少的集成到整个开发过程的每一阶段。我们在开发过程中应尽可能的早进行性能分析,多进行性能分析。这样,我们才能更快的找出有问题的地方。性能分析是一个反复进行的过程,尽可能频繁的进行性能分析将使我们受益无穷。

使用Flex Builder 3.x 分析工具

(原文见Using the Flex Builder 3.x Profiler)

最近接触了许多内存泄漏方面的问题。 现在我终于有时间将我在分析内存泄漏中用到的技术写下来了。

我创建了一个SWF文件来代替 PowerPoint。这样,在看报告的同时,大家也可以学习如何使用Profiler。在文章中,我说明了分析工具中显示的内存与通过 System.totalMemory获取的内存及进程(Flash Player, IE, Firefox)所占用内存的不同,并演示了针对内存泄漏问题中常见的两个情景,该如何分析。

查看报告(英文)

和往常一样,以下是附加的说明。

经常听到的一个话题是如何实现XML驱动或数据驱动的用户界面。在这个SWF文件中,我演示了一种实现方法。报告内容由一个XML 文件控制。一个独立的引擎解析这个XML文件,根据解析的结果创建特定的交互部件。改变报告的内容仅需要修改XML文件即可。我也可以轻松的添加新的部件。源码可以从这里得到:

下载Flex Builder项目

这个SWF同时也演示了一种改进启动时间的技术。我们的Blog系统用起来太痛苦了,我不希望在发表日志的时候上传两个以上的文件,而这篇报告有成打的图片需要上传。因此,我将这些图片嵌入到SWF中,而不是从外部将它们加载进来。但是,下载这些图片所花费的时间将延迟启动。为了避免这种情况,我将所有的图片塞进SWF的第三帧,这样Flex就能迅速启动并运行,而这些图片在SWF文件末尾才会被下载。之所以这样做,是因为这些图片并会立刻被使用。

希望这些能帮助大家!

如何将图片嵌入到第三帧?
//将第三帧加载图片以改善加载时间。对于MXMLC,添加: 
//		--frame=Three,Frame3Assets
//作为编译参数
// 能够这样做是因为我们首次呈现给用户的内容中不包含这些图片
参见Flex应用程序启动详解

分析工具情景

(原文见profiler scenarios

情景

  • 如何使用Flex Builder 3.x分析工具找出内存泄漏依赖于应用程序自身的行为
  • 有两种常见情景
    • 创建/销毁
    • 替换当前

创建/销毁

  • 在一个创建/销毁情景中,一连串的对象被创建,之后被销毁

    • 弹出式对话框是一个经典的例子
  • 所有被创建的对象所占用的内存都应被回收
  • 内存占用量在开始的时候有一个初始值,对象被创建后,该值增长,对象被删除,内存被回收后,该值应恢复为初始值

替换当前

  • 在一个“替换当前”的情景中,一连串的对象被类似的一系列新对象所替换

    • 这个幻灯片就是一个例子
    • 当改变幻灯片的时候,老的幻灯片被新的所替换
  • 因为对象被替换而不是销毁,内存占用量应大致保持在相同的水平

分析工具最适合创建/销毁情景

  • 分析工具被设计出来主要用于诊断创建/销毁情景

    • “游荡对象”视图显示出存在于最近一张内存快照中,之前的内存快照中没有出现过的对象
    • 在“替换当前”情景中“游荡对象”将显示所有(或大多数)对象,因为替换的对象总是在之前的内存快照中不存在的对象
  • 十分不幸,这意味着分析“替换当前”情景时,我们不得不手动的比较内存快照
在创建/销毁情景中,我们可以很简单的通过“游荡对象”视图,找到那些应当被销毁,但是依然存在的对象。因为,只要是出现在视图中的,都是被怀疑的
对象。浏览并锁定那些应当被销毁的对象即可。
在“替换当前”情景中,因为“游荡对象”显示出了所有的对象,即使它不应当被销毁。因此,我们没办法简单的判断一个对象是否该存在,只能手动的在内
快照中寻找答案。
游荡对象(Loitering Objects):应当被销毁,释放掉其所占用的内存,但是因为某种原因仍在内存中驻留的对象。

分析外部应用程序

  • 这一SWF文件能够用来演示如何分析外部应用程序的性能

    • 通常情况下,我们会打开一个项目,并分析这个项目中的应用程序
    • 我们一起来看看如何分析“创建/销毁”和“替换当前”情景
  • 开始之前,打开“Profiling”视图,在“Profile”菜单下选择“Profile External Application”

Image:Using the Flex Builder 3.x Profiler image001.jpg

启动分析工具

  • 我们会看到一个如下的对话框

Image:Using the Flex Builder 3.x Profiler image002.jpg

  • 点击“New”按钮来添加一个新的应用程序

添加新的应用程序

  • 我们将看到一个如下的对话框

Image:Using the Flex Builder 3.x Profiler image003.jpg

  • 输入如图所示的URL,然后点击“OK”

启动应用程序

  • 应该有一个这样的对话框出现

Image:Using the Flex Builder 3.x Profiler image004.jpg

  • 点击这一SWF文件的URL使它高亮
  • 点击“Launch”按钮

配置分析工具

  • “Configure Profiler”对话框出现

Image:Using the Flex Builder 3.x Profiler image005.jpg

  • 取消对“Enable performance profiling”的选择,选择“Generate object allocation stack traces”
  • 点击“Resume”按钮

Live Objects

  • 几秒钟后,显示器的屏幕看起来应该是这样的:

Image:Using the Flex Builder 3.x Profiler image006.jpg

  • “Live Objects”视图显示出到目前为止,在应用程序执行过程中,已经被实例化了的一些类
  • 实际上这个视图能显示出所有被实例化的类,但是默认情况下,许多类被过滤掉了
  • 右侧的四列数据有如下的含义和用途:
    • “Cumulative Instances”,到目前为止,该类被创建过的实例总数。这并不是当前存在的实例数目。这一数据很难被用到,除非要考虑回收实例是否比创建新的更好的时候
    • “Instances”,当前存在的属于该类的实例数目。如果为0,则表明所有的实例都被作为垃圾回收了
    • “Cumulative Memory”,“Cumulative Instances”所占用的内存总量
    • Memory”,“Instances”所占用的内存总量

内存测量指标——分析工具中的内存占用量

  • 内存占用量可能会误导我们。它包含的仅仅是为了保留该类中定义的所有属性,所分配的字节数(每一属性大概需要4字节)。它并不包含:

    • 子对象或字符串占用的内存。这在它们自己的行里
    • 播放器追踪对象所需的内存。每一对象有许多内存控制块没有被计算进来,分析工具中也没能显示出所有“display objects”背后的隐藏对象所占用的内存
    • 或者用于处理网络通信或文件访问的内存
    • 浏览器占用的内存
    • 存储Flash Player编码及预定义变量所需的内存
    • JIT缓存占用的内存
  • 当前的内存占用量与操作系统告诉我们的——通常是通过任务管理器得到的——应用程序所占用的内存完全不同。操作系统告诉我们的通常会远大于前者

内存测量指标——System.totalMemory

  • 另一种测量内存占用量的方法是System.totalMemory。通过检查这一数值,我们会发现该值在分析工具和操作系统两者所显示的内存占用量之间。
  • Flex Player 以每4096字节为一个块,将内存从操作系统中划分给小的对象。比如,一个4096字节的块包含了256个16字节的对象,而另一个却包含了16个256字节的对象。
  • flash.system.System.totalMemory测量从操作系统分配的块的数量以及其他的一些较大的内存分配。它不包括:
    • 操作系统用来显示可视部分、获取事件、处理网络或文件I/O所需的内存
    • 浏览器占用的内存
    • 存储Flash Player编码及预定义变量所需的内存
    • JIT缓存占用的内存
  • 当Flash Player需要分配第257个16字节数据块的时候,它会从操作系统分配另一个4096字节的块,同时将其预留给16字节的对象
  • 如果不再需要一个块中的所有16字节对象,Flash Player会将该块所占用的内存释放,返给操作系统
  • 可能会出现这样的情景:分配了上千个16字节对象,但是每256个对象只释放其中的255个,这样的话最终会得到很多块,每个块只包含了很少的几个16字节对象
  • Flash Player不会合并这些对象稀疏的块,这样System.totalMemory以及操作系统告诉我们的将远远大于分析工具显示的,因为大量的系统内存被分配出去却未的到充分的利用

分析一个创建/销毁情景

  • 让我们来分析一个创建/销毁情景中的内存泄漏问题。按下“Memory Snapshot”按钮

Image:Using the Flex Builder 3.x Profiler image007.jpg

  • 应用程序下面,一个内存快照入口将出现在“Profile”选项卡中

Image:Using the Flex Builder 3.x Profiler image008.jpg

  • 现在按下这个应用程序底部的“Show Leak Dialog”按钮
  • 现在使用右上方的关闭按钮关闭该对话框
  • 理论上,这个对话框应该已经被销毁了。让我们来找出它。
  • 再次按下“Memory Snapshot”按钮。应用程序下面,第二个内存快照入口将出现在“Profile”选项卡中

生成“游荡对象”视图

  • 点选一个内存快照入口,按住CTRL再点选第二个,接着按下“Loitering Objects”按钮

Image:Using the Flex Builder 3.x Profiler image009.jpg

“游荡对象”分析

  • “游荡对象”视图看起来应该是这样的

Image:Using the Flex Builder 3.x Profiler image010.jpg

  • 噢!看起来有很多东西泄漏了
  • 尽快有很多东西泄漏了,但通常是由一到两个根本原因引起的
  • 找到并排除这一两个根本原因,将阻止大多数的内存泄漏——如果不是所有对象都泄漏的话
  • 我经常由“高层”对象开始,因为通常它们会包含许多来自子对象的引用,而释放这一高层对象,同时也能释放其子对象

对象引用

  • 足够确定的是,“LeakyDialog”是一个在“游荡对象”视图中有一个实例的高层对象,这意味着它没有被释放
  • 我们需要检查一下谁引用了LeakyDialog。在“游荡对象”视图中双击“LeakyDialog”查看对它的所有引用

Image:Using the Flex Builder 3.x Profiler image011.jpg

  • 一共有31个引用。但并不是所有引用都有问题。大多数是“循环引用”
  • 一个“循环引用”可能是“LeakyDialog”的某个子对象对其的一个引用
    • 一个循环可以追溯到孙对象或者曾孙对象

循环引用

  • 让我们看看UITextField中的一个引用

Image:Using the Flex Builder 3.x Profiler image012.jpg

  • 我们展开UITextField的引用后,会立刻发现其引用对象的ID指向的是LeakyDialog的ID或者该UITextField的ID
  • 一旦我们碰到一个ID与LeakyDialog的相同,或者与正在检查的引用的ID相同,我们不需要展开这个ID来查看它是否为内存泄漏的根源,因为这是一个“循环引用”
  • 这个过程可能会花一些时间,但最终我们会找到内存泄漏的根源
  • 有时,内存泄漏可能是一个直接到“LeakyDialog”的引用造成的,但有时也可能是由于引用其子对象而造成的

一个造成内存泄漏的引用

  • 最终我们会展看并检查“[function]”引用,如下图所示

Image:Using the Flex Builder 3.x Profiler image013.jpg

  • 我们看到 flash.utils:SetIntervalTimer. 计时器经常引起内存泄漏。检查代码的时候我们会发现计时器并未被清理掉
  • 作为一个练习,我们可以将这个项目导入到Flex Builder中,并尝试修复这一内存泄漏
  • 这个例子中有不止一个内存泄漏,这些都是由清理代码所引起的。清理代码会抛出一个被捕获的异常

替换当前情景

  • 现在让我们来审视一下替换当前情景。正如你将看到的,这个过程与前面的有点不同
  • 这个幻灯片是一个“替换当前”情景。当你点击“Next”或者“Previous”按钮的时候,当前的幻灯片将被新的所替换
  • 这个程序中,每一张幻灯片都有一个内存泄漏。我们来看看如何找到它们
  • 首先,截取一张内存快照,然后进入下一张幻灯片
  • 理论上,所有上一幻灯片所使用的对象都应消失。让我们来找到它们
  • 截取另一内存快照,将两张内存快照选中,生成新的“游荡对象”视图
  • “游荡对象”视图会告诉我们有一张幻灯片造成了内存泄漏
  • 然而,幻灯片并没有内存泄漏。如果我们深入挖掘,我们会发现“游荡对象”视图中幻灯片实例正是当前这张新幻灯片的
  • 那是因为“游荡对象”给我们展示的是第二张内存快照中没有出现在第一张内存块找中的对象,当前的幻灯片表现为一个“错误肯定”
错误肯定——当前幻灯片没有造成内存泄漏却出现再了“游荡对象”视图中,分析工具“误认”该幻灯片为“游离对象”。

手动比较内存快照

  • 那么,如何在“替换当前”情景中找出内存泄漏呢?
  • 通过手动比较内存快照。这里显示了我是如何做的
  • 双击每一张内存快照

Image:Using the Flex Builder 3.x Profiler image014.jpg

  • 点击“Show Percentages”按钮取消显示百分比

Image:Using the Flex Builder 3.x Profiler image015.jpg

  • 点击“Filters”按钮

Image:Using the Flex Builder 3.x Profiler image016.jpg

“Filters”对话框

  • 我通常会从过滤器中移除“mx.*.*”和“flash.*.*”

    • 如果有太多的数据,我将添加不同的过滤器并重复这一过程
  • 我通常会不选“Exclude global built-in items”复选框,除非我已经得到了太多的数据

Image:Using the Flex Builder 3.x Profiler image017.jpg

比较实例的ID

  • Array实例的数量在增长。因此在每一个内存快照中双击“Array”。这些:

Image:Using the Flex Builder 3.x Profiler image018.jpg

  • 通过比较ID我能找到哪些实例是新添加的
  • 使用滚动条和黄色箭头按钮来保证我们不会放过某些实例
  • 在可能的替换实例被创建之前获得一张内存快照通常很有用。这些实例的ID将作为一个基线,因为所有这些ID所代表的对象在替换之前就已存在并且可以被忽略掉
  • 另一个技术是比较高层对象的ID而不是处于问题之中的ID
  • 实例的ID是有次序的。如果我们看看第一张内存快照中幻灯片实例的ID,我们会发现大多数Array的实例都有较低的ID数值,因此它们都是在幻灯片之前被创建的,基本上没有什么问题
  • 现在来看看第二张内存快照中幻灯片实例的ID,然后再看看Array的ID。我们应当发现这些Array,要么是从第一张内存快照中延续下来的(因此它们没有问题,因为那时它们就没有问题);要么是ID数值比第二张幻灯片高的,它们可能属于第二张幻灯片,因此没有问题
  • 因此这些Array不是造成内存泄漏的原因。通过这种方法可以证明是哪些对象造成了内存泄漏。看看我们能否在分析工具中识别出它们,然后“Allocation trace”将告诉我们它们是在代码的什么位置被创建的
作者在这里告诉我们,如果在第二张内存快照中发现某个对象的ID数值大于第一张幻灯片的ID,小于第二张幻灯片的ID,这表明它是在第一张幻灯片被
创建后,第二张幻灯片被创建前,创建的,很有可能造成了内存泄漏

已知问题

  • 一些类会表现出“错误肯定”,它们是:

    • Strings
    • WeakMethodClosures
    • Dictionaries
  • 如果这些类的实例没有向后引用,它们可能是“错误肯定”
  • 如果我们怀疑它们会造成内存泄漏,让我们导出发布版的应用程序,用发布版的Flash Player来跑,并观察进程占用的内存或者Sysetm.totalMemory。这些数值或许一时之间会不断增长,但是一段时间之后应该稳定下来
这表明Strings, WeakMethodClosures, Dictionaries不会造成内存泄漏。

抱歉!评论已关闭.