Java程序性能优化

来源:互联网 发布:linux nginx 全局变量 编辑:程序博客网 时间:2024/06/11 05:26

一、避免在循环条件中使用复杂表达式


在不做编译优化的情况下,在循环中,循环条件会被反复计算,如果不使用复杂表达式,而使循环条件值不变的话,程序将会运行的更快。

例子:
import java.util.Vector;
class CEL {
    void method (Vector vector) {
        for (int i = 0; i <vector.size (); i++)  // Violation
            ; //...
    }
}

更正:
class CEL_fixed {
    void method (Vector vector) {
        int size = vector.size ()
        for (int i = 0; i < size;i++)
            ; //...
    }
}

二、为'Vectors'和 'Hashtables'定义初始大小


JVM为Vector扩充大小的时候需要重新创建一个更大的数组,将原原先数组中的内容复制过来,最后,原先的数组再被回收。可见Vector容量的扩大是一个颇费时间的事。
通常,默认的10个元素大小是不够的。你最好能准确的估计你所需要的最佳大小。

例子:
import java.util.Vector;
public class DIC {
    public void addObjects (Object[] o) {
        // if length > 10, Vectorneeds to expand
        for (int i = 0; i<o.length;i++) {    
            v.add(o);   //capacity before it can add more elements.
        }
    }
    public Vector v = new Vector();  // noinitialCapacity.
}

更正:
自己设定初始大小。
    public Vector v = new Vector(20);  
    public Hashtable hash = new Hashtable(10);

参考资料:
Dov Bulka, "Java Performance and Scalability Volume 1: Server-SideProgramming
Techniques" Addison Wesley, ISBN: 0-201-70429-3 pp.55 – 57

三、在finally块中关闭Stream


程序中使用到的资源应当被释放,以避免资源泄漏。这最好在finally块中去做。不管程序执行的结果如何,finally块总是会执行的,以确保资源的正确关闭。
         
例子:
import java.io.*;
public class CS {
    public static void main (String args[]) {
        CS cs = new CS ();
        cs.method ();
    }
    public void method () {
        try {
            FileInputStreamfis = new FileInputStream ("CS.java");
            intcount = 0;
            while(fis.read () != -1)
                count++;
            System.out.println(count);
            fis.close();
        } catch (FileNotFoundExceptione1) {
        } catch (IOException e2) {
        }
    }
}
         
更正:
在最后一个catch后添加一个finally块

参考资料:
Peter Haggar: "Practical Java - Programming Language Guide".
Addison Wesley, 2000, pp.77-79

四、使用'System.arraycopy()'代替通过来循环复制数组

'System.arraycopy()' 要比通过循环来复制数组快的多。

例子:
public class IRB
{
    void method () {
        int[] array1 = new int [100];
        for (int i = 0; i <array1.length; i++) {
            array1[i] = i;
        }
        int[] array2 = new int [100];
        for (int i = 0; i <array2.length; i++) {
            array2[i] = array1[i];                 //Violation
        }
    }
}
         
更正:
public class IRB
{
    void method () {
        int[] array1 = new int [100];
        for (int i = 0; i <array1.length; i++) {
            array1[i] = i;
        }
        int[] array2 = new int [100];
        System.arraycopy(array1, 0,array2, 0, 100);
    }
}
         
参考资料:
http://www.cs.cmu.edu/~jch/java/speed.html

五、让访问实例内变量的getter/setter方法变成”final”


简单的getter/setter方法应该被置成final,这会告诉编译器,这个方法不会被重载,所以,可以变成”inlined”

例子:
class MAF {
    public void setSize (int size) {
         _size = size;
    }
    private int _size;
}

更正:
class DAF_fixed {
    final public void setSize (int size) {
         _size = size;
    }
    private int _size;
}

参考资料:
Warren N. and Bishop P. (1999), "Java in Practice", p. 4-5
Addison-Wesley, ISBN 0-201-36065-9

六、避免不需要的instanceof操作


如果左边的对象的静态类型等于右边的,instanceof表达式返回永远为true。
         
