美文网首页
Java 性能优化系列之一 [设计与程序优化]

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

作者: AlbenXie | 来源:发表于2017-07-10 10:34 被阅读34次

    性能

    一般来说,性能通过以下几个方面来表现:
    执行速度
    内存分配
    启动时间
    负载承受能力

    定量评测的性能指标:
    执行时间
    CPU时间
    内存分配
    磁盘吞吐量
    网络吞吐量
    响应时间

    调优的层面
    设计调优
    代码调优
    JVM调优
    数据库调优
    操作系统调优

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

    设计优化

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

    2. 代理模式
      代理模式可以用来实现延迟加载,从而提升系统的性能和反应速度。

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

    1. 享元模式
      好处:
    1. 可以节省重复创建对象的开销
    2. 对系统内存的需求减少
    1. 装饰者模式
      实现性能组件与功能组件的完美分离

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

    3. Value Object 模式
      将一个对象的各个属性进行封装,将封装后的对象在网络中传递,从而使系统拥有更好的交互模型,并且减少网络通信数据,从而提高系统性能。

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

    思考:
    单例模式, 工厂模式和享元模式的差异?

    常用优化组件和方法

    1.缓冲I/O 操作很容易成为性能瓶颈,所以,尽可能在 I/O 读写中加入缓冲组件,以提高系统的性能。

    1. 缓存
      缓存可以保存一些来之不易的数据或者计算结果,当需要再次使用这些数据时,可以从缓存中低成本地获取,而不需要再占用宝贵的系统资源。
      Java缓存框架:
      EHCache, OSCache,JBossCache
    2. 对象复用 -- "池"
      最熟悉的线程池和数据库连接池。
      目前应用较为广泛的数据库连接池组件有C3P0 和Proxool.
      4.并行替代串行
    3. 负载均衡
      跨JVM虚拟机,专门用于分布式缓存的框架--Terracotta, 使用Terracotta可以实现Tomcat的Session共享。
    4. 时间换空间
    5. 空间换时间

    程序优化

    1. 字符串优化处理

    [java] [view plain]

    (http://blog.csdn.net/oscar999/article/details/44801677#) [copy](http://blog.csdn.net/oscar999/article/details/44801677#)
    
    String str1 ="abc";  
    String str2 ="abc";  
    String str3 = new String("abc");  
      
    System.out.println(str1==str2);  //true  
    System.out.println(str1==str3);  //false  
    System.out.println(str1==str3.intern()); //true  
    
    1. subString() 方法的内存泄漏
      如果原字串很长,截取的字串却有比较短,使用以下方式返回:

    [java] [view plain]

    (http://blog.csdn.net/oscar999/article/details/44801677#) [copy](http://blog.csdn.net/oscar999/article/details/44801677#)
    
    new String(str1.substring(begin,end));  
    
    1. 字符串分割和查找
      可以使用的方法:
      split()方法 -- 最慢, 写法简单
      StringTokenizer 方法 -- 较快,写法一般
      indexOf()和subString() 方法 - 最快, 写法麻烦

    [java] [view plain]

    (http://blog.csdn.net/oscar999/article/details/44801677#) [copy](http://blog.csdn.net/oscar999/article/details/44801677#)
    
    package performance.program.string;  
      
    import java.util.StringTokenizer;  
      
    public class StringSplit {  
      
        public static void splitMethod(String str) {  
      
            long beginTime = System.currentTimeMillis();  
            for (int i = 0; i < 10000; i++) {  
                str.split(";");  
            }  
      
            long endTime = System.currentTimeMillis();  
            System.out.println("splitMethod use " + (endTime - beginTime));  
        }  
      
        public static void tokenizerMethod(String str) {  
      
            long beginTime = System.currentTimeMillis();  
      
            StringTokenizer st = new StringTokenizer(str, ";");  
            for (int i = 0; i < 10000; i++) {  
                while (st.hasMoreTokens()) {  
                    st.nextToken();  
                }  
                st = new StringTokenizer(str, ";");  
            }  
      
            long endTime = System.currentTimeMillis();  
            System.out.println("tokenizerMethod use " + (endTime - beginTime));  
        }  
      
        public static void IndexMethod(String str) {  
      
            long beginTime = System.currentTimeMillis();  
            String tmp = str;  
            for (int i = 0; i < 10000; i++) {  
                while (true) {  
                    String splitStr = null;  
                    int j = tmp.indexOf(";");  
                    if(j<0) break;  
                        splitStr = tmp.substring(0,j);  
                    tmp = tmp.substring(j+1);  
                }  
                tmp = str;  
            }  
      
            long endTime = System.currentTimeMillis();  
            System.out.println("IndexMethod use " + (endTime - beginTime));  
        }  
      
        /** 
         * @param args 
         */  
        public static void main(String[] args) {  
            // TODO Auto-generated method stub  
            String orgStr = null;  
            StringBuffer sb = new StringBuffer();  
            for (int i = 0; i < 1000; i++) {  
                sb.append(i);  
                sb.append(";");  
            }  
            orgStr = sb.toString();  
            splitMethod(orgStr);  
            tokenizerMethod(orgStr);  
            IndexMethod(orgStr);  
      
        }  
      
    }  
    
    1. 使用ChartAt 代替 startsWith 和 endsWith
      性能要求比较高时,可以使用这条。
    2. StringBuffer 和 StringBuilder
      String result = "String" + "and" + "string"+"append";
      这段看起来性能不高的代码在实际执行时反而会比StringBuilder 来的快。
      原因是Java 编译器本身会做优化。
      但是不能完全依靠编译器的优化, 还是建议显示地使用StringBuffer 或StringBuffer对象来提升系统性能。
      StringBuffer 和 StringBuilder 的差异是:
      StingBuffer 几乎所有的方法都做了同步
      StringBuilder 并没有任何同步。
      所以StringBuilder 的效率好于StringBuffer, 但是在多线程系统中,StringBuilder 无法保证线程安全。
      另外,预先评估StringBuilder 的大小,能提升系统的性能。
    1. 核心数据结构
    1. List 接口
      3种List实现: ArrayList, Vector, 和LinkedList
      ArrayList 和Vector 使用了数组实现, Vector 绝大部分方法都做了线程同步, ArrayList 没有对任何一个方法做线程同步。(ArrayList 和Vector 看上去性能相差无几)
      使用LinkedList 对堆内存和GC的要求更高。
      如果在系统应用中, List对象需要经常在任意位置插入元素,则可以考虑使用LinkedList 替代ArrayList
      对于ArrayList从尾部删除元素时效率很高,从头部删除元素时相当费时,
      而LinkedList 从头尾删除元素时效率相差无几,但是从中间删除元素时性能非常槽糕。
    Paste_Image.png
    1. Map 接口
      实现类有: Hashtable, HashMap, LinkedHashMap和TreeMap
      HashTable 和HashMap 的差异
      HashTable大部分方法做了同步, HashTable 不允许key或者Value 使用null值;(性能上看起来无明显差异)

    2. Set 接口

    3. 优化集合访问代码

    1. 分离循环中被重复调用的代码
      for(int i=0;i<collection.size();i++)
      替换成
      int count = collection.size();
      for(int i=0;i<count;i++)

    5). RandomAccess接口

    1. 使用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 的), 它以块为基本单位处理数据。
    1. 引用类型。
      强引用、软引用、弱引用和虚引用
      强引用:
      a)强引用可以直接访问目标对象
      b) 强引用所指向的对象在任何时候都不会被系统回收
      c)强引用可能导致内存泄漏

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

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

    虚引用
    一个持有虚引用的对象,和没有引用几乎是一样的,随时都可能被垃圾回收器回收。但试图通过虚引用的get()方法去跌强引用时,总是会失败。并且,虚引用必须和引用队列一起使用,它的作用在于跟踪回收过程。

    WeakHashMap类及其实现
    WeakHashMap 是弱引用的一种典型应用,它可以作为简单的缓存表解决方案。

    改善系统性能的技巧

    1. 慎用异常
      try catch 应用与循环体之外

    2. 使用局部变量
      调用方法时传递的参数以及在调用中创建的临时变量都保存在栈中,速度较快。
      其他变量,如静态变量、实例变量等,都在堆中创建,速度较慢。
      局部变量的访问速度远远高于类的成员变量。

    3. 位运算代替乘除法

    4. 替换Switch .
      这一条应该注意的是一个新的思路问题, 使用不同的方式代替switch 语句。
      这里的例子性能测试起来, switch 的性能反而更好一些。

    [java] [view plain]

    (http://blog.csdn.net/oscar999/article/details/44801677#) [copy](http://blog.csdn.net/oscar999/article/details/44801677#)
    
    package com.oscar999.performance.skill;  
      
    public class SwitchReplaceSkill {  
      
        /** 
         * @param args 
         */  
      
        protected void oldMethod() {  
            int re = 0;  
            for (int i = 0; i < 100000000; i++) {  
                re = switchInt(i);  
            }  
        }  
      
        public void newMethod() {  
            int re=0;  
            int[] sw= new int[]{0,3,6,7,8,10,16,18,44};  
            for(int i = 0; i < 100000000; i++) {  
                re = arrayInt(sw,i);  
            }  
        }  
      
        protected int switchInt(int z) {  
            int i = z % 10 + 1;  
            switch (i) {  
            case 1:  
                return 3;  
            case 2:  
                return 6;  
            case 3:  
                return 7;  
            case 4:  
                return 8;  
            case 5:  
                return 10;  
            case 6:  
                return 16;  
            case 7:  
                return 18;  
            case 8:  
                return 44;  
            default:  
                return -1;  
            }  
        }  
        protected int arrayInt(int[] sw,int z)  
        {  
            int i=z%10+1;  
            if(i>8||i<1)  
            {  
                return -1;  
            }else{  
                return sw[i];  
            }  
        }  
      
        public static void main(String[] args) {  
            // TODO Auto-generated method stub  
            SwitchReplaceSkill skill = new SwitchReplaceSkill();  
            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));  
              
        }  
      
    }  
    
    1. 一维数组代替二维数组
      直接看例子:

    [java] [view plain]

    (http://blog.csdn.net/oscar999/article/details/44801677#) [copy](http://blog.csdn.net/oscar999/article/details/44801677#)
    
    package com.oscar999.performance.skill;  
      
    public class OneDime2TwoDime {  
      
        protected void oldMethod(){  
            int[][] array = new int[1000][1000];  
            int re = 0;  
            for(int k=0;k<100;k++)  
                for(int i=0;i<array[0].length;i++)  
                    for(int j=0;j<array[0].length;j++)  
                        array[i][j] = i;  
              
            for(int k=0;k<100;k++)  
                for(int i=0;i<array[0].length;i++)  
                    for(int j=0;j<array[0].length;j++)  
                        re=array[i][j];       
              
        }  
          
        protected void newMethod(){  
            int[] array = new int[1000000];  
            int re = 0;  
            for(int k=0;k<100;k++)  
                for(int i=0;i<array.length;i++)  
                    array[i] = i;  
            for(int k=0;k<100;k++)  
                for(int i=0;i<array.length;i++)  
                    re = array[i];    
        }     
        /** 
         * @param args 
         */  
        public static void main(String[] args) {  
            // TODO Auto-generated method stub  
            OneDime2TwoDime skill = new OneDime2TwoDime();  
            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: 453New Method use: 281
    一维数字用的时间少了很多。

    1. 提取表达式
      有些重复运算的部分可以提取出来。

    [java] [view plain]

    (http://blog.csdn.net/oscar999/article/details/44801677#) [copy](http://blog.csdn.net/oscar999/article/details/44801677#)
    
    package com.oscar999.performance.skill;  
      
    public class ExtraExpression {  
      
        protected void oldMethod(){  
            double d = Math.random();  
            double a = Math.random();  
            double b = Math.random();  
            double e = Math.random();  
            double x,y;  
            for(int i=0;i<10000000;i++)  
            {  
                x = d*a*b/3*4*a;  
                x = e*a*b/3*4*a;  
            }  
        }  
          
        protected void newMethod(){  
            double d = Math.random();  
            double a = Math.random();  
            double b = Math.random();  
            double e = Math.random();  
            double x,y,t;  
            for(int i=0;i<10000000;i++)  
            {  
                t = a*b/3*4*a;  
                x = d*t;  
                x = e*t;  
            }  
        }  
          
        /** 
         * @param args 
         */  
        public static void main(String[] args) {  
            // TODO Auto-generated method stub  
            OneDime2TwoDime skill = new OneDime2TwoDime();  
            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: 109New Method use: 79

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

    [java] [view plain]

    (http://blog.csdn.net/oscar999/article/details/44801677#) [copy](http://blog.csdn.net/oscar999/article/details/44801677#)
    
    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: 78New Method use: 47

    1. 布尔运算代替位运算
      虽然位运算的速度远远高于算术运算,但是在条件判断时,使用位运算替代布尔运算却是非常错误的选择。
      对于"a&&b&&c", 只要有一项返回 false, 整个表达式就返回 false.

    [java] [view plain]

    (http://blog.csdn.net/oscar999/article/details/44801677#) [copy](http://blog.csdn.net/oscar999/article/details/44801677#)
    
    package com.oscar999.performance.skill;  
      
    public class BooleanBit {  
        protected void oldMethod(){  
            boolean a = false;  
            boolean b = true;  
            int d = 0;  
            for(int i=0;i<10000000;i++)  
                if(a&b&"Java_Perform".contains("Java"))  
                    d = 0;  
        }  
          
        protected void newMethod(){  
            boolean a = false;  
            boolean b = true;  
            int d = 0;  
            for(int i=0;i<10000000;i++)  
                if(a&&b&&"Java_Perform".contains("Java"))  
                    d = 0;        
        }  
          
        /** 
         * @param args 
         */  
        public static void main(String[] args) {  
            // TODO Auto-generated method stub  
            BooleanBit skill = new BooleanBit();  
            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: 265New Method use: 32

    1. 使用 arrayCopy()
      Java API 提高了数组复制的高效方法: arrayCopy().
      这个函数是 native 函数, 通常native 函数的性能要优于普通的函数, 仅出于性能考虑, 在软件开发时, 应尽可能调用native 函数。

    [java] [view plain]

    (http://blog.csdn.net/oscar999/article/details/44801677#) [copy](http://blog.csdn.net/oscar999/article/details/44801677#)
    
    package com.oscar999.performance.skill;  
      
    public class ArrayCopy {  
        protected void oldMethod(){  
            int size = 100000;  
            int[] array = new int[size];  
            int[] arraydst = new int[size];  
            for(int i=0;i<array.length;i++)  
            {  
                array[i]=i;  
            }  
            for(int k=0;k<1000;k++)  
                for(int i=0;i<size;i++)  
                    arraydst[i]=array[i];  
        }  
          
        protected void newMethod(){  
            int size = 100000;  
            int[] array = new int[size];  
            int[] arraydst = new int[size];  
            for(int i=0;i<array.length;i++)  
            {  
                array[i]=i;  
            }  
            for(int k=0;k<1000;k++)  
                System.arraycopy(array, 0, arraydst, 0, size);  
        }  
          
        /** 
         * @param args 
         */  
        public static void main(String[] args) {  
            // TODO Auto-generated method stub  
            ArrayCopy skill = new ArrayCopy();  
            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: 156New Method use: 63

    1. 使用 Buffer 进行 I/O 操作
      除NIO 外, 使用Java 进行I/O 操作有两种基本方式
    2. 使用基于InputStream 和 OutoutStream的方式
    3. 使用Writer 和Reader
      无论使用哪种方式进行文件I/O , 如果能合理地使用缓冲, 就能有效提高I/O 的性能


    [java] [view plain]

    (http://blog.csdn.net/oscar999/article/details/44801677#) [copy](http://blog.csdn.net/oscar999/article/details/44801677#)
    
    package com.oscar999.performance.skill;  
      
    import java.io.BufferedOutputStream;  
    import java.io.DataOutputStream;  
    import java.io.FileOutputStream;  
      
    public class BufferStream {  
      
        protected void oldMethod() {  
            int count = 10000;  
            try {  
                DataOutputStream dos = new DataOutputStream(new FileOutputStream(  
                        "testfile.txt"));  
                for (int i = 0; i < count; i++)  
                    dos.writeBytes(String.valueOf(i) + "\r\n");  
                dos.close();  
      
            } catch (Exception e) {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
            }  
        }  
      
        protected void newMethod(){  
            int count = 10000;  
            try{  
                DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("testfile.txt")));      
                for (int i = 0; i < count; i++)  
                    dos.writeBytes(String.valueOf(i) + "\r\n");  
                dos.close();  
            }catch(Exception e)  
            {  
                e.printStackTrace();  
            }  
              
        }  
      
        /** 
         * @param args 
         */  
        public static void main(String[] args) {  
            // TODO Auto-generated method stub  
            BufferStream skill = new BufferStream();  
            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: 516New Method use: 0

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

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

    相关文章

      网友评论

          本文标题:Java 性能优化系列之一 [设计与程序优化]

          本文链接:https://www.haomeiwen.com/subject/cfjyhxtx.html