Java 性能优化系列之1[设计与程序优化]

性能

通常来讲,性能经过如下几个方面来表现:java

  • 执行速度
  • 内存分配
  • 启动时间
  • 负载承受能力

定量评测的性能指标:mysql

  • 执行时间
  • CPU时间
  • 内存分配
  • 磁盘吞吐量
  • 网络吞吐量
  • 响应时间

调优的层面算法

  • 设计调优
  • 代码调优
  • JVM调优
  • 数据库调优
  • 操做系统调优

性能调优必须有明确的目标,不要为了调优而调优,若是当前程序并无明显的性能问题,盲目地进行调整,其风险可能远远大于收益。sql

 

设计优化

1. 单例模式数据库

对于系统的关键组件和被频繁使用的对象,使用单例模式能够有效地改善系统的性能数组

 

2. 代理模式缓存

代理模式能够用来实现延迟加载,从而提高系统的性能和反应速度。安全

 

另外,能够考虑使用动态代理的方式 。 动态代理的方法有: JDK自带的动态代理, CGLIB, Javassist, 或ASM库。网络

 

3. 享元模式数据结构

好处:

1) 能够节省重复建立对象的开销

2) 对系统内存的需求减小

 

4. 装饰者模式

实现性能组件与功能组件的完美分离

 

5. 观察者模式

观察者模式能够用于事件监听、通知发布等场合。能够确保观察者在不使用轮询监控的状况下,及时收到相关的消息和事件。

 

6. Value Object 模式

将一个对象的各个属性进行封装,将封装后的对象在网络中传递,从而使系统拥有更好的交互模型,而且减小网络通讯数据,从而提升系统性能。

 

7. 业务代理模式

将一组由远程方法调用构成的业务流程,封装在一个位于展现层的代理类中。

 

思考:

单例模式, 工厂模式和享元模式的差别?

 

 

 

经常使用优化组件和方法

1.缓冲

I/O 操做很容易成为性能瓶颈,因此,尽量在 I/O 读写中加入缓冲组件,以提升系统的性能。

2. 缓存

缓存能够保存一些来之不易的数据或者计算结果,当须要再次使用这些数据时,能够从缓存中低成本地获取,而不须要再占用宝贵的系统资源。

Java缓存框架:

EHCache, OSCache,JBossCache

3. 对象复用 -- "池"

最熟悉的线程池和数据库链接池。

目前应用较为普遍的数据库链接池组件有C3P0 和Proxool.

4.并行替代串行

5. 负载均衡

跨JVM虚拟机,专门用于分布式缓存的框架--Terracotta, 使用Terracotta能够实现Tomcat的Session共享。

6. 时间换空间

7. 空间换时间

 

 

 

程序优化

1. 字符串优化处理

1)

 

1 [java] view plain copy
2  
3 String str1 ="abc";  
4 String str2 ="abc";  
5 String str3 = new String("abc");  
6   
7 System.out.println(str1==str2);  //true  
8 System.out.println(str1==str3);  //false  
9 System.out.println(str1==str3.intern()); //true  

 

2) subString() 方法的内存泄漏

 

若是原字串很长,截取的字串却有比较短,使用如下方式返回:

 

1 [java] view plain copy
2  
3 new String(str1.substring(begin,end));  

 

3) 字符串分割和查找

 

可使用的方法:

split()方法   -- 最慢, 写法简单

StringTokenizer 方法  -- 较快,写法通常

