您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
   
 
 
     
   
 订阅
  捐助
最大化 AIX 上的 Java 性能(下)
 
作者:BingWay 来源:新浪微博 发布于 2015-12-9
  2053  次浏览      15
 

最大化 AIX 上的 Java 性能,第 3 部分: 更多就是更好

引言

这是由五部分组成的有关 AIX 上的 Java 性能优化的系列中的第三篇文章。强烈建议您在进一步继续之前阅读本系列中的第 1 部分(如果您还没有这样做的话)。

本文集中于涉及各种类型的内存结构(Java 堆、本机堆、堆栈)的优化,并研究用于优化系统以进行大小调整的方法。

您应该查看第一部分,以了解适用于大多数情况的一般技巧。我们还提供了对于内存瓶颈检测和研究非常有用的工具的快速参考。下一部分将描述各种类型的应用程序以及如何优化它们。此讨论将利用您的应用程序知识来决定哪些技巧最适合您。第三部分将描述各种技巧。本文在结束时将讨论一下本系列中的下一篇文章。

内存瓶颈

本文讨论如何使您的应用程序扩展到更多数量的线程或具有更大的堆,或者同时实现这两个目的。您可能还希望通过迫使应用程序以受约束的方式使用资源,从而使其更加“文明”。在系统由多个应用程序共享的环境中,这是特别重要的。

当 AIX Java 应用程序尝试扩展时,存在若干可能会产生影响的资源瓶颈。Java 堆只是其中之一,并且在大多数情况下,您只需切换到更大的堆大小即可向上扩展。但是还存在另外三个内存方面,它们对于确定 Java 应用程序的内存占用空间和可伸缩性具有重要的影响。

除 Java 堆以外的第一个重要内存方面是本机堆(native heap),文章“Getting more memory in AIX for your Java applications” 描述了如何监视本机堆和调整其大小。Java 堆由垃圾收集器(Garbage Collector)管理,但是垃圾收集器不会对本机堆执行任何操作。因此,如果您看到本机堆稳定地增加,可能是由于不匹配的 JNI 分配(举例而言)所导致的。

第二个方面是用 -Xss 指定的本机堆栈(native stack)。这是针对每个线程分配的,并且不基于具体的使用情况,因此如果计划运行一百个线程,则要在指定 -Xss2m 前慎重考虑;它将消耗 200 MB 的本机内存,每个线程消耗 2 MB。在运行更多数量的线程时,这尤其成为一个限制因素,建议的解决办法是使用较小的值而不是使用缺省值。与 JVM 配套的 SDK 指南中提供了有关此主题的更多信息。

最后,第三个方面是通过 -Xoss 控制的 Java 堆栈(Java stack)。使用 -Xoss 指定的值是上限,因此指定一个较大的值的效果不如使用 -Xss 那么显著。请注意,由于 JIT 编译,您需要调整 -Xss 以满足大多数需要,并且 -Xoss 通常可以保留不变。例如,需要调整 -Xoss 的一个重要场合是在用完了 JNI 引用的时候。

我们不会集中于上述任何内存方面,因为其调整通常是在调试中出现的,而不是在性能优化中出现的。但是如果您陷入了资源不足的情况,现在可以检查这额外的三个方面。当您将系统利用到极限时,尤其必须了解这些方面。

谈到极限,您应该确保 ulimit 设置不会在大小调整期间成为瓶颈。在理想的情况下,我们建议将某些 ulimit 值设置为无限大,但是需要将此行为与某个进程消耗完所有本地资源的风险进行评估。对于性能优化工作,您可以首先将 ulimit 值设置为无限大,一旦达到所需的目标,您应该将这些设置设定为有限值。

您可以使用 ulimit -a 命令检查当前的 ulimit 设置,并且应该作为将启动 Java 的用户帐户至少运行以下三个命令:

ulimit -m unlimited

ulimit -d unlimited

ulimit -f unlimited

在某些情况下,您可能不允许执行上述操作,因为运行 Java 的用户帐户可能没有分配足够的硬限制:有关必需的 ulimit 设置,请参阅位于 IBM developer kits for AIX, Java technology edition 的 JVM 配套 SDK 指南。

请注意,与 GC 相关的问题通常会作为 CPU 资源紧张的问题出现,因此如果遇到与 GC 相关的问题,您还应该阅读本系列的第 2 部分。此外,如果 RMI 触发了 GC,第 4 部分将对此进行讨论。

本部分的其余内容将简单介绍一些常用的工具和如何检测特定于 Java 的问题。有关更多详细信息,请参见 AIX 5L Performance Tools Handbook 和 Understanding IBM eServer pSeries Performance and Sizing。

vmstat

vmstat 已在本系列的第 2 部分进行了介绍。要从 vmstat 输出中最需要了解的信息是是否发生了分页,以及分页是否是由于堆大小大于可用的物理内存而导致的。在大多数情况下,由于更大的堆而获得的任何可伸缩性好处,都抵不过由于堆分页而导致的严重性能降低,因此应该避免使用较大的堆。

svmon

