Java性能权威指南笔记
来自Dennis的知识库
2016年6月4日 (六) 08:25Dennis zhuang(讨论 | 贡献)的版本
目录 |
全面的性能优化
- 编写更好的算法
- 编写更少的代码
- 过早优化,大的架构或者算法调整不必过早,但是细节性的代码改进应当时刻注意。
- 其他系统可能是瓶颈。
- 常见的优化:借助性能分析来优化代码,关注性能分析中最耗时的部分;利用奥卡姆剃刀原则来诊断性能问题,最可能也最容易解释的:新代码比机器配置更可能引入性能问题,机器配置比JVM或者操作系统的 Bug 更容易引入性能问题,隐藏的Bug 可能存在,但是不应该把最可能引起性能问题的原因首先归咎于它,而只有在测试用例通过某种方式触发了隐藏的 Bug 时才关注,但是不应该一上来就跳到这种不太可能出现的场景;为应用中最常用的操作编写简单的算法。
性能测试方法
- 原则1: 测试真实的应用,编写微基准、介基准和宏基准测试,来分别测试微小代码单元、需要复杂代码调用某方面的功能、以及整体应用。一个好的测试,应该是必须使用测试的结果(防止被优化忽略),不要包括无关的操作(例如随机数据生成,应该是提前准备好),并且必须输入合理的参数。宏基准测试要考虑到多个 JVM 系统在一台机器上的影响,他跟单 JVM 系统的结果是不同的,其实这里还应该考虑虚拟化的影响。
- 原则2: 理解批处理流逝时间、吞吐量和响应时间。批处理流逝时间要考虑程序的热身,吞吐量就是衡量 TPS、QPS,而响应时间有两个指标——平均响应时间和百分位请求数(比如 90% 请求的响应时间)。
- 原则3: 用统计方法应对性能的变化,统计学上的 t 检验,提供性能变化本身的置信度。
- 原则4: 尽早频繁测试,自动化一切、测试一切(收集所能想到的所有数据)、在真实系统上运行测试。
一些工具:
- 负载生成工具:http://faban.org/
- t 检验: https://en.wikipedia.org/wiki/Student%27s_t-test 以及 http://commons.apache.org/proper/commons-math/apidocs/org/apache/commons/math3/stat/inference/TTest.html
系统工具箱
- CPU ,我们的目标是尽可能使用 cpu, CPU 空闲可能因为同步、等待第三方或者确实无所事事。
- vmstat 查看 CPU
- iostat 查看磁盘,注意请求数目,以及请求大小,这两个值才能确定磁盘的利用率是否合理。 iostat 有 util 列。命令详解参考 http://blog.csdn.net/lhf_tiger/article/details/8926232
- nicstat 查看网络使用率。
java 工具箱
- jcmd 获取 vm 信息
jcmd process_id ( VM.uptime | VM.system_properties | VM.version | VM.command_line | VM.flags -all)
- jinfo,比较奇怪,在我的机器上用不了。 info 可以对 manageable 的 flag 做动态更改,特别是 GC 日志相关。
- 打印特定平台的 flag
java -XX:+PrintFlagsFinal -version
- jstack 用于获取线程堆栈。
- jconsole, jmap, jstat 可以用于观察内存和 GC 等。
- dump 后的快照查看可以用 jvisualvm 和 jhat,更推荐 Eclipse Memory Analyzer Tool http://www.eclipse.org/mat/
- 性能探查工具: jvisualvm, jpofiler etc
- java 任务控制 java mission control,oracle 并购 BEA 后从 jrocket 拿过来的,但是貌似是不允许商用的,需要购买 license。最关键的特性是飞行记录 JFR,但是需要商业许可 UnlockCommercialFeatures
JIT 编译调优
- 选择 client 或者 server,或者分层编译,启动时间选择 client,批处理选择分层编译,长时间运行的选择 server。分层编译选项 -XX:+TieredCompilation,分层编译隐含了 -server 选项。
- 64 位机器上, java8 默认开启了 server 编译,同时也是分层编译。隐藏的逻辑是 32 位机器更看重启动时间,而 64 位更看重长期运行的性能。
- 中级调优
- 调整代码缓存,如果在日志(标准错误或者输出)中看到 CodeCache is full的信息,表示 JIT 代码缓存满了,可以通过 -XX:ReservedCodeCacheSize 来设置代码缓存大小,分层编译容易达到代码缓存上限,特别是在 JDK7 上,因此应该监控(jconsole memory)。
- 编译阈值 :方法调用计数和循环回边计数器,通过 -XX:CompileThreshold 选项控制,默认是 1500 (client)或者 10000 (server),通常不建议修改,做微基准测试也许可以考虑修改。
- 监控编译过程: -XX:+PrintCompilation,将打印编译日志:
timestamp compilation_id attribute (tiered_level) method_name size deopt 完成时间 内部任务id(可能乱序) 编译状态 分层层级 方法 大小 逆优化
其中编译状态包括
% OSR,栈上替换。 s 方法是同步的 ! 方法有异常处理器 b 阻塞模式发生的编译 n 为封装本地方法发生的编译
逆优化包括 made not entrant 或者 made zombie。
使用 jstat -compiler <pid> 来监控编译状况。或者 jstat -printcompilation pid interval-ms 来监控最近被编译的方法。
- 高级编译调优
- 设置编译线程数,通过 -XX:CICompilerCount=N ,默认这个值会根据 CPU 来计算,多个 JVM 运行的时候应该适当减少。编译队列默认是异步执行,可以通过 -XX:+BackgroundCompilation 来修改(默认 true)。编译队列并不是严格按照先进先出顺序来编译的,热点方法可能被优先编译。
- 方法内联默认开启,不建议关闭,关闭可以通过 -XX:-Inline 关闭,方法是否内联取决于它有多热以及它的大小。方法如果很热,但是只有在它的大小小于 325 字节的时才会内联( -XX:MaxFreqInlineSize=N 修改),否则,只有方法很小时,即小于 35 字节(-XX:MaxInlineSize=N 设置)时才会内联。几乎用不着调节内联参数。
- 逃逸分析, -XX:+DoEscapeAnalysis,默认为 true,JVM会做一些相对激进和复杂的优化,逃逸分析可能会给不正确的同步代码引入 bug。
- 逆优化
- 先前的优化不再有效(例如所涉及的对象类型发生了变化),就会发生代码逆优化,回到之前编译的版本。
- 逆优化会对性能产生一些小而短暂的影响,不过新编译的代码会尽快地再次热身。
- 分层编译的时候,如果代码之前由 client 编译器编译而现在由 server 编译器优化,也会发生逆优化。
- 分层编译的层级,5种执行级别
0 : 解释 1: 简单 c1 编译代码 2: 受限的 c1 编译代码 3: 完全 c1 编译代码 4: c2 编译代码
多数方法第一次编译级别是 3,如果足够频繁,可能编译成级别 4.不建议人为修改级别。
- 无论有没有 final 关键字,都不会影响性能。关于 final https://www.zhihu.com/question/21762917 https://www.zhihu.com/question/28730233
- 不用担心 getter/setter,因为他们很小,很容易被内联。
- 代码越简单,优化越多。