Java性能调优01 —— 基础

本系列均取自《Java性能权威指南》这里只是一个个人备忘笔录

JVM调优标志

除了少数例外,JVM 主要接受两类标志:布尔标志和附带参数的标志。

布尔标志采用以下语法:-XX:+FlagName 表示开启,-XX:-FlagName 表示关闭。(即:【+】号代表开启,【-】号代表关闭)

附带参数的标志采用以下语法:-XX:FlagName=something,表示将标志 flagName 的值设置为 something。其中 something 通常可以为任意值。例如 -XX:NewRatio=N,表示 NewRatio 可以设置为任意值 N。

介绍每个标志时,我们会讨论它的默认值。默认值的选取通常综合考虑了不同因素:运行 JVM 的物理平台,以及其他传给 JVM 的命令行参数。

在给定的命令行上,添加 -XX:+Printflagsfinal(默认为 false,即“关闭”)就能获得具体运行环境中特定标志的默认值。基于环境对标志进行自动调优的过程称为自动优化(Ergonomics)

Client 和 Server 类虚拟机

Java 的自动优化前提是机器被分为“Client”和“Server”。这两个术语直接与特定平台上的默认 JVM 编译器相关,它们也设定了默认的调优标志。例如,机器类别决定了平台默认的垃圾收集器。

Windows 上运行的任何 32 位 JVM(无论机器上 CPU 的个数是多少),以及单 CPU 机器(不论是什么操作系统)上运行的任何 32 位 JVM,都是 Client 类机器。所有其他机器(包括所有 64 位 JVM)都被认为是 Server 类。

编写高性能算法代码

归根结底,应用的性能取决于它的代码如何编写。例如,如果程序循环遍历数组中的所有元素,JVM 就可以优化数组的边界检查,使循环更快,展开循环能提供额外的加速。但如果循环是为了找到特定元素,那目前还没有什么优化的办法,使得遍历数组和采用 HashMap 的版本一样快。

需要更高性能时,算法是否优秀就是重中之重了。

编写更少的代码

有些人写代码是为钱,有些是为乐趣,还有些人将代码回馈社区,但不管怎样,大家都是码农(或者在写程序的团队里工作)。很难想象,我们对项目的贡献是少写代码,因为仍然有肤浅的管理者通过所写的代码量来评估开发人员的绩效。。。

然而同样是正确的程序,小程序运行起来要比大程序快。对所有的计算机程序来说都是如此,Java 程序自然也不例外。要编译的代码越多,等待程序启动所耗费的时间就越长;要创建和销毁的对象越多,垃圾收集的工作量就越大;要分配和持有的对象越多,GC 的周期就越长;要从磁盘装载进 JVM 的类越多,程序启动所花费的时间就越长;要执行的代码越多,机器硬件缓存的效率就越低;而执行的代码越多,花费的时间就越长。

与直觉相反(和令人沮丧)的是,所有应用的性能都会随着时间,即应用新版本的发布而降低。但由于硬件的改善使得新程序的运行速度可以被接受,所以通常都不会有人注意到性能上的差异。

想象一下,在20多年前曾经运行 Windows 95 的机器上运行 Windows 10,会是什么样子?随着新特性的添加和新要求的采纳,程序会越来越大,越来越慢。

这被总结为“积少成多”原则。开发人员总争辩说,只是增加了很小的功能,压根就不会有什么时间损耗(特别是不使用该功能的时候)。接着项目中的其他开发人员也同样拍着胸脯保证,结果却发现性能突然下降了好几个百分点。下次发布的时候又重复出现这样的情景,而此时程序性能已经下降了 10%,反复几次这样的过程之后,性能测试就会检测到资源瓶颈——内存使用达到临界点、代码缓存溢出等情况。对于这些情形,常规的性能测试可以捕获发生状况的原因,性能调优小组也可以修正主要的性能衰减。但随着时间的推移,小衰减积少成多,会越来越难以修复。

当然这里并不是在鼓吹永远不要为产品增加新特性或者新代码,很显然增强程序是有利可图的。但你得小心权衡,尽可能提高效能。

过早优化

“过早优化”一词公认是由高德纳发明的,开发人员常常据此宣称:只有在运行时才能知道代码的性能有多要紧。但你可能从来没注意到,完整的原话是“我们不应该把大量时间都耗费在那些小的性能改进上;过早考虑优化是所有噩梦的根源”。

这句名言的重点是,最终你应该编写清晰、直接、易读和易理解的代码。这里的“优化”应该理解为虽然算法和设计改变了复杂程序的结构,但是提供了更好的性能。那些真正的优化最好留到以后,等到性能分析表明这些措施有巨大收益的时候才进行。