svmon 是监视 Java 进程(尤其是本机堆)时最有用的工具。文章“When segments collide”(http://www.ibm.com/developerworks/eserver/library/es-segcollide.html)提供了如何使用 svmon -P <pid> -m 来监视 AIX 上的 Java 进程本机堆的示例。但是还存在另一种形式 svmon -P <pid> -m -r,此命令对于确定本机堆碎片非常有效。 -r 开关打印正在使用的地址范围,因此它提供了每个段的当前使用情况的更准确视图。例如,请考虑下面经过部分编辑的输出:

Pid Command          Inuse      Pin     Pgsp  Virtual 64-bit Mthrd LPage
10556 java 681613 2316 2461 501080 N Y
Vsid Esid Type Description LPage Inuse Pin Pgsp Virtual
22ac4 9 mmap mapped to sid b1475 - 0 0 - -
21047 8 mmap mapped to sid 30fe5 - 0 0 - -
126a2 a mmap mapped to sid 91072 - 0 0 - -
7908c 7 mmap mapped to sid 6bced - 0 0 - -
b2ad6 b mmap mapped to sid b1035 - 0 0 - -
b1475 - work - 65536 0 282 65536
30fe5 - work - 65536 0 285 65536
91072 - work - 65536 0 54 65536
6bced - work - 65536 0 261 65536
b1035 - work - 45054 0 0 45054
Addr Range: 0..45055
e0f9f 5 work shmat/mmap - 48284 0 3 48284
19100 3 work shmat/mmap - 46997 0 463 47210
c965a 4 work shmat/mmap - 46835 0 281 46953
7910c 6 work shmat/mmap - 37070 0 0 37070
Addr Range: 0..50453
e801d d work shared library text - 9172 0 0 9220
Addr Range: 0..3086
a0fb7 f work shared library data - 105 0 1 106
Addr Range: 0..2521
21127 2 work process private - 50 2 1 51
Addr Range: 65300..65535
a8535 1 pers code,/dev/q109waslv:81938 - 11 0 - -
Addr Range: 0..11

如果阅读过文章“Getting more memory in AIX for your Java applications”,您应该能够判断出上述配置是使用 LDR_CNTRL=MAXDATA=0x40000000。“Inuse”列中显示 4K 页的值,因此一个段(大小为 256 MB)在此列中的最大值将为 65536。正如上述输出所示,这个特定的应用程序在使用大量的本机堆;没有为段 3-5 打印范围的原因在于,尽管“Inuse”计数不是 65536,但是该段已经完全分配了。此外,如果没有使用 -r,可以认为既然段 6 的“Inuse”值为 37070,则该段只有 56% 的使用率。但是可以判断出,段 6 正在使用的实际地址范围是 0 到 50453,或换句话说该段差不多达到 77% 的使用率。这会对应用程序的大小调整具有重要的影响。

要注意的另一个有趣部分在于,通过 svmon 无法看到 Java 堆碎片。段 7 至 A 看起来已完全利用(对应 SID 的“Inuse”值为 65536),而段 B 正在使用前 45056 个页面。这只是告诉了您该堆的大小约为 1100 MB,但是还存在找到此信息的更容易方法(即查看 Java 的命令行参数!)。

特定于 Java 的技巧

Fine-tuning Java Garbage Collection Performance 介绍了 Java 堆的优化。在 1.4 以前的 Java 版本上,您可能需要调整某些环境设置才能使用超出 1 GB 的堆,“Getting more memory in AIX for your Java applications”对此进行了解释。

如果您了解 verbosegc 输出的各项意义,以及如何使用 svmon,对于 AIX 上的任何 Java 应用程序的大多数性能监视工作应该足够了。

存在若干有关 GC 优化的文章可以使用,因此基于您希望了解此主题的深度,您可以使用大量的信息。Diagnostics 页还指向一个总结 IBM Java 的 GC 是如何工作的文档,对于开发人员来说这应该特别有用。下面几个部分将坚持使用基于特征的优化方式,但是如果您希望更进一步研究任何特定的技巧,应该参考“IBM Garbage Collection and Memory Allocation techniques”一文,您可以从 http://www.ibm.com/developerworks/java/jdk/diagnosis/ 访问它。

基于特征的优化技巧

下面我们将看一下典型应用程序的不同特征。您应该定位到与您的应用程序类似的行为(无论是设计上的还是观察到的),并应用对应的技巧。除非明确指明,否则术语“堆”是指 Java 堆。

堆使用情况

对于大多数应用程序,使用固定与可变堆的决定是很容易做出的。任何具有或多或少的有限堆要求的应用程序都可以使用 技巧 MEM001,而任何会定期猛增堆使用量的应用程序使用技巧 MEM002 效果会更好。但是,如果应用程序逐渐地增长,或者如果您在考虑由于可变堆大小而导致的性能影响,则技巧 MEM001 也许仍然是可行的。位于 IBM developer kits - diagnosis documentation 的诊断指南包括一个有关如何调整 Java 堆大小的不错的内容。正如“Fine-tuning Java Garbage Collection Performance”所提到的,要遵循的规则是:分配应用程序需要的堆大小,但是决不多分配。但是在决定使用技巧 MEM001 之前,请参阅有关“堆回收”的部分。

堆增长速度

如果应用程序堆需要迅速增长,则技巧 MEM003 将会非常有用。能从快速内存增长中获益的应用程序类型是在工作日的特定时间具有内存需求高峰的应用程序。如果优化得当,堆扩展次数将会减少,因为每次扩展会将堆增长更大的量。

另一方面,如果希望控制堆增长速度,请参见技巧 MEM004。当您看到堆扩展相当大并且希望控制扩展增量时,这是非常有用的。此类情况非常罕见,因为 Java 通常会基于定义良好的规则来扩展堆,除非您首先使用技巧 MEM003,否则堆不会快速增长。不过在需要时使用技巧 MEM004 还是有用的。

如果应用程序堆只应该增长而决不会收缩,可以使用 技巧 MEM005 来强制实现此目的。GC 周期不断监视堆使用情况,并在看到分配的堆大于当前需求时收缩堆。毕竟,这就是使用可变大小的堆的本意。但是有时,您可能注意到在堆收缩后,由于堆需求的增长而紧跟着进行堆扩展。在此情况下,您可以要求 JVM 不要收缩堆。

堆回收

如果应用程序生成大量的临时对象,如果可以设置足够小的堆,则技巧 MEM001 将会很有帮助。其基本思路在于,如果堆增长到 200 MB 后触发一个 200 毫秒的 GC 周期,而不是增长到 1 GB 后触发一个 1500 毫秒的 GC 周期,则使用较小的堆效果会好得多,因为应用程序无论如何都不需要较大的内存占用空间。这当然是假设最大的堆大小始终满足用于长期分配所需的堆空间量。此技巧与 第 2 部分的技巧 CPU012 相同,但现在是集中于应用程序的内存占用空间,而不只是集中于应用程序的性能。

即使分配相当迅速,固定大小的堆通常也工作得很好。但是如果这些临时对象的大小通常相当大,则堆会很快变得零碎,从而导致假性 OOM。不适合使用技巧 MEM001 的另一个场景是固定 (Pinned) 对象正在导致碎片的情况。如果必须将对象固定在堆中,则将它们分配在堆中尽可能低的位置是有帮助的。但是除非非做不可,否则 Java 不会收集垃圾,因此只要有堆可用,就不会触发任何 GC 周期,并且这会转化为以导致碎片的方式分配固定对象。

较新版本的 Java 能够执行更好的碎片管理,并且可以使用新的开关来调整固定集群的大小(请参见 -Xk 和 –Xp)。但是如果您遇到堆碎片,技巧 MEM002 也许能满足您的所有需要。在许多情况下,堆扩展和收缩能够比压缩周期更好地消除堆中的空隙。堆碎片会严重影响应用程序的可伸缩性,技巧 MEM002 是在这些情况下使用的理想调整。

GC 活动

下面是基于 verbosegc 输出的简单指示信息。有关更多详细信息,请参阅“Fine-tuning Java Garbage Collection Performance”。

如果您在使用技巧 MEM002,并且在应用程序稳定时观察到太多的 GC,请参见技巧 MEM007。您可能还希望尝试一下技巧 MEM001,并确定它是否有帮助。

如果观察到堆扩展或收缩得太频繁,可以使用技巧 MEM001 来同时消除收缩和扩展。技巧 MEM005 将消除任何堆收缩。

如果 GC 周期中的标记时间太高,您应该尝试 MEM006。这也可能是由于“标记堆栈溢出 (Mark Stack Overflow)”所导致的。

如果 GC 周期不是由于“分配故障 (Allocation Failure)”所导致的,可以使用技巧 MEM007。不过分布式 GC 调用不会受到此设置的影响。

如果 verbosegc 中指示了过多的压缩,这可能与某个大小调整不足的堆有关。

本机堆

如果应用程序的本机代码发出多个小请求,则使用技巧 MEM008 也许会获得性能提升。但是本机堆调整的最重要部分是确保每一次本机堆分配都与相应的回收相匹配。可以使用 svmon 监视本机堆,并且您可能希望设置 IBM_JAVA_MMAP_JAVA_HEAP=true,以便更清楚地区分 Java 堆和本机堆。除了确保应用程序不会在运行时耗尽本机堆以外,通常没有多少针对本机堆的性能优化余地。

一般技巧集合

下文将把 Java 的命令行参数(在 class/jar 文件名称之前指定)称为“开关”。例如,命令行 java -mx2g hello 具有单个开关 -mx2g。

技巧 MEM001:固定大小的堆

通过同时为初始 (-Xms) 和最大 (-Xmx) Java 堆大小指定相同的值,从而创建固定大小的 Java 堆。指定的值应该足够高到不会导致 OOM,同时要足够低到不会显著增加 GC 周期时间。

请注意:指定固定大小的堆意味着您将不能使用 -Xminf/-Xmaxf/-Xmine/-Xmaxe 来微调 GC 特征。固定大小的堆在许多情况下还容易导致碎片。

技巧 MEM002:可变大小的堆

通过为 -Xms 和 -Xmx 指定不同的值,或者仅指定 -Xmx,从而创建可变大小的 Java 堆。指定的值应该足够高以避免 OOM,但是应该进行调整以避免过多的堆收缩或扩展。

请注意:如果优化不当,使用可变大小的堆会产生严重的性能影响。

技巧 MEM003:快速的堆增长

使用高于缺省值 (1 MB) 的 -Xmine 值。这将允许 Java 堆的最小扩展变得更迅速。例如, -Xmine5m 将允许堆一次增长 5 MB(或更多,最多可达 -Xmaxe)。

请注意:-Xmine 只是在进行堆扩展时发挥作用的若干因素之一。有关堆扩展的更多信息,请参见 Diagnostics Guides。

技巧 MEM004:受控的堆增长

使用与缺省值 (0) 不同的 -Xmaxe 值。这会强制将 Java 堆的最大扩展保持在指定的限制之内。例如,-Xmaxe2m 将强制堆在一个周期中的增长不能超过 2MB。

请注意: -Xmaxe 只是在进行堆扩展时发挥作用的若干因素之一。有关堆扩展的更多信息,请参见位于 IBM developer kits - diagnosis documentation 的 Diagnostics Guides。

技巧 MEM005:禁用堆收缩

要禁用堆收缩,可以使用 -Xmaxf1,此开关将最大空闲堆百分比设置为 100%。

请注意:这将强制堆只能增长,如果堆具有固定大小,则此开关不起作用。

技巧 MEM006:使用并发标记 (Concurrent Mark)

使用开关 -Xgcpolicy:optavgpause 以启用并发标记。

请注意:对于 CPU 密集型应用程序,此设置可能会影响性能。

技巧 MEM007:禁用显式的 GC 调用

使用开关 -Xdisableexplicitgc 禁用任何 System.gc() 调用以避免触发 GC。

请注意:确保您的应用程序功能不会受到此开关的影响。

技巧 MEM008:优化本机堆分配

设置环境变量:

 export MALLOCTYPE=buckets

以切换到基于 Bucket 的本机堆模型。

请注意:Java 堆分配不受此开关的影响。而且,如果使用不当,此开关会影响应用程序的性能。

总结

本文向您介绍了如何使用 AIX 工具进行 Java 性能监视,并提供了可用来优化应用程序的内存使用的常用调整列表。本系列中的下一篇文章将讨论“AIX 上的 Java 应用程序的网络和磁盘 I/O 调整”。

最大化 AIX 上的 Java 性能,第 4 部分: 监视流量

引言

这是由五部分组成的有关 AIX 上的 Java 性能优化系列中的第四篇文章。强烈建议您在进一步继续之前阅读本系列中的第 1 部分(如果您还没有这样做的话)。

本文讨论可能成为性能瓶颈的另外两个方面:

网络

磁盘 I/O

这两个方面通常作为特定于 AIX 的问题出现,需要独立于 Java 应用程序进行优化。因此,本文暂不使用第 2 部分和第 3 部分 中使用的形式,而是集中于如何找到完成优化工作所需要的信息。因此,本文仅提供了少量的技巧,但是我们希望将整体的性能工具讨论与这里的少量技巧相结合,为您提供足够的信息以着手开始性能优化工作。

I/O 和网络瓶颈

本文的目的是讨论 I/O 或网络可能成为瓶颈的情况。

如果您已经阅读了本系列中之前的每篇文章,我们希望您开始了解每个较小的部分是如何适应全局的。我们已尝试基于这些技巧的常见适用领域对其进行分类,但是此分类决不是互斥的。对于网络和 I/O,您不会如此容易地看到实际问题原因,但是您最终会感觉到它们对应用程序的影响。只有对应用程序的充分了解才能指导您确定问题根源。例如,在本系列的前面,我们讨论了确保堆不分页的重要性。使用 -Xmx 开关指定的最大堆大小应该小于系统上安装的物理内存总量(通过“bootinfo –r”或“lsattr -El sys0 -a realmem”来显示,有关更多的此类命令,请参见“AIX Commands you should not leave home without”。

诸如 topas 和 iostat 等工具可以显示各个磁盘的使用情况,但是在大多数情况下,问题根源要么是 GC 周期,要么是某个已知的功能部分;如果您了解自己的应用程序,则确定问题根源应该是相当简单的。诸如 filemon 等工具甚至会告诉您哪些文件正在受到访问,从而从优化工作中排除猜测成分。如果您的 Java 应用程序性能由于错误配置的系统而受到影响,则是改变重点并改为考虑系统性能优化的时候了。例如,磁盘瓶颈的解决方案可以是把数据进行有效的分布或者是选择使用更加高速的磁盘。此主题超出了本文的范围;有关此主题的更多信息,请参阅诸如 Understanding IBM eServer pSeries Performance and Sizing 等红皮书。

配置网络缓冲区和优化其他网络参数可以对网络密集型应用程序产生重要影响。有关网络可优化参数的很好参考资料是 Performance Management Guide 的 Network Tunable Parameters 部分。一些流行的调整涉及到 thewall、socketthresh、sbmax、somaxconnect、tcp_sendspace、tcp_recvspace、rfc1323 等等。此信息既不特定于 AIX,也不特定于 Java,但是对于网络密集型应用程序,这应该是进行性能优化的第一步。

本部分的其余内容将简单介绍一些常用的工具和如何检测特定于 Java 的问题。有关更多详细信息,请参阅 AIX 5L Performance Tools Handbook 和 Understanding IBM eServer pSeries Performance and Sizing。

vmstat

多用途的 vmstat 命令应该已经是您的好朋友了。对于 I/O 工作,应查看 CPU 部分中的 wa(I/O 等待)值。如果此值非常高,则可能存在磁盘瓶颈,然后可以使用 iostat 更详细地查看磁盘使用情况。

iostat

iostat 是用于确定系统是否具有 I/O 瓶颈的理想工具。它显示针对所有磁盘的读写速度。这使其成为确定您是否需要在多个磁盘间“分散”磁盘工作负载的理想工具。该工具还会报告与 vmstat 相同的 CPU 活动。

当您的应用程序正在运行时,从简单的 iostat -s 开始,以确定系统总体上在做什么。此命令打印的内容如下:

 tty:      tin         tout   avg-cpu:  % user    % sys     % idle    % iowait
0.3 232.9 13.8 19.1 27.4 39.6

Disks: % tm_act Kbps tps Kb_read Kb_wrtn
hdisk0 28.7 291.4 35.0 176503 2744795
hdisk1 0.0 0.4 0.0 3537 0
hdisk7 1.7 34.9 9.8 8920 341112
hdisk14 24.5 1206.1 36.2 1188404 10904509
hdisk18 0.0 1.2 0.1 10052 2046
hdisk8 2.1 36.8 10.5 10808 357910

查看 %iowait 数据以确定系统是否在花太多的时间等待 I/O 完成。如果系统正在分页,这就是要观察的数据。但是要注意,单凭此数据还不足以确定系统上发生的情况。例如,如果您在应用程序中写入顺序文件,则较高的 %iowait 值是很正常的。
%tm_act 显示特定磁盘的活动时间百分比。上面的跟踪显示了一个非常有趣的场景;其中 %iowait 接近 40%,但是 tm_act was 无论如何都没有接近 100%,而是仅在 30% 下面徘徊。在其上取得上述跟踪的系统有一个通过光纤通道连接的存储,结果证明瓶颈是到 SAN 存储的路由。一旦弄清楚后,看起来就非常容易了!
还可以使用 # iostat -at <interval> <count> 或 iostat -sat ...,这两个命令将给出适配器的 tps 和 KBps 值(以及读写速度)。-s 标志将为您提供总体系统统计信息。
netstat
对于网络优化,netstat 是理想的工具。netstat -m 可用于查看 mbuf 内存使用情况,它将告诉您有关套接字和网络内存使用情况的信息。如果使用了 no -o extendednetstats=1,则 netstat -m 将显示更多详细信息,但这样会产生性能影响,只应用于诊断目的。当使用 netstat -m 时,相关的信息显示在输出的顶部,如下所示:

 67 mbufs in use:
64 mbuf cluster pages in use
272 Kbytes allocated to mbufs
0 requests for mbufs denied
0 calls to protocol drain routines
0 sockets not created because sockthresh was reached

以及输出的底部,如下所示:

 Streams mblk statistic failures:
0 high priority mblk failures
0 medium priority mblk failures
0 low priority mblk failures

如果您在 netstat -m 输出中看到故障,AIX 5L Performance Tools Handbook 提供了有关要调整哪些参数的清楚描述。您可能还希望尝试 netstat -i x(将 x 替换为收集数据的间隔),以查看网络使用情况和可能丢弃的数据包。对于网络密集型应用程序,这是检查是否“一切都正常”的第一步。
netpmon
netpmon 使用跟踪功能来获得某个时间间隔期间的网络活动详细状况。它还显示进程 CPU 统计信息,其中显示了:
该进程使用的 CPU 时间总量
进程的 CPU 使用(总时间的百分比)
该进程执行与网络相关的代码所花的总时间
要开始优化工作,可以尝试以下命令:

netpmon -o /tmp/netpmon.log; sleep 20; trcstop

此命令行运行 netpmon 命令 20 秒钟,然后使用 trcstop 停止该命令,并将输出写到 /tmp/netpmon.log。查看所生成的数据,可以看到我们选择的示例非常适合于 Java 性能优化文章:

 Process CPU Usage Statistics:
-----------------------------
Network
Process (top 20) PID CPU Time CPU % CPU %
----------------------------------------------------------
java 12192 2.0277 5.061 1.370
UNKNOWN 13758 0.8588 2.144 0.000
gil 1806 0.0699 0.174 0.174
UNKNOWN 18136 0.0635 0.159 0.000
dtgreet 3678 0.0376 0.094 0.000
swapper 0 0.0138 0.034 0.000
trcstop 18460 0.0121 0.030 0.000
sleep 18458 0.0061 0.015 0.000

跟踪的另一个有用部分是适配器使用情况:

----------- Xmit -----------   -------- Recv ---------
Device Pkts/s Bytes/s Util QLen Pkts/s Bytes/s Demux
------------------------------------------------------------------------------
token ring 0 288.95 22678 0.0%518.498 552.84 36761 0.0222
...
DEVICE: token ring 0
recv packets: 11074
recv sizes (bytes): avg 66.5 min 52 max 1514 sdev 15.1
recv times (msec): avg 0.008 min 0.005 max 0.029 sdev 0.001
demux times (msec): avg 0.040 min 0.009 max 0.650 sdev 0.028
xmit packets: 5788
xmit sizes (bytes): avg 78.5 min 62 max 1514 sdev 32.0
xmit times (msec): avg 1794.434 min 0.083 max 6443.266 sdev 2013.966

假设您认为信息太多了,或者您希望查看更特定的信息。让我们尝试以下命令:

netpmon -O so -o /tmp/netpmon_so.txt; sleep 20; trcstop

“-O so”使得 netpmon 集中于套接字级别的流量。现在我们可以深入查看 Java 进程信息:

PROCESS: java   PID: 12192
reads: 2700
read sizes (bytes): avg 8192.0 min 8192 max 8192 sdev 0.0
read times (msec): avg 184.061 min 12.430 max 2137.371 sdev 259.156
writes: 3000
write sizes (bytes): avg 21.3 min 5 max 56 sdev 17.6
write times (msec): avg 0.081 min 0.054 max 11.426 sdev 0.211

有用吗?让我们更进一步,并查找线程级别的活动。向该命令添加“-t”,如下所示:

netpmon -O so -t -o /tmp/netpmon_so_thread.txt; sleep 20; trcstop

现在,所生成的输出包含特定于线程的信息,如下所示:

THREAD TID: 114559
reads: 9
read sizes (bytes): avg 8192.0 min 8192 max 8192 sdev 0.0
read times (msec): avg 988.850 min 19.082 max 2106.933 sdev 810.518
writes: 10
write sizes (bytes): avg 21.3 min 5 max 56 sdev 17.6
write times (msec): avg 0.389 min 0.059 max 3.321 sdev 0.977

现在可以创建一个 Java 转储,查看该线程是什么,并判断它是否在按预期工作。尤其是对于具有多个网络连接的应用程序,netpmon 允许捕获全面的活动视图。

filemon

filemon 可用于确定正在被活跃地使用的文件。此工具提供非常全面的文件访问视图,并在 vmstat/iostat 确认磁盘成为瓶颈后对于深入分析非常有用。此工具也使用跟踪功能,因此其使用方式与 netpmon 类似:

filemon -o /tmp/filemon.log; sleep 60; trcstop

生成的日志文件相当大。一些可能有用的部分包括:

  Most Active Files
------------------------------------------------------------------------
#MBs #opns #rds #wrs file volume:inode
------------------------------------------------------------------------
25.7 83 6589 0 unix /dev/hd2:147514
16.3 1 4175 0 vxe102 /dev/mailv1:581
16.3 1 0 4173 .vxe102.pop /dev/poboxv:62
15.8 1 1 4044 tst1 /dev/mailt1:904
8.3 2117 2327 0 passwd /dev/hd4:8205
3.2 182 810 1 services /dev/hd4:8652
...
------------------------------------------------------------------------
Detailed File Stats
------------------------------------------------------------------------

FILE: /var/spool/mail/v/vxe102 volume: /dev/mailv1 (/var/spool2/mail/v) inode: 581
opens: 1
total bytes xfrd: 17100800
reads: 4175 (0 errs)
read sizes (bytes): avg 4096.0 min 4096 max 4096 sdev 0.0
read times (msec): avg 0.543 min 0.011 max 78.060 sdev 2.753
...

此工具在前面引用的参考资料中作了介绍,更详细的研究超出了本文的范围。

特定于 Java 的技巧

用于避免 I/O 和网络瓶颈的 Java 通用技巧归根结底是良好的设计,并且已经在多个地方做了清楚的文档说明。不过还是看一下技巧 NI004 和技巧 NI005

基于特征的优化技巧

下面让我们看一下典型应用程序的不同特征。您应该定位到与您的应用程序类似的行为(无论是设计上的还是观察到的),并应用对应的技巧。

网络密集型应用程序

对于网络密集型应用程序,您应该使用 netstat 来确保不存在丢弃的数据包等情况。AIX 5L Performance Tools Handbook 中的 netstat 和 netpmon 部分描述了在监视期间观察到故障时可执行的各种调整,因此这里就不再重复了。

如果怀疑网络吞吐量是瓶颈,技巧 NI001 对于确定是否存在问题会非常有用。此外,如果根本不使用 IPv6,也可以使用技巧 NI002。

如果您在考虑 AIX 和其他平台之间的应用程序性能差异,并且怀疑这种差异是由于所设置的套接字选项导致的,您可以查看一下技巧 NI004。

RMI 应用程序

如果应用程序是 RMI 客户机或服务器,您可能观察到 verbosegc 输出中有一些未做出说明的行。例如,下面的内容摘自某个 RMI 应用程序的 verbosegc 输出:

<GC(4057): GC cycle started Thu Apr 15 11:14:28 2004
<GC(4057): freed 254510616 bytes, 55% free (453352000/810154496), in 1189 ms>
<GC(4057): mark: 991 ms, sweep: 198 ms, compact: 0 ms>
<GC(4057): refs: soft 0 (age >= 32), weak 2, final 330, phantom 0>
<GC(4057): stop threads time: 10, start threads time: 260>
<GC(4058): GC cycle started Thu Apr 15 11:15:29 2004
<GC(4058): freed 267996504 bytes, 56% free (454445800/810154496), in 1243 ms>
<GC(4058): mark: 1041 ms, sweep: 202 ms, compact: 0 ms>
<GC(4058): refs: soft 0 (age >= 32), weak 0, final 253, phantom 0>
<GC(4059): GC cycle started Thu Apr 15 11:16:31 2004
<GC(4059): freed 248113752 bytes, 56% free (455754152/810154496), in 1386 ms>
<GC(4059): mark: 1095 ms, sweep: 291 ms, compact: 0 ms>
<GC(4059): refs: soft 0 (age >= 32), weak 0, final 263, phantom 0>

这些 GC 周期几乎准确地相隔 60 秒钟触发一次,并且不是由于“分配故障 (Allocation Failure)”而触发的。在确保应用程序没有直接调用 System.gc() 以后,技巧 NI003 可能适用于这里。

对于 RMI 密集型应用程序,应该考虑技巧 NI005,但是要注意该技巧中提到的注意事项。

磁盘密集型应用程序

使用 iostat 和 filemon,您应该能够找出瓶颈的根源。解决方案通常是调整应用程序设计以停止依赖磁盘访问,或者是调整系统以优化磁盘访问。由于这两类调整都超出了本文的范围,我们建议您熟悉 iostat 和 filemon。前一部分中的信息应该能让您开始上路。

一般技巧集合

下文将把 Java 的命令行参数(在 class/jar 文件名称之前指定)称为“开关”。例如,命令行 java -mx2g hello 具有单个开关 -mx2g。

技巧 NIO001 检查网络连接的速度

可以在需要分析其连接速度的两个系统之间建立 FTP 会话,并且可以执行以下 FTP 命令:

ftp> put "|dd if=/dev/zero bs=32k count=1000" /dev/null
200 PORT command successful.
150 Opening data connection for /dev/null.
1000+0 records in.
1000+0 records out.
226 Transfer complete.
32768000 bytes sent in 130.4 seconds (245.4 Kbytes/s)
local: |dd if=/dev/zero bs=32k count=1000 remote: /dev/null

上面的快速测试尝试传输 1000 个零块 (blocks of zeroes),每个块的大小为 32 KB,并提供了一种确定两台 AIX 计算机之间的连接吞吐量的简单方法。上面的示例显示吞吐量为 245.4 KBps,这指示存在网络问题,因为两台 AIX 计算机都在使用 100 Mbps 的全双工网络适配器。假如上面的测试显示 1.140 E+4 Kbytes/s,这是应该改为集中于应用程序的很好暗示。您可以改变块大小和计数,以更详细地模拟您的应用程序行为。

技巧 NI002 IPv4 堆栈

如果不希望在应用程序中使用 IPv6,可以将属性 preferIPv4Stack 设置为 true,如下所示:

java -Djava.net.preferIPv4Stack=true <classname>

技巧 NIO003 远程 GC

如果您的应用程序是 RMI 客户机或服务器,也可以将 http://java.sun.com/j2se/1.4.2/docs/guide/rmi/sunrmiproperties.html 处定义的 sun.rmi.dgc.client.gcInterval 和/或 sun.rmi.dgc.server.gcInterval 属性用于 IBM Java。这两个属性都缺省设置为 60 秒,并且基于应用程序的需要,可以增加该间隔以减少多余 GC 周期带来的性能影响。

请注意:该链接顶部的警告以及与未释放分布式对象相关联的风险同样适用于 IBM Java。

技巧 NI004 套接字缓冲区大小

如果您在设置发送和接收缓冲区大小,要注意对 setSendBufferSize(int) 的调用仅用作提示。因此,如果观察到平台之间的性能差异,您应该添加一个对 getSendBufferSize() 的调用,并查看当前平台是否拾取了该提示。在最近报告的一个有关 AIX 的性能问题中,应用程序从其代码中调用了 setSendBufferSize(4096)。AIX 使用了该提示并按请求设置了缓冲区大小,而其他平台则忽略了此调用。因此,在 AIX 上察觉到的性能是糟糕的!从代码中删除此调用会使 AIX 上的应用程序性能提高四倍以上。

一般情况下,您可能希望从应用程序中省略用于调整 TCP/IP 堆栈的调用,因为 AIX 网络堆栈是已经预先微调好了的。

技巧 NI005 连接池

对于 RMI 密集型应用程序,启用线程池允许重用现有的连接而不是为每个新的 RMI 调用创建新连接。要启用线程池,可以设置以下属性:

java -Dsun.rmi.transport.tcp.connectionPool=true <classname>

也可以禁用线程池,如下所示:

java -Dsun.rmi.transport.tcp.noConnectionPool=true <classname>

请注意:最好仅对 RMI 密集型应用程序使用线程池。AIX 上的最新版 Java(1.3.1 SR7 以上,以及 1.4.1 SR2 以上)缺省禁用了线程池。

结束语

本文描述了在处理网络和磁盘 I/O 瓶颈时的常用工具和技术。

下一篇文章将以一般性观察和指向有用参考资料的链接结束本系列。

最大化 AIX 上的 Java 性能,第 5 部分: 参考资料和结论

引言

这是由五个部分组成的有关 Java 性能的系列的结束部分。本系列中的第一篇文章奠定了性能优化的基础,第 2、3 和 4 篇文章考虑了会影响系统可伸缩性和吞吐量的各种瓶颈。本文介绍两个先前未涉及到的重要主题,并提供案例研究和参考资料。

一个常见问题 (FAQ) 是如何将特定于 Sun 的命令行开关转换为特定于 IBM 的开关。此外,任何严格的性能优化工作,例如基准测试,都不能忽略系统范围的优化。我们将在下一个部分中简要讨论这些主题。

随后是几个案例研究,尝试说明如何应用本系列中描述的工具和技巧来解决实际中的问题。重点在于了解和学习如何使用本系列中提到的工具和技术。

最后通过回顾有用的参考资料结束本文和本系列。

其他重要注意事项

本部分讨论如何将 Sun Java 配置转换为 IBM Java 配置,以及针对 AIX 应用程序的系统范围的优化。这些主题的范围相当大,我们只是略微触及皮毛。

转换 Sun Java 开关

如果您有一个为 Sun Java 而优化过的应用程序,并且正在尝试将应用程序迁移到 AIX(或迁移到任何运行 IBM Java 的平台),您可能已经完成了最艰巨的工作。了解应用程序特征只是成功的一半。基于对使用 Sun Java 进行优化工作的了解,您可以使用第 2 部分和第 3 部分 中阐述的优化技巧。

但是,我们经常收到有关如何将特定 Sun Java 命令行开关转换为等效的 IBM Java 命令行开关的询问。这些开关几乎始终对应于垃圾收集,因为经过充分优化的 GC 对任何基于 Java 的应用程序的性能都非常重要。由于 JVM 体系结构方面的区别,Sun 和 IBM 开关之间的映射非常困难。IBM Java 没有包含分代的垃圾收集器 (Generational Garbage Collector),并且不支持任何以 -XX 开头的命令行开关。IBM Java 的“完全独立”体系结构也不基于 Sun HotSpot 体系结构。最容易并且在大多数情况下最快的方法是在 IBM 平台上运行应用程序时丢弃所有特定于 Sun 的设置,并根据需要进行微调。但是如果您对某些 Sun 开关如何映射到 IBM 开关感到好奇,请继续阅读。

下表尝试将 Sun Java GC 命令行开关转换为等效的 IBM 开关。此映射基于特定于 Sun 的文章“Tuning Garbage Collection with the 1.4.2 Java? Virtual Machine”中描述的 Sun 开关的功能。此表只用于定位 IBM Java 的等效(或近似)开关这个非常特定的目的。这并不意味着取代优化实践,因为即使是堆大小要求也会差异相当大。有关 IBM Java 的一般 GC 优化技巧,以及有关 IBM Java 的这些和其他 GC 开关的信息,请参见“Fine-tuning Java garbage collection performance”。此表的创建并不涉及任何性能相关的测试,而是完全基于上面引用的参考资料中的 Sun 开关用法的文档记录。

正如上表所示,大多数情况下,把 Sun 平台上的开关转换到 IBM 平台的方式就是丢弃这些开关。这使得针对 IBM Java 的GC 优化工作相当简单,同时仍然提供卓越的性能。

系统范围的优化

使用诸如 schedo 和 vmo 等 AIX 工具具有系统范围的影响,因此对这些工具的全面介绍超出了本系列的范围。有关这些工具的更多信息,请参见参考资料部分。

但是对于大多数多层应用程序(尤其是基准测试),系统范围的优化是不可避免的。存在若干可用于 AIX 性能优化的优秀资源可供您参考。要了解通常需要的优化类型,您可以查看实际的已发布基准。如果查看最近针对 AIX 上的 IBM Java 的 SpecJBB 2000 结果,比方说http://www.spec.org/osg/jbb2000/results/res2003q3/jbb2000-20030624-00194.html,则操作系统优化如下所示:

操作系统优化
SPINLOOPTIME=2000
vmo -r -o lgpg_regions=256 -o lgpg_size=16777216
setsched -S rr -P 40 -p $$
schedtune -t 400 -F 1
vmtune -S 1

警告:切勿在没有充分了解后果的情况下应用这些设置,因为不正确使用这些设置实际上会恶化系统性能。

那么上述设置有何作用呢?参阅 AIX 文档,您很快就可以更好地了解这其中每个设置是做什么用的。让我们依次研究一下其中每个设置。

SPINLOOPTIME 控制系统在让步于另一个进程之前重试某个繁忙锁的次数。由于缺省值为 40,更高的值将告诉系统应该尝试更长的时间以待锁释放。在多处理器系统上,这样会导致更好的性能,因为忙锁重试的开销要比进程上下文切换的开销低。

vmo 行设置大型页的大小和数量。查看一下 Java 命令行开关,您将看到正在使用 -Xlp。该开关启用 Java 中的大型页支持,白皮书”AIX Support for Large Page“对该开关进行了更详细的描述。如果您有内存密集型应用程序,可以试验大型页以确定它是否有帮助。与 Java 配套的 SDK 指南中提供了有关此主题的更多信息。

setsched 行实际上是一个脚本而不是 AIX 命令。它调用 thread_setsched 内核服务以选择固定优先级的循环调度,并对该 Java 进程使用优先级 40。

schedtune 命令也是 AIX 5.2 以上版本中的一个脚本,它将传递的参数映射到新的 schedo 命令。上面这一行命令是将固定优先级线程的时间片更改为 400 个计时单元。它还强制固定优先级的线程驻留在全局运行队列中。

最后,vmtune 命令将该调用转换为等效的 vmo 调用,并且上面的行还启用了共享内存段的固定。

因此可以看到系统被切换到大型页和固定优先级的调度程序,以达到 SpecJBB 2000 基准的记录数据。您能否在自己的应用程序中使用这些相同的设置呢?也许不能,但是您现在能够研究这些命令和调度策略,并进行试验以适合自己的应用程序特征。这是性能优化中的下一步。

案例研究

在本部分中,我们将查看一些示例,这些示例取自 Java Service 团队处理过的具体问题。这些示例能够让您清楚了解如何进行性能优化,以及如何使用各种工具来收集可用于性能优化工作的信息。请注意,本部分的案例不是基于问题在实际工作中的出现频率来选择的。重点在于了解如何使用本系列中讨论的各种工具和技术来定位和纠正性能问题。

案例 1 糟糕的应用程序响应时间

报告的问题是基于 Java 的应用程序的响应时间不可接受。使用 topas,然后使用 vmstat,结果发现 Java 是消耗最多 CPU 的应用程序。使用 tprof,出现的函数中具有 GC 相关的术语,因此这指示可能存在 Java 堆大小调整方面的问题。

观察 GC 日志确认了堆扩展得太频繁。这与接二连三的多次分配故障和非常满的堆相结合,导致了 Java 应用程序花大量的时间尝试分配空闲块,未找到空闲块,扩展堆,然后仅满足当前分配请求。

这可以通过为 -Xmine 指定更大的值来修复,从而迫使堆增长得更快(请参见第 3 部分中的技巧 MEM003)。其结果是单次扩展即可避免多次潜在的分配故障。

第一步是使用 AIX 工具来确认这是与 Java 相关的问题。在 AIX 工具指示这是与 GC 相关的问题这个事实的指导下,第二步可以是直接集中于 GC 日志。第三步是使用可用的优化参数来中断应用程序陷入的异常周期,从而允许恢复多余的 CPU 时间。

案例 2JVMPI 让您绝处逢生

另一个有趣的场景是作为性能问题提出的,其中 CPU 在大多数时间都处于繁忙状态。通过查看 verbosegc,我们看到有一个 GC 周期被调用的太过频繁,从而导致应用程序的大多数时间只是花在执行 GC 上。

verbosegc 跟踪表明,大多数 GC 活动都是由于超大型对象的多次分配所导致的,该对象的大小约为 10 MB 或更大。这些活动导致了堆碎片,使得 GC 周期更长,从而影响了性能。但是通过查看 verbosegc 周期,客户无法确定这些对象是什么。

确定问题根源的最容易方法是使用 HeapRoots 工具来分析堆转储 (heapdump)。但是该场景还存在另一个难解之处:那些大型对象并没有存活到 GC 周期之后。因此堆转储没有显示任何具有此类大小的对象。

对于定位和纠正应用程序源代码中的问题来说,这是分析(profiling)如何能够成为有用助手的经典示例。Java 虚拟机分析接口 (Java Virtual Machine Profile Interface) 使得此问题变得微不足道。对于这个特定示例,我们使用了 Using JVMPI to Identify Large Memory Allocations 上描述的方法的一种变化形式,并且能够快速确定执行此分配的代码。

案例 3无限增长

作为本文的最后一个案例研究,我们将讨论一个其表现似乎为简单的大小调整问题的场景。曾经尝试将该应用程序扩展到 1000 个用户,并且应用程序将会耗尽 Java 堆空间。基于用户数量来计算堆需求后,将堆大小从 1 GB 增加到了 1.5 GB。

但是这样触发不是由于 Java 堆带来的 OOM 错误。Java 堆显示了足够的空闲空间,但应用程序日志表明发生了 OOM。通过使用 svmon,发现仅有大约 4 个段(或 1 GB)用于本机堆,第四个段似乎差不多是空的(请参见“Getting more memory in AIX for your Java applications”中的“Balancing Memory”)。

为了进一步深入研究,我们添加了命令行开关 -verbose:jni。作为此开关的结果而打印的额外消息表明全局 JNI 引用池被耗尽了,发生这种事情是非常少见的。全局 JNI 引用池足够大,以确保大多数正常应用程序决不会耗尽它(甚至是接近耗尽它)。

一般情况下,我们尝试通过增加 JNI 引用数量来绕过该问题(如果指定更高的 -Xoss 值,则会呈比例地增加上限)。但这只是延迟了不可避免的问题,大量的固定 JNI 引用导致的严重 Java 堆碎片并没有任何好转。

进一步研究应用程序的设计揭示了真正的根源:应用程序创建了无限数量的线程。随着测试的继续,线程将等待终结器,而且由于终结器是不可预测的,因此存在大量的此类线程在等待释放它们的 JNI 引用。在此情况下,唯一可行的解决方案是更改应用程序代码以纠正这两个问题。一旦应用程序将无限数量的线程替换为线程池,并且尽可能替换终结器,大小调整工作就胜利完成了。

此案例表明,有时候问题的原因是一些深层次的因素。Java 堆耗尽的问题最终证明是设计问题。Java 和本机堆的平衡通常是性能优化的关键部分,但在此例中是不足够的。手边拥有如此多的工具和技术可以让您了解更全面的情况,使您能够做出应该优化什么对象的精明决策。

结束语

本文将结束整个系列。希望本系列成为最大化 AIX 上的 Java 应用程序性能的宝贵指南。

   
2053 次浏览       15
相关文章 相关文档 相关课程



深度解析:清理烂代码
如何编写出拥抱变化的代码
重构-使代码更简洁优美
团队项目开发"编码规范"系列文章
重构-改善既有代码的设计
软件重构v2
代码整洁之道
高质量编程规范
基于HTML5客户端、Web端的应用开发
HTML 5+CSS 开发
嵌入式C高质量编程
C++高级编程
最新课程计划
信息架构建模(基于UML+EA)3-21[北京]
软件架构设计师 3-21[北京]
图数据库与知识图谱 3-25[北京]
业务架构设计 4-11[北京]
SysML和EA系统设计与建模 4-22[北京]
DoDAF规范、模型与实例 5-23[北京]

基于模型的整车电子电气架构设计
嵌入式设备上的 Linux 系统开发
Linux 的并发可管理工作队列
ARM嵌入式系统的问题总结分析
嵌入式系统设计与实例开发
WinCE6.0的EBOOT概要
更多...   


UML +RoseRealtime+嵌入式
C++嵌入式系统开发
嵌入式白盒测试
手机软件测试
嵌入式软件测试
嵌入式操作系统VxWorks


中国航空 嵌入式C高质量编程
使用EA和UML进行嵌入式系统分析设计
基于SysML和EA的嵌入式系统建模
上海汽车 嵌入式软件架构设计
北京 嵌入式C高质量编程
北京 高质高效嵌入式开发
Nagra linux内核与设备驱动原理
更多...