内容


使用 IBM Rational Application Developer 剖析 Java 程序

使用 Rational Application Developer 剖析功能来剖析本地和远程 Java 程序的指南

Comments

概述

下载 Rational Application Developer 试用版  |   在线试用 Rational Application Developer
获取免费的 Rational 软件工具包系列,下载更多的 Rational 软件试用版

随着硬件处理能力与存储技术不断改进,引入了大量的新技术与有趣的技术。这些技术会改进程序性能并使原本一些次要的领域得到关注,例如性能效率或者系统的灵活性。这一类技术领域包括了像垃圾收集汇编语言之类的技术,例如 Java™,以及整体系统虚拟化技术。

随着计算机的处理能力和速度增长地越来越快,而每一计算处理单元处理功能的成本却在不断降低,所以看起来每一个独立应用程序效率的需要也在降低。但是,当面对足够大量的用户时,就算是最小的程序也会发生崩溃。同样,最大型的程序可能会掉入难堪的性能瓶颈和内存泄漏问题,这些问题会损害程序的可用性,并且由于页面载入时间问题影响到它的实用性,并存在潜在意义上的更新修复成本。

IBM® Rational® Application Developer 与 IBM® Rational® Software Architect 的剖析工具提供了功能强大的工具,开发员可以使用这些工具来识别和解决这些性能问题。两种产品都包含了本指南中所描述的剖析工具(Profiling tool);不过,虽然两个产品在功能和特性一直在更新,本文只关注于 Rational Application Developer。这一剖析功能基于开放源 Eclipse Test and Performance Tools Project (TPTP) Java™ Virtual Machine Tool Interface (JVMTI) Profiling 代理,如果您想要得到更多信息,那么您可以访问 参考资料 部分。

Rational Application Developer 剖析平台提供了程序行为的三种不同分析方法:

  • 内存使用情况分析
  • 方法层次的执行分析
  • 线程分析

内置的集成及已往版本的 Rational Application Developer 启动类型集成到一起,使得剖析您的程序变得和选择停表剖析图标一样容易,然后您要从启动列表中选择已存在的 Run/Debug 启动配置选项。虽然当您已剖析的程序启动时,数据便已开始进行收集,但对程序剖析术语与概念有一个基本的了解,将会对尽可能地发掘剖析功能的潜力有很大的帮助。

本教程向您提供了使用 Rational Application Developer 来剖析 Java 程序的指南。它首先会提供 Performance Tools 功能的相关背景。

JVMTI 接口与代理结构

Rational Application Developer Java profiler(Java 剖析器)由一系列 Java Virtual Machine 的 JVMTI 接口实现的代理组成。JVMTI 接口是一种标准的接口,它允许本地的库(就是所谓的代理)去控制虚拟机(Virtual Machine,VM),并获得关于执行 Java 程序的相关信息。一个 JVMTI 代理可以收集 VM 的事件信息(在需要的地方,还包括类字节码),并确保在该 VM 之上执行的 Java 程序涉及的所有剖析事件都通知到。

剖析器会通过 JVMTI 接口来注册它的本地功能:当注册的事件发生时会调用这些功能。有了 Rational Application Developer Java 剖析代理(Profile Agent),剖析数据就会实时生成了。这就是说,代理 不会 等待,直到程序终止以提供和显示数据为止。产品支持代理去收集关于上面所列出三个范围的信息。

一个重要的注意点:Rational Application Developer JVMTI Java Profiler 并不是一个基于范例的剖析器,与之类似,没有被剖析筛选规则所筛选的 所有 JVM 事件将会被转移回工作台上。这种剖析的方法可以确保高层次的精确度,但是付出的代价则是与基于范例的剖析相比更大的剖析负荷。

剖析代理

剖析代理 是在 JVM (以及 JVM 过程之内)随着特定 JVMTI 的参数一起运行时执行。当剖析代理运行时,它们以执行、收集或者线程事件的形式,从 JVM 中收集数据。在 Rational Application Developer 剖析区之内,这些代理被引用为 Execution Analysis、Heap Analysis 以及 Thread Analysis 代理。

  • 支持分析代理 用于收集执行的统计数据,例如在每一种方法中花费的时间,方法调用的数量,以及总体的访问图。
  • 积累分析代理 用于生成内存使用的统计数据。它收集一些对象分配的细节信息,例如活动实例以及内存中的活动空间。
  • 线程分析代理 用于获得关于 Java 程序所扩展的线程的具体信息,并追踪目标程序的对象监视使用情况以及满意程度。

为了促进代理的启动和客户端-代理之间的交流,剖析工具使用一种称为 代理控制器 的第二种构件,以允许代理去与工作台相交流。这种构件是与 Rational Application Developer 预绑定的。

使用代理控制器,一个目标 Java 程序可能位于与开发员工作台不同的机器之上。代理控制器的功能就好像是一个中介一样,在工作台与剖析代理之间发送命令和数据。接下来的图显示了一个远程的剖析场景。开发员的工作台与代理控制器之间的交流是通过插座进行的,同时代理控制器与代理之间的交流是通过所谓的管道和共享内存来进行的。

图 1. 远程剖析
客户与代理之间的插座连接
客户与代理之间的插座连接

除了代理支持与交流之外,代理控制器还提供了一些额外的服务,例如进程启动,终止,监视以及文件转移。代理控制器进程需要一直在目标机器上运行,以使用该台机器之上的剖析功能。幸运的是,启动这种进程有很多种方法。

