Java性能小技巧

局部决定整体。一个应用的整体性能取决于每个组件的性能。下面是一些帮助你提高应用性能的Java编程技巧:

编程技巧 原因及策略
避免重复创建对象 为什么:

  • 更少的对象会需要更少的垃圾回收
  • 使用的空间越少,应用的性能越好

怎么做:

  • 重复利用一个对象,而不是在每次需要的时候都去创建一个功能一样的对象
  • (这样做)
  • String s = “No longer silly”;
  • (不要这样)
  • String s = new String(“silly”);
  • 不可变类中既提供构造函数,又提供了静态工厂方法的,优先考虑使用静态工厂方法。
  • 复用那些一旦初始化(使用静态初始化)就不会改变的对象
避免循环引用 为什么:

  • 一组相互引用的对象,如果他们没有被其他对象直接引用的话,它们会变得不可达,这样会导致它们一直都保留在内存里。

怎么做:

  • 你可以使用强引用来表示“父到子“的引用关系,使用弱引用来表示“子到父”的引用关系。
使用==操作符来替代equals(Object)方法 为什么:

  • ==操作符的性能更好
  • 例如,对于字符串比较,equals()方法会去比较字符串对象里的字符。==操作符会比较两个对象的引用,来比较它们是否指向同一个实例。

怎么做:

  • 当且仅当a == b 的时候才会有a.equals(b)
  • 例如,对于重复调用的地方,使用静态工厂方法来返回相同的对象。
清除无用的对象的引用 为什么:

  • 无用的对象引用会导致更多的垃圾回收动作,从而降低性能
  • 无用的对象引用会导致更多的内存占用,从而降低性能

怎么做:

  • 如果一个引用时废弃的话,把它设置为null
  • (这样做)
  • public Object pop() {
        if (size == 0)
            throw new EmptyStackException();
    
        Object result = elements[--size];
        elements[size] = null;  // 清除无用对象的引用
        return result;
    }
  • (不要这样)
  • public Object pop(){
        if (size == 0)
            throw new EmptyStackException();
        return elements[--size];
    }
避免使用finalizer 为什么:

  • 垃圾回收器需要单独记录等待终结的对象
  • 调用finalize方法也有一定的开销
  • Finalizer是不安全的,因为它有可能会复活一个对象,这样会干扰垃圾回收。
避免使用引用对象 为什么:

  • 和finalizer一样,垃圾回收器需要特别处理软引用、弱引用以及幽灵引用。
  • 尽管引用对象在某些方面很有作用,例如,简化cache的实现,但是大量引用对象的存在会使得垃圾回收运行缓慢。
  • 记录一个引用对象的开销远远超过一个普通对象(强引用)的开销
避免使用对象池 为什么:

  • 对象池不仅会使得更多的数据对象保持活动,同时会使得对象的存活时间延长
  • 值得注意的是,大量存活的数据对象的处理是GC的瓶颈,GC被优化成适合于处理许多寿命较短的对象
  • 并且,创建新的对象而不是保持旧的对象存活,会对缓存的局部性有益
  • 不过,在一个包含大量大对象的环境下,例如大的数组,性能或许会因为使用对象池而有所提升。
选择好的算法和数据结构 为什么:

  • 考虑一下通过链表来实现队列的场景
  • 即使你的程序不需要遍历整个链表,但是垃圾回收器还是需要这样做的。
  • 如果元素的封装者没有把元素没有把元素放在内存中邻近的位置,这样会破坏缓存局部性。因而会导致程序长时间的暂停,尤其是对象的指针分散在一个很大的堆区时,垃圾回收器会在标记阶段追随指针的时候频繁遭遇缓存失效。
避免使用System.gc 为什么:

  • Java语言规范里没有保证调用System.gc会做什么事情。如果它规定了的话,或许会超出你的期望,也或许每次调用都做不同的事情。
避免使用太多的线程 为什么:

  • 进程上下文切换的次数会随着要调度的进程的数目相应地增长,这样会对性能有隐性的影响。
  • 例如,Intel A-64处理器上的本地线程上下文的大小大概是几千KB
避免使用竞争锁 为什么:

  • 竞争锁一般都是程序的瓶颈,因为它的出现意味着多个线程想访问同一个资源或者执行同一段代码。
避免不需要的异常 为什么:

  • 异常处理会占用一定的事件,并且会打断程序的正常执行流程。
  • 作者曾经遇到这样一场景,在客户的应用里,一个正常的执行流程每秒会抛出成千上万的NullPointerException。这个错误被纠正后,应用的性能里面有了一个数量级的提升。
避免使用大对象

为什么:

  • 大对象有时候需要直接在堆而不是在线程本地存储区(thread local areas, TLA)进行内存分配。
  • 大对象直接在堆上分配是有坏处的,因为它会更快地产生内存碎片。
  • 在虚拟机(例如JRockit)上分配大对象会降低性能,因为分配内存的时候会使用堆的全局锁。
  • 过度使用大对象会造成频繁的全栈压缩,这样做是具有破坏性的,而且这样会导致导致所有的线程暂停很长一段时间。

 

参考书籍

  1. Oracle JRockit
  2. Effective Java by Joshua Bloch

 

英文原文:xmlandmore,编译:ImportNew - 朱伟杰

译文链接:http://www.importnew.com/1531.html

【如需转载,请在正文中标注并保留原文链接、译文链接和译者等信息,谢谢合作!】

关于作者: 朱伟杰

Java开发工程师,业余翻译

