`
vipshichg
  • 浏览: 266756 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

解读Java性能方面的9个误区

    博客分类:
  • java
阅读更多

Java性能问题被冠以某种黑暗魔法的称谓。一部分是因为其平台的复杂性,在很多情况下,无法定位其性能问题根源。然而,在以前对于Java性能的技巧,有一种趋向:认为其由人们的智慧,经验构成,而不是应用统计和实证推理。在这篇文章中,我希望去验证一些最荒谬的技术神话。

1. Java运行慢

在所有最过时的Java性能谬论当中,这可能是最明显的言论。

是的,在90年代和20年代初期,Java确实有点慢。

然而,在那之后,我们有超过10年的时间来改进虚拟机和JIT技术,现在Java整个体系的性能已经快的令人惊讶。

在6个单独的web性能测试基准中,Java框架占据了24个当中的22个前四的位置。

JVM性能分析组件的使用不仅优化了通用的代码路径,而且在优化那些严重领域也很有成效。JIT编译代码的速度在大多数情况下跟C++一样快了。

尽管这样,关于Java运行慢的言论还是存在,估计是由于历史原因造成的偏见,这个偏见来自当时那些使用Java早期版本的人们。

我们建议,在匆忙下结论之前先保留意见和评估一下最新的性能结果。

2. 单行java代码意味着任何事都是孤立的

考虑以下一小段代码:

MyObject obj = new MyObject();

对一个Java开发者而言,很明显能看出这行代码需要分配一个对象和运行一个对应的构造方法。

从这来看,我们可以开始推出性能边界。我们知道有一些精确数量的工作必须继续,因此基于我们的推测,我们可以计算出性能影响。

这儿有个认知偏见,那就是根据以往的经验,任何工作都需要被做。

实际上,javac和JIT编译器都可以优化无效代码。就拿JIT编译器来说,代码甚至可以基于数据分析而被优化掉,在这种情况下,该行代码将不会被运行,因此它就不会有什么性能方面的影响。

而且,在一些Java虚拟机(JVM)中,例如JRockit中,即使代码路径没有完全失效,JIT编译器为了避免分配对象甚至可以执行分解对象操作。

这段文字在这里的意义就是当处理Java性能方面的问题时,上下文很重要。而且过早的优化可能会产生意料之外的结果。所以为了获得最好的结果,不要试图过早的优化。与其不断构建你的代码不如用性能调整技术去定位并且改正代码性能的潜在危险区。

3. 一个微基准测试意味着你认为它是什么

正如以上看到的,推理一小段程序比分析应用程序的整体性能更不准确。尽管如此,开发者还是喜欢写为基准测试。有些人似乎从摆弄平台的某些低层次方面获取无穷无尽的内心快感。

理查德·费曼曾说:“第一个原则是,不要欺骗自己,而且自己是最容易被骗的人”。没有比编写Java为基准测试更切合这个的例子了。

写好微基准测试是极其困难的。Java平台很复杂,而且很多微基准测试只对测量瞬态效应或平台的其他意外方面有效。

例如,一个想当然的微基准测试频繁地在测量子系统时间或垃圾回收,而不是在试图捕捉效果时结束。

只有当开发者和团队有真正基准需求的时候才需要写微基准测试。这些基准测试应该打包到项目(包括源代码)随项目一起发布,并且这些基准测试应该是可重现的并可提供给他人审阅和进一步的审查。

Java平台的许多优化结果都指的是只运行单一基准测试用例时所得到的统计结果。一个单独的基准测试必须要多次运行才能得到一个比较趋近于真实答案的结果。

如果你认为到了必须要写微基准测试的时候,首先请读一下Georges, Buytaert, Eeckhout所著的”Statistically Rigorous Java Performance Evaluation”。如果没有统计学的知识,你会很容易误入歧途的。

网上有很多好的工具和社区来帮助你进行基准测试,例如Google的Caliper。如果你不得不写基准测试时,不要埋头苦干,你需要从他人那里汲取意见和经验。

4.算法慢是性能问题的最普遍原因

在程序员(和普通大众)中普遍存在一个错误观点就是他们总是理所当然地认为自己所负责的那部分系统才是最重要的。

就Java性能这个问题来说,Java开发者认为算法的质量是性能问题的主要原因。开发者会考虑如何编码,因此他们本性上就会潜意识地去考虑算法。

实际上,当处理现实中的性能问题时,算法设计占用了解决基本问题不到10%的时间。

相反,相对于算法,垃圾回收,数据库访问和配置错误会更可能造成程序缓慢。

大多数应用程序处理相对少量的数据,因此即使主算法有缺陷也不会导致严重的性能问题。因此,我们得承认算法对于性能问题来说是次要的;因为算法带来的低效相对于其他部分造成的影响来说是相对较小的,大多的性能问题来自于应用程序栈的其他部分。

因此我们的最佳建议就是依靠经验和产品数据来找到引起性能问题的真正原因。要动手采集数据而不是凭空猜测。

缓存能解决一切问题

“关于计算机科学的每一个问题都可以通过附加另外一个层面间接的方式被解决”

这句程序员的格言,来至于David Wheeler(幸亏有因特网,至少有另外两位计算机科学家),是惊人的相似,特别是在web开发者中。

通常出现这种谬论是因为当面对一个现有的,理解不够透彻的架构时出现的分析瘫痪。

与其处理一个令人生畏的现存系统,开发者经常会选择躲避它通过添加一个缓存并且抱着最大的希望。当然,这个方法仅仅使整个架构变的更复杂,并且对试图理解产品架构现状的下一位开发者而言是一件很糟糕的事情。

夸大的说,不规则架构每次被写入一行和一个子系统。然而,在许多情况下,更简单的重构架构会有更好的性能,而且它们几乎也更易于被理解。

因此当你评估是不是需要缓存时,计划去收集基本用法统计(缺失率,命中率等)去证明实际上缓存层是个附加值。

6. 所有应用都要考虑到STW

(译注:“stop-the-world” 机制简称STW,即,在执行垃圾收集算法时,Java应用程序的其他所有除了垃圾收集帮助器线程之外的线程都被挂起)

Java平台的一个存在事实是,所有应用线程必须周期性的停止以便让垃圾搜集器GC运行。这有时被夸大为严重的弱点,即使是在缺少真实证据的情况下。

实证研究已经说明,人类通常无法察觉到频率超过每200毫秒一次的数字数据的变化(例如价格变动)。

因此对以人类作为首要用户的应用,一条有用的经验就是200毫秒或低于200毫秒的 Stop-The-World (STW)停顿通常无需考虑。有些应用(例如视频流)需要比这个更低的GC波动,但是很多GUI应用不是的。

有少数应用(比如低延迟交易,或者机械控制系统)对200毫秒停顿是不可接受的。除非你的应用属于那个少数,否则你的用户察觉到任何由垃圾回收带来的影响是不太可能的。

值得注意的是,在具有比物理内核更多应用线程的系统中,操作系统任务计划将会干涉对CPU的时间分片访问。Stop-The-World听起来吓人,但实际上,每个应用(无论是不是JVM)都必须处理对稀缺计算资源的内容访问。

如果不做测量,JVM的方法对应用性能带来的额外影响具有何等意义将无法看清。

总体来说,判断停顿的次数实际对应用的影响是通过打开GC日志的办法。分析此日志(或者手工,或者用脚本或工具)来确定停顿的次数。然后再判定这些是否确实给你的应用域带来问题。最重要的是,问自己一个最尖锐的问题:有用户确实抱怨了吗?

7. 手工处理的对象池对很大范围内的应用都是合适的

对Stop-The-World停顿的坏感觉引起一个常见的应对,即在java堆的范围内,为应用程序组发明它们自己的内存管理技术。经常这会归结为实现一个对象池(或甚至是全面引用计数)的方法,并且需要让任何使用了领域对象的代码参与进来。

这种技术几乎总是被误导。它通常具有自身久远以前的根源,那时对象定位代价昂贵,突然的变化被认为是不重要的。但现在的世界已经非常不同。

现代的硬件具有难以想象的定位效率;近来桌面或服务器硬件的内存容量至少达到了2到3GB。这是一个很大的数字;除了专业的使用情形,让实际的应用充满那么大的容量不是很容易。

对象池一般很难正确的实现(特别是有多个线程在工作的时候),并且有几个消极的要求使得把它作为一般场景使用成为一个艰难选择:

  • 所有接触到代码的开发者都必须清楚对象池并正确的处理它
  • “对池清醒”代码与“对池不清醒”代码之间的界限必须要通晓并明文规定
  • 所有这些附加的复杂性必须保持最新,并定期评估
  • 如果这里任何地方失败了,无声损坏的风险(类似C中的指针重用)将被再次引入

总之,只有在GC停顿不能被接受,而且在调试与重构过程中聪明的尝试也不能缩减停顿到可接受水平的时候,对象池才可以使用。

8. 在GC中CMS总是比Parallel Old更好

Oracle JDK默认使用一个并行的,全部停止(stop-the-world STW)垃圾收集器来收集老年代的垃圾。

另外一个选择是并发标记清除(CMS)收集器。这个收集器允许程序线程在大部分的GC周期中仍然继续工作,但它需要付出一些代价和带来一些警告。

允许程序线程和GC线程一起运行不可避免地导致对象表的变异同时又影响到对象的活跃性。这不得不在发生后进行清楚,所以CMS实际上有两个STW阶段(通常非常短)。

这会带来一些后果:

  1. 所有程序线程不得不放进一个安全点并且在每次完全收集时停止两次;
  2. 在收集并发运行地同时,程序吞吐量会减少(通常是50%)
  3. 在JVM从事通过CMS来收集垃圾的总体数据上(包括CPU周期)比并行收集更加高的。

依据程序的情况这些成本或者是值得的或者又不是。但并没有免费的午餐。CMS收集器是一个卓越的工程品,但它不是万能药。

所以在介绍前,CMS是你正确的GC策略,你得首先考虑Parallel Old的STW是不可接收的和不能调和的。最后,(我不能足够地强调),确定所有的指标都从相当的生产系统上得到。

9. 增加堆内存会解决你内存溢出的问题

当一个应用程序崩溃,GC中止运行时,许多应用组会通过增加堆内存来解决问题。在许多情况下,这可以很快解决问题,并争取时间来考虑出一个更深的解决方案。然而,在没有真正理解性能产生的根源时,这种解决策略实际上会使情况更糟糕。

试想一下,一个编码很烂的应用构造了非常多的领域对象(生命周期大概维持2,3秒)。如果内存分配率足够高,垃圾回收就会很快地执行,并把这些领域对象放到年老代。一旦进入了老年代,对象就会立即死去,但直到下一次完全回收才会被垃圾回收器回收。

如果这个应用增加其堆内存,那么我们能做的是增加空间,为了存放那些相对短期存在,然后消逝的领域对象。这会使得 Stop-The-World 的时间更长,对应用毫无益处。

在改变堆内存和或其他参数之前,理解一下对象的动态分配和生命周期是很有必要的。没做调查就行动,只会使事情更糟。在这里,垃圾回收器的老年分布信息是非常重要的。

总结

当说道Java性能调优时直觉通常会误导人。我们需要经验数据和工具来帮助我们具象化和了解平台的特性。

垃圾收集也许提供了这方面最好的例子。GC子系统对于调优和生产数据指导调整有惊人的潜力,但对于生产程序它是很难去不借助工具来让产生的数据有意义。

运行任何Java进程,默认都应该最少有这些标记:

-verbose:gc(打印GC日志)

-Xloggc:(更全面的GC日志)

-XX:+PringGCDetail(更详细的输出)

-XX:+PrintTenuringDistribution(显示由JVM设定的保有阈值)

然后使用工具来分析日志——手写脚本和一些生成图,或一个可视化工具如(开源的)GCViewer或JClarity Censum。

英文原文:9 Fallacies of Java Performance  

3
3
分享到:
评论

相关推荐

    关于Java在软件开发中的误区分析.pdf

    在认识方面,第一个误区是对使用期限的关注。人们往往忽视了软件的使用时间限制,过分关注功能实现而忽略软件架构的灵活性和可拓展性,这不利于软件工程的完整性。第二个误区是误解了对象关系映射(ORM)和存储过程...

    Java 异常处理的误区和经验总结

    然而,许多开发者在实际操作中常常陷入一些常见的异常处理误区,这不仅可能导致程序的错误难以追踪,还可能影响程序性能。以下是对Java异常处理的一些误区和经验总结。 **误区一:过度使用try-catch块** 有些开发者...

    深度剖析Ruby vs Java误区

    在深入探讨Ruby与Java之间的误区时,我们首先要澄清一个普遍的误解:Ruby只适合小型项目,而Java更适合大型复杂项目。事实上,根据James Halloway的观点,选择哪种语言取决于项目的特点和需求。对于小型项目,Ruby ...

    营养师解读100个营养误区.exe

    营养师解读100个营养误区

    关于Java软件开发中存在的误区分析.pdf

    在 Java 软件开发中,存在许多的误区,例如,软件开发人员和使用人员由于对 Java 软件缺乏深入的了解,从而导致在实际使用过程中不能有效地驾驭,所开发出来的 Java 系统不仅性能上较为缓慢,而且存在着直接死机的...

    解读小型审计部门的九大误区.docx

    解读小型审计部门的九大误区.docx

    Java编程语言程序的认识误区.zip

    这是早期Java的一个常见误解。随着JIT(Just-In-Time)编译器的发展,Java的性能已经显著提高,尤其是在处理大量数据和并发任务时。尽管在某些特定场景下C++可能更快,但Java在许多现代应用中已经能够提供足够的...

    性能测试常见误区.doc

    一般建议系统在性能方面保留约30%的扩展空间,超过一定阈值的性能问题才需解决。 7. 误区七:设定不切实际的性能指标。这往往源于对软件应用需求的误解。与用户充分沟通,确保性能指标的实际性和可行性,避免资源...

    JAVA 多线程的两点误区

    在Java多线程编程中,一个常见的误区是关于如何正确启动线程以及理解线程的状态。根据提供的代码片段,可以看到有一个`ReturnThreadInfo`类继承自`Thread`,并在其构造函数中初始化了一个字符串`str`为`"Hello"`。`...

    浅谈JAVA软件开发的几大误区.pdf

    其次,忽视性能优化是另一个误区。有些开发者认为Java天生慢,但实际上,Java虚拟机(JVM)提供了高效的内存管理和垃圾回收机制。通过合理的内存管理、算法优化以及使用并发处理,可以实现高性能的Java应用。同时,...

    提高Oracle数据库性能的四个误区

    在提高Oracle数据库性能的过程中,我们常常陷入一些误区,这些误区可能导致优化效果适得其反。以下四个误区将详细解析: 1. **共享服务器模式(MTS)** Oracle的默认设置是专用服务器模式,每用户连接对应一个...

    Java基础方面陷阱.

    综上所述,Java基础方面的陷阱涵盖了语法理解、数据类型使用、运算符和流程控制的误用、异常处理的不当、内存管理的误区,以及面向对象设计原则的应用等多个方面。通过深入学习和实践,可以有效地避免这些陷阱,提升...

    Java编程语言程序的认识误区.pdf

    最后,领域驱动设计(DDD)方面存在的认识误区。DDD是一种软件设计方法论,它强调围绕业务领域构建模型,并与软件开发紧密联系。在Java编程语言程序开发中,许多开发人员和专家首先强调分层架构的重要性。然而,在...

    java performance, the definitive guide

    它旨在提供关于Java平台性能方面的深入理解,帮助开发者掌握如何通过调整和优化来提升Java应用程序的性能。 首先,在这本书的标题和描述中提到了“Java平台性能”,这是本书的核心主题。Java平台性能涉及到多个层面...

    关于Java在软件开发中的误区分析.zip

    这是一个普遍的误解,源于早期Java性能与C++等编译型语言相比的差距。然而,随着JVM(Java虚拟机)的持续优化,如JIT(即时编译器)和垃圾回收机制的改进,Java的运行速度已经显著提升。现代的Java应用可以达到非常...

    Java学习时容易陷入的误区

    Java学习时,初学者往往容易陷入一些误区,这些误区可能会阻碍他们的学习进程和技能提升。以下是一些常见的问题和相应的解决建议: 1. **盲目追求高级特性**:有些学习者在接触Java时,过于热衷于尝试最新的特性和...

    Oracle数据库性能调整误区.pdf

    【Oracle数据库性能调整误区】 Oracle数据库作为广泛应用的关系型数据库,其性能优化对于医院信息系统乃至其他领域的关键业务至关重要。然而,在实际操作中,许多数据库管理员(DBA)常常陷入一些性能调整的误区,...

    性能测试新手误区

    我大概从事性能测试一年左右时遇到了这个问题,那时我觉得性能测试的过程没有太多挑战,遇到的每一个系统,仿佛都可以用同样的流程完成。半天时间填写测试方案,一天时间来准备测试环境,一天时间准备测试脚本,一到...

    Java自学编程误区.pdf

    ### Java自学编程误区知识点解析 #### 一、编程语言选择误区 - **纠结于语言的选择**:初学者往往会花费大量时间在选择编程语言上,比如是否应该选择PHP、Java、Python等,而不是专注于掌握一门语言的基础知识。 - ...

Global site tag (gtag.js) - Google Analytics