例子:         
public class UISO {
    public UISO () {}
}
class Dog extends UISO {
    void method (Dog dog, UISO u) {
        Dog d = dog;
        if (d instanceof UISO) //always true.
            System.out.println("Dogis a UISO");
        UISO uiso = u;
        if (uiso instanceof Object) //always true.
            System.out.println("uisois an Object");
    }
}
         
更正:         
删掉不需要的instanceof操作。
         
class Dog extends UISO {
    void method () {
        Dog d;
        System.out.println ("Dogis an UISO");
        System.out.println ("UISOis an UISO");
    }
}

七、避免不需要的造型操作


所有的类都是直接或者间接继承自Object。同样,所有的子类也都隐含的“等于”其父类。那么,由子类造型至父类的操作就是不必要的了。
例子:
class UNC {
    String _id = "UNC";
}
class Dog extends UNC {
    void method () {
        Dog dog = new Dog ();
        UNC animal =(UNC)dog;  // not necessary.
        Object o =(Object)dog;         // notnecessary.
    }
}
         
更正:         
class Dog extends UNC {
    void method () {
        Dog dog = new Dog();
        UNC animal = dog;
        Object o = dog;
    }
}
         
参考资料:
Nigel Warren, Philip Bishop: "Java in Practice - Design Styles and Idioms
for Effective Java".  Addison-Wesley, 1999. pp.22-23

八、如果只是查找单个字符的话,用charAt()代替startsWith()


用一个字符作为参数调用startsWith()也会工作的很好,但从性能角度上来看,调用用String API无疑是错误的!
         
例子:
public class PCTS {
    private void method(String s) {
        if(s.startsWith("a")) { // violation
            // ...
        }
    }
}
         
更正         
将'startsWith()' 替换成'charAt()'.
public class PCTS {
    private void method(String s) {
        if ('a' == s.charAt(0)) {
            // ...
        }
    }
}
         
参考资料:
Dov Bulka, "Java Performance and Scalability Volume 1: Server-SideProgramming
Techniques"  Addison Wesley, ISBN: 0-201-70429-3

九、使用移位操作来代替'a / b'操作


"/"是一个很“昂贵”的操作,使用移位操作将会更快更有效。

例子:
public class SDIV {
    public static final int NUM = 16;
    public void calculate(int a) {
        int div = a /4;            //should be replaced with "a >> 2".
        int div2 = a /8;         // should be replacedwith "a >> 3".
        int temp = a / 3;
    }
}

更正:
public class SDIV {
    public static final int NUM = 16;
    public void calculate(int a) {
        int div = a >>2;  
        int div2 = a >> 3;
        int temp = a /3;       // 不能转换成位移操作
    }
}

十、使用移位操作代替'a * b'


同上。
[i]但我个人认为,除非是在一个非常大的循环内,性能非常重要,而且你很清楚你自己在做什么,方可使用这种方法。否则提高性能所带来的程序晚读性的降低将是不合算的。

例子:
public class SMUL {
    public void calculate(int a) {
        int mul = a *4;            //should be replaced with "a << 2".
        int mul2 = 8 *a;         // should be replacedwith "a << 3".
        int temp = a * 3;
    }
}

更正:
package OPT;
public class SMUL {
    public void calculate(int a) {
        int mul = a <<2;  
        int mul2 = a << 3;
        int temp = a * 3;       //不能转换
    }
}

十一、在字符串相加的时候,使用 ' ' 代替" ",如果该字符串只有一个字符的话



例子:
public class STR {
    public void method(String s) {
        String string = s +"d"  // violation.
        string = "abc" +"d"      // violation.
    }
}

更正:
将一个字符的字符串替换成' '
public class STR {
    public void method(String s) {
        String string = s + 'd'
        string = "abc" +'d'   
    }
}

十二、不要在循环中调用synchronized(同步)方法


方法的同步需要消耗相当大的资料,在一个循环中调用它绝对不是一个好主意。

例子:
import java.util.Vector;
public class SYN {
    public synchronized void method (Object o) {
    }
    private void test () {
        for (int i = 0; i <vector.size(); i++) {
            method(vector.elementAt(i));    // violation
        }
    }
    private Vector vector = new Vector (5, 5);
}