而这里所指的过早优化,并不包括避免那些已经知道对性能不好的代码结构。每行代码,如果有两种简单、直接的编程方式,那就应该选择性能更好的那种。

思考以下代码:

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()});
}

除非启用了日志功能,否则就可以在避免字符串连接(消息体中有格式化字符,不会提高性能,但使代码更清晰)的同时,避免方法调用或者对象分配。

这样写出来的代码仍然清晰易读,与原来的代码相比,没有太多额外工作。好吧,我们还是需要多敲几下键盘,多加一行逻辑。不过这仍然不属于应该避免的过早优化,它是好码农所熟悉的选择。在你思考如何写代码的时候,请不要生搬硬套前辈们的教条。

数据库很可能就是瓶颈

如果你开发的是独立运行不使用外部资源的 Java 应用,性能就(几乎)只与应用本身相关。一旦添加了外部资源(例如数据库),那这两者的性能就都很重要了。在分布式环境中,比如 Java EE 应用服务器、负载均衡器、数据库和后台企业信息系统,Java 应用服务器的性能问题可能只是其中很小的部分。

这里并不关注整体系统的性能。对于整体系统,我们需要采取结构化方法针对系统的所有方面分析性能。CPU 使用率、I/O 延迟、系统整体的吞吐量都必须测量和分析。只有到那时,我们才能判定到底是哪个组件导致了性能瓶颈。关于这个主题有大量优秀的资源,相关的方法和工具也不只针对 Java。假定你已经完成了分析,并且判断出是运行环境中 Java 组件的性能需要改善。(后面会单独对操作系统,网络,数据库等各部分的优化)

如果数据库是瓶颈(如果的确是的话而不是自身写的慢查询,连接池问题),那么无论怎么优化访问数据库的 Java 应用,都无助于整体性能;实际上可能适得其反。作为一般性原则,系统负载增加越大,系统性能就会越糟糕。如果更改了 Java 应用使得它更有效,这只会增加已经过载的数据库的负载,整体性能实际反而会下降。导致的风险是,可能会得出错误结论,即认为不应该改进 JVM。

增加系统某个组件的负载从而导致整个系统性能变慢,这项原则不仅限于数据库。CPU 密集型的应用服务器增加负载,或者越来越多线程试图获取已经有线程等待的锁,还有许多其他场景,也都适用这项原则。

几个基本原则

如果所有的性能问题同等重要,从而“积少成多”地改进性能,那是多么吸引人。但常见的用例场景才是真正应该关注的重点。

  1. 借助性能分析来优化代码,重点关注性能分析中最耗时的操作。然而请注意,这并不意味着只看性能分析中的叶子方法

  2. 利用奥卡姆剃刀原则(“如无必要,勿增实体”,即“简单有效原理”)诊断性能问题。
    性能问题最可能的原因应该是最容易解释的:新代码比机器配置更可能引入性能问题,而机器配置比 JVM 或者操作系统的 bug 更容易引入性能问题。隐藏的 bug 确实存在,但不应该把最可能引起性能问题的原因首先归咎于它,而只在测试用例通过某种方式触发了隐藏的 bug 时才关注。但不应该一上来就跳到这种不太可能的场景。

  3. 为应用中最常用的操作编写简单算法。以估算数学公式的程序为例,用户可以决定他所期望的最大容许误差为 10% 或 1%。如果 10% 的误差适合多数用户,那么优化代码就意味着即便误差范围缩小为 1%,但是速度变慢了。

小结

Java引入了大量新特性和工具,使得 Java 应用的性能更容易发挥到极致。

不过请记住,许多情况下,JVM 只占整体性能的一小部分。你需要对 Java 所在的环境进行整体系统调优,数据库和其他后台运行系统性能的重要性不亚于 JVM。不过整体的性能分析不是这部分的关注重点,我们假设已经做过详细的调查,确定环境中的 Java 组件是系统的重要瓶颈。

此外,JVM 与系统其他部分的交互对性能的影响也同样重要,无论是直接交互(例如以最佳方式使用JDBC),还是间接交互(例如优化应用所使用的本地内存,这类应用与大型系统的各种组件共享机器)。

更新时间:2020-03-07 09:58:02

本文由 寻非 创作,如果您觉得本文不错,请随意赞赏
采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
原文链接:https://www.zhouning.group/archives/java性能调优01基础
最后更新:2020-03-07 09:58:02

评论

Your browser is out of date!

Update your browser to view this website correctly. Update my browser now

×