indexOf()和subString() 方法 - 最快, 写法麻烦

 

 1 [java] view plain copy
 2  
 3 package performance.program.string;  
 4   
 5 import java.util.StringTokenizer;  
 6   
 7 public class StringSplit {  
 8   
 9     public static void splitMethod(String str) {  
10   
11         long beginTime = System.currentTimeMillis();  
12         for (int i = 0; i < 10000; i++) {  
13             str.split(";");  
14         }  
15   
16         long endTime = System.currentTimeMillis();  
17         System.out.println("splitMethod use " + (endTime - beginTime));  
18     }  
19   
20     public static void tokenizerMethod(String str) {  
21   
22         long beginTime = System.currentTimeMillis();  
23   
24         StringTokenizer st = new StringTokenizer(str, ";");  
25         for (int i = 0; i < 10000; i++) {  
26             while (st.hasMoreTokens()) {  
27                 st.nextToken();  
28             }  
29             st = new StringTokenizer(str, ";");  
30         }  
31   
32         long endTime = System.currentTimeMillis();  
33         System.out.println("tokenizerMethod use " + (endTime - beginTime));  
34     }  
35   
36     public static void IndexMethod(String str) {  
37   
38         long beginTime = System.currentTimeMillis();  
39         String tmp = str;  
40         for (int i = 0; i < 10000; i++) {  
41             while (true) {  
42                 String splitStr = null;  
43                 int j = tmp.indexOf(";");  
44                 if(j<0) break;  
45                     splitStr = tmp.substring(0,j);  
46                 tmp = tmp.substring(j+1);  
47             }  
48             tmp = str;  
49         }  
50   
51         long endTime = System.currentTimeMillis();  
52         System.out.println("IndexMethod use " + (endTime - beginTime));  
53     }  
54   
55     /** 
56      * @param args 
57      */  
58     public static void main(String[] args) {  
59         // TODO Auto-generated method stub  
60         String orgStr = null;  
61         StringBuffer sb = new StringBuffer();  
62         for (int i = 0; i < 1000; i++) {  
63             sb.append(i);  
64             sb.append(";");  
65         }  
66         orgStr = sb.toString();  
67         splitMethod(orgStr);  
68         tokenizerMethod(orgStr);  
69         IndexMethod(orgStr);  
70   
71     }  
72   
73 }  

 


4) 使用ChartAt 代替  startsWith 和 endsWith

 

性能要求比较高时,可使用这条。

5) StringBuffer 和 StringBuilder

String result = "String" + "and" + "string"+"append";

这段看起来性能不高的代码在实际执行时反而会比StringBuilder 来的快。

缘由是Java 编译器自己会作优化。

可是不能彻底依靠编译器的优化, 仍是建议显示地使用StringBuffer 或StringBuffer对象来提高系统性能。

StringBuffer 和 StringBuilder 的差别是:

StingBuffer 几乎全部的方法都作了同步

StringBuilder 并无任何同步。

因此StringBuilder 的效率好于StringBuffer, 可是在多线程系统中,StringBuilder 没法保证线程安全。

另外,预先评估StringBuilder 的大小,能提高系统的性能。

 

2. 核心数据结构

1) List 接口

3种List实现: ArrayList,  Vector, 和LinkedList

ArrayList 和Vector 使用了数组实现, Vector 绝大部分方法都作了线程同步, ArrayList 没有对任何一个方法作线程同步。(ArrayList 和Vector 看上去性能相差无几)

使用LinkedList 对堆内存和GC的要求更高。

若是在系统应用中, List对象须要常常在任意位置插入元素,则能够考虑使用LinkedList 替代ArrayList

对于ArrayList从尾部删除元素时效率很高,从头部删除元素时至关费时,

而LinkedList 从头尾删除元素时效率相差无几,可是从中间删除元素时性能很是槽糕。

 

  头部 中间 尾部
ArrayList 6203 3125 16
LinkedList 15 8781 16

 

 

List 遍历操做

 

  ForEach 迭代器 for 循环
ArrayList 63 47 31
LinkedList 63 47 无穷大

 

 

2) Map 接口

实现类有: Hashtable, HashMap, LinkedHashMap和TreeMap

HashTable 和HashMap  的差别

HashTable大部分方法作了同步, HashTable 不容许key或者Value 使用null值;(性能上看起来无明显差别)

 

3) Set 接口

 

4) 优化集合访问代码

1. 分离循环中被重复调用的代码

for(int i=0;i<collection.size();i++)

替换成

int  count = collection.size();

for(int i=0;i<count;i++)

 

5). RandomAccess接口

 