更正:
不要在循环体中调用同步方法,如果必须同步的话,推荐以下方式:
import java.util.Vector;
public class SYN {
    public void method (Object o) {
    }
private void test () {
    synchronized{//在一个同步块中执行非同步方法
            for(int i = 0; i < vector.size(); i++) {
                method(vector.elementAt(i));   
            }
        }
    }
    private Vector vector = new Vector (5, 5);
}

十三、将try/catch块移出循环


把try/catch块放入循环体内,会极大的影响性能,如果编译JIT被关闭或者你所使用的是一个不带JIT的JVM,性能会将下降21%之多!
         
例子:         
import java.io.FileInputStream;
public class TRY {
    void method (FileInputStream fis) {
        for (int i = 0; i < size;i++) {
            try{                                      //violation
                _sum+= fis.read();
            } catch(Exception e) {}
        }
    }
    private int _sum;
}
         
更正:         
将try/catch块移出循环         
    void method (FileInputStream fis) {
        try {
            for(int i = 0; i < size; i++) {
                _sum+= fis.read();
            }
        } catch (Exception e) {}
    }
         
参考资料:
Peter Haggar: "Practical Java - Programming Language Guide".
Addison Wesley, 2000, pp.81 – 83

十四、对于boolean值,避免不必要的等式判断


将一个boolean值与一个true比较是一个恒等操作(直接返回该boolean变量的值). 移走对于boolean的不必要操作至少会带来2个好处:
1)代码执行的更快 (生成的字节码少了5个字节);
2)代码也会更加干净 。

例子:
public class UEQ
{
    boolean method (String string) {
        return string.endsWith("a") == true;   // Violation
    }
}

更正:
class UEQ_fixed
{
    boolean method (String string) {
        return string.endsWith("a");
    }
}

十五、对于常量字符串,用'String' 代替 'StringBuffer'


常量字符串并不需要动态改变长度。
例子:
public class USC {
    String method () {
        StringBuffer s = newStringBuffer ("Hello");
        String t = s +"World!";
        return t;
    }
}

更正:
把StringBuffer换成String,如果确定这个String不会再变的话,这将会减少运行开销提高性能。

十六、用'StringTokenizer'代替 'indexOf()' 和'substring()'


字符串的分析在很多应用中都是常见的。使用indexOf()和substring()来分析字符串容易导致StringIndexOutOfBoundsException。而使用StringTokenizer类来分析字符串则会容易一些,效率也会高一些。

例子:
public class UST {
    void parseString(String string) {
        int index = 0;
        while ((index =string.indexOf(".", index)) != -1) {
            System.out.println(string.substring(index, string.length()));
        }
    }
}

参考资料:
Graig Larman, Rhett Guthrie: "Java 2 Performance and Idiom Guide"
Prentice Hall PTR, ISBN: 0-13-014260-3 pp. 282 – 283

十七、使用条件操作符替代"if (cond) return; else return;" 结构


条件操作符更加的简捷
例子:
public class IF {
    public int method(boolean isDone) {
        if (isDone) {
            return0;
        } else {
            return10;
        }
    }
}

更正:
public class IF {
    public int method(boolean isDone) {
        return (isDone ? 0 : 10);
    }
}

十八、使用条件操作符代替"if (cond) a = b; else a = c;" 结构


例子:
public class IFAS {
    void method(boolean isTrue) {
        if (isTrue) {
            _value= 0;
        } else {
            _value= 1;
        }
    }
    private int _value = 0;
}

更正:
public class IFAS {
    void method(boolean isTrue) {
        _value = (isTrue ? 0 :1);       // compact expression.
    }
    private int _value = 0;
}

十九、不要在循环体中实例化变量


在循环体中实例化临时变量将会增加内存消耗

例子:         
import java.util.Vector;
public class LOOP {
    void method (Vector v) {
        for (int i=0;i <v.size();i++) {
            Objecto = new Object();
            o =v.elementAt(i);
        }
    }
}
         
更正:         
在循环体外定义变量,并反复使用         
import java.util.Vector;
public class LOOP {
    void method (Vector v) {
        Object o;
        for (int i=0;i<v.size();i++){
            o =v.elementAt(i);
        }
    }
}

二十、确定StringBuffer的容量


