`

关于Java性能的9个谬论

    博客分类:
  • java
 
阅读更多

Java的性能有某种黑魔法之称。部分原因在于Java平台非常复杂,很多情况下问题难以定位。然而在历史上还有一种趋势,人们靠智慧和经验来研究Java性能,而不是靠应用统计和实证推理。在这篇文章中,我希望拆穿一些最荒谬的技术神话。

 

 

1.Java很慢

 

关于Java的性能有很多谬论,这一条是最过时的,可能也是最为明显的。

 

确实,在上世纪90年代和本世纪初处,Java有时是很慢。

 

 

 

然而从那以后,虚拟机和JIT技术已经有了十多年的改进,Java的整体性能现在已经非常好了。

 

在6个独立的Web性能基准测试中,Java框架在24项测试中有22项位列前四。

 

尽管JVM利用性能剖析仅优化常用的代码路径,但这种优化效果很明显。很多情况下,JIT编译的Java代码和C++一样快,而且这样的情况越来越多了。

 

尽管如此,依然有人认为Java平台很慢,这或许源自体验过Java平台早期版本的人的历史偏见。

 

在下结论之前,我们建议保持客观的态度,并且评估一下最新的性能结果。

 

 

2.可以孤立地看待单行Java代码

 

考虑下面这行短小的代码:

 

MyObject obj = new MyObject();

 

对Java开发者而言,看似很明显,这行代码一定会分配一个对象并调用适当的构造器。

 

我们也许可以据此推出性能边界了。我们认为这行代码一定会导致执行一定量的工作,基于这种推定,就可以尝试计算其性能影响了。

 

其实这种认识是错误的,它让我们先入为主地认为,不管什么工作,在任何情况下都会进行。

 

事实上,javac和JIT编译器都能够将死代码优化掉。就JIT编译器而言,基于性能剖析数据,甚至可以通过预测将代码优化掉。在这样的情况下,这行代码根本不会运行,所以不会影响性能。

 

此外,在某些JVM中——比如JRockit——JIT编译器甚至可以将对象上的操作分解,这样即便代码路径还有效,分配操作也可以避免。

 

这里的寓意是,在处理Java性能问题时,上下文非常重要,过早的优化有可能产生违反直觉的结果。所以最好不好过早优化。相反,应该总是构建代码,并且使用性能调校技术来定位性能热点,然后加以改进。

 

 

3.微基准测试和你想象的一样

 

正如我们上面看到的那样,检查一小段代码不如分析应用的整体性能来的准确。

 

尽管如此,开发者还是喜欢编写微基准测试。似乎对平台底层的某些方面进行修修补补会带来无穷的乐趣。

 

理查德·费曼曾经说过:“不要欺骗自己,你自己正是最容易被欺骗的人。”这句话用来说明编写Java微基准测试这件事是再合适不过了。

 

编写良好的微基准测试极其困难。Java平台非常复杂,而且很多微基准测试只能用于测量瞬时效应,或是Java平台的其他意想不到的方面。

 

例如,如果没有经验,编写的微基准测试往往就是测一下时间或垃圾收集,却没有抓住真正的影响因素。

 

只有那些有实际需求的开发者和开发团队才应该编写微基准测试。这些基准测试应该完全公开(包括源代码),而且是可以复现的,还应接受同行评审及进一步的审查。

 

Java平台的很多优化表明统计运行和单次运行对结果影响很大。要得到真实可靠的答案,应该将一个单独的基准测试运行多次,然后把结果汇总到一起。

 

如果读者感觉有必要编写微基准测试,Georges、Buytaert和Eeckhout等人的论文《利用严格的统计方法评测Java 性能(Statistically Rigorous Java Performance Evaluation)》是个不错的开始。缺乏适当的统计分析,我们很容易被误导。

 

有很多开发好的工具以及围绕这些工具的社区(比如Google的Caliper)。如果确实有必要编写微基准测试,那也不要自己编写,这时需要的是同行的意见和经验。

 

 

4.算法慢是性能问题的最常见原因

 

在开发者之间有一个很常见的认知错误(普通大众也是如此),即认为系统中他们控制的那部分很重要。

 

在探讨Java性能时,这种认知错误也有所体现:Java开发者认为算法的质量是性能问题的主要原因。开发者考虑的是代码,因此他们自然会偏向于考虑自己的算法。

 

实际上在处理一系列现实中的性能问题时,人们发现算法设计是根本问题的几率不足10%。

 

相反,与算法相比,垃圾收集、数据库访问和配置错误导致应用程序缓慢的可能性更大。

 

大部分应用处理的数据量相对较小,因此,即使主要算法效率不高,通常也不会导致严重的性能问题。可以肯定,我们的算法不是最优的;尽管如此,算法带来的性能问题还是算小的,更多性能问题是应用栈的其他部分导致的。

 

因此我们的最佳建议是,使用实际生产数据来揭开性能问题的真正原因。要测量性能数据,而不是凭空猜测!

 

 

5.缓存可以解决所有问题

 

“计算机科学中的所有问题都可以通过引入一个中间层来解决。”

 

David Wheeler的这句程序员格言(在互联网上,这句话至少还被认为是其他两位计算机科学家说的)非常常见,尤其是在Web开发者之中很流行。

 

如果未能透彻理解现有的架构,而且分析也已停顿,往往就是“缓存可以解决所有问题”这种谬论抬头的时候了。

 

在开发者看来,与其处理吓人的现有系统,还不如在前面加一层缓存,将现有系统隐藏起来,以此期待最好的情况。无疑,这种方式只是让整体架构更复杂了,当下一个接手的开发者打算了解系统现状时,情况会更糟糕。

 

规模庞大、设计拙劣的系统往往缺乏整体的设计,是一次一行代码、一个子系统这样写出来的。然而很多情况下,简化并重构架构会带来更好的性能,而且几乎总是更容易让人理解。

 

所以当评估是否真的有必要加入缓存时,应该先计划收集一些基本的使用统计信息(比如命中率和未命中率等),以此证明缓存层带来的真正价值。

 

 

6.所有应用都需要关注Stop-The-World问题

 

Java平台存在一个无法改变的事实:为运行垃圾收集,所有应用线程必须周期性停顿。有时这被当作Java的一个严重缺点,即使没有任何真凭实据。

 

实证研究表明,如果数字数据(如价格波动)变化的频率超过200毫秒一次,人就无法正常感知了。

 

应用主要是给人用的,因此我们有一个有用的经验法则,200毫秒或低于200毫秒的Stop-The-World(STW)通常是没有影响的。有些应用可能有更高的要求(如流媒体),但很多GUI应用是不需要的。

 

少数应用(比如低延迟交易或机械控制系统)无法接受200毫秒的停顿。除非编写的就是这类应用,否则用户基本感觉不到垃圾收集器的影响。

 

值得一提的是,在应用线程数量超过物理核数的任何系统中,操作系统必须控制对CPU的分时访问。Stop-The-World听着可怕,但实际上任何应用(不管是JVM还是其他应用)都要面对稀缺计算资源的争用问题。

 

如果不去测量,JVM对应用性能有何附加影响是不清楚的。

 

总之,请打开GC日志,以此来确定停顿时间是否真的影响了应用。通过分析日志来确定停顿时间,这里既可以手工分析,也可以利用脚本或工具分析。然后再判定它们是否真的给应用于带来了问题。最重要的是,问自己一个关键的问题:确实有用户抱怨吗?

 

 

7.手写对象池适合一大类应用

 

认为Stop-The-World停顿在某种程度上是不好的,应用开发团队的一个常见反应就是在Java堆内实现自己的内存管理技术。这往往会归结为实现一个对象池(甚至是全面的引用计数),而且需要使用了领域对象的任何代码都参与进来。

 

这种技术几乎总是具有误导性的。它基于过去的认知,那时对象分配非常昂贵,而修改对象则廉价的多。现在的情况已经完全不同了。

 

现在的硬件在分配时非常高效;最新的桌面或服务器硬件,内存带宽至少是2到3GB。这是一个很大的数字,除非专门编写的应用,否则要充分利用这么大的带宽还真不容易。

 

一般来说,正确实现对象池非常困难(尤其是有多个线程工作时),而且对象池还带来了一些负面的要求,使这种技术不是一个通用的良好选择:

 

  • 所有接触到对象池代码的开发者必须了解对象池,而且能正确处理
  • 哪些代码知道对象池,哪些代码不知道对象池,其界限必须让大家知道,并且写在文档中
  • 这些额外的复杂性要保持更新,而且定期复审
  • 如果有一条不满足,悄然出现问题(类似于C 中的指针复用)的风险就又回来了

 

总之,只有GC停顿不能接受,而且调校和重构也未能将停顿减小到可以接受的水平时,才能使用对象池。

 

 

8.在垃圾收集中,相对于Parallel Old,CMS总是更好的选择

 

Oracle JDK默认使用一个并行的Stop-The-World收集器来收集老年代,即Parallel Old收集器。

 

Concurrent-Mark-Sweep (CMS)是一个备选方案,在大部分垃圾收集周期,它允许应用线程继续运行,但这是有代价的,而且有一些注意事项。

 

允许应用线程与垃圾收集线程一起运行,不可避免地带来一个问题:应用线程修改了对象图,可能会影响对象的存活性。这种情况必须在事后加以清理,因此CMS实际上有两个STW阶段(通常非常短)。

 

这会带来一些后果:

 

  1. 必须将所有应用线程带到安全点,每次Full GC期间会停顿两次;
  2. 尽管垃圾收集与应用同时执行,但应用的吞吐量会降低(通常是50%);
  3. 在使用CMS进行垃圾收集时,JVM所用的簿记信息(和CPU周期)远高于其他的并行收集器。

 

这些代价是不是物有所值,取决于应用的情况。但是天下没有免费的午餐。CMS收集器在设计上值得称道,但它不是万能的。

 

所以在确定CMS是正确的垃圾收集策略之前,首先应该确认Parallel Old的STW停顿确实不能接受,而且已经无法调校。最后,我重点强调一下,所有指标必须从与生产系统等价的系统中获得。

 

 

9.增加堆的大小可以解决内存问题

 

当应用陷入困境,并且怀疑是GC的问题时,很多应用团队的反应就是增加堆的大小。在某些情况下,这样做可以快速见效,而且为我们留出了时间来考虑更周详的解决方案。然而,如果没有充分理解性能问题的原因,这种策略反而会让事情变得更糟糕。

 

考虑一个编码非常糟糕的应用程序,它正在产生很多领域对象 (它们的生存时间很有代表性,比如说是2-3秒)。如果分配率高到一定程度,垃圾收集会频繁进行,这样领域对象会被提升到老年代。领域对象几乎是一进入年 老代,生存时间就结束了,从而直接死亡,但它们直到下一次Full GC时才会被回收。

 

如果增加了应用的堆大小,我们所做的不过是增加了相对短命的对象进入和死亡所用的空间。这会导致Stop-The-World停顿时间更长,对应用并无益处。

 

在修改堆大小或者调校其他参数之前,理解对象的分配和生存时间的动态是很有必要的。没有测量性能数据就盲目行动,只会使情况更糟糕。在这里,垃圾收集器的老年代分布情况特别重要。

 

 

结论

 

当谈到Java的性能调校时,直觉常常起误导作用。我们需要实验数据和工具来帮助我们将平台的行为可视化并加强理解。

 

垃圾收集就是最好的例子。对于调校或者生成指导调校的数据而言,GC子系统拥有无限的潜力;但是对于产品应用而言,不使用工具很难理解所产生数据的意义。

 

默认情况下,运行任意Java进程(包括开发环境和产品环境),应该至少总是使用如下参数:

 

-verbose:gc(打印GC日志)
-Xloggc:(更全面的GC日志)
-XX:+PrintGCDetails(更详细的输出)
-XX:+PrintTenuringDistribution(显示JVM所使用的将对象提升进入老年代的年龄阈值)

 

然后使用工具来分析日志,这里可以利用手写的脚本,可以用图生成,还可以使用GCViewer(开源的)或jClarity Censum这样的可视化工具。

 

『号外』:JavaOne 2013大会将于7月22–25日在上海世博中心举行,内容涵盖使用Java SE构建现代应用程序、打造针对下一代智能设备的移动和嵌入式Java应用程序、编制基于Java EE的复杂企业解决方案以及在云环境中安全、无缝地构建和部署业务应用程序等,报名或查看详情请点击

 

关于作者

 

Ben Evans是 jClarity(这是一家创业公司,主要设计辅助开发和运维团队的性能工具)的CEO。他是LJC(伦敦Java用户组)的组织者之一,也是JCP执行 委员会的成员之一,JCP执行委员会负责帮助定义Java生态系统中的相关标准。他还是Java Champion和JavaOne Rockstar。他与人合著了《The Well-Grounded Java Developer》一书。此外,他还经常进行公开演讲,探讨Java平台、性能、并发及相关话题。

分享到:
评论

相关推荐

    网络盛传的关于WordPress的九大谬论

    对于一个刚接触Wordpress的新手来说,或多或少都会受到网络上关于Wordpress的一些评论影响。有的甚至被奉为秘籍。其实这其中好多都是谬论。下面笔者针对网络最流行的关于Wordpress的说法做一个简单的评说,顺便纠正...

    迭代测试的谬论与事实

    谬论一:在多数的软件开发项目中,我们带着抛弃这个代码的想法,很快的编写一个原型应用程序,用来降低风险和证明概念的正确性。 事实是,这个方法没有任何问题。但是,由于时间的压力或者是结果的奖励,我们并没有...

    网络盛传的关于Wordpress的九大谬论

    对于一个刚接触Wordpress的新手来说,或多或少都会受到网络上关于Wordpress的一些评论影响。

    一课经济学--经济生活中盛行的谬论

    ### 经济学中的谬论解析 #### 一、引言 《一课经济学》是一部经典之作,由亨利·黑兹利特撰写,旨在揭示并批判那些在经济生活中广泛流传却缺乏逻辑支撑的观点和理论。这些所谓的谬论并未成为主流经济学的一部分,...

    从仿真到硬件加速仿真–可完全重复使用的UVM架构.pdf

    这种方法在各种客户环境中均已获得了显著的成效,性能比纯仿真提高了 50 ~ 5000 倍,并显著地减少了加速用验证平台的开发时间。通过这种新方法,用户可以拥有一整套适用于模块、子系统和系统级验证的解决方案。

    fallacies:逻辑谬论的离线友好速查表

    9. **诉诸权威(Appeal to Authority)**:仅仅因为某个专家或权威人士支持就接受一个观点,而不考虑其证据或逻辑。 10. **事后归因(Post Hoc Fallacy)**:认为A事件之后发生B事件,所以A必然导致B,忽略了其他...

    zookeeper 编程文档

    ZooKeeper的设计中确保了请求顺序,避免了分布式计算中常见的几个谬论,特别是网络是可靠的,这是非常关键的。 文档提到了ZooKeeper的一些核心特点,如分布式、层次化的文件系统、高可用性、容错能力以及高性能。...

    谬论!手机电池长时间充电会爆炸?

    标题中的“谬论!手机电池长时间充电会爆炸?”是一个常见的误解,实际上,现代智能手机普遍采用的锂聚合物电池在正常情况下长时间充电并不会导致爆炸。锂聚合物电池是锂离子电池的一种,其主要区别在于电解质的形式...

    pydis:Python 3中的Redis克隆以证明有关性能的一些谬误

    皮迪斯pydis是一项实验,旨在反驳有关软件和特别是解释型语言的性能和优化方面的一些谬论。 在下面,您将找到一个克隆pydis ,它以约250行惯用的Python代码编写,提供了功能的子集,并提供了。 简而言之, pydis的...

    论文研究 - 骗子骗子:揭秘骗子悖论证据的真相讲师指南

    从这个角度来看,讲真话的人是一个谬论。 悖论是一个像悖论一样的难题,但是是一致的。 有时,接受附加原则会将伪装成悖论。 相反,在某些情况下,撤消或限制原则会使悖论转化为次要悖论。 最后一点提出了一种避免...

    basysPrint驳斥关于CTcP技术的十大悖论.docx

    4. **CTP版材更易操作**:这是一个谬论。CTP版对环境因素如划痕、化学处理、温度和光敏感,不如普通版材稳定、耐用和可靠。 5. **CTcP仅对大型印刷公司经济**:实际上,CTcP设备对中型及以上规模的印刷企业都是经济...

    惠水2019年事业编招聘考试真题及答案解析版.docx

    2. 生态学谬论:第二个问题提到了生态学谬论的概念,这是一种错误的推理方式,通过整体特征推断个体。例子中提到的女生数理化成绩、地区经济与生育率的关系,提示考生理解和辨别这种逻辑谬误。 3. 进化与生物脑部...

    为什么谬论似乎比实际更好-研究论文

    这篇论文解释了一个错误的论点如何通过看起来比实际更好的论点来具有欺骗性。 该解释结合了启发式和论证方案。 启发式是解决问题的快速而节俭的捷径。 它们使用起来很合理,但有时会得出不合理的结论。 在错误的情况...

    微服务环境下的系统治理与容错.pptx

    关于微服务的两个“谬论”,即强隔离和服务是大型系统的唯一选择,实际上需要根据具体情况进行权衡。服务间应有适当的解耦,但完全的隔离可能导致过度复杂;而微服务架构只是解决复杂性的诸多方法之一,适合于大型、...

    新青事业编招聘2019年考试真题及答案解析可复制版.docx

    9. 风筝艺术:第九题中,作者很可能介绍"四艺"的具体技艺内容,即扎、糊、绘、放的详细步骤和技术要点,以便读者更深入理解中国传统风筝的制作工艺。 10. 飞行原理:第十题提到的飞机飞行时间差异,主要是因为中...

Global site tag (gtag.js) - Google Analytics