3. 使用NIO 提高性能

在Java 的标准I/O中, 提供了基于流的I/O 实现, 即InputStream 和 OutputStream. 这种基于流的实现是以字节为单位处理数据, 而且很是容易创建各类过滤器。

NIO是 New I/O 的简称, 与旧式的基于流的 I/O 方法相对, 它表示新的一套Java I/O 标准。是在java 1.4 中被归入到JDK中的, 特性有

- 为全部的原始类型提供 Buffer 缓存支持

-  使用Java.nio.charset.Charset 做为字符集编码解码解决方案

- 增长通道对象(Channel), 做为新的原始I/O 抽象

- 支持锁和内存映射文件的文件访问接口

- 提供了基于Selector 的异步网络I/O

与流式的I/O 不一样, NIO 是基于块(Block 的), 它以块为基本单位处理数据。

 

 

 

4. 引用类型。

强引用、软引用、弱引用和虚引用

强引用:

a)强引用能够直接访问目标对象

b) 强引用所指向的对象在任什么时候候都不会被系统回收

c)强引用可能致使内存泄漏

 

软引用:

软引用能够经过java.lang.ref.SoftReference来使用。 一个持有软引用的对象,不会被JVM很快回收, JVM会根据当前的使用情况来判断什么时候回收.当堆使用率临近阈值时,才会去回收软引用的对象。只要有足够的内存,软引用即可能在内存中存活相对长一段时间。所以,软引用能够用于实现对内存敏感的Cache.

 

弱引用:

在系统GC时,只要发现弱引用,无论系统堆空间是否足够,都会将对象进行回收。

 

虚引用

一个持有虚引用的对象,和没有引用几乎是同样的,随时均可能被垃圾回收器回收。但试图经过虚引用的get()方法去跌强引用时,老是会失败。而且,虚引用必须和引用队列一块儿使用,它的做用在于跟踪回收过程。

 

WeakHashMap类及其实现

WeakHashMap 是弱引用的一种典型应用,它能够做为简单的缓存表解决方案。

 

改善系统性能的技巧

1. 慎用异常

try catch 应用与循环体以外

 

2. 使用局部变量

调用方法时传递的参数以及在调用中建立的临时变量都保存在栈中,速度较快。

其余变量,如静态变量、实例变量等,都在堆中建立,速度较慢。

局部变量的访问速度远远高于类的成员变量。

 

3. 位运算代替乘除法

 

4. 替换Switch .

这一条应该注意的是一个新的思路问题, 使用不一样的方式代替switch 语句。

这里的例子性能测试起来, switch 的性能反而更好一些。

 

 1 [java] view plain copy
 2  
 3 package com.oscar999.performance.skill;  
 4   
 5 public class SwitchReplaceSkill {  
 6   
 7     /** 
 8      * @param args 
 9      */  
10   
11     protected void oldMethod() {  
12         int re = 0;  
13         for (int i = 0; i < 100000000; i++) {  
14             re = switchInt(i);  
15         }  
16     }  
17   
18     public void newMethod() {  
19         int re=0;  
20         int[] sw= new int[]{0,3,6,7,8,10,16,18,44};  
21         for(int i = 0; i < 100000000; i++) {  
22             re = arrayInt(sw,i);  
23         }  
24     }  
25   
26     protected int switchInt(int z) {  
27         int i = z % 10 + 1;  
28         switch (i) {  
29         case 1:  
30             return 3;  
31         case 2:  
32             return 6;  
33         case 3:  
34             return 7;  
35         case 4:  
36             return 8;  
37         case 5:  
38             return 10;  
39         case 6:  
40             return 16;  
41         case 7:  
42             return 18;  
43         case 8:  
44             return 44;  
45         default:  
46             return -1;  
47         }  
48     }  
49     protected int arrayInt(int[] sw,int z)  
50     {  
51         int i=z%10+1;  
52         if(i>8||i<1)  
53         {  
54             return -1;  
55         }else{  
56             return sw[i];  
57         }  
58     }  
59   
60     public static void main(String[] args) {  
61         // TODO Auto-generated method stub  
62         SwitchReplaceSkill skill = new SwitchReplaceSkill();  
63         long begTime = System.currentTimeMillis();        
64         skill.oldMethod();  
65         long endTime = System.currentTimeMillis();        
66         System.out.println("Old Method use: "+(endTime-begTime));  
67           
68         begTime = System.currentTimeMillis();     
69         skill.newMethod();  
70         endTime = System.currentTimeMillis();     
71         System.out.println("New Method use: "+(endTime-begTime));  
72           
73     }  
74   
75 }  

 