StringBuffer的构造器会创建一个默认大小(通常是16)的字符数组。在使用中,如果超出这个大小,就会重新分配内存,创建一个更大的数组,并将原先的数组复制过来,再丢弃旧的数组。在大多数情况下,你可以在创建StringBuffer的时候指定大小,这样就避免了在容量不够的时候自动增长,以提高性能。

例子:         
public class RSBC {
    void method () {
        StringBuffer buffer = newStringBuffer(); // violation
        buffer.append ("hello");
    }
}
         
更正:         
为StringBuffer提供寝大小。         
public class RSBC {
    void method () {
        StringBuffer buffer = newStringBuffer(MAX);
        buffer.append("hello");
    }
    private final int MAX = 100;
}
         
参考资料:
Dov Bulka, "Java Performance and Scalability Volume 1: Server-SideProgramming
Techniques" Addison Wesley, ISBN: 0-201-70429-3 p.30 – 31

二十一、尽可能的使用栈变量


如果一个变量需要经常访问,那么你就需要考虑这个变量的作用域了。static? local?还是实例变量?访问静态变量和实例变量将会比访问局部变量多耗费2-3个时钟周期。
         
例子:
public class USV {
    void getSum (int[] values) {
        for (int i=0; i <value.length; i++) {
            _sum +=value[i];           //violation.
        }
    }
    void getSum2 (int[] values) {
        for (int i=0; i <value.length; i++) {
            _staticSum+= value[i];
        }
    }
    private int _sum;
    private static int _staticSum;
}     
         
更正:         
如果可能,请使用局部变量作为你经常访问的变量。
你可以按下面的方法来修改getSum()方法:         
void getSum (int[] values) {
    int sum = _sum;  // temporary local variable.
    for (int i=0; i < value.length; i++) {
        sum += value[i];
    }
    _sum = sum;
}
         
参考资料:         
Peter Haggar: "Practical Java - Programming Language Guide".
Addison Wesley, 2000, pp.122 – 125

二十二、不要总是使用取反操作符(!)


取反操作符(!)降低程序的可读性,所以不要总是使用。

例子:
public class DUN {
    boolean method (boolean a, boolean b) {
        if (!a)
            return!a;
        else
            return!b;
    }
}

更正:
如果可能不要使用取反操作符(!)

二十三、与一个接口 进行instanceof操作


基于接口的设计通常是件好事,因为它允许有不同的实现,而又保持灵活。只要可能,对一个对象进行instanceof操作,以判断它是否某一接口要比是否某一个类要快。

例子:
public class INSOF {
    private void method (Object o) {
        if (o instanceof InterfaceBase){ }  // better
        if (o instanceof ClassBase) {}   // worse.
    }
}

class ClassBase {}
interface InterfaceBase {}

 

Java代码优化--尽可能地使用stack()变量(方法内部的局部变量)  

Java程序包含了大量的对象,我们需要了解它们是从哪里被访问的,变量存储于何处对程序的性能有显著的影响--尤其是某些需要被频繁访问的变量。

我们写一个Java类,在其内部方法中定义的局部变量或对象是存储在stack(堆栈)中的,且JVM是一种stack-based的,因此访问和操纵stack中的数据时性能最佳。而Java类的instance变量(这个类的field)和static变量是在constant pool(常量池)中存储和得到访问的。constant pool中保存了所有的符号引用(symbolic references),指向所有型别(types)、值域(field),以及每个型别所使用的所有函数(mothods)。访问instance和static变量时,由于它们存放于constant pool中,所以JVM需要使用更多更耗时的操作码(分析程序生成的bytecode可以看出来)来访问它们。

下面给出一段代码示例,对比后说明怎么尽可能地使用stack变量:

package test;

public classStackVars {

   private int x;    // instance变量
    private static int staticX; //static 变量

   public void stackAccess(int val) {  //访问和操作stack变量j
        int j = 0;
        for (int i = 0; i < val; i++) {
            j += 1;
        }
    }

   public void instanceAccess(int val) {//访问和操作instance变量x
        for (int i = 0; i < val; i++) {
            x += 1;
        }
    }

   public void staticAccess(int val) {//访问和操作static变量staticX
        for (int i = 0; i < val; i++) {
            staticX +=1;
        }
    }
}

