HP-UX中的Java应用性能调优概述
HP-UX中的Java应用性能调优简介
本指南旨在为读者提供一系列有用的方法来排除与Java相关的性能问题,并帮助在HP-UX系统中进行Java应用的性能调优。
本文并没有详细地讨论这一课题,只是为该领域的工作进行了概述。参考资料部分提到了几本关于Java性能调优相关的书籍和参考文章,可以参阅它们来了解更多详细信息。本文的内容与包含HotSpot Runtime编译器版本1.3.1的Java Software Developer Kit(SDK)1.3.1及以后版本保持一致。
文中讨论的许多主题,在下面的网站有更加详细的分析
http://www.hp.com/dspp(搜索“Java Performance Tuning on HP-UX”)
或直接访问
http://h21007.www2.hp.com/dspp/tech/tech_TechDocumentDetailPage_IDX/1,,1602!0!,00.html
在许多报告有性能问题的情况中,对垃圾回收行为的调查表明JVM运行的heap配置存在问题。在其它情况中,也可能是应用的线程锁定或内存溢出引起了该问题。我们建议初学者采用以下方法的结合来解决JAVA应用性能的问题:
● 使用Glance/gpm工具来找出瓶颈所在
● 分析–Xverbosegc(来自垃圾回收器的详细报告)JVM选项的输出结果;
● 使用Hpjmeter工具分析–Xeprof(扩展的简档描述)JVM选项的输出结果;
● 浏览kill –3
它们将提供有用的数据,可以帮助您对碰到的问题进行彻底的初始分析。本文将会对这些领域进行深入的分析。
以上所述四种方法,以及对硬件设置、操作系统和JVM版本及选项的详细描述,是性能工程师着手解决问题时最初的一些步骤。
Glance/gpm是一个HP的工具,在HP-UX Application Software光盘的软件包中。它也可以用于其它操作系统,例如Solaris和AIX。其它工具,如“top”、“vmstat”、“netstat”和“sar”也提供了类似的性能信息。这些均被记录在Sauers和Weygant [Sauers]所著的《HP-UX性能调优》一书中。鉴于本文的目的,我们将侧重于介绍使用Glance/gpm进行系统和进程层的监视。
采用系统化的方法
Java性能分析中存在许多的不确定因素。HP-UX内核参数、在运行时指定的JVM选项以及应用设计等所有一切都会带来相应的影响。正是由于该原因,所以在分析性能问题时应采取从上至下系统化的方法(从最外层的系统角度)并在首次分析时考虑所有的可能性,这一点非常重要。采取从上至下的方法,我们首先将系统作为一个整体来考虑(将一台或多台协作的计算机作为一个集合,它们互相之间具有网络影响,可能在某些点还存在数据库访问影响)。
在考虑了整个系统中所有的可变因素之后,我们将分析范围缩小到单个计算机,再进一步缩小到该计算机中的单个进程,从而找出问题的根本原因。之所以采用从上之下的分析方法,是因为导致性能问题的原因可能存在于程序、计算机、数据源和网络等构成整个系统的各个部分。有多种工具可以帮助我们逐一分析每台计算机,HP-UX Glance/gpm便是其中最强大的工具之一,本文后面的部分会再次提到该工具。
过去,研发实验室进行的分析工作发现性能瓶颈的原因也可能源于非Java的技术。性能工程师必须时刻考虑到这一点。系统中各台计算机之间以及计算机与其外部数据源之间的数据交换也必须进行检查。这项工作可以通过使用“netstat”等网络与数据库监视工具来完成。
推荐流程中的步骤包括:
● 评估整体的系统配置、吞吐量和负载情况;
● 测量性能(使用我们将进一步详细介绍的工具);
● 分析来自性能测量工具的数据;
● 确定一个或多个可能的瓶颈;
● 每次仅更改或调节一个项目;
● 再次测量性能以检查该调节步骤所带来的变化。
在本文以后的章节我们将会更加详细地分析这些步骤。在分析过程中考虑来自多个工具的数据并反复核对其输出结果也非常重要。例如,Glance/gpm生成的线程信息应该与将“kill –3
较高的吞吐量水平并非总是性能问题的原因所在。许多生产系统在运行时都一直保持高容量的交易流。性能工程师所要面对的真正问题是“进程中的瓶颈在哪里,并且它们如何被触发?”
为了找出这些瓶颈,有时候需要为系统添加负载,再现峰值用户处理水平时的负载,这时候系统将非常繁忙。有多种工具可以在基于Web的应用上实现这种高负载,它们将在本文最后的工具章节中介绍。
避免在信息不充分的情况下妄加猜测
不经过分析和测量便对问题原因作出最初的猜测非常具有诱惑力,特别是在项目小组承受着压力要解决一个问题时。这种情况应该避免。通常情况下,我们所作的猜测会造成误导。
相反,我们建议先使用各种工具(Glance/gpm、Hpjmeter、Hpjtune、sar、–Xverbosegc和–Xeprof的JVM选项等等)来收集性能数据,在这些工具的数据经过分析并被了解之后,便可以就所见的症状给出原因了。该分析需要一定的时间,通常性能工程师进行性能调优的第一步是先搜集系统运行数据,而不是上来就修改系统参数。
准备一个修改记录非常重要,用于记录所作出的每一个变更,以及应用变更后所带来的性能影响。性能工程师非常容易对系统一次进行多个变更,这种情况应该避免。因为多个变更、多种结果以及许多的外部影响因素很容易给工程师造成混乱,所以应该以书面的形式将所应用的变更记录下来。
测试大型应用具有代表性的较小样本
计算机编程中的一个常用故障排除技巧是将问题尽可能分解到能够反映症状的最小代码块。但是这种方法在性能分析中却非常危险,因为较小样本的行为通常与实际生产系统有很大差别。建议尽可能在“真实的”系统上进行性能分析,而不是采用大型系统的子集。这可能会占用测试的时间,但是所获得结果的将会更加准确。
在分析一个系统时,测量工具本身确实也会对性能产生影响。例如Glance/gpm便是HP-UX系统中的一个进程,它会占用该机器的一部分CPU资源。我们所使用的工具已经尽可能使这种影响将到最低。但我们在使用一个测量工具时,这种影响总是存在的。
将系统作为一个整体进行评估
当前许多基于web的体系结构可能采用一组计算机作为web服务器层,另一组计算机作为应用服务器层以及一个数据库层。所有这些都通过网络连在一起,并互相作用以响应每一个客户请求(例如通过浏览器发出的请求)。
进行整体系统评估的主要工具有Glance/gpm(图形进程监视)以及类似的工具(如HP PerfView),它能够显示单个计算机中操作系统和进程的状态。Glance/gpm将就计算机不同组件的状态为工程师提供一个可视、直观的评估,例如其CPU负载、输入输出负载、网络流量,以及内存消耗率等。
在每台HP-UX计算机上使用Glance/gpm工具使工程师能够清楚地看到参与整个系统的所有机器中哪些负载紧张,哪些比较空闲。下图显示了Glance/gpm窗口中最顶层的计算机视图,我们可以看到该计算机的“系统时间”消耗水平非常高,而被使用的“用户时间”水平非常低。这表明该计算机存在问题。实际上我们希望有更高的CPU消耗被用于“用户时间”(用于执行应用的时间),而不是“系统时间”(用于执行操作系统功能的CPU时间)。
机器中出现这种非常高的“系统时间”占用率可能是由于操作系统调用,它们负责锁定被称为“mutexes”的数据结构,占用很长的系统时间来解决争夺,或者等待访问这些结构的线程被强制进入休眠状态。因此以下的情况可能是Java程序中的线程锁争夺所引起的,该话题将在本文以后的部分深入讨论。
到目前为止,我们已经知道太多的时间被用于执行系统代码,而不是应用代码。下一步是检查机器中的哪一个特定进程造成了这种情况。
图1:HP-UX中Glance/gpm工具的主窗口
Glance主窗口 — 高系统时间举例
低用户CPU时间
高系统CPU时间
从整体系统视图缩小至单个计算机,再进一步缩小至特定的进程是使用Glance/gpm工具以及其它HP-UX性能分析工具的一个练习。它们将在参考1[Sauers]中详细描述。
数据收集
性能分析的第一个步骤是记下用户交易执行路径中每一台计算机的机器设置(从而找出怀疑对象)。我们需要的信息包括:
● 计算机中CPU的数量以及这些CPU的速度(CPU的数量可以中Glance/gpm中看到)
● 物理内存的大小(也可以在Glance/gpm中看到)
● 磁盘空间的大小(已占用和可用的容量,在“df”命令的输出结果中)
● 操作系统可调节参数的值(使用Hpjconfig工具可以看到)
● 需要的操作系统补丁(Hpjconfig也能提供帮助)
● 使用的JVM版本(使用“java –version”命令可以找到)
● 使用的JVM选项(如–Xms
以下表1中是“java –version”命令所输出的Java版本信息的举例。
Java version "1.3.1.00-release" |
表1. “Java –version”命令输出的Java运行时版本信息
以上的每一个值均能帮助性能工程师测量机器中Java程序的性能特征。只需简单地升级至最新的Java SDK版本,升级操作系统并应用最新的补丁,或者使用Java运行时的正确选项,便能够解决一部分性能问题了。
建议性能工程师先升级至最新的Java SDK版本和最新的补丁,以证明这样是否能够改善系统的状况。下一章(“hp-ux内核参数”)中描述的Hpjconfig工具也能够检查是否已经应用了正确的HP-UX补丁。
表2显示了Java虚拟机的一种误用情况。该实例显示了用户使用了较旧、未经优化的Java运行版本(“classic JVM”),它不适合用于服务器端长期运行的应用。建议在这种情况下使用默认的Java运行(“HotSpot JVM”)。
$ java –classic ClassName |
表2. 使用较旧、低效的Java运行时版本 — 这种情况应该避免。
hp-ux内核参数
对操作系统进行正确的设置,使其以最优的方式来运行Java程序极其重要。几个HP-UX可调参数会影响这一正确的设置。
用于确定这些可调参数最佳值的工具是Hpjconfig,它本身也是一个Java程序。Hpjconfig是一个免费的程序,可以从以下网址下载
http://www.hp.com/products1/unix/java/java2/hpjconfig/index.html。
表3显示了Hpjconfig的调用。
$ java –cp HPjconfig.jar:HPjconfig_data.jar HPjconfig |
表3. 使用Hpjconfig工具
该工具首先检查运行其的计算机的型号,然后允许性能工程师来选择各种不同的应用类型,例如“应用服务器”、“Web服务器”和“WAP服务器”等。随后它将就某些HP-UX内核可调参数应该采用的值提出合理的建议。它还能够将输出结果数据保存在一个文件中。然后系统管理员便可以使用SAM系统管理工具将这些推荐的值应用到操作系统中。图2显示了Hpjconfig的初始界面。此时,工具正在检查应用到当前HP-UX系统中的补丁是否适合于Java。
性能分析流程
Hpjconfig:检查补丁
图2:HP-UX系统中Hpjconfig工具的主界面
图3显示了针对特定应用类型所推荐的内核参数举例,如Hpjconfig工具中所见。
性能分析流程
Hpjconfig:内核参数
更新至推荐的值
图3:Hpjconfig工具的HP-UX内核参数窗口
了解有关这些参数进一步的详细信息,请访问中国惠普公司技术动力社区并在技术论上进行交流:
http://210.82.87.227/partnerportal/solutioncommunity.aspx
请注意Hpjconfig窗口“推荐的调整值”一列中所显示的值仅仅是一些建议。如果一台计算机上运行了多个应用进程或数据库进程, 那么HP-UX内核可调参数的最优值将需要在Java程序以及共享这台机器的其它程序之间实现平衡。
对Java程序最为重要的具体HP-UX内核可调参数包括:
● max_thread_proc — 决定任何单个进程中所允许的最大线程数量;
● maxdsiz — 决定任何进程的数据段大小;
● maxfiles — 为任何进程能够打开的最大文件数量指定一个软限制;
● maxfiles_lim — 为任何进程能够打开的最大文件数量指定一个硬限制;
● ncallout — 决定I/O等待的最大超时值;
● nfile — 决定计算机系统中所能打开的最大文件数量;
● nkthread — 决定计算机系统支持的最大内核线程数量;
● nproc — 指定计算机系统中所允许的最大进程数量
垃圾回收
Java程序需要一定的对象,它们运行时将会占用部分内存。这些对象可能在程序开始运行时创建,也可能在程序运行过程中创建。在创建一个新的Java语言对象时,分配给它的内存将被放置在一个被称为“heap”的区域。
该heap内存的大小是有限制的。对于任何JVM运行其默认最大值被设定为128MB,除非用户使用JVM选项–mx或–Xmx调整了最大值。
以下命令将以默认的heap大小值来运行JVM(未指定选项)。
$ java ClassName |
表4. 以默认的128Mb最大heap值运行Java
以下命令限制该第二个JVM运行至多占用240MB的heap大小。
$ java –Xmx240m ClassName |
表5. 带有240Mb最大heap值的Java运行
在第二个程序试图为对象分配内存(创建新的对象)时,如果对象需要占用超过240Mb的内存,那么JVM将会产生一个异常信息“OutOfMemoryException”,程序的执行便会终止。
Java程序与C或C++程序不同,它不会显式地重新分配或释放占用的heap内存(而C/C++程序器会使用“delete”运算符来释放内存)。
这是由JVM本身来完成的,而不是程序。JVM通过确保程序的任何部分都不再引用该对象,从而检测到程序已经不再使用该对象。清除不再使用的对象被称为“垃圾回收”,它可以被视为JVM运行间隔期间的一种分离、独立的活动。这些间隔由多种因素决定,其中之一便是要求更多的heap空间。
该内存管理策略的影响是一旦决定JVM的垃圾回收应该开始,执行Java字节代码的所有其它线程,无论是解释或编译的形式,在垃圾回收过程期间都必须返回至安全点并停止。如果该过程持续较长的时间,那么它便会给Java程序的性能造成严重影响。
由于这个原因,在分析Java程序的性能时,分析垃圾回收的行为总是非常重要。
图4显示了单个JVM在一台8CPU的计算机上执行的过程中,垃圾回收活动发生时的瞬间影响。可以看到,只有一个CPU正在执行有用的工作,即垃圾回收器本身。其它的CPU几乎未被使用。
Glance屏幕 — 垃圾回收的影响
图4:在运行HP-UX的8CPU系统上,Glance/gpm工具的CPU屏幕中看到的垃圾回收
因此过多的垃圾回收活动是造成Java程序缓慢的重要原因,所以必须通过评估和更改影响heap的JVM选项来进行调整,选项包括-Xms、-Xmx、-Xmn和–XX:SurvivorRatio。但是,第一步是检查JVM的垃圾回收部分是否发生了过多的活动。
在Glance/gpm的主窗口中我们可以获得很好的线索,来判断垃圾回收活动是否频繁。如下图5所示“尖峰”状的CPU行为类型能够表明出现了我们不希望的垃圾回收活动。图5表明用户程序时间在垃圾回收器接管CPU后经历了非常大的下跌。
Glance — 频繁的垃圾回收
图5:HP-UX Glance/gpm工具中“尖峰”状的CPU行为类型
由于我们拥有JVM的“-Xverbosegc”选项,这一检测工作可以通过HP-UX上的Java轻松实现,该选项的使用方式如下。
$ java –Xverbosegc:file=myfile.out ClassName |
表6. 利用-Xverbosegc运行Java
这使JVM能够在文件“myfile.out”(文件名仅仅是这里的举例)中生成详细的垃圾回收器数据。
注:该选项在运行时对JVM只有非常低的影响(除了向磁盘写入输出数据造成的影响),所以必要时可以在生产应用上使用。
输出数据(在上例所说的“myfile.out”文件中)很难读懂,因为其原始格式如下:
|
表7. JVM“–Xverbosegc”选项的输出结果
该输出文件中有19列数据,描述了垃圾回收器heap消耗期间的各个时间和区域。针对该输出文件,推荐下载HPjtune GUI工具(http://www.hp.com/go/java)或者从性能调节网站下载“processVerboseGC.awk”文件,地址:
http://h21007.www2.hp.com/dspp/tech/tech_TechDocumentDetailPage_IDX/1,1701,1602,00.html
并应用如下的命令:
$ cat myfile.out |awk –f processVerboseGC.awk > output.txt |
表8. 在包含–Xverbosegc输出的文件上运行
processVerboseGC.awk脚本
该命令能够生成非常用户友好的输出文件,示例如下表9所示:
GC: Full GC required - reason: Old generation expanded on last scavenge |
表9. 在–Xverbosegc输出文件上运行
processVerboseGC122.awk脚本后的输出结果
在免费下载的HPjtune工具中查看GC数据能够获得对垃圾回收器行为的图形化视图。这是该情形下推荐使用的方法。
以上数据描述了程序执行过程中按顺序连续发生的3次垃圾回收事件。每次事件发生的时间,从程序开始时间算起,以秒为单位显示在“Full”或“Scav”之后。即使在了解“eden”、“survivor”、“tenure”和“old”等术语的含义(将会在下文中解释)之前,我们也能够从以上数据中获得许多信息。首先,1次“Full”垃圾回收(GC)事件所占用的时间要比“Scavenge”或“Scav”垃圾回收更多。如果我们看到很多的“Full GC”条目,例如每分钟一次甚至更多,那么我们便需要采取一些措施。
如果在数据中看到一个条目包含以下内容:
“Full GC required – reason : call to System.gc() or Runtime.gc()”
那么我们便知道是用户的Java代码或应用使用的一些库或基础设施代码正在显式调用垃圾回收器。这种情况永远都不应该发生。要解决该问题,我们可以将“call to System.gc() or Runtime.gc()”从代码中移除,或者使用JVM选项–XX:+DisableExplicitGC来禁止它,如下所示:
$ java –XX:+DisableExplicitGC ClassName |
表10. 通过禁止显式调用垃圾回收器的选项运行Java
第二,我们在“Full”或“Scav”字符串之后便可以看到从程序启动开始计算的时间。该时间(以秒为单位)累计程序运行的时间,使我们能够了解到事件发生时程序执行的进度。
“since last”字符串之后是与上一次GC活动相隔的时间。“gc time”字符串表明了该特定的GC事件完成所用的时间。我们应特别注意这些最后的数字。一般来说,正常JVM执行Full GC的次数要少于Scavenge GC。Scavenge GC之间的间隔应该在5秒左右或更长,并且完成的时间不应超过300-400毫秒。
如果Scavenge GC完成的时间达到1分钟甚至更长,那么我们便知道程序暂停了那些执行Java字节代码的线程至少1分钟,出现了性能问题。我们应该考虑调整控制heap内存使用的JVM选项来解决该问题。这可以通过更改与JVM选项–Xms、-Xmx和–Xmn相关的值来实现,如下所述。一旦了解到heap内存的哪些区域正被过度使用或未被充分利用,那么我们便可以更改这些值。这将会在下一章中讨论。
Full GC可能需要1分钟甚至更长的时间,取决于需要执行回收的heap空间的大小。一般来说,好的实践是对JVM运行进行配置,使Full GC能够在1分钟甚至更短的时间内完成,并且相对于Scavenge GC发生的次数更少。每5至10分钟发生一次Full GC,每次持续10秒或更少,以及每隔5-10秒发生一次较小、持续时间很短(300-400毫秒或更少)的Scavenge Gcevery,表明JVM运行比较正常。
heap内存中空间
本章节对heap管理进行一定的深入分析。通过了解new和old的对象在JVM整个heap内存中的存放,我们便可以看到该空间被使用的情况。然后我们便可以借助JVM选项–Xms、-Xmx和-Xmn来采取措施,从而影响行为的改变。
下图6是JVM中整个heap内存的布局图。
垃圾回收
Heap布局
图6:用于Java对象的heap内存结构
在图6中我们可以看到在整个heap内存中共有4个“空间”,有时也称“区域”。垃圾回收这一“分代”类型设计的目的是,寿命较短的对象可以在所有时间内一直停留在“New区域”中,并使用一种快速的算法实现回收。寿命较长的对象必须寻找合适的方式进入“Old区域”并保留在那里,直到一种较慢的垃圾回收算法找到它,如果它已经不再使用,那么便可能被清除掉。
在上图6中我们没有画出heap内存中的“Permanent区域”空间,本文不对其进行讨论。我在这里提到它,以表示完整性。heap内存中的“Permanent区域”空间被用于存放永久存在的对象以及其它对象。在Java SDK 1.3.1中其默认值是64MB。这一默认的最大值,加上默认的新和旧区域的最大值(分别为16和64Mb)便构成了整个heap默认的128MB最大值,前文中已经提到。
Scavenge GC在“New区域”中完成,因此速度更快。Full GC同时包含了“New”和“Old”的区域,因此速度比Scavenges要慢。
“New”区域的默认起始大小是2MB。“New”区域的默认最大值是16MB。
“Old”区域的默认起始大小是4MB。“Old”区域的默认最大值是48MB,如上图中的阴影区域所示。这些大小可以使用下面的选项进行更改:
● –Xms(heap起始大小)
● -Xmx(最大heap大小)
● –Xmn(New区域的大小)
● -XX:SurvivorRatio=
JVM选项。以上前3个选项中每一个后面都可以跟一个数字,表示分配的MB数。例如,–Xmn256m表示为New区域分配256MB的空间。
建议将–Xmn的值设为–Xmx值的三分之一。在临时对象使用严重的应用中,也可以将–Xmn的值设为–Xmx值的一半。-Xmn的值需要根据具体应用进行适当的调节。
“New”区域中的“Eden”子空间是新的对象初次创建时用于存放的内存空间。这是一个非常重要的空间,因为所有用户定义的对象都会在程序运行过程中的某个时刻出现 — 可能持续较长或较短的时间。以下是一个Java程序创建Panel类(一个通用的用户接口类)的一个新对象的实例。这一新的对象会在“Eden”子空间内存放一段时间,如果它在程序中使用很长时间,此后将被拷贝到其它地方,也许是“Old”区域。
Panel p = new Panel(); |
表10. 创建Panel类的一个新对象
很自然,Eden空间随着时间会被填满,因为有越来越多的新对象被存放到其中。在Eden区域被填满时,一个Scavenge GC便会启动,并将那些当前存在引用的对象从Eden区域拷贝到“to”区域中。
“to”空间是两个“survivor”空间之一,可用于保留一段时间正在使用中的对象,延迟它们进入“旧”的区域,直到它们“经历”多次GC事件。另一个“survivor”空间称为“from”空间。
此时,没有引用的对象将会被从Eden区域中清除,从而释放出它们此前所占用的空间。这些便是被垃圾回收的对象。
前一次GC事件中可能在“from”子空间内处于等待状态的所有对象也会被拷贝到“to”区域,然后这两个空间交换名称 — “from”成为“to”,“to”成为“from”。
对象被从“from”和“to”区域之间来回拷贝,直到到达一定的拷贝限制,该限制由“MaxTenuringThreshold”变量设定,如上图,其默认值为32。这意味着对象在“新”的区域中最多可以“经历”32次Scavenge GC和32次from-to交换名称活动,直到最终被“永久化/tenured”并被存放到“Old”区域中。Old区域旨在用于存放程序运行期间一直存在的对象。
现在我们便能够较为轻松地理解–Xverbosegc的输出结果了。以上表8中包含如下内容的条目:
eden: 1834928->0/3670016 |
表11. processVerboseGC122.awk输出结果中的Eden大小数字
表明了该GC事件发生之前Eden子空间被占用的大小(箭头前的数字),该事件后Eden空间被占用的大小(箭头后的数字)以及整个Eden空间大小(“/”标记后的数字)。该条目的含义如下:
● 该GC活动之前新对象占用了1834928字节的Eden空间。
● 作为该GC操作的一部分,这些对象被完全从Eden中移除(被存放到其它地方),因为GC之后的Eden占用为零(箭头后的数字)。
● GC完成之后Eden空间的总大小为3670016字节。
相同的读取方式也可以应用到“survivor”和“Old”空间上。在这种情况下,术语“survivor”指名为“from”和“to”的两个子空间的和。
每次Scavenge GC时清理Eden子空间是垃圾回收的一种正常行为。但是,如果我们看到每次Scavenge GC之后“Survivor”的数字(“from”和“to”)降到零,那么我们便知道某个地方出现了问题。对象没有足够多地在“from”和“to”子空间之间来回拷贝。相反,它们以超出正常的速度被存放到了“旧”区域中。这一操作可能会导致“旧”区域最终被充满并非长时间存在的对象。这一症状称为“溢出”,如果发现连续的GC事件中MaxTenuringThreshold的值都非常低(2-10),便可以确定是这种症状。
如果我们发现“New”区域中没有足够的空间来使对象在其中保留合适的时间,那么我们可以首先使用-Xmn工具来调整“新”区域的大小。这将会扩大所有“新”区域子空间的大小,包括“eden”、“from”和“to”。使用方式如下:
$ java –Xmn160m –Xmx480m –Xms480m ClassName |
表12. 使用–Xmn、-Xmx和–Xms选项运行Java,
分别指定“新”区域、最大总heap值
和初始heap值的大小
建议“New”区域的大小与整个heap最大值的比率在1:3到1:2范围之间,后一数字被认为是非常激进的调整。
我们也可以使用JVM选项–XX:SurvivorRatio=
$ java –Xmn120m –Xmx480m –Xms480m –XX:SurvivorRatio=8 ClassName |
表13.借助-Xverbosegc选项运行Java
这会使Eden子空间的大小为“from”或“to”空间的8倍(后两者的大小相等)。上一实例中的“新”区域将被分成一个96MB的Eden子空间(即12MB的8倍)以及“from”和“to”两个大小均为12MB的子空间,总空间为120MB。
建议–XX:SurvivorRatio选项保留其默认值,在JDK 1.3.1中为8。即“Eden”的大小是“from”或“to”子空间的8倍。
我们还建议采取以上的操作,目的是减少FullGC的数量并保持MaxTenuringThreshold的值尽可能接近32。MaxTenuringThreshold的值会随着垃圾回收器的执行而调整,所以在程序运行过程中可能会不同。该值越低,对象便会越容易被从“from”或“to”子空间拷贝到“Old”区域中。这是我们希望避免的一种情形。
因为JVM的–Xverbosegc选项在较长的时间内会生成非常多的数据,所以另一个技巧是将基本的–Xverbosegc输出数据导入到电子表格中(如MS Excel)并画出程序运行时间内“新”或“旧”区域增长率的图形。该技巧在开发人员解决方案合作伙伴门户(DSPP)站点上有详细的记录,地址: http://www.hp.com/dspp
线程、锁定与争夺
Java是一种编程语言,它允许在程序中轻松地创建线程或异步的过程。但是这可能会给对多线程程序不熟悉的程序员带来麻烦。
无限制地创建线程当然是不好的实践,应该尽量避免。许多应用服务器中间件系统依赖于创建数千个线程。HP-UX支持在一个进程中拥有许多线程。线程的数量取决于HP-UX内核参数“max_thread_proc”的值。该值可以通过执行“kmtune”命令来查看,如表14所示。
$ kmtune |grep max_thread_proc |
表14.使用HP-UX kmtune命令查看内核可调参数的值
幸运的是,我们有多种工具可以在程序执行期间或执行结束后来查看程序的线程行为。这些工具包括Glance/gpm、Hpjmeter和被称为“kill –3”的方法。下面的内容将对它们详细进行介绍。
图7显示了用户通过Glance/gpm查看特定Java进程(也许在一系列Java进程中)并检查Java程序运行中创建的所有线程时的数据图形。下图最左边一列中的TID表示每个线程的TID或唯一的线程ID。
应该指出的是JVM本身在启动时需要11个线程,它们与用户程序代码所需的线程是分开的。
图7:在Glance/gpm中通过进程界面查看正在运行的Java程序的所有线程
Java程序员在使用线程时面临的风险包括:
● 创建了太多的线程,超出了操作系统的限制。Java程序中会出现“OutOfMemoryException”的错误信息,或者一个提示线程过多的消息。这个问题非常容易解决。主要方法是在系统上使用Hpjconfig工具来查看内核参数“max_thread_proc”的值,并使用SAM(系统管理)工具进行修改。
Hpjconfig工具可以从HP Java网站免费下载。http://www.hp.com/java
● 一个线程可能长时间占用访问共享资源的锁,从而导致一些或所有其它的线程不能访问CPU。图8显示了一个线程占用了其它线程需要访问的资源时的问题情形。锁争夺是一种测量方式,用于测量多少线程试图获得该锁以及尝试获取的频率。在争夺很高时,线程需要花时间等待进入对象监视器而不是执行有用的工作。
HP-UX版本Java SDK 1.3.1(或以后版本)中的-Xeprof选项为用户提供了关于锁争夺的准确数据,并且该数据可以被Hpjmeter解释。
图8:控制多个线程访问资源的操作系统监视器结构
线程T1占用了另一线程T2需要访问的资源A的锁。线程T2占用了T1需要访问的资源B的锁。这两个线程进入“死锁”状态,除非一个线程让步,否则程序将不能继续执行。
这些问题都与应用软件的设计相关。因此,通过修改软件的结构(涉及重新编译)基本上便可以解决这类问题了。但是,工程师必须使用能够发现这些问题的工具,并通过它们快速发现问题的原因。
我们能够获得的有关线程和锁争夺问题的第一个线索来自Glance/gpm的输出。在图9显示的系统图形中,过多的CPU时间被消耗在“系统”时间(较深颜色)而不是“用户”时间(颜色较浅的区域)上。正常系统中的情形应该相反。“用户”时间应该是CPU花费的大部分时间,因为这一时间用于执行应用代码。
图9: Glance/gpm中看到的系统时间使用超出正常的证据
这是可能出现了线程和锁定问题的一个迹象。然后我们再次使用Glance/gpm来查看系统上各个独立JVM进程所使用的单个系统调用。
我们可以获得关于出现锁争夺问题的单个进程的图像,它可能与下图相似。这里,特定HP-UX操作系统调用(如“sched_yield”、“ksleep”和“kwakeup”)被调用的次数非常高,超出了Glance/gpm“累计系统调用计数”一列中测量的所有其它调用。这些调用可能因JVM版本的不同而有所差异,但是性能工程师必须在最高被调用列表中查找所有的调用。图10中指向“send”和“recv”调用的系统调用率的箭头表明这些调用的频率远远低于“sched_yield”和“ksleep”调用。而“send”和“recv”的调用频率应该更高,因为它们在该基于网络的程序中需要完成主要的工作。
如果我们解决了这里明显的线程争夺问题(例如,通过围绕轮询(polling)线程和线程池模式重新组织线程设计),我们将会看到非常高的“send”和“recv”调用率。
图10:单个进程对特定系统调用非常高的系统调用率 — 可能出现了线程争夺
如果设计中每个对网络流量开放的接口都使用一个线程,那么这种情形的问题便会出现。在同时建立上百或上千个网络连接的情况下,这可能会导致在线程争夺上耗费非常高的系统开销。这类问题在过去的设计中已通过实施HP Poll API得到解决。HP Poll API是设计应用的一种方法,以使用较少的线程。该主题不在本文的讨论范围之内。在Java SDK 1.4中也有一个子系统可用于对异步I/O环境中的线程进行更好的控制。
使用KILL –3获得线程数据转储
该方法在分析JVM线程时十分简单,但是非常强大。在一个正在运行的JVM进程上使用“kill”命令和参数“-3”或“-s SIGQUIT”不会影响正在运行的JVM,只是使它生成一个关于其当时所保留线程的所有信息的详细转储。使用Glance/gpm,或者简单的
“ps –ef|grep Java”
命令来获得我们想要查看的JVM的进程ID。然后下表15中所示的命令将会通过该程序的标准输出通道生成有关线程数据的全部转储。
# kill –3 3493 (if 3493 were the PID of the running JVM) |
表15. 在JVM上使用kill –3命令使其进行线程转储
在开始调用JVM时,我们还可以将标准输出重新定向到一个文件中。
以上的kill命令可能需要用户具有超级用户权限。正在运行的JVM生成的标准输出数据(如果超过一定的大小,可能会被要求重新定向到一个文件),可能与下面的示例相似。
图11:在由
“kill –3
命令时的部分输出数据
图11显示了一个特定的线程(其轻量进程ID,图中lwp_id,为14165)正处于挂起状态,因为它一直在等待锁以访问一个特定的对象(由其十六进制地址指定)。这只是一个较大数据转储的一个较小的快照,但能够使读者了解到这一输出。在该输出中我们需要特别注意的是,一个线程长时间占用访问一个对象的锁,而该对象需要被其它线程访问。这导致整个JVM的速度变慢,从而出现问题。
我们可以在问题程序运行过程中多次重复“kill –3
使用hpjmeter工具来检测线程锁定问题
分析JVM线程行为的一个更简单的方法是采用HP特定选项–Xeprof运行该程序至结束,然后使用Hpjmeter工具分析其输出结果。
如果正在JVM中执行的程序能够被完全地终止(即,使用System.exit()或一些适当的关闭过程),那么JVM必须采用如下扩展的分析选项(-Xeprof)来运行。
$ java –Xeprof:file=myfile.eprof ClassName |
表16.使用–Xeprof选项来运行Java程序,
以便在运行中进行监视
我们分析生成到文件(例如myfile.prof)中输出结果的工具是Hpjmeter分析工具,它可以通过以下地址免费下载
http://www.hpjmeter.com
该工具本身以Java语言编写,因此它可以在任何支持JVM的平台上运行。该工具将读取标准JVM选项–Xprof生成的输出文件。但是,如果读取HP JVM特定–Xeprof选项生成的输出文件,那么该工具将能够带来更多优势和详细信息。
运行Hpjmeter工具(在HP-UX或任何支持Java的平台上)的命令如下表17所示。
$ java –cp /opt/hpjmeter/HPjmeter.jar HPjmeter |
表17. 运行Hpjconfig工具
该免费的工具除了与线程运行时间相关的测量方法以外,还拥有许多有用的测量方法。这些测量方法在下载站点的指南中有详细的介绍。在本文的该部分中我们仅侧重于使用其进行线程分析。Hpjmeter工具生成JVM运行中关于线程状态及其运行时间的图形页面。我们将–Xeprof选项生成的文件载入到工具中,然后选择名为“Metric”的菜单项显示线程柱形图,便可以得到以下的图形。
表12.在Hpjmeter工具中显示线程运行时间及其状态的页面信息
这使我们能够一次看到所有线程的运行时间,并可以通过双击代表线程的彩色条集中于有问题的某个线程或多个线程,从而进行深入的分析。以上图中的线程0有77.1%的时间在CPU内执行;这是正常的标志。该线程有大约21.4%的报告时间被用于profiler系统开销,只有非常少的时间被用在等待执行或锁争夺上。
使用HP的Java SDK 1.3.1+和HPjmeter 1.2+,我们可以获得关于线程争夺问题的非常准确的图形,这些问题可能会降低Java程序的运行速度。
除了线程分析以外,Hpjmeter在分析许多其它情况时也非常有用。使用该工具提供的一些测量方法还可以追踪消耗大量CPU和wall时钟时间的方法(method)。这将会在以后的“昂贵的方法调用”一章中详细讨论。
线程总结
Java程序线程被Java虚拟机映射到操作系统线程,也称HP-UX轻量进程(LWP)。HP-UX中调度单元为单个的线程。每一个线程在Glance工具中都是可见的,例如拥有唯一的LWP编号(LWP ID),使用“Thread List”特性。
最初,每个线程优先级的初始值与操作系统中的所有用户进程相同。由于线程在CPU中执行,在正常条件下,其优先级会随着其获得更多的CPU时间而降低。这导致线程在离开CPU时优先级变低。因此操作系统在下一次根据优先级来调度线程时,该线程相对于其它线程可能会处于不利的地位。
如果一个特定的Java进程是一台计算机上唯一最重要的用户进程,那么该进程可以通过超级用户干预,在开始或执行过程中被授予最高的调度优先级。这可以分别使用“nice”或“renice”命令来完成,或使用Glance/gpm的“renice”功能。
“nice”命令如下表18中所示。
# nice –-20 java ClassName (notice there are 2 minus signs) |
表18. 使用“nice”命令运行Java程序
“renice”命令需根据正在运行的Java程序的进程ID来执行,进程ID可以使用“ps –ef”命令或者在Glance/gpm中使用进程列表页面获得。下表19中使用的示例Java进程ID为1234。
# renice –20 1234 (notice there is 1 minus sign) |
表19. 在运行中的Java程序上运行“renice”命令
nice和renice这两个命令在执行时都必须非常谨慎。它们只有在以超级用户身份登录到计算机时才能被执行。这些命令可以改变被调整(reniced)进程中所有线程的优先级。
执行以上此类命令的结果是相同计算机上的其它进程与“reniced”的进程相比将处于不利的地位,可能会比不使用该命令时获得更少的CPU时间。但是,在有些情况下这种折衷是值得的。
昂贵的方法调用
在Java程序中经常会出现这样的情况,少量的程序方法(method)占用了大部分该程序所消耗的时间或资源。很显然,这些是应用设计时需要调整的主要领域。
一些方法(method)可能无需修改应用设计或源代码便可完成调整。第一步是发现这些方法的编号和性能特征。Hpjmeter工具最合适于进行这种分析。
Hpjmeter具有独特的优势,它可以免费在所有支持Java的环境中运行。第二,JVM的–Xeprof选项可生成供Hpjmeter分析的数据,并且经过特别设计,针对其分析的程序只有最低的入侵影响。
调用Hpjmeter的方法如以上表17中所示。图13显示了Hpjmeter中关于方法调用次数最多的输出。
图13.Hpjmeter工具所见的最高Java程序方法调用计数
很明显,在图13中Mark.SimSearch.bel()方法在该程序所有被调用的方法中被调用的次数最多。“bel()”方法调用的次数远远超过的所有其它方法的调用。我们可以使用其它的Hpjmeter测量方法,例如“Exclusive CPU Method Time”来详细分析该方法,以检查它是否占用了大量的程序资源,例如CPU周期或其它方面。然后我们对该方法的设计及其在程序中的使用进行一些修改,从而清除该瓶颈。
在一些情况下,特别是与Java程序中格式化日期类的对象相关时,我们无需修改源代码本身便可以实现对频繁调用的方法的修改。当Java程序中格式化日期对象使用非常频繁时,方法“currentTimeMillis”或“getTimeOfDay”便可能会出现在Hpjmeter方法调用计数测量输出图形的顶端。
在使用JDBC向数据库写入记录或从数据库中读取记录时,日期对象的使用可能会非常频繁。
假设我们已经确定这对程序的性能存在非常严重的负面影响,我们可以使用表20中所示的选项来解决该问题。该选项能够减轻程序执行过程中JVM所使用的日期/时间函数(操作系统中的微小运算)的影响。
$ java –XX:-UseHighResolutionTimer ClassName |
表20. 利用该选项运行Java程序,以减轻计时/日期方法的影响
内存溢出
Java开发世界中最受关注的问题之一是程序可能因为内存不足而导致失败。此时可能会出现一个“java.lang.OutOfMemoryException”提示,或者也可能以不太友好的形式出现,例如程序执行发生中断。
Java程序员为新对象分配内存的方式如以上垃圾回收一章中所述。理论上,他们不必要担心程序中的对象一旦完成处理之后很快就会被从内存中清除。
但是,除非所有关联到对象的引用都被清除(设为“null”或超出范围),否则JVM会认为这些对象在程序执行过程中会被再次使用。这被称为是“内存占用”问题。它与其它语言,如C++中的内存溢出概念有很大的不同。
在C++中,发生内存溢出可能是因为代码出现了以下给出的类型(一个程序中很小的片断)。
// where ptr is a pointer of a correct type |
表21. C++中的内存泄漏示例
该问题与Java中的情形不同,因为将关联到对象的引用设定为“null”是一个很好的实践,表明程序已经完成了对象处理。而且,Java程序员没有机会以明显的方式来释放分配给对象的内存,因为该语言中没有“free”或“delete”运算符。在Java中,程序员在处理完对象之后遗漏了将引用设为“null”,从而导致了“内存占用”问题。
如果该对象非常大,并且还可能引用其它的对象,从而形成一个链,那么这些对象占用的整个内存区域,在对象处于范围之内时将不可用。在执行较长的时间时,为这类行为异常的对象重复分配内存很可能会导致程序出现内存不足。这将会导致JVM在不可预知的时刻以不友好的方式发生中断。
Java内存占用问题一般很难解决,除非重新编写有问题的代码片段。纠正这些问题不在本文中进一步讨论。Java程序员正迅速采用各种工具,如Sitraka公司的Jprobe Profiler工具,以便在开发早期检测出这些问题。读者可以参考该工具的指南和文档了解更多信息,[Jprobe]。
然而,检测这类内存溢出或占用问题初学者都可以胜任。因此,Glance/gpm工具中的“Memory Regions”界面是非常有价值的资产。性能工程师选择需要检查的进程(JVM通常以“java”的名字出现在Glance/gpm的进程列表页面中),然后使用Glance工具来显示关于该进程所使用的所有内存区域的报告。
这将会生成一个与图14相似的窗口。
图14. Glance/gpm中显示的特定Java进程所使用的内存区域。
如图14中所示,Data RSS和Data VSS(常驻集大小和虚拟集大小)的值表示分配给C程序heap的内存。Other RSS、Other VSS和Private RSS的大小表示该程序中分配给Java程序heap的内存。这些空间或其中任一空间的持续增长,都会使我们相信该Java程序中存在内存占用问题。
许多Java程序会在后台调用C/C++程序。通常情况下,分析表明内存溢出或占用问题出现在一些Java程序之下的C/C++程序中。因此我们在查找这类问题时应该分析Data RSS和VSS大小的行为。
如果我们监视的程序或程序集需要较长的时间才能表现出其行为,那么程序员将不能看到这段较长时间内以上所示的页面。此时,Glance/gpm工具集中的“adviser mode”特性将会非常有用。然后我们使用该工具的一个批处理模式,将数据收集到一个文件中以便以后进行分析。
例如,下表22中所示的命令能够使Glance/gpm每隔5秒(时间间隔可以指定)写入一次样本,并使用adviser_commands文件中所指定的命令。
$ /opt/perf/bin/glance -adviser_only -syntax adviser_commands -j 5 |
表22. 在Adviser Mode下使用Glance
这类语法可以在“adviser_commands”文件中指定,示例如下。
表23.Glance Adviser Mod命令文件示例
以下图15显示了Glance在adviser mode下运行时的输出示例,其中具有明显的问题
图15:Glance工具在adviser mode下的输出示例,其中具有明显的问题
基准性能测试症状
Java软件开发套件(SDK)1.3.1在本文撰写时已经包括了HotSpot运行时编译器1.3.1(称为“HotSpot”)。
HotSpot技术是用户调用JVM时默认使用的运行时(除非用户指定了–classic选项,这在一般情况下应该避免)。它在运行时分析Java代码的行为,并将部分字节代码段编译为一个中间的表达式,并进一步编译为本地二进制代码,从而优化运行。在Java程序运行过程中的某些情况下,Java字节代码的执行可以在HotSpot的控制下从已解释的形式转换为已编译的本地代码。该“编译”流程的一个重要条件是重复执行一个拥有许多后向分支或循环的方法。
在分析一些基准性能测试的结果时,我们发现一些基准性能测试在HotSpot默认JVM下的速度似乎比在其它JVM下的速度要慢。这是HotSpot运行时在“预热”阶段的一种症状,它需要利用这段时间来检查一个方法被频繁地执行,并存在合适的逻辑类型供HotSpot将其编译为本地代码。
该现象一般可以通过将应该被优化的方法放置到一个循环中来解决。这会使HotSpot运行时编译器真正地对其进行编译。
该技巧能够使部分基准性能测试的执行时间出现呈数量级的提升。
这类情形的示例是,程序中的“main”方法完成所有的处理,并且从不调用另一个方法。示例如表24中所示。
表24. 不能从HotSpot运行时编译中受益的程序示例
请注意在表24中,“main”方法完成了所有的工作。HotSpot运行时编译器没有机会对代码进行优化,从而允许执行编译过的代码,而不是字节代码。表25中的示例程序也完成同样的工作,但是它的运行速度比其等价程序更慢。
表25. 能够从HotSpot运行时编译中受益的程序示例
这里可以看到,重复10000000次的“big”循环现在包含在一个方法中,并且在本身循环内被该类的“main”方法调用。“runTest()”方法在10000000次重复循环中包含了大量的后向分支,并且本身被从“main”中反复调用,因此它完全适合在JVM固有的HotSpot运行时编译器中进行运行时编译。所以,它的运行速度比表24中等价的程序要快很多。
该现象在HP Java性能调节网站上的另一篇单独的文章中有更加详细的介绍。
http://devresource.hp.com/devresource/Docs/TechPapers/Java/HotSpotBench.html
读者可以参考该网站了解关于本主题的更多信息。
结论
本文中所介绍的工具和技巧能够帮助工程师调节Java程序的行为,以获得更高的性能。性能工程师不应该因初始测试中获得的较差结果而感到气馁。最初结果之下的问题通常都可以被解决,从而获得更好的结果。
在采取任何措施修改程序的运行时性能之前,必须首先使用Glance/gpm、Hpjmeter和HPjtune等工具来对垃圾回收进行分析,进而对可能影响性能行为的问题作出诊断。OpenView Transaction Analyzer工具也能够就基于J2EE的应用中可能存在的性能瓶颈点提供非常有用的分析和诊断。
我们建议按顺序进行体系结构评估、测量、数据分析、瓶颈确定、逐步调节和重新测试的流程。本本介绍了初学者如何使用HP工具来检查各个领域的问题,特别是
● 内存管理
● 线程行为
● 系统与JVM配置
● 程序资源占用
因为这些问题在调节Java应用的性能时能够带来富有成效的结果。
参考文献
[Sauers] Sauers, R. and Weygant, P., HP-UX Performance Tuning, HP Press
[Shirazi] Shirazi, J., Java Performance Tuning, O’ Reilly Press
[Halter] Halter, S. and Munroe, S., Enterprise Java Performance, Sun Microsystems Press
[Wilson] Wilson, S. and Kesselman, J., Java Platform Performance, Addison Wesley
[Austin] Austin, C. and Pawlan, M., Advanced Programming for the Java 2 Platform, Addison Wesley
http://www.Javaperformancetuning.com/
工具
性能分析工具
HPjmeter http://www.hpjmeter.com — 免费下载
Glance/gpm 包含在HP-UX应用工具光盘中
HPjconfig http://www.hp.com/java — 免费下载
Jprobe http://www.sitraka.com
Introscope http://www.wily.com
OptimizeIt http://www.intuisys.com
负载测试工具
LoadRunner http://www.mercuryinteractive.com
SilkPerformer http://www.seque.com
e-Load http://www.empirix.com
本文来源:https://www.2haoxitong.net/k/doc/c2aeebc608a1284ac850437f.html
文档为doc格式