5. 一维数组代替二维数组

 

直接看例子:

 

 1 [java] view plain copy
 2  
 3 package com.oscar999.performance.skill;  
 4   
 5 public class OneDime2TwoDime {  
 6   
 7     protected void oldMethod(){  
 8         int[][] array = new int[1000][1000];  
 9         int re = 0;  
10         for(int k=0;k<100;k++)  
11             for(int i=0;i<array[0].length;i++)  
12                 for(int j=0;j<array[0].length;j++)  
13                     array[i][j] = i;  
14           
15         for(int k=0;k<100;k++)  
16             for(int i=0;i<array[0].length;i++)  
17                 for(int j=0;j<array[0].length;j++)  
18                     re=array[i][j];       
19           
20     }  
21       
22     protected void newMethod(){  
23         int[] array = new int[1000000];  
24         int re = 0;  
25         for(int k=0;k<100;k++)  
26             for(int i=0;i<array.length;i++)  
27                 array[i] = i;  
28         for(int k=0;k<100;k++)  
29             for(int i=0;i<array.length;i++)  
30                 re = array[i];    
31     }     
32     /** 
33      * @param args 
34      */  
35     public static void main(String[] args) {  
36         // TODO Auto-generated method stub  
37         OneDime2TwoDime skill = new OneDime2TwoDime();  
38         long begTime = System.currentTimeMillis();        
39         skill.oldMethod();  
40         long endTime = System.currentTimeMillis();        
41         System.out.println("Old Method use: "+(endTime-begTime));  
42           
43         begTime = System.currentTimeMillis();     
44         skill.newMethod();  
45         endTime = System.currentTimeMillis();     
46         System.out.println("New Method use: "+(endTime-begTime));  
47     }  
48   
49 }  
50 Old Method use: 453
51 New Method use: 281

 

 

 

一维数字用的时间少了不少。

 

6. 提取表达式

有些重复运算的部分能够提取出来。

 

 1 [java] view plain copy
 2  
 3 package com.oscar999.performance.skill;  
 4   
 5 public class ExtraExpression {  
 6   
 7     protected void oldMethod(){  
 8         double d = Math.random();  
 9         double a = Math.random();  
10         double b = Math.random();  
11         double e = Math.random();  
12         double x,y;  
13         for(int i=0;i<10000000;i++)  
14         {  
15             x = d*a*b/3*4*a;  
16             x = e*a*b/3*4*a;  
17         }  
18     }  
19       
20     protected void newMethod(){  
21         double d = Math.random();  
22         double a = Math.random();  
23         double b = Math.random();  
24         double e = Math.random();  
25         double x,y,t;  
26         for(int i=0;i<10000000;i++)  
27         {  
28             t = a*b/3*4*a;  
29             x = d*t;  
30             x = e*t;  
31         }  
32     }  
33       
34     /** 
35      * @param args 
36      */  
37     public static void main(String[] args) {  
38         // TODO Auto-generated method stub  
39         OneDime2TwoDime skill = new OneDime2TwoDime();  
40         long begTime = System.currentTimeMillis();        
41         skill.oldMethod();  
42         long endTime = System.currentTimeMillis();        
43         System.out.println("Old Method use: "+(endTime-begTime));  
44           
45         begTime = System.currentTimeMillis();     
46         skill.newMethod();  
47         endTime = System.currentTimeMillis();     
48         System.out.println("New Method use: "+(endTime-begTime));  
49     }  
50   
51 }  
52 
53 Old Method use: 109
54 New Method use: 79

 

 

 

