Java SE和Java EE应用的性能调优

时间:2023-03-08 15:28:35
Java SE和Java EE应用的性能调优

凡事预则立,不预则废,和很多事情一样。Java性能调优的成功。离不开行动计划、方法或策略以及特定的领域背景知识。为了在Java性能调优工作中有所成就。你得超越“花似雾中看”的状态,进入“悠然见南山”或者已然是“一览众山小”的境地。

这三个境地的说法可能让你有些糊涂吧。以下进一步解释。

  • 花似雾中看(I don't know what I don't know)。

    有时候下达的任务会涉及你所不熟悉的问题域。理解陌生问题域首先面临的困难就是怎样竭尽所能地学会它,由于你对它差点儿一无所知。对于这类问题域。你有很多东西不了解,或者不知道重点。

    换句话说,这个问题域有哪些东西须要了解,你还傻傻看不清楚。这个阶段就是“花似雾中看”。

  • 悠然见南山(I know what I don't know)。刚进入不熟悉的问题域时,你对它知之甚少,随着时间的推移,你对它的很多重要方面都已有所认识,仅仅是对重要的详细细节还缺乏了解。这时。你能够算是刚刚“见南山”。
  • 一览众山小(I already know what I need to know)。

    还有些时候,你对任务的问题域很熟悉。或者已经具有该领域所必备的技能和知识,是这方面的专家。或者你对问题域足够了解,处理起来得心应手。比方你已经掌握了必要的知识,解决问题游刃有余。假设达到这个境地。那就意味着你已经是“一览众山小”了。

通常觉得,传统的软件开发过程主要包含4个阶段:分析、设计、编码和測试。如图1-1所看到的。

Java SE和Java EE应用的性能调优

图1-1 传统软件开发过程

替换图字: start:開始; analysis:分析; design:设计; code:编码; test:測试;
quality:质量合格?; yes:是; no:否; deploy:部署

分析是开发过程的第一步,用于评估需求、权衡各种架构的利弊以及构思高层抽象。设计则根据分析阶段的基本架构和高层抽象,进行更精细的抽象并着手考虑详细实现。编码自然就是设计的实现。编码之后是測试。用以验证实现是否合乎应用需求。

值得注意的是。測试阶段通常仅仅包含功能測试。即检验应用的运行是否合乎需求规格。一旦測试完毕。应用就能够公布给客户了。

遵循这样的传统软件开发过程的应用。通常要到測试或即将公布时才会关注性能或扩展性。

为了解决问题。Wilson和Kesselman对传统软件开发过程做了些补充,在传统开发模型基础上引入了性能測试分析阶段。參见他们的畅销书Java Platform Performance

他们建议在測试阶段之后添加性能測试。并将“性能測试是否通过”设定为产品是否公布的标准。假设达到性能和扩展性标准,应用就能够公布,否则就要转向性能分析。并根据分析结果回到之前的某个或者某些步骤。换句话说,通过性能分析来定位性能问题。Wilson和Kesselman加入的性能測试分析如图1-2所看到的。

Java SE和Java EE应用的性能调优

图1-2 Wilson和Kesselman加入性能測试分析之后的软件开发过程

替换图字: start:開始; analysis:分析; design:设计; code:编码; performance test:性能測试; performance acceptable:性能測试是否通过; profile:性能分析; yes:是; no:否; deploy:部署

对分析阶段提炼出来的性能需求,Wilson和Kesselman建议以用例(use case)的方式特别标识出来,这有助于在分析阶段制定性能评估指标。只是应用的需求文档中通常都不会明白描写叙述性能或扩展性需求。假设你正在开发的应用还没有明白定义这些需求。那就应该想办法将它们挖掘出来。

拿吞吐量和延迟性需求举例。以下清单列举了挖掘这些需求所要考虑的问题。

  • 应用预期的吞吐量是多少?
  • 请求和响应之间的延迟预期是多少?
  • 应用支持多少并发用户或者并发任务?
  • 当并发用户数或并发任务数达到最大时,可接受的吞吐量和延迟是多少?
  • 最差情况下的延迟是多少?
  • 要使垃圾收集引入的延迟在可容忍范围之内,垃圾收集的频率应该是多少?

需求和相应的用例文档应该回答上述问题,并以此制定基准測试和性能測试,确保应用能够满足性能和扩展性需求。基准測试和性能測试应该在性能測试阶段运行。评估用例时有些用例的风险过高,难以实现,应该在分析阶段后期。通过一些原型、基准測试和微基准測试来减少此类风险。分析结束后再变更决策的代价很高,这种方法能够让你事先对决策进行评估。

软件开发周期中的软件缺陷、低劣设计和糟糕实现发现得越晚,修复的代价就越大,这是一条颠扑不破的金科玉律。减少用例的高风险有助于避免这些代价昂贵的错误。

如今很多应用在开发过程中都会使用自己主动构建和測试。Wilson和Kesselman建议改进软件开发过程。在自己主动构建或測试中进一步加入自己主动性能測试。

自己主动性能測试能够发出通知,比方用电子邮件将性能測试结果(如性能是衰减还是改善,或性能指标的达成度)发送给干系人。这个过程能够将因不满足应用性能指标而失败的測试,以及測试的统计数据自己主动记录到追踪系统。

将性能測试集成到自己主动构建过程中后。每次代码变更提交到源码库时。都能很easy地追踪因变更而导致的性能变化,也就能在软件开发的早期发现性能衰减。

另外。将统计方法和自己主动统计分析加入到自己主动性能測试系统中也值得考虑。运用统计方法能够进一步验证性能測试的结果。

自顶向下和自底向上是两种经常使用的性能分析方法。

顾名思义,自顶向下(Top Down)着眼于应用顶层,从上往下寻找软件栈中的优化机会和问题。相反,自底向上(Bottom
Up)则从软件栈最底层的CPU统计数据(比如CPU缓存未命中率、CPU指令效率)開始,逐渐上升到应用自身的结构或该应用常见的使用方式。应用开发者经常使用自顶向下的方法。而性能问题专家则通常採用自底向上的方法,用以辨别因不同硬件架构、操作系统或不同的Java虚拟机实现所导致的性能差异。如你所想。不同方法能够用来查找不同类型的性能问题。

自顶向下大概是最经常使用的性能调优方法。假设须要更改应用软件栈的顶层代码进行调优,这也是最经常使用的方法。

使用自顶向下的方法时。通常你须要从干系人发现性能问题的负载開始监控应用。应用的配置变化或日常负荷变化可能导致性能减少,这样的情况下。须要持续地监控应用。此外,当应用的性能和扩展性需求发生变化时,应用可能无法满足新的要求。这时也须要监控应用程序的性能。

无论何种原因引起的性能调优。自顶向下的第一步总是对运行在特定负载之下的应用进行监控。监控的范围包含操作系统、Java虚拟机、Java EE容器以及应用的性能測量统计指标。基于监控信息所给出的提示再开展下一步工作,比如JVM垃圾收集器调优、JVM命令行參数调优、操作系统调优,或者应用程序性能分析。

性能分析可能导致应用程序的更改,或者发现第三方库或Java SE类库在实现上的不足。

在不同平台(指底层的CPU架构和数量不同)上进行应用性能调优时。性能专家常使用自底向上的方法。

将应用迁移到其它操作系统上时,也经常使用这样的方法改善性能。在无法更改应用源码时,比如应用已经部署在生产环境中,或者系统供应商为了在竞争中占得先机而必须将性能发挥到极致,也经常会使用这样的方法。

自底向上须要收集和监控最底层CPU的性能统计数据。

监控的CPU统计数据包含运行特定任务所须要的CPU指令数(通常称为路径长度,path length),以及应用在一定负载下运行时的CPU缓存未命中率。尽管还有其它重要的CPU统计数据,但这两项是自底向上中最经常使用的。在一定负载下,应用运行和扩展所需的CPU指令越少。运行得就越快。

减少CPU缓存未命中率也能改善应用的性能,由于CPU缓存失效会导致CPU为了等待从内存获取数据而浪费若干个周期,而减少CPU缓存未命中率,意味着CPU能够减少等待内存数据的时间,应用也就能运行得更快。

自底向上关注的一般是在不更改应用的前提下。改善CPU使用率。假如应用能够更改。自底向上也能为怎样改动应用提供建议。这些更改包含应用源码的变动。如将经常使用的数据移到一起,使得訪问同一条CPU缓存行(CPU cache line)就能获取这些数据,而不用等待从内存中获取数据。

这个改动能够减少CPU缓存未命中率。从而减少CPU等待内存数据的时间。

现代Java虚拟机集成了成熟的JIT编译器,能够在Java应用的运行过程中进行优化,比方根据应用的内存訪问模式或应用特定的代码路径。生成更有效的机器码。也能够调整操作系统的设置来改善性能,比如更改CPU调度算法。或者改动操作系统的等待时间(指操作系统在将应用运行线程迁移到其它CPU硬件线程之前所等待的时间)。

假设你觉得能够用自底向上的方法,那应该先从收集操作系统和JVM的统计数据開始。监控这些统计数据能够为下一步应该关注哪些重点提供线索。

本文内容摘自《Java性能优化权威指南》

Java SE和Java EE应用的性能调优