当您直接在本地工作台上剖析本地启动的程序时,一个 集成式代理控制器 (IAC)会自动启动,不需要用户的干预,不管何时需要的功能被激活时都是这样。只要工作台处于打开状态,那么 IAC 就会一直运行下去,并直到工作台关闭时才会停止。默认的 IAC 设置可以通过 Preferences > Agent Controller 之下的配置视图来进行调整。

除了通过 IAC 来进行本地启动,您还可以使用代理控制器来在任意支持的远程主机上启动程序。一个 独立代理控制器,作为 IBM® Rational® Agent Controller daemon 进程包裹起来,可以为 32 位及 64 位的 Linux®、 Microsoft® Windows®、IBM® AIX®、Solaris、IBM® z/OS® 及 Linux on IBM® System z® 系统进行下载。它们向程序提供了在远程主机上直接执行的能力,同时使得远程代理上生成的数据,通过对远程机器的插座连接发送回到本地的工作台上。

您可以在本文的结尾部分查看 参考资料 部分以得到下载 Rational Agent Controller 的地址。

您需要什么来完成本文中范例的学习

本文中的所有范例是使用 Rational Application Developer 7.5.4 版本来概述的。剖析工具可以通过 Rational Application Developer 或者 Rational Software Architect 来获得,或者一个支持的基于 Eclipse 的产品与 Eclipse TPTP 插件一起获得。您可以查看 参考资料 部分以得到更多的信息。

对剖析对话框的介绍

开始时,您可以切换至一个基于 Java 的视角并查看剖析对话框图标的绿色播放按钮(剖析对话框图标的绿色播放按钮)。Profile Configurations 剖析对话框就是 Rational Application Developer 中所有剖析功能的中心起始点。您还可以从菜单中选择 Run >Profile Configurations,来从大多数的视角中访问它。

Rational Application Developer 剖析功能支持几乎所有的标准启动类型 :

  • Eclipse Application
  • Java Applet
  • Java Application
  • Test
  • Java Unit (JUnit)
  • IBM® WebSphere® Application Server 6.0 版本,6.1 版本与 7.0 程序客户端
  • 其他类型

另外,支持被两种特定剖析器的类型扩展了,这两种类型就是 Attach to AgentExternal Java Application

  • Attach to Agent 允许对任意的 Java 虚拟器进行剖析,程序对 JVM 的独立使用,以及适当 JVMTI 剖析代理 VM 论断的使用。因为该条目与任意的程序一起工作,它可以用于其他未支持的程序配置,不管是本地的还是远程的,以及在位于程序服务器上还是完全独立的程序上。这需要您去配置适当的类路径,环境变量以及来自命令行的程序参数,还有需要的 JVMTI 代理论断(稍后将会在指南中进行描述)。
  • External Java Application,与 Attach to Agent 相类似,支持剖析任意类型的独立 JVM。与 Attach to Agent 不同的地方在于,外部性的 Java 程序需要您去指定类路径,环境变量,程序参数,以及工作台中的剖析类型(启动器中),而不是从命令行中的。一个值得注意的重要地方是:所有需要的类文件,Java 档案(JAR)文件,以及其他的附件必须在目标主机上已经显示了出来,因为代理控制器支持类或者文件的远程文件转移,以满足程序的需求。

联系方法层次的执行统计数据

在指南中以上讨论过的三种剖析代理之中,最常用的代理是方法层次上的执行分析代理,它提供了对方法执行的大量统计性数据。接下来的范例提供了可用执行统计数据功能的直接解释。

  1. 首先,选择一个 Java 程序进行剖析。
  2. 右击 Java 下面并选择 Profile as
  3. 如果这是您首次剖析该项目,那么就会显示 Profiling 对话框。否则它会自动转换为选中项目的最后一次保存过的剖析配置。
  4. 为了编辑以前的剖析配置,您可以使用 Profile Configuration 对话框,如前面所描述的那样。

Profiling 选项包括了执行时间分析、内存分析与线程分析,如图 2 所示。

图 2. 编辑配置与启动对话框中的对话框
剖析对话框的剖析选项 UI
剖析对话框的剖析选项 UI

在剖析对话框中,您将会看到一些剖析的选项:Java Profiling 之下以前提到过的剖析选项,就是本篇指南的主题。第四项,Probe Insertion,描述了一个使用 Probekit 工具描述一个附加功能,以使用通用探索功能来指导程序的方法,但是这已经超出了本文的讨论范围。

  1. 在这里点击 Edit Options 按钮。

执行时间分析拥有三个选项:

  • Execution Flow 提供了更大量的剖析数据视图,但是增加了剖析的工作负担,剖析数据的量以及工作台的内存使用情况。对于拥有大量剖析数据集合的程序来说,它也许有点不太适用。
  • Execution Statistics 有一个较大幅度降低的工作负荷以及工作台内存使用情况,但是付出的代价却是一些剖析功能的丧失。只有 Execution Statistics 与 Method Invocation Details 功能才能够使用。
  • Collect method CPU time information:在上述的任意一种模式之中,剖析器都可以收集 CPU 花在执行剖析方法的时间。这不同于上述的任何一种方法,因为 I/O 与等待时间并没有包含在 CPU 时间之中。

操作的最佳实施顺序是从 Execution Flow 开始,然后如果数据量过于巨大时,就切换至 Execution Statistics (或者调整的筛选规则集)。

  1. 对于这个实例,您可以选择 Execution Flow,因为它提供了所有可得到剖析视图的一个范例。