7.  展开循环

展开循环是一种在极端状况下使用的优化手段,由于展开循可能会影响代码的可读性和可维护性, 因此取舍之间, 就要根据实际情况来看了。

看例子:

 

[java] view plain copy
 
package com.oscar999.performance.skill;  
  
public class ExpandCycle {  
    protected void oldMethod(){  
        int[] array = new int[9999999];  
        for(int i=0;i<9999999;i++){  
            array[i]=i;  
        }  
    }  
      
    protected void newMethod(){  
        int[] array = new int[9999999];  
        for(int i=0;i<9999999;i+=3){  
            array[i]=i;  
            array[i+1]=i+1;  
            array[i+2]=i+2;  
        }  
    }  
    /** 
     * @param args 
     */  
    public static void main(String[] args) {  
        // TODO Auto-generated method stub  
        ExpandCycle skill = new ExpandCycle();  
        long begTime = System.currentTimeMillis();        
        skill.oldMethod();  
        long endTime = System.currentTimeMillis();        
        System.out.println("Old Method use: "+(endTime-begTime));  
          
        begTime = System.currentTimeMillis();     
        skill.newMethod();  
        endTime = System.currentTimeMillis();     
        System.out.println("New Method use: "+(endTime-begTime));  
    }  
  
}  
Old Method use: 78
New Method use: 47

 

 

 

8. 布尔运算代替位运算

虽然位运算的速度远远高于算术运算,可是在条件判断时,使用位运算替代布尔运算倒是很是错误的选择。

对于"a&&b&&c", 只要有一项返回 false, 整个表达式就返回 false.

 

 1 [java] view plain copy
 2  
 3 package com.oscar999.performance.skill;  
 4   
 5 public class BooleanBit {  
 6     protected void oldMethod(){  
 7         boolean a = false;  
 8         boolean b = true;  
 9         int d = 0;  
10         for(int i=0;i<10000000;i++)  
11             if(a&b&"Java_Perform".contains("Java"))  
12                 d = 0;  
13     }  
14       
15     protected void newMethod(){  
16         boolean a = false;  
17         boolean b = true;  
18         int d = 0;  
19         for(int i=0;i<10000000;i++)  
20             if(a&&b&&"Java_Perform".contains("Java"))  
21                 d = 0;        
22     }  
23       
24     /** 
25      * @param args 
26      */  
27     public static void main(String[] args) {  
28         // TODO Auto-generated method stub  
29         BooleanBit skill = new BooleanBit();  
30         long begTime = System.currentTimeMillis();        
31         skill.oldMethod();  
32         long endTime = System.currentTimeMillis();        
33         System.out.println("Old Method use: "+(endTime-begTime));  
34           
35         begTime = System.currentTimeMillis();     
36         skill.newMethod();  
37         endTime = System.currentTimeMillis();     
38         System.out.println("New Method use: "+(endTime-begTime));  
39     }  
40   
41 }  
42 
43 Old Method use: 265
44 New Method use: 32

 

 

9. 使用 arrayCopy()

Java API 提升了数组复制的高效方法: arrayCopy().