查看朱伟杰的更多文章 >>



相关文章

发表评论

Comment form

(*) 表示必填项

11 条评论

  1. zangxt 说道:

    几个问题:
    1.“重复利用一个对象,而不是在每次需要的时候都去创建一个功能一样的对象”这个和“对象池”思想有点类似吧,这样不和后面的矛盾吗?
    2.“一组相互引用的对象,如果他们没有被其他对象直接引用的话”
    实际jvm实现中又不是用引用技术进行垃圾标记的,这有什么问题?
    3.清除无用的对象的引用
    这里的例子貌似是Effective Java中的,记得这本书和JavaOne上强调的是,这些数据结构中不要忘记类似的清理,方法中的局部变量赋值为null大多数不需要的。

    Thumb up 1 Thumb down 0

    • 朱伟杰 说道:

      1、这里的重复使用是针对于局部的变量,比如一个for循环里使用的变量,而“对象池”更多的是一个全局的概念
      2、即使不是用引用技术进行垃圾标记,但是也需要通过引用来判断一个对象是否需要回收。
      3、这里的局部变量通过return result逸出到外部,所以需要这样处理

      Thumb up 0 Thumb down 0

      • zangxt 说道:

        1.估计要具体问题具体分析吧。而且jvm专门优化了对小对象的分配,用不了多少条指令,在young区的回收也很方便。这方面感觉更多的应该从代码易读性方面考虑。
        2.看原文中”一组相互引用的对象,如果他们没有被其他对象直接引用的话,它们会变得不可达,这样会导致它们一直都保留在内存里。“这明显是错误的吧。如果没有从root指向它们的引用,自然就是垃圾,这个很容易测试出来。
        3.这里return的实际是”成员“,就是我上面留言中提到的情形,也是Effective Java中提到的例子。

        感觉本文不算很好,把一些特殊问题当做一般化概念提出来了,比如”使用==操作符来替代equals(Object)方法“、”清除无用的对象的引用“等适用于特殊条件的操作当做条目提出来的时候,可能会对读者造成误导。

        Thumb up 0 Thumb down 0

        • 朱伟杰 说道:

          1、这个问题的关键不在于示例代码上,而在于“更少的对象会需要更少的垃圾回收”,对于这个示例而言可能没多大的性能影响,但是这个说法应该是对的。
          2、假设有Root->A->Parent->Child,Child->Parent这样的引用,如果A被设置为Null,那么Parent和Child没有被Root引用,但是自身又相互引用,这种情况下如果采用的是引用计数器的方式来判断的话,这两个对象就没法回收了。如果是根据Root引用的话,它们就会被回收。所以,这句话不能绝对的说是错的。
          3、这个地方虽然是“成员”,但是它的引用逸出带外面了,如果调用这个方法的地方一直保持这个引用的话,那么这个“成员”就不会被回收。

          这篇文章虽然有些不合适的地方,但是读者可以透过它去思考垃圾回收背后的一些机制,就如同我们现在在讨论一样。正所谓“仁者见仁智者见智”,这篇文章至少能够提供给读者一些思考的空间,这样的文章才是难能可贵的。

          Thumb up 0 Thumb down 0

          • zangxt 说道:

            2.没记得哪个jvm用到引用计数了,这个我回头再确认一下。好久不用java了。
            3.说的不是一回事。我说的是局部变量比如{ Object obj = new Object();}这种obj一般不需要特殊处理的。 文章中举的那个例子,其实和你的说法也不尽一致,这个可以参考下effective java,里面提到了为什么不清空有问题。外不外带不是关键。

            呵呵,不是否定文章的价值,而是不思考当金科玉律背诵条目的读者会不少。写文章的时候可能需要注意的一个问题是,论点是不是精确限定了边界,例子是不是精确论证论点的。

            Thumb up 0 Thumb down 0

          • 朱伟杰 说道:

            2、引用计数只是我举的一种例子,实际应该没有这样用的。
            3、如果是局部变量的话,这个地方确实是没有必要的。文章里关于边界性确实没有很好的界定。

            您的考虑确实很周到,我翻译的时候完全没考虑到这些问题。非常感谢!

            Thumb up 0 Thumb down 0

  2. steven0lisa 说道:

    elements[size] == null; // 清除无用对象的引用

    这个代码多了一个=

    Thumb up 0 Thumb down 0

  3. pony 说道:

    避免循环引用 这个不对吧? 那你说hibernate里面那么多的循环依赖就不回收了?
    java 垃圾回收不是说相互强引用就不回收了,还必须要被GC roots对象引用才行。

    Thumb up 0 Thumb down 0

    • 朱伟杰 说道:

      您好,对于目前的JVM而言,这个观点是不对的,可以参考zangxt的回复。

      Thumb up 0 Thumb down 0

  4. tifa 说道:

    关于您的说法:用==代替equal ,这一说法有问题吧?!equal本来从Object那里来,不同行子类不同实现,而字符串中的equal是比较字符而不比较引用,但是其他继承Object的子类并不一定跟String同样的实现,但您说:“当且仅当a == b 的时候才会有a.equals(b) ” —>> 那么如果我这儿写:String a = new String(“aa”);String b = new String(“aa”); a == b为false,而a.equals(b)为true,楼主,这说法怎么破?您这么一说,很多初学者信以为真然后都用==代替了equals,然后他们就悲剧了

    Well-loved. Like or Dislike: Thumb up 6 Thumb down 0

跳到底部
返回顶部