筛选

在选择剖析按钮之前,适当的筛选规则应该已经就位,这一点非常重要。大量的剖析数据是作为剖析过程的一部分生成的,因为每一个单个的方法调用,对象分配或者线程事件,都需要生成、转移和处理一个事件。

剖析数据的净数量有时可能是巨大的,不论是对于执行程序来说,还是对于工作台本身来说,如此巨大的数量都是吃不消的。但是,这些数据中只有一部分对于分析目的是有用的。幸运的是,剖析的数据提供了一个方法去筛选去除掉一些不相关的信息,这样您就可以为剖析程序降低代理数据的量。

例如,在剖析一个 Java 程序的过程中,您可能只关注那些您的程序的方法,以及一些不在执行时间之中的像 java.*、sun.* 等等标准 Java 语言包。使用特定于程序中类的一种筛选非常重要,它可以尽可能多地从外部类中降低剖析的工作负荷。

  1. 为了设置筛选规则,您可以双击 Java profiling – JRE 1.5 or newer 头目。然后您将会看到一个与图 3 中窗口相类似的窗口。筛选规则指定了剖析哪些包和类;筛选规则本身都包含在可配置的筛选规则集合中。
图 3. 选择筛选规则以及它们的内容
筛选与筛选规则创建 UI
筛选与筛选规则创建 UI

在这个对话框中,您可以定义新的筛选规则或者编辑已存在的筛选规则。提供了一些在大多数剖析场景中有用的筛选规则,如图 3 所示。筛选规则集列在了顶部的窗格之中,而筛选规则本身位于底部。筛选规则的顺序是从底部到顶部,这意味着列表顶部的筛选规则将会压制列表底部有冲突性的筛选规则。筛选规则与筛选规则集可以使用对话框右边的按钮来添加和删除。

  1. 对于这个范例,您对默认的筛选规则感到满意并选择 Finish 以返回到剖析对话框。
  2. 从这里,选择 Execution Analysis,然后点击 Profile 按钮。工作台将会确认对 Profiling 与 Logging 视角的切换,而剖析后的程序也将开始在 Profiling Monitor 视图中运行。
  3. 点击 Execution Time Analysis 以打开如图 4 所示的 Execution Statistics 视图。
图 4. Execution Statistics 视图中剖析类的一个小型数据集合
表格中的四个方法,以及它们的执行状态
表格中的四个方法,以及它们的执行状态

Session summary 项提供了拥有最高基底时间的最重要十种方法的概述。但是,所有的数据都是从 Execution Statistics 项中获得的。在这里您可以找到关于方法革新及其统计的具体信息:访问的时间,平均花费的时间,累积的时间等等。

有一些关键的定义有助于弄清该视图中的列:

  • Base Time:执行方法内容所花的时间,排除了 对其他方法的调用(在表格中, Base Time 字段会总结该方法所有的调用)。
  • Average Base Time:完成特定方法所需要的平均时间, 排除了 对其他方法调用方法的时间(在表格中,这就是访问数量所划分的 Base Time )。
  • Cumulative Time:执行方法本身所花的时间, 包括了 对其他方法的调用。

这些统计数据有助于您去定位一个程序中的性能瓶颈。

基于这些定义,有三个比较重要的地方需要您考虑一下:

  • Average Base Time:它是完成一个方法所需要的平均时间。所以,平均看来,这就是一个方法需要完成单个革新所需要的时间(如上面所述的那样,它排除了该方法调用子方法所需要的时间,更具体的说,排除了未筛选子方法的时间 )。
  • Base Time:这就是完成一项方法所需要的总体时间。这就是我们花在该方法上所有时间的混合体(排除了对其他未筛选方法的调用)。
  • Cumulative CPU Time:累计 CPU 时间代表了花在执行特定方法之上的总体 CPU 时间。但是,JVM 提供的数据的组成性变得更加粗糙,但是却更需要它。结果,如果时间要少于 JVM 所报告的单个特定平台的单元,那么 CPU 时间可能报告为零。同样,CPU 时间不会考虑其他类型的性能瓶颈,例如那些涉及到的交流类型以及 I/O 访问时间。结果,基底时间通常可以当作性能瓶颈降低的工具。

一眼看去,平均的基底时间好像是决定什么方法降低系统速度的关键数据点。但是,虽然平均基底时间可能会精确指出花费长时间执行的方法,但是并没有考虑到访问一个方法的次数。您可以问您自己哪个更差:一个只运行一次的方法并花费 5.0 秒,或者另一个运行 1000 次并花费 0.5 秒?第一种方法的平均基底时间是 5.0 秒,而第二个方法的平均基底时间是 0.5 秒。

但是,第一种方法的基底时间是 5.0 秒,而第二种方法的基底时间是 500 秒。将程序的运行时间降低 500 秒要比仅仅降低 5 秒显著的多。因此您可以毫不犹豫地说出,由于基底时间差异的存在,第二种方法要比第一种方法获得更多的关注。因此基底时间是降低一个程序中性能瓶颈的主要关注点。因为基底时间代表了整个程序运行的混合物,通常所说的降低基底时间就相当于说降低运行时间。

当基底时间只代表了执行方法本身所花的时间(除了对其他方法的访问),累计时间则代表了执行一种方法所花费的总体 时间,包括子访问。所以,举个例子,在单一线程的程序之中, main(…) 方法的累计时间相当于程序中所有其他方法的所有基底时间的总和,因为 main(…) 方法就是程序的起始点,因为它就是所有方法访问的起始点。因此,它就相当于总体的程序运行时间。

