Java性能权威指南1.3  全面的性能调优_Java性能权威指南1.3  全面的性能调优试读-查字典图书网
查字典图书网
当前位置: 查字典 > 图书网 > 编程 > Java性能权威指南 > 1.3  全面的性能调优

Java性能权威指南——1.3  全面的性能调优

本书关注于如何以最佳方式利用JVM 和Java 平台API,让程序运行得更快。但除了这两点,还有许多外在的因素影响性能。书中这些因素时不时会出现,但因为它们不只影响Java,所以不会深入讨论。JVM 和Java 平台的性能只是高性能主题中的一小部分。 本书会覆盖一些外部因素,这些因素的重要性不亚于Java 的性能调优。本书中基于Java 的调优方法可以和这些因素相互补充,但这些因素多数已经超过了本书讨论的范围。 1.3.1  编写更好的算法 Java 的许多细节和性能标志都可以影响应用的性能, 只不过从来都没有一个叫-XX:+RunReallyFast 的神奇标志。 归根结底,应用的性能取决于它的代码如何编写。例如,如果程序循环遍历数组中的所有元素,JVM 就可以优化数组的边界检查,使循环更快,展开循环能提供额外的加速。但如果循环是为了找到特定元素,那目前还没有什么优化的办法,使得遍历数组和采用HashMap 的版本一样快。 需要更高性能时,算法是否优秀就是重中之重了。 1.3.2  编写更少的代码 有些人写代码是为钱,有些是为乐趣,还有些人将代码回馈社区,但不管怎样,大家都是码农(或者在写程序的团队里工作)。很难想象,我们对项目的贡献是少写代码,因为仍然有管理者通过所写的代码量来评估开发人员的绩效。 我能理解这种想法,不过这种想法与现实并不吻合。同样是正确的程序,小程序运行起来要比大程序快。对所有的计算机程序来说都是如此,Java 程序自然也不例外。要编译的代码越多,等待程序启动所耗费的时间就越长;要创建和销毁的对象越多,垃圾收集的工作量就越大;要分配和持有的对象越多,GC 的周期就越长;要从磁盘装载进JVM 的类越多,程序启动所花费的时间就越长;要执行的代码越多,机器硬件缓存的效率就越低;而执行的代码越多,花费的时间就越长。 无法取胜的战争 与直觉相反(和令人沮丧)的是,所有应用的性能都会随着时间,即应用新版本的发布而降低。但由于硬件的改善使得新程序的运行速度可以被接受,所以通常都不会有人注意到性能上的差异。 想象一下,在曾经运行Windows 95 的机器上运行Windows Aero 界面,会是什么样子?我以前喜欢Mac Quadra 950,但它无法运行Mac OS X(如果真这么做了,它将比Mac OS 7.5 慢许多许多)。从更小的层次上看,Firefox 23.0 比Firefox 22.0 快,但它们之间的版本差别很小。具有按tab 页浏览、同步滚动和安全特性的Firefox 要比之前的Mosaic 强大,但Mosaic 从我硬盘里装载基本HTML 文件的速度比Firefox 23.0 快50%。 当然,Mosaic 几乎不能从任何的热门网站上装载实际的URL,所以不太可能把Mosaic 作为主要的浏览器。一般来说,特别是在两个小版本之间,代码会进行优化, 从而运行得更快。性能优化工程师应该注意到这点。如果我们擅长这份工作,那就能赢得这场战斗。这是美好而有意义的事。我认为我们应该改善现有应用的性能。 但铁一般的事实是:随着新特性的添加和新要求的采纳(为了与对手竞争),程序会越来越大,越来越慢。 我把这总结为“积少成多”原则。开发人员总争辩说,只是增加了很小的功能,压根就不会有什么时间损耗(特别是不使用该功能的时候)。接着项目中的其他开发人员也同样拍着胸脯保证,结果却发现性能突然下降了好几个百分点。下次发布的时候又重复出现这样的情景,而此时程序性能已经下降了10%,反复几次这样的过程之后,性能测试就会检测到资源瓶颈——内存使用达到临界点、代码缓存溢出等情况。对于这些情形,常规的性能测试可以捕获发生状况的原因,性能调优小组也可以修正主要的性能衰减。但随着时间的推移,小衰减积少成多,会越来越难以修复。 我并不是在鼓吹永远不要为产品增加新特性或者新代码,很显然增强程序是有利可图的。但你得小心权衡,尽可能提高效能。 1.3.3  老调重弹的过早优化 “过早优化”一词公认是由高德纳发明的,开发人员常常据此宣称:只有在运行时才能知道代码的性能有多要紧。但你可能从来没注意到,完整的原话是“我们不应该把大量时间都耗费在那些小的性能改进上;过早考虑优化是所有噩梦的根源”。 这句名言的重点是,最终你应该编写清晰、直接、易读和易理解的代码。这里的“优化” 应该理解为虽然算法和设计改变了复杂程序的结构,但是提供了更好的性能。那些真正的优化最好留到以后,等到性能分析表明这些措施有巨大收益的时候才进行。 而这里所指的过早优化,并不包括避免那些已经知道对性能不好的代码结构。每行代码, 如果有两种简单、直接的编程方式,那就应该选择性能更好的那种。 在某种程度上,有经验的Java 开发人员都能很好地领会到这点(这也是一个例证,说明他们日积月累而掌握了调优艺术)。思考以下代码: log.log(Level.FINE, "I am here, and the value of X is" + calcX() + " and Y is " + calcY()); 代码包含了一个看起来不太必要的字符串连接。因为除非日志级别很高,否则字符串的信息并不会记录到日志中,如果不打印日志消息,那就没必要调用calcX() 和calcY()。有经验的Java 开发人员会下意识地避免这种写法。有些IDE(例如NetBeans)会在代码上打标记并建议更改。(然而没有完美的工具:NetBeans 会在字符串连接操作上打标记,却不会建议去掉不必要的方法调用。) 像这样的日志代码会更好: if (log.isLoggable(Level.FINE)) { log.log(Level.FINE, "I am here, and the value of X is {} and Y is {}", new Object[]{calcX(), calcY()}); } 除非启用了日志功能,否则就可以在避免字符串连接(消息体中有格式化字符,不会提高性能,但使代码更清晰)的同时,避免方法调用或者对象分配。 这样写出来的代码仍然清晰易读,与原来的代码相比,没有太多额外工作。好吧,我们还是需要多敲几下键盘,多加一行逻辑。不过这仍然不属于应该避免的过早优化,它是好码农所熟悉的选择。在你思考如何写代码的时候,请不要生搬硬套前辈们的教条。 本书中我们还会看到其他例子,例如第9 章讨论了处理Vector 前先进行循环的性能。 1.3.4  其他:数据库很可能就是瓶颈 如果你开发的是独立运行不使用外部资源的Java 应用,性能就(几乎)只与应用本身相关。一旦添加了外部资源(例如数据库),那这两者的性能就都很重要了。在分布式环境中,比如Java EE 应用服务器、负载均衡器、数据库和后台企业信息系统,Java 应用服务器的性能问题可能只是其中很小的部分。 本书并不关注整体系统的性能。对于整体系统,我们需要采取结构化方法针对系统的所有方面分析性能。CPU 使用率、I/O 延迟、系统整体的吞吐量都必须测量和分析。只有到那时,我们才能判定到底是哪个组件导致了性能瓶颈。关于这个主题有大量优秀的资源,相关的方法和工具也不只针对Java。假定你已经完成了分析,并且判断出是运行环境中Java 组件的性能需要改善。 不只JVM 有bug 和性能问题 这节以数据库的性能为例,但运行环境的任何部分都可能会引起性能问题。我曾经遇到过一个问题,客户正在安装新版本的应用服务器,而测试显示请求发送到服务器上的时间变得越来越长。于是我根据奥卡姆剃刀原则(参见下一条贴士),考察应用服务器中所有可能产生问题的部分。 逐一排除之后,性能问题依旧,而且我也没发现后台数据库有问题。因此最可能的原因是测试框架,通过性能分析判定负载发生器——Apache JMeter——才是性能衰退的原因。它将每个响应保留在列表中,每次有新响应到来时,它都要遍历整个列表,以便找到响应时间90% 的请求(如果不熟悉这些词,请参见第2 章)。 部署应用的系统,它的任何部分都可能会引起性能问题。常规案例分析建议应该首先考虑系统最新变动的部分(通常是JVM 中的应用),但仍然要准备检查环境的每一个可能出现问题的组件。 另一方面,不要忽视初步分析。如果数据库是瓶颈(提示:的确是的话),那么无论怎么优化访问数据库的Java 应用,都无助于整体性能;实际上可能适得其反。作为一般性原则,系统负载增加越大,系统性能就会越糟糕。如果更改了Java 应用使得它更有效,这只会增加已经过载的数据库的负载,整体性能实际反而会下降。导致的风险是,可能会得出错误结论,即认为不应该改进JVM。 增加系统某个组件的负载从而导致整个系统性能变慢,这项原则不仅限于数据库。CPU 密集型的应用服务器增加负载,或者越来越多线程试图获取已经有线程等待的锁,还有许多其他场景,也都适用这项原则。第9 章展示了一个仅涉及JVM 的极端例子。 1.3.5  常见的优化 如果所有的性能问题同等重要,从而“积少成多”地改进性能,那是多么吸引人。但常见的用例场景才是真正应该关注的重点。 我们可以从以下几方面阐述这条原则。 • 借助性能分析来优化代码,重点关注性能分析中最耗时的操作。然而请注意,这并不意味着只看性能分析中的叶子方法(参见第3 章)。 • 利用奥卡姆剃刀原则诊断性能问题。性能问题最可能的原因应该是最容易解释的:新代码比机器配置更可能引入性能问题,而机器配置比JVM 或者操作系统的bug 更容易引入性能问题。隐藏的bug 确实存在,但不应该把最可能引起性能问题的原因首先归咎于它,而只在测试用例通过某种方式触发了隐藏的bug 时才关注。但不应该一上来就跳到这种不太可能的场景。 • 为应用中最常用的操作编写简单算法。以估算数学公式的程序为例,用户可以决定他所期望的最大容许误差为10% 或1%。如果10% 的误差适合多数用户,那么优化代码就意味着即便误差范围缩小为1%,但是速度变慢了。

展开全文

推荐文章

猜你喜欢

附近的人在看

推荐阅读

拓展阅读

《Java性能权威指南》其他试读目录

• 1.1  概述
• 1.2  平台版本约定
• 1.3  全面的性能调优 [当前]
• 1.4  小结