这个函数是 native 函数, 一般native 函数的性能要优于普通的函数, 仅出于性能考虑, 在软件开发时, 应尽量调用native 函数。

 

 1 [java] view plain copy
 2  
 3 package com.oscar999.performance.skill;  
 4   
 5 public class ArrayCopy {  
 6     protected void oldMethod(){  
 7         int size = 100000;  
 8         int[] array = new int[size];  
 9         int[] arraydst = new int[size];  
10         for(int i=0;i<array.length;i++)  
11         {  
12             array[i]=i;  
13         }  
14         for(int k=0;k<1000;k++)  
15             for(int i=0;i<size;i++)  
16                 arraydst[i]=array[i];  
17     }  
18       
19     protected void newMethod(){  
20         int size = 100000;  
21         int[] array = new int[size];  
22         int[] arraydst = new int[size];  
23         for(int i=0;i<array.length;i++)  
24         {  
25             array[i]=i;  
26         }  
27         for(int k=0;k<1000;k++)  
28             System.arraycopy(array, 0, arraydst, 0, size);  
29     }  
30       
31     /** 
32      * @param args 
33      */  
34     public static void main(String[] args) {  
35         // TODO Auto-generated method stub  
36         ArrayCopy skill = new ArrayCopy();  
37         long begTime = System.currentTimeMillis();        
38         skill.oldMethod();  
39         long endTime = System.currentTimeMillis();        
40         System.out.println("Old Method use: "+(endTime-begTime));  
41           
42         begTime = System.currentTimeMillis();     
43         skill.newMethod();  
44         endTime = System.currentTimeMillis();     
45         System.out.println("New Method use: "+(endTime-begTime));  
46     }  
47   
48   
49 }  
50 Old Method use: 156
51 New Method use: 63

 

 

 

10. 使用 Buffer 进行 I/O 操做

除NIO 外, 使用Java 进行I/O 操做有两种基本方式

1. 使用基于InputStream 和 OutoutStream的方式

2. 使用Writer 和Reader

不管使用哪一种方式进行文件I/O , 若是能合理地使用缓冲, 就能有效提升I/O 的性能

 

 1 [java] view plain copy
 2  
 3 package com.oscar999.performance.skill;  
 4   
 5 import java.io.BufferedOutputStream;  
 6 import java.io.DataOutputStream;  
 7 import java.io.FileOutputStream;  
 8   
 9 public class BufferStream {  
10   
11     protected void oldMethod() {  
12         int count = 10000;  
13         try {  
14             DataOutputStream dos = new DataOutputStream(new FileOutputStream(  
15                     "testfile.txt"));  
16             for (int i = 0; i < count; i++)  
17                 dos.writeBytes(String.valueOf(i) + "\r\n");  
18             dos.close();  
19   
20         } catch (Exception e) {  
21             // TODO Auto-generated catch block  
22             e.printStackTrace();  
23         }  
24     }  
25   
26     protected void newMethod(){  
27         int count = 10000;  
28         try{  
29             DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("testfile.txt")));      
30             for (int i = 0; i < count; i++)  
31                 dos.writeBytes(String.valueOf(i) + "\r\n");  
32             dos.close();  
33         }catch(Exception e)  
34         {  
35             e.printStackTrace();  
36         }  
37           
38     }  
39   
40     /** 
41      * @param args 
42      */  
43     public static void main(String[] args) {  
44         // TODO Auto-generated method stub  
45         BufferStream skill = new BufferStream();  
46         long begTime = System.currentTimeMillis();  
47         skill.oldMethod();  
48         long endTime = System.currentTimeMillis();  
49         System.out.println("Old Method use: " + (endTime - begTime));  
50   
51         begTime = System.currentTimeMillis();  
52         skill.newMethod();  
53         endTime = System.currentTimeMillis();  
54         System.out.println("New Method use: " + (endTime - begTime));  
55     }  
56   
57 }  
58 
59 Old Method use: 516
60 New Method use: 0

 

 

 

11. 使用clone() 代替new 

对于重量级对象, 优于对象在构造函数中可能会进行一些复杂且耗时额操做, 所以, 构造函数的执行时间可能会比较长。Object.clone() 方法能够绕过对象构造函数, 快速复制一个对象实例。因为不须要调用对象构造函数, 所以, clone 方法不会受到构造函数性能的影响, 快速生成一个实例。

 

12. 静态方法替代实例方法

使用static 关键字描述的方法为静态方法, 在Java 中, 优于实例方法须要维护一张相似虚函数表的结构,以实现对多态的支持。 与静态方法相比, 实例方法的调用须要更多的资源。 所以,对于一些经常使用的工具类方法,没有对其进行重载的必要,那么将它们声明为static, 即可以加速方法的调用。

相关文章
相关标签/搜索