在对执行统计数据有了详细的理解之后,让我们看一下分析性能问题的具体形式。为了得到调用什么特定方法的更好理解,以及特定的方法是从哪里调用的,您可以双击 Execution Statistics 项中想要的方法。这将会打开 Method Invocation Details 项,如图 5 所示。

图 5.进一步深入了解特定方法的细节信息
方法调用项与范例数据
方法调用项与范例数据

图 5 的大图

Method Invocation Details 项就是那些执行统计数据的直接代表,其中的统计数据与选中的方法直接相关。项中的第二个表是 Selected method is invoked by,这将会列出程序运行期间访问选择方法的所有方法。

第三个表, Selected method invokes,将会列出选择方法所访问的所有方法。Execution Statistics 项的相同统计数据在这里会重新生成,您可以从任意的这些表格中选择一个方法,以更新视图顶部的选择方法。

接下来需要注意的一点是 Call Tree 项,当您剖析 Execution Flow 剖析选项时它才可用。Call Tree 项会为访问特定线程的所有方法分解访问调用。表格中的第一层次项目是程序运行期间展开的线程。在它下面是方法调用的混合体,以及树形结构前面层次所访问方法的下一个层次的方法。

图 6. 从更加线程集中的视角来显示的 Call Tree 项
显示的一个线程及其所有的方法调用
显示的一个线程及其所有的方法调用

图 6 的大图

在最顶级的层次上,每一个线程的 累计时间 代表了线程在程序中运行的总体时间。那些拥有更高累计时间的线程,就是分析和优化的潜在对象。

Percent Per Thread 字段代表了执行一个方法所花费的总体时间,它用执行线程所花费总体时间所占的百分比来表示。它代表了一个最顶级方法对线程调用的总体累计时间(就是花在执行线程上的总体时间)。它还提供了其他的统计数据,例如完成对线程执行方法的最大时间,最小时间以及平均时间,以及访问的总体数量。

在顶部的访问树表之下,是方法调用栈的表格,它显示了访问树表中当前所选中项目每一个方法调用的栈的内容。可得到栈的数量,与所列出访问的数量相等。在向分析特定的方法调用实例时这一点十分的重要。

最后,您可以右击方法条目并从任意的视图中选择 Open Source。如果相关的 Java 文件出现在工作区中,工作台文件将会打开,而方法定义也会得到定位。当性能瓶颈得到识别之后,您就可以编辑它们,然后再次进行剖析以查看它们之间的差异。

积累分析:定位内存泄漏

查看剖析内存使用情况的程序开发员的主要目的是 :

  • 在程序运行时分析积累的内容(支持生成一类类的统计数据 )
  • 通过在用户请求收集垃圾之后,通过使用积累分析来识别内存泄漏情况(去识别属于或者不属于收集垃圾的对象)。

为了开始从程序中收集积累信息,您可以使用 Memory Analysis 剖析类型从 Profiling 对话框中启动程序。内存分析中可用的唯一剖析器选项,是是否要 Track Object Allocation Sites。分配站址就是对象实例化的地方(含蓄地或者明显地)。选中这个选项,您就能够选择 Object Allocations 视图中的类,并识别这些对象是从哪些方法中创建的。

选中 Track Object Allocation Sites 选项的唯一缺点在于,这会极大程度地增加生成的剖析数据的量。如果剖析性能受到了影响,或者工作台响应性受到了影响,那么您可以考虑清除这些选项或者使用更新的筛选规则集来降低数据量。

除了对象分配之外,您要确保为程序设置正确的筛选规则。与执行或者线程分析相比,积累剖析拥有更大的总体实施负担,它本身会对程序性能造成一个潜在的压力。最终,当选择筛选时:如果您想要看到,例如,Java 占据的空间例如 String 或者 Integer,您就需要向筛选规则添加这些项了。默认条件下,它们是由 java* *EXCLUDE 规则来筛选的。

对象分配视图

当您可以从剖析程序中得到数据时,它会显示在 Object Allocation 视图中。Object Allocation 视图是一个主视图,积累剖析代理收集的所有信息都会显示在该视图中。

Object Allocation 列:

  • Live Instances:指定类积累中对象的当前数量,当前已经被使用了(尚未收集垃圾)。
  • Total Instances:JVM 生命周期内创建的积累中对象的总体数量(包括那些收集的垃圾)。
  • Active Size (比特):特定类所有对象实例的总体大小,一般由 JVM 所使用(换句话说,未被收集的垃圾)。注意对象的大小是 JVM 实施独立的。
  • Total Size (比特):类的全部对象实例的总体数量,包括了程序生命周期早期所收集的垃圾。
  • Average Age:在收集垃圾之前对象的平均时期,用该对象保存的收集垃圾的数量评价。如果程序不再需要的话,保存有大量收集垃圾的对象通常被认为是一种内存泄漏情况。

内存统计数据表格中的数据可以使用视图工具栏在包层次与类层次之间进行切换。当您在处理大量类时,这一点将会十分的有用。可以将数据看做从上一次刷新期间已存在数据的百分比。图 7 显示了从左至右的图标,报告生成,筛选,包/类视图选项,以及百分比和 delta 选项、

图 7. Object Allocations 视图工具栏中的图标
对象分配视图中的头图标
对象分配视图中的头图标