经测试,发现运行instanceAccess()和staticAccess()方法的时间大约相同,但却比运行stackAccess()方法慢了2~3倍。因此我们对instanceAccess()、staticAccess()两个方法的代码作以下调整,以得到更快的性能:

 public voidinstanceAccess(int val) {//访问和操作instance变量x
        int tempX=x;
        for (int i = 0; i < val; i++) {
            tempX += 1;
        }
        x=tempX;
    }

   public void staticAccess(int val) {//访问和操作static变量staticX
        int tempStaticX=staticX;
        for (int i = 0; i < val; i++) {
            tempStaticX+= 1;
        }
        staticX=tempStaticX;
    }

改善之处就是将instance和static变量放到循环之外,而用一个stack变量来完成多次局部运算,最后再将这个stack变量的值传回instance或static变量,从而提高了代码的性能。

Sun JDK自带JVM内存使用分析工具HProf

      使用Sun JDK自带JVM内存使用分析工具HProf可以分析JVM堆栈,从而找到占用内存较大的对象。这对应经常出现内存泄漏(OOM)的JAVA系统进行调优很有帮助。

 

HProf使用方法

         在WeblogicServer启动脚本中增加-Xrunhprof:heap=sites,重新启动WeblogicServer。

         使用kill -3 <pid> 或退出WeblogicServer均会生成java.hprof.txt文件,直接打开此文件便可分析JVM的具体运行情况。

 

