开始
级别:高级
软件工程师,IBM
2004 年 4 月
本文介绍了可以自 Eclipse.org 获得的核心工具(Core Tools)之一:Runtime Spy。Runtime Spy 是特别设计的一个透视图及一组视图,用于帮助您找到并诊断插件启动性能问题。
我们几乎每天都会听说有新的公司选用 Eclipse 作为他们的应用程序开发平台。尽管所有这些公司的产品(更不用说所有的 Eclipse 组织成员公司的产品了)的安装设置可能是正在趋于相同,但是内存使用过多和性能下降的风险还是很大。本文介绍了一个非常有用却鲜为人知的工具,Runtime Spy,来帮助插件开发者。Runtime Spy 透视图是核心团队(Core team)的侦探和工具(Spies and Tools)小组的一部分(在 参考资料 查找下载链接)。
注意:核心工具只能运行于 Eclipse 版本 2.x。在本文发表时,它们还不能运行于 Eclipse 3.0 驱动程序上;编号为 47518 的 bug 描述了这一问题。
为什么 Eclipse 需要快速启动
Eclipse 的体系结构是为了在运行期发现其环境的扩展而设计的。这种体系结构化扩展能力让很多工具可以无缝地集成到 Eclipse 中。Eclipse 架构师在项目早期就意识到,这些扩展不能在客户机代码中以程序控制的方式定义,因为当 Eclipse 集成了越来越多的扩展后,累积的启动开销将变得无法接受。从而,这些扩展由 插件 定义。
为了在保持灵活性的同时避免启动开销,插件静态地将它的扩展定义在一个 清单文件(manifest file)中。插件清单定义了足够的信息,使 Eclipse 平台可以延迟加载代码,同时仍可以识别扩展的初始影响。例如,用户界面扩展点需要足够的信息来描绘初始的用户界面元素(例如,所提供的工具条按钮的图标和热区提示(tooltip)文本),这样平台可以推迟插件代码的加载,直到用户真正选择一个菜单选项、选择一个工具条按钮、打开一个属性页或者启动某个向导时才加载。插件的最初开销只是对其清单的解析。XML 格式的解析很快,结果保存到磁盘,下次还可再用,这样,当定义了新的插件或者扩展时,启动不会受到显著影响。不过,有一些方法会无意中让这种好处失效,从而增加了启动时间和内存消耗。
幸运的是,Eclipse 插件开发者可以使用 Runtime Spy 透视图来帮助追踪这些问题。本文介绍了核心工具插件和它的 Runtime Spy,并介绍了 readme 文件(参阅 参考资料)内容之外的对其实用工具的一些零散描述。
安装核心工具
安装很简单。只需要下载核心工具压缩包并解压缩到您的 <inst_dir>\eclipse\plugins 目录。接下来决定您是不是希望侦探您的底层 Eclipse 设施(使用 -debug 命令行选项),或者是不是希望侦探您的运行期工作台(使用它的启动配置的 Tracing 页。我们将在“侦探运行期工作台”回到这一话题)。现在让我们来看第一个选择:侦探您的底层 Eclipse 设施。
首先,从 plugins\org.eclipse.core.tools_1.0.2 子目录下拷贝 .options 文件到您的 <inst_dir>\eclipse 目录来,以启用所有可用的 Spy 选项。这将启用除了类监视以外的所有选项。要监视类的加载,您必须在 plugins\org.eclipse.core.boot_1.0.2\trace.properties 文件中列出包含您所感兴趣的类的包或者插件。在“查看插件的哪些类被加载”部分我们再向您介绍如何指定这些内容。
然后启动 Eclipse,不要忘记指定 -debug 命令行选项,这会去读位于 <inst_dir>\eclipse 目录下 .options 文件。另一种方法,您可以将 .option 文件所在的位置标识为 -debug 选项的一个参数(例如,-debug file:d:\...\.options)。
侦探工作台
我们假定您已经安装了核心工具文件,并重新启动了 Eclipse。由于已经指定了 -debug 命令行参数,您将会看到定向到标准输出的一些启动消息。当处于调试模式时,这些将显示在一个单独的命令提示窗口中,如图 1 所示。
图 1. 对 -debug 选项的指定打开了一个显示标准输出消息的命令提示窗口
既然每个 Runtime Spy 的视图都已经启动并运行,让我们来快速浏览一下这些视图。不要忘记,Spy 作为“spied”插件在同一个工作台中运行,因此在使用工具本身的正常过程中可能会有一些插件被激活。这通常没关系,因为它只是用到了底层的功能,而这些功能应该已经加载或者早晚都得加载。有一种情况未必会出现,但确实重要,那就是别忘了它的视图只是根据需要而更新,所以 Runtime Spy 透视图第一次打开时,它将只显示出在它自己启动之 前 就已经在运行的那些插件。
查看哪些插件在运行
选择 Window > Open Perspective > Runtime Spy 打开四个视图,如图 2 所示。
图 2. Runtime Spy 透视图由 Activated Plugins、Loaded Classes、Plugin Datasheet 和 Stack Trace 视图组成
如果您忘记了指定 -debug 选项,您将看到 Activated Plugins 视图中显示出“Plugin monitoring is not enabled”消息。由于默认没有类在被监视,因此 Loaded Classes 视图将包含“Class monitoring is not enabled”消息。捕获类加载信息会使 Eclipse 变慢,因此您必须通过指定包含您所感兴趣的类的包或插件来列出那些类。现在我们只是关心哪些插件被加载了。图 3 显示了 Runtime Spy 的主要视图,Activated Plugins。
图 3. Runtime Spy 中显示的 Activated Plugins 视图
如果您希望在 Runtime Spy 视图中显示内存使用率统计(Alloc、Used 和 Rom Used 列),您必须用支持 J9 技术的 IBM Java 运行期环境(Runtime Environment)。这个 JRE 包含在名为 WebSphere Studio Workbench 的 IBM 版本 Eclipse 中,您在 IBM PartnerWorld for Developers 注册后就可以免费下载得到它(在 参考资料 中可以找到链接)。记住将 J9 指定为 Java 运行期环境的一个参数(例如,eclipse -debug -vmargs -Xj9)。
点击第一列的标题,Plugin,将排序改为升序、降序和分组升序。在“+”号之下分组的插件是父插件运行的时候需要运行的那些插件。当加载的插件被分组的时候,行的值对应于插件以及它所有子插件。当您想要将重量级(资源)消耗者以一个组来查看时,可以使用这种排序。
后面跟有一个星号的插件名是那些在启动的时候就加载的插件。不管名字意味着什么,Activated Plugins 视图中标记了星号的启动插件集合里,并不包括工作台处理其 org.eclipse.ui.startup 扩展点时加载的那些插件。更确切地说,工作台是在初始启动以后才处理这些扩展的部分。
特别有趣的是 Order 列。点击这一列的标题,将插件列表以加载顺序排序。如果您希望快速查看给定的动作激活了哪个插件,先选中所有的插件(Ctrl+A),执行那个动作,再回到 Activated Plugins 视图,然后选择
按钮。没有被选中的就是那些刚被激活的插件。另外一种方法是,记录下在您的动作之前已经在运行的最后一个插件的序号值,然后更新,来查看那些排序更高的插件。查看插件的哪些类被加载
插件的类是按需要加载的。通过延迟这些引用或者减少对一些类的引用,您有可能会节约内存和启动时间。Loaded Classes 视图将帮您查看所选择的插件到现在为止哪些类已经被加载了。要更新 Loaded Classes 视图,在 Activated Plugin 列表中选择一个或多个插件,然后选择
按钮。图 4 中显示的是 org.eclipse.jdt.core 插件已加载的类,以加载顺序排序。图 4. Runtime Spy 中显示的 Loaded Classes 视图
除了插件激活次序外,我发现,通过对引用次序进行排序,这个视图还可以用来得到一个给定动作所初始化的类和序列的“整体图(big picture)”。这个列表中包括了插件启动代码,让您充分认识到执行它的代价。这个故事的寓意通常是“启动时做的太多了”。
追踪一个类为什么被加载
要更清楚地了解是什么导致了一个插件的激活或者一个类的加载,首先您必须为引起您关注的类所在的插件或者包启用追踪选项。在这个例子中,我创建了一个 traces.properties 文件,其中一行为 packages=org.eclipse.jface.text。然后您需要:
在 Activated Plugins 列表中选择 org.eclipse.jface.text 插件。
按下
按钮以更新 Loaded Classes 列表。选择 org.eclipse.jface.text.ITextViewer 类。
选择
按钮来更新 Stack Trace 视图。这将显示出哪些代码让类加载器加载了那个类(如果在这之前它还没有被加载)并激活了选中的插件,如图 5 所示。
图 5. Runtime Spy 中显示的 Stack Trace 视图
栈顶通常不值得关注,因为那追踪的是类加载器代码本身。有用的信息在中下部。在这个例子中,栈记录显示,是由于 Runtime Spy 透视图的打开而最终导致了 ITextViewer 类的加载,如高亮的栈记录行底部所示。透视图打开的初始化视图中包括 Plugin Datasheet 视图,这个视图将用 JFace Text 类 TextViewer 来显示它的数据。在 defineClass 期间核实那个类时,JVM 发现还需要 ITextViewer,因为 TextViewer 实现的是这一接口。如您可以看到的,JVM 运行期的类加载可以嵌套得相当深;出于性能目的您通常应该关注那些引起类加载器调用的代码,像图 5 中显示的栈记录的高亮部分。
追踪插件为什么被加载
上一个例子说明了一个特定类为什么会被加载。您还可以查看一个给定的插件为什么被加载,但是插件的激活原因看起来不是那么明显,因为起因是间接的。一般情况下,类是由于被另一个类的方法引用而被加载(并且您还可以参考相应的 import 语句),与之不同,插件的加载是一些间接引用的结果。不要忘记,我们的目的是,不到插件被用到的时候尽量不去加载它,所以对插件本身的引用这样实现:
显示地声明标识,比如插件清单中的 <import plugin="org.eclipse.ui">,或者
隐式地标识,比如为那个插件导出包到运行期 JAR 中。
这两个例子都在“Hello, Eclipse”插件清单节选中突出显示了,见清单 1。
清单 1. “Hello, Eclipse”的扩展点样例
<?xml version="1.0" encoding="UTF-8"?>
<plugin ...>
... lines omitted ...
<runtime>
<library name="hello.jar"/>
<export name="*"/>
</library>
</runtime>
<requires>
<import plugin="org.eclipse.core.resources"/>
<import plugin="org.eclipse.ui"/>
</requires>
... lines omitted ...
<extension point="org.eclipse.ui.actionSets">
<actionSet
label="Sample Action Set"
visible="true"
id="hello.actionSet">
<menu
label="Sample &Menu"
id="sampleMenu">
<separator
name="sampleGroup">
</separator>
</menu>
<action
label="&Sample Action"
icon="icons/sample.gif"
class="hello.actions.SampleAction"
tooltip="Hello, Eclipse world"
menubarPath="sampleMenu/sampleGroup"
toolbarPath="sampleGroup" id="hello.actions.SampleAction">
</action>
</actionSet>
</extension>
对 org.eclipse.ui.actionSets 扩展点动作设置的贡献隐式地依赖于定义插件,即 Workbench UI 插件(org.eclipse.ui)。Workbench 插件会去读取插件注册表,那里包含了对它的 org.eclipse.ui.actionSets 扩展的这一贡献,并创建相应的动作设置。SampleAction 类将不会在这里被加载,因此包含它的插件也不会被加载。取而代之的是,Workbench 插件定义了一个委派动作来代表用户界面中的选择,这个委派动作会等待,直到用户真正选择它才创建 SampleAction 的一个实例来处理响应。
为创建 SampleAction 的一个实例,动作代表(action delegate)调用插件注册表实例 IConfigurationElement 的 createExecutableExtension 方法,对应于我们的例子清单文件中指定的 <action class="hello.actions.SampleAction" ...> 标签。虽然那样不错,但是有时在栈记录结果中不容易看出来。让我们来仔细看一个比“Hello, Eclipse”按钮选择更难的例子。图 6 显示了一个由可执行扩展处理而导致的典型(插件)激活。现在让我们用 Stack Trace 视图来确定其原因,步骤同前。
图 6. Stack Trace 视图显示了可执行扩展的启动
在这个例子中,我们从栈记录高亮部分的底部可以看出,这些都是源于 PDE 的 Plug-ins 视图中的一次双击。默认的动作处理者负责处理请求 Workbench 打开 Plug-in Manifest 编辑器的动作,随后激活 org.eclipse.ui.editors 插件。栈记录的高亮部分只是显示了扩展点处理器的代码,因为它对 Workbench 插件类 (org.eclipse.ui) 的引用是间接的。通过六次类似的栈记录,您将识别出对 IConfigurationElement.createExecutableExtension 调用前后重要的部分,并很快看出是谁启动了它以及结果是什么。您可以再次选择
按钮来更新加载的类并重新按加载顺序排序,以更好地了解插件启动后发生了什么事情。其他有用的视图
最后,Plugin Datasheet 视图总结了一些有趣的统计数据,例如插件定义了多少资源和扩展,如图 7 所示。
图 7. Plugin Datasheet 显示被使用的资源
这个视图追踪了由 IPluginDescriptor.getResourceString 方法及其变量加载的资源束(resource bundle)数据。这一总结信息得益于这样一个事实,即 Eclipse 平台运行期有其自己的类加载器,并且类加载器像处理类一样处理资源束,因此保持对资源数据统计的追踪很简单。“not loaded yet”消息表示实际上插件注册表写到了磁盘上,它所引用的部分只是在需要的时候才加载。
侦探运行期工作台
前一节介绍了侦探 Eclipse 本身的底层设施。更实际的是,您会希望侦探测试版工作台,称为 运行期工作台。您可以选择 Run > Run As... > Run-time Workbench 并转到 Tracing 页,来配置您希望启动的运行期工作台的实例,如图 8 所示。
图 8. 在 Tracing 页中设置 Runtime Spy 选项
org.eclipse.core.boot 插件的调试选项也位于我们前面讨论的 <inst_dir>\eclipse\plugins\org.eclipse.core.boot_2.1.1\.options 文件选择中。这个文件定义了您几乎肯定会期望的默认值 (每个都打开)。不过,如果您想对所用的时间进行比较精确的性能测量,那么您应该将启用的选项减少到最少,尤其是需要使用栈记录的那些(即 trace/pluginactivation 等)。将 monitor/plugins 设置为 true,其他都设置为 false,这样带来的性能开销很少。
其他信息
您可以找到很多关于 Java 性能调整的资料,但是很少是专门针对 Eclipse 的。本文向您介绍的工具是用来理解和诊断插件运行相关的启动性能问题最好的工具之一。结束之前,有必要关注核心工具向您提供的一些其他有用的诊断信息:
Plug-in Dependency 透视图显示的信息与 Plug-in Registry 视图中显示的信息类似(通过 Window > Show View > Other... > PDE Runtime > Plug-in Registry 调用),但是带有一个关于选中插件所依赖插件的详尽列表。
考虑过工作空间(Workspace)的 .metadata 目录中有什么内容吗?Metadata 透视图将帮您在它的结构中漫游。不过前提是得对工作空间实现有相当深入的理解。
一类 Resource Tools 视图描述的是对资源改变监听程序和 resource delta、builder 等等的洞察。那些学习工作空间 API 的人尤其应该注意 Resource、Delta 和 Builder/Listener 视图。选择 Window > Show View > Other... > Resource Tools 来访问这些视图。
您可以在核心工具的 readme 中进一步了解这些工具(参阅 参考资料)。
本系列文章的第 2 部分将向您介绍我是如何使用 Runtime Spy 来诊断 WebSphere Studio Application Developer 版本 5.1.1 的几个启动问题的。其后,取决于运行的视图和透视图,启动性能提高了 11% 到 37%,这就证明,对您的插件何时被激活及被激活的原因的理解,可以帮助实现 Eclipse 的快速启动。