如果您选择了 Object Allocation 剖析选项,那么您双击 Memory Statistics 表格中的任意条目,将会切换至 Allocation Details 项。该项代表了程序中所有位置的表格,其中该类型的对象将会得到分配。当特定的类型将会识别为积累中所代表的,对于使用视图中的数据来决定那些对象分配到哪里,这一点将会十分的有用,允许您识别并消除过多的分配。

使用该功能来识别积累问题

当工作台开始从目标程序收集剖析数据时,您就可以在任意时间咨询 Object Allocation 视图,以决定积累的当前内容。表格将会实时反映所有的对象分配情况以及未分配事件。这就提供了内存内容的实时视图,表达为总体的百分比,或者绝对的术语(比特)。

通过分类数据表并选择类,开发员在需要改进时可以设定目标问题区域。那些类的分配细节会提供对象创建源的列表,然后您可以使用它,调查问题的来源。

该范例代表了一个简单的聊天室网络程序,它允许用户登录并且相互之间进行交流。聊天室的聊天者会转移到所有的参与者,然后所有的会话都会写入到服务器上的日志文件中。但是,使用 Rational Application Developer 的 Heap 剖析功能,您就可以识别一个严重的内存泄漏问题了(可能会调用特定的类)。

在列出总体百分比的 Memory Statistics 视图中,您可以看到 ChatlineMessage 类代表了积累中分配对象的大约 98%,组成了 61% 的总体积累内容。对于程序开发员来说,这可能会是一个严重的警告信号,积累内容中一个或者多个类得到了重复代表,并且导致了程序中的内存泄漏问题。

图 8. 使用选择的 delta 选项来查看
Class 列表,以及最差的入侵者
Class 列表,以及最差的入侵者

图 8 的大图

Profiling Monitor 视图允许您去请求收集 JVM。当在和 delta 表格一起使用时,在决定不相关对象中包含有多少 JVM 时这一点十分的有用,这是另一种类型的内存泄漏。

在选择无用收集之前,您可以从 Object Allocations 工具栏中选择 Show Delta Columns 图标。这将会引入四个新的 delta 列,它是已存在列的 delta 版本,并在您选中 Refresh 按钮(见于以下的图 9)时,这一点会反映这些值中的更改。当您在 delta 表格中工作时,您可以从 Profiling Monitor 中点击 Run Garbage Collection (带有一个垃圾箱图标的绿色播放箭头)图标 带有一个垃圾箱图标的绿色播放箭头,以开始一种 JVM 垃圾收集。垃圾收集的结果在您选择刷新按钮时反映在 Memory Statistics 表格中,如接下来的图 9 中所示。

对垃圾收集内容作出贡献的那些类,可以通过 Delta: Active Size 表格列进行分类。收集期间这就是失去最大规模的类,它们会对总体未使用的对象汇作出最大的贡献。

当 JCM 执行垃圾收集操作时,它会搜索单独积累中分配的对象(这就是说,积累中的其他对象没有引用它们,它本身也没有被积累对象所引用)。使用与上面讨论相同的聊天程序范例,现在您可以指导 JVM 来通过工作台来执行一次垃圾收集。您可以看到最初的结果就是分配 ChatlineMessage 对象数量的下降,以及积累总体数量的下降。

图 9. delta 列提供了关于对象收集实时统计数据的信息
垃圾收集减少了活动对象的数量
垃圾收集减少了活动对象的数量

图 9 的大图

该屏幕截图显示了您在请求垃圾收集之后, Memory Statistics 视图的内容。积累的内容已经得到了剧烈的降低,减少了 22,079 个活动实例以及相应的百分比下降。在五秒或者六秒之内, 活动实例 百分比将会降至接近 0 的值。在这个程序中,您会发现 ChatlineMessage 对象已经得到了分配,简单地使用,然后丢弃掉。

使用积累分析,以及垃圾收集,您就可以识别被分配但是没有收集垃圾的对象,或者那些没有被程序生命周期内其他对象引用的对象,它们是单独对象的一个重要来源。程序开发员可以分析并降低程序的总体足迹,并且可以降低系统交换处罚,提高响应时间,并降低程序的系统需要和成本。

线程分析:追踪线程行为

这一部分面对的读者是那些希望识别并校正程序中与线程相关问题的数量的用户。您可以使用这些剖析工具,来检查这些线程多久堵塞一次(被谁堵塞),并查看程序的线程特征。

对于全局性的关注,例如线程行为在什么地方是潜在怀疑点的实例性能关注,目标就是识别可能会影响程序性能的一般线程问题。那些想要剖析线程行为的程序开发员的主要目标在于,找到一个要么一会运行,要么一个运行更加快速,作为替代程序的来源和线程特征。

在开始从目标程序中收集线程信息时,您可以使用 Thread Analysis 剖析类型来从 Profiling 对话框中启动程序。Rational Application Developer 的 Thread Analysis 视图,对于线程剖析代理所收集的所有信息来说,都是中央的视图。

Thread Analysis 视图被分解为了三项 :

  • Thread Statistics:一个程序启动的每一个线程的统计数据表,不管这些线程是过去的还是现在的。列出的信息还包括线程表格;总体运行时间,等待时间以及堵塞时间;每一个线程堵塞和死锁的数量。
  • Monitor Statistics:提供了关于类统计数据的具体信息,包括私人监视类的堵塞和等待统计数据。
  • Threads Visualizer:提供了目标程序中由状态所剖析的所有线程的可视化代表。