从java.hprof.txt记录的JVM堆栈结果中可以发现JVM占用内存较大的对象:

          percent         live       alloc'ed  stack class

 rank   self  accum    bytes objs   bytes objs trace name

    1  4.57%  4.57%  2289696 47702 8392224 174838  4251 [C

    2  3.99%  8.57%  2000016    1 2000016    1 12308 [C

    3  3.65% 12.22%  1827552 9622 1852672 10082 43265 [C

    4  2.58% 14.80%  1293912 53913 3929424 163726  4258 java.lang.String

    5  2.05% 16.85%  1028664 7585 3207272 24923  4252 [C

    6  2.03% 18.88%  1015816  159 1015816  159 18694 [B

    7  1.88% 20.77%   942080  230 2740224  669 20416 [B

    8  1.61% 22.37%   805752 2142 2150856 4635 45318 [B

    9  1.60% 23.98%   802880  772  802880  772 24710 weblogic.servlet.utils.URLMatchMap$URLMatchNode

   10  1.60% 25.57%   799400 19985 2781400 69535 45073 cnc.util.Field

   11  1.36% 26.93%   679360 3805  679360 3805   494 [B

   12  1.35% 28.28%   674856 28119 5181240 215885  2985 java.util.HashMap$Entry

……

……

   96  0.19% 63.73%    94776 3112   94776 3112  9146 [C

   97  0.19% 63.92%    93456 3894  123936 5164 23631 java.lang.String

   98  0.19% 64.10%    93224 3884  123968 5165 23644 java.lang.String

   99  0.19% 64.29%    93192 3883  123936 5164 23636 java.lang.String

  100  0.18% 64.47%    89528  238  240264  520 33227 [B

  101  0.17% 64.64%    86448 1901  103472 2255 18715 java.lang.Object

  102  0.17% 64.81%    85464  676   85768  695 18715 [S

  103  0.17% 64.98%    85184 1331   85184 1331 28266 weblogic.ejb20.internal.MethodDescriptor

  104  0.17% 65.15%    84224  752   84224  752 24148 weblogic.servlet.internal.dd.ServletDescriptor

  105  0.17% 65.32%    84136  528 50471136 348769    63 [C

  106  0.16% 65.48%    79968 1428  388976 6946  5503 java.lang.reflect.Method

  107  0.15% 65.63%    77520 1615   77520 1615 27967 weblogic.ejb20.deployer.mbimpl.MethodInfoImpl

  108  0.15% 65.79%    77056 4816  469808 29363 20250 java.lang.Object

  109  0.15% 65.94%    76960   74   76960   74 23695 [B

  110  0.15% 66.09%    76104 3171  215040 8960 45071 cnc.util.FyCol

  111  0.15% 66.24%    74688 3112   74688 3112  9152 java.util.Hashtable$Entry

  112  0.15% 66.39%    74688 3112   74688 3112  9147 java.lang.String

  113  0.15% 66.54%    74280   61  794328  788 45313 [C

  114  0.14% 66.68%    72480 1510  436032 9084 45353 [C

  115  0.14% 66.82%    70720   68   70720   68 25869 [B

  116  0.14% 66.97%    70720   68   70720   68 27448 [B

  117  0.14% 67.11%    70272 1279  142672 2439  5503 [C

  118  0.14% 67.24%    69256   86   69256   86  6584 [S

  119  0.13% 67.38%    67056   66   67056   66 28882 java.lang.Object

  120  0.13% 67.51%    66176  752   66176  752 24170 weblogic.servlet.internal.dd.UIDescriptor

  121  0.13% 67.64%    65688  715   65688  715 25389 [C

  122  0.13% 67.77%    65600    4  885600   54 23939 [C

  123  0.13% 67.90%    65600    4  623200   38 40639 [C

  124  0.13% 68.03%    65576  367   65576  367 51686 [C

  125  0.13% 68.17%    65568    2   65568    2 30610 java.util.HashMap$Entry

  126  0.13% 68.30%    65568    2  130816   16 43271 java.util.HashMap$Entry

  127  0.13% 68.43%    65552    1   65552    1 16617 [B

  128  0.13% 68.56%    64600 1615   64600 1615 27969 java.util.HashMap

  129  0.13% 68.68%    63888 2662   64032 2668 16951 java.util.HashMap$Entry

  130  0.13% 68.81%    63888 2662   64032 2668 16997 java.util.HashMap$Entry

  131  0.13% 68.94%    63888 2662   64032 2668 16996 weblogic.rmi.internal.ClientMethodDescriptor

  132  0.13% 69.07%    63888 2662   99120 4130 16949 java.lang.String

  133  0.13% 69.19%    63888 2662   64032 2668 16976 java.lang.String

  134  0.13% 69.32%    63232  152   63232  152  9655 weblogic.utils.collections.ConcurrentHashMap$Entry

  135  0.13% 69.45%    63232  152   63232  152  9704 weblogic.utils.collections.ConcurrentHashMap$Entry

  136  0.12% 69.57%    62168 3885   82632 5164 23628 [B

  137  0.12% 69.69%    61680  406   66904  468     1 [C

  138  0.12% 69.82%    61504    4  246016   16 47372 [B

  139  0.12% 69.94%    61144   36 91019160 23904    92 [B

  140  0.12% 70.06%    61040  763   61040  763 24194 weblogic.servlet.internal.dd.ServletMappingDescriptor

  141  0.12% 70.18%    60400 1510  363360 9084 45338 java.util.Hashtable

  142  0.12% 70.30%    59544  827   59544  827 24746 weblogic.servlet.internal.ServletRuntimeMBeanImpl

  143  0.12% 70.42%    59248 1058  484984 8664 33236 oracle.jdbc.ttc7.TTCItem

  144  0.12% 70.53%    58152  232  187176  764   748 [C

  145  0.12% 70.65%    57888 2412  161904 6746 16621 java.lang.String

  146  0.11% 70.77%    57400 1435   57400 1435 16855 java.util.HashMap

……

……

 

根据以上的结果,在java.hprof.txt中定位到导致分配大内存的操作如下:

TRACE 63:

         java.lang.StringBuffer.expandCapacity(StringBuffer.java:202)

         java.lang.StringBuffer.append(StringBuffer.java:401)

         java.util.zip.ZipFile.getEntry(ZipFile.java:148)

         java.util.jar.JarFile.getEntry(JarFile.java:198)

TRACE 92:

         java.util.zip.InflaterInputStream.<init>(InflaterInputStream.java:71)

         java.util.zip.ZipFile$1.<init>(ZipFile.java:240)

         java.util.zip.ZipFile.getInputStream(ZipFile.java:212)

         java.util.zip.ZipFile.getInputStream(ZipFile.java:183)

再进一步分析则需要应用开发人员对应用代码做相应的分析定位。

 

注意:使用HProf非常消耗资源,切记不要在生产系统使用。

0 0