所有视图中的线程是由线程组所组织的。线程分析视图中的数据将 只会 更新什么时候其他的数据会从剖析程序中接受。这一点恨重要;表格和图 只是 在线程相关的事件从目标程序剖析器中接受时才会得到更新。如果它出现了就好像数据没有得到更新时,这是因为没有接收到新的线程事件,而数据仍然处于它的前一个状态之中。

列出的线程名就是传递给 Thread(String name) 构造器的名字,或者使用 Thread.setName 方法来进行设置。在目标程序中调用这种方法可能是有益的,以支持更容易的线程识别。线程统计数据是基于所有的 Java 线程来收集的,它包含了 VM 线程,并且可能包含了程序构造器或者程序框架所使用到的线程。幸运的是,您可以选择线程筛选图标(是一个中间黄色箭头划过垂直绿色线的三个箭头 ),来从视图中筛选出去那些您不感兴趣的线程,并清除掉那些不想要的线程。

七种线程状态分别是 :

  • 运行
  • 睡眠: 这种状态下清晰调用 sleep 方法
  • 等待: 这种状态下清晰调用 wait 方法,并等待在其监视对象上的 notify 或者 notifyAll 访问
  • 堵塞: 对一个被对象堵塞实例的引用,该对象被另一个线程所使用(例如,一个线程在一个同步化的状态下维护对象监视器的线程)
  • 死锁: 对于目标程序的每一个线程,在每一个线程的层次上收集统计数据。当两个或者更多的线程都含有资源时就会发生死锁现象,其中资源关系图包含了一个循环(这就是说,所有的死锁线程都需要它们所不能获得的额外资源,而不用释放需要资源的其他死锁线程 )。
  • 停止
  • 未知

在 Java 中,在很多种情况下都可以发生死锁现象,例如 :

  • 在两种线程中,两个类中的同步化方法会试着调用同步化的方法。
  • 当一个线程同步化资源 A 并试着在第二个资源 B 上进行同步化,同时第二个线程同步化资源 B 并试着同步化资源 A 时就会发生死锁现象。
  • 另外一种情况中,不可能让两个或者更多的死锁的线程解锁和完成。

线程统计

该项列出了程序的整个生命周期内,当前所运行的所有的线程。线程就算在程序终止以后仍然停留在表格之中。该视图列出了运行时间、等待时间以及堵塞的时间。其中 运行时间 被定义为线程的总体运行时间,减去等待或者堵塞之后的时间。等待时间 被定义为线程花在等待监视上的时间,而 Blocked time 就是线程花在被其他线程监视器拥有权堵塞上的总体时间。另外,还有一些记录的数量 :堵塞数量 以及 死锁数量 分别是线程生命周期之内线程堵塞或者死锁的次数。

在这个范例之中,您要剖析一个 Eclipse 插件,图 10 显示了所有运行线程、它们的运行时间、等待时间、堵塞时间以及死锁时间还有堵塞次数的状态。程序开发员可能会使用该视图,来查看程序的整体线程情景的情况,或者深入研究特定线程的统计数据。

图 10. 在剖析 Eclipse 插件上运行的线程的列表
当前状态列出的运行线程
当前状态列出的运行线程

图 10 的大图

等待时间,堵塞时间以及死锁时间是非常重要的统计数据,您可以参考它们来考虑程序的性能情况。这些值应该得到详细的审查,以确保它们都是合适的,特别是对时间独立的线程更应该详细审查。

监视统计数据

Thread Analysis 视图中的第二项是 Monitor Statistics,如图 11 所示。Java 中所有的对象都有一个相应的监视器,这是 Java 中所有并发操作的基础。当您进入到一个同步化块的内部时,就会调用监视器,或者在调用 wait 或者 notify 方法时,会分别等待可用性的附属或者信号。该项提供了线程-线程基础之上的监视器状态。

图 11. Thread Analysis 视图的 Monitor Statistics 项
类及其监视状态列出了它们的监视数据
类及其监视状态列出了它们的监视数据

在图 11 中, Thread Statistics 表格包含了剖析程序中的一系列线程,以及来自首项的线程的各种统计数据。选择 Thread Statistics 表格中的一个线程,来显示线程所引用的监视器,包括这些监视器的各种统计数据。然后您可以选择监视器以打开关于其类的信息,包括监视器访问者的块与统计数据,还有计时与对象信息。这就支持您去识别特定的对象。

线程可视化

在如图 12 所示的 Threads Visualizer 项中,七个线程状态中的每一个都由变化的背景栏和线性模式代表。线程根据线程组和线程名来分组。图的 x 轴代表了时间,它的范围可以使用放大或者缩小按钮来进行调整。

表格的每一行都包含了一个代表线程执行情况的工具栏。在每一个栏中都是事件的持续性列表,它代表了线程状态的更改。您可以双击一个事件以在 Call Stack 视图中显示它的访问栈,而且您可以使用 Threads Visualizer 项右上角的 Select Next EventSelect Previous Event 按钮,来从一个事件移动到另一个事件。

在图中, Waiting 和 Blocked 状态是由点线代表的,而 Deadlocked 与 Stopped 状态是由一条实线表示的。对于识别性能问题的程序开发员来说,最重要的是 Deadlocked (红色), Waiting (橙色)与 Blocked(黄色)。

一个重要的 UI 注意点:当一个线程终止,它会继续在表格上维护一个暗灰色的代表(而不是从图表中完全地消失掉),可能与预期的行为相反。

图 12. Threads Visualizer 以一个图显示了所有线程的线程状态
Thread 图,每一个都有线程状态
Thread 图,每一个都有线程状态

图 12 的大图

线程分析工具栏上的按钮用于将注意力转自或者转至特定的线程。从左到右,如图 13 所示,按钮分别是:Legend, Show Call Stack, Reset Timescale,Zoom In/Out,Select Next/Previous Event,Select Next/Previous Thread,Group Threads, Filter Threads 以及 Visualize Thread Interactions。它们中的大多数是不言自明的:例如, Next/Previous Thread 按钮会更改当前选中的线程,而 Select Next/Previous Event 按钮则会将游标移动到下一个项目,或者当前选中线程的前一个事件。另外,您可以根据自己的需要来分组并筛选线程。

图 13. 使用视图顶部的按钮来与 Threads Visualizer 图相交流
 Threads Visualizer 头上的按钮
Threads Visualizer 头上的按钮

当您使用 Rational Application Developer 线程剖析功能时,有几点您需要进行考虑一下 :

  • 对于处于工作-睡眠循环状态下的线程,它需要多长的时间来完成循环的工作阶段,又需要多久的时间来完成睡眠阶段呢 ?
  • 对于独立于外部性资源的线程来说,它们堵塞了多长时间以等待可用的资源 ?
  • 在输入-处理-输出型的程序中,例如 Web 程序,不同的线程需要多成的时间以响应用户输入,处理数据,以及产生相应的输出 ?
  • 有一些线程会阶段性地工作,以检查一种状态或者执行一项功能,然后返回至睡眠状态。线程分析允许您使用 Threads Visualizer 来观察这些关系。
  • 您可以使用剖析功能来监视生产者-消费者和读者-作者关系。

线程剖析功能提供了各种的视图,来分析程序线程的性能与行为。这些视图允许您去收集信息,并分析程序执行的各个方面,以得到失败状况的潜在性瓶颈。

远程机器上的剖析程序

到目前为止,本文讨论了在本地机器上运行的剖析 Java。Rational Application Developer 剖析还提供了一些功能,去启动并剖析在与工作台相隔离机器上运行的程序。为了激活这项功能,您可以下载 Rational Agent Controller component 并将其单独安装到 Windows,Linux x86/x64,Linux for System Z,IBM AIX,IBM z/OS,Solaris SPARC 以及 Solaris x86 上。

安装与启动 Rational Agent Controller 的指南可以在 IBM 下载网站上获得;您可以咨询 参考资料 部分以得到具体的信息。当它完成安装以后,您就可以运行 SetConfig 创建脚本了,然后您可以使用远程机器上的 ACServer.exe (Windows)或者 ACStart.sh (UNIX®)。Linux on System Z 配置的一个范例如图 14 所示。

图 14. 从命令行中启动 Rational Agent Controller 进程并创建 Java Profiler 环境变量
运行的配置脚本,以及启动剖析器
运行的配置脚本,以及启动剖析器

图 14 的大图

剖析器需要您设置附加的额外环境变量。在 Linux 上,例如,就需要特定代理的 LD_LIBRARY_PATHPATH 变量。前面图 14 所示的其他变量出于方便考虑,只使用它们的值一次,而不用输入很长的一段路径。您可以将其添加至全局环境变量中,在终端会话中指定它们,或者将它们添加至 Java 程序的启动脚本中。另外,有些平台允许您去剖析,而不用设置这些环境变量(但是要在命令行中指定路径)。您可以咨询 Rational Agent Controller 安装的 Getting Started 文件,以得到更多的信息。

除了设置这些环境变量,您还需要决定需要的剖析数据的类型,并设置 JVM 论断以在启动程序时反映这些类型。

Windows:
Windows 上的 JVM Arguments :

-agentlib:JPIBootLoader=JPIAgent:server=<agent-behaviour>;<profile-option>

All UNIX 变量
Linux 上的 JVM Arguments :

'-agentlib:JPIBootLoader=JPIAgent:server=<agent-behaviour>;<profile-option>'

(注意整个字符串附近的单个引用;之所以需要它们是因为内核并没有将一个半冒号理解成一个新的线字符)

注意上面范例命令行 JVM 论断中 CGProf (通用命令行中的 <profile-option> )的使用:这就是一个与数据收集剖析类型相对应的选项:

  • CGProf:这相当于工作台剖析 UI 中的 Execution Time Analysis。正如以前提到过的那样,通过将执行时间分解为具体方法的基础,该选项用于识别性能瓶颈。
  • HeapProf:这相当于工作台剖析 UI 中的 Memory Analysis。如前面所述的那样,该选项通过追踪对象分配情况和垃圾收集事件来追踪积累的内容。
  • ThreadProf:这相当于工作台剖析 UI 中的 Thread Analysis。该选项追踪线程以及程序执行期间的使用情况。

您需要选择一种数据收集类型,并将该值放到上面的剖析-选项 JVM 论断值中,您可能一次只能指定一个。

另外,您需要选择代理行为(例如,图 14 中的范例使用 controlled):

  • controlled:该代理行为会阻止 JVM 初始化,直到代理具体化(从工作台处)并给定指引以开始监视。一旦代理连接建立了起来,JVM 就会启动了。因为 JVM 会一直等到工作台建立了连接,所以剖析代理将会为程序的整个生命周期生成数据。
  • enabled:有了这种代理行为,剖析代理就会在 JVM 创建时启动。但是,JVM 会立即得到初始化,并开始运行,而不用等待工作台去连接。剖析代理并不会开始生成数据,直到工作台连接到代理并开始监视为止。没有剖析的数据生成,直到工作台工作之后。在工作台连接之前,所有发生的程序执行都 不会 被记录。

另外一个代理行为是 standalone,在本指南的讨论范围之外。通过将数据写到本地文件系统上的追踪文件中,它支持剖析而不用一个代理控制器,这 可以直接导入到 Rational Application Developer 中。与之类似,可以得到其他的命令行选项,以精确地调试剖析器数据。如果您想要得到更多的信息,那么您可以咨询 Rational Agent Controller Getting Started 文件。

范例 1

-agentlib:JPIBootLoader=JPIAgent:server=enabled;HeapProf

(Windows 上的积累剖析,前面提到过的激活模式)

范例 2

-agentlib:JPIBootLoader=JPIAgent:server=controlled;CGProf

(执行时间剖析,在 Linux 提到过的受控模式)

当目标程序 JVM 与适当的 JVM 论断一起运行时,那么您已经做好准备去连接到工作台上了。为了连接至工作台,您可以打开 Profile Configurations 对话框,如图 15 所示。

图 15. 从工作台 UI 中选择 Profiling Configurations 对话框
使用停表图标访问的 Profiling 对话框
使用停表图标访问的 Profiling 对话框

一个对话框会出现,显示可用的剖析选项,如图 16 所示。

图 16. 剖析启动配置选项
剖析对话框的启动配置的列表
剖析对话框的启动配置的列表

图 16 的大图

双击该选项以创建一个新 的 Attach to Agent 启动配置(以前描述过)。您还可以添加远程机器作为一个新主机。远程机器上的代理控制器通过端口 10002 (默认的端口号)来获得,如图 17 所示。

图 17. 添加主机对话框
用户特定的远程主机名与端口
用户特定的远程主机名与端口

在它作为一个主机添加之后,远程机器上运行的代理(在本例中是执行统计数据代理)可以在 Agents 项上获得。如果不是的话,它有助于确认 Agent Controller 的创建与状态。当您做好准备之后,选择代理并点击 Profile。工作台将会附属于代理,并切换至剖析的对话框处。现在您可以按照需要收集并分析数据。

在一台远程机器上剖析一个程序的另外一种方法,是使用以前描述过的 External Java Application 选项(如图 18 所示),而不是 Attach to Agent

图 18. 在 External Java Application 下指定类名和类路径
Profile Configurations 对话框与指定的类名和路径
Profile Configurations 对话框与指定的类名和路径

图 18 的大图

在 External Java Application 的一个新配置之中,您可以指定远程机器上 Java 主类的位置与名字,如图 18 所示。Monitor 项可以帮助您指定使用剖析代理的种类(Execution 剖析器, Memory 剖析器, 或者 Thread 剖析器)。当您点击 Profile 按钮时,程序会在远程主机上运行,但是输入与输出会指向本地工作台之上的操控台窗口。

Profiling Eclipse Rich Client Platform 插件

Rational Application Developer 支持剖析的 Eclipse Rich Client Platform (RCP)插件。您可以通过 Profiling Configurations 对话框中的 Eclipse Application 选项 来执行这种剖析。这个选项可以使用工作台中开发下的插件,来剖析一个新的 Eclipse 实例。当您剖析 Eclipse 插件时,最好使用筛选规则,来限制剖析数据直接到与特定插件相关的包中。对于其他的启动配置,剖析的启动构建在已存在的启动 UI 之上,这意味着工作台在不同的程序类型之间维护了一个稳定的剖析 UI。剖析一个插件与剖析一个本地程序或者其他的其他类型一样容易。

Eclipse Web Tools Platform 集成与使用 WebSphere 来剖析

另外,Rational Application Developer 支持剖析像 WebSphere Application Server 或者 Tomcat 之类的服务器,可以在本地机器上运行,也可以连接到一个远程的机器之上。Rational Application Developer 的剖析功能会与已存在的服务器配置紧密地集成到一起。当您在 WebSphere Application Server 上,或者其他受支持的程序服务器上开发 Web 程序或 Web 服务时,您可以在 Server 视图中选择需要剖析的服务器,然后选择 Profile 图标,启动已经剖析过的应用。在这里,会在 Server 对话框上显示出 Profile,然后您可以选择概述的类型,以及诸如筛选规则和剖析选项之类的概述类型。

对于服务器概述有一个值得注意的方面:的 JVMTI 剖析代理会在 JVM 层次上收集数据,而不是在特定程序的基础上收集数据。这意味着在 JVM 上所运行的 Java 代码会生成事件数据(包括服务器本身)。您必须确定适当地创建了筛选以精确地定位到程序上。

您学到了什么

本篇指南探讨了 Rational Application Developer 所提供的多面剖析功能。Rational Application Developer 提供了一个用户友好且本质的接口,去检查有助于调试 Java 程序的那些具体内容,它们对于那些已存在的程序配置完美地集成到了一起。您可以从多个平台得到概述,并快速且轻松地支持任意和所有的 JVM 配置。对概述工具做了适当的应用,并谨慎分析程序性能与特征之后,您就可以在问题出现之前,或者需要高昂的代价才能解决问题之前,就发现并处理性能问题。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Rational
ArticleID=550006
ArticleTitle=使用 IBM Rational Application Developer 剖析 Java 程序
publish-date=10092010