天天看点

系统性能优化总结—java代码优化

       做了一段时间的性能优化,感觉不错,接触到了不少基础知识,也锻炼了自己的逻辑思维,写代码的时候考虑的更加全面一些,自己写出来的代码质量也会有所提高,写完一段代码会回头看看是不是有更简单更优的实现方法,还有也会自动会去考虑遵循一些基本的规范,比如嵌套循环不能超过三层、方法和类都要遵循单一职责的原则等等。

       下面就将自己这段时间的工作总结一下,记录的同时也给刚做这方面工作的同志们一些建议,希望能够快速上手。系统性能优化一般分为下面几步走:

1、发现问题

    这一步需要先进行一些性能测试,根据测试结果找出不达标的来进一步分析优化。比如:B/S系统可以在浏览器上安装一些插件(我用的是httpwatch),打开浏览器后进入系统,启动插件的功能,点击查询按钮,该插件就会自动记录查询整个过程的耗时,可以方便你后面的分析,下图为httpwatch某个查询的耗时记录:

系统性能优化总结—java代码优化

    根据性能测试结果要写一份性能测试报告为下一步工作提供依据,写性能测试报告一般有以下几点需要写进去:

         a、测试使用数据是否为现场数据

         b、测试使用的客户端是什么配置

         c、服务器数据库端的配置

         d、选择的测试条件

         e、每个功能测试次数,平均耗时为多少

         f、每次查询返回的数据量

         g、数据库里对应表里的数据总量

         h、将测试记录写成文档,对于不达标的记录高亮显示

2、分析问题

    根据测试报告找出不达标的功能进行单独分析,确定存在问题的具体位置,比如是某个方法还是某个功能段导致整体性能不能达标,一般会先大概分析一下整体的代码,然后从外向里添加日志,打印出各方法的执行时间,这样很快就能确定出问题的具体位置,将分析结果写入文档,为下一步提供依据。

3、提出方案

    根据分析结果提出解决方案,然后写成文档提交审批。

4、解决问题

    根据审批通过的文档进行修改,然后测试修改后的结果,符合要求了就可以提交测试部进行最总测试。

5、结果报告

    测试部测试通过后一个优化就算结束了,写好优化文档上传保存。

    上面的这几步每一步都很重要,但是真正难点还是在第三步,提出解决方案,代码优化、逻辑优化甚至是数据库的优化都需要有丰富的编程经验,这个不是一下子能做到的需要长期的积累,在项目里不断的实践才能够越做越好,下面我就将一些看到过的优化方法结合自己的工作总结出来以供参考:

java代码优化

1、循环

通常把大循环放在里面,把小循环放在外面,例如:

for(int i=0; i<10;i++)
	{
	    for(int j=0; j<1000000;j++)
	    { 
	            ...
	    }
	}
           

把与循环index不相关的移到循环的外面,例如:

for(int i=0; terminal=x.length;i<terminal;i++)
	{x = x/scaleA * scaleB;}
	//应该该成:
	Double scale = scaleB*scaleA;
	for(int i=0; terminal=x.length;i<terminal;i++)
	{x = x/scale ;}
           

在重要的循环里,消除循环终止判断时的方法调用,例如:

for(int i=0; i<collection.size();i++)
	{ ... }
	//尽量减少对变量的重复计算
	for(int i=0; n=collection.size();i<n;i++)
	{...}
           

尽量 不要在循环中使用:

Try {
	} catch() {
	}
           

      应把其放置在最外层

循环内不要创建大量的临时变量

for(int i=1;i<=domainCount;i++){
	        ... 
	       AuditResult auditResult = new AuditResult();
	        ...
	}
	//这种做法会在内存中保存N份这个对象的引用,会浪费大量的内存空间,改为
	AuditResult auditResult;
	for(int i=1;i<=domainCount;i++){
	        ... 
	       auditResult=new AuditResult();
	        ...
	}
           

2、字符串

     ■ 消除字符串连接

  ■创建长字符串时,总是使用StringBuffter代替String

  ■预先分配StringBuffer空间 StringBuffer sb = new StringBuffer(5000);

    ■ StringBuffer 和StringBuilder的区别:

    java.lang.StringBuffer线程安全的可变字符序列。一个类似于String 的字符串缓冲区,但不能修改。

    StringBuilder,与该类相比,通常应该优先使用java.lang.StringBuilder类,因为它支持所有相同的操作,但由于它不执行同步,所以速度更快。

3、异常

         ■ 异常只用于单个真正的错误条件,抛出一个异常和执行一个catch代码块花费是很高的(主要由于当创建一个异常时要获得线程栈的一个快照),只当条件真的异常时才抛出一个异常

  ■ 抛出异常首先要创建一个新的对象。

  Throwable接口的构造函数调用名为fillInStackTrace()的本地(Native)方法,fillInStackTrace()方法检查堆栈,收集调用跟踪信息。

  只要有异常被抛出,JVM就必须调整调用堆栈,因为在处理过程中创建了一个新的对象。

  异常只能用于错误处理,不应该用来控制程序流程。

  ■使编译器和运行时最优化,将几个方法调用放在一个try/catch块中,而不是为每个方法调用实现几个try/catch块,例如:

try{ 
	    Some.method1(); //Difficut for java1.4 
	}catch(method1Exception e){ 
	    handle exception 1 // to optimize this code 
	}
	try{ 
	    Some.method2(); //Difficut for java1.4 
	}catch(method2Exception e){ 
	    handle exception 2 // to optimize this code 
	}
	try{ 
	    Some.method3(); //Difficut for java1.4 
	}catch(method3Exception e){ 
	    handle exception 3 // to optimize this code 
	}
	//应该写为:
	try{ 
	    //Difficut for java1.4 
	    Some.method1(); 
	    Some.method2(); 
	    Some.method3(); 
	}catch(method1Exception e){ 
	    handle exception 1 
	}catch(method2Exception e){ 
	    handle exception 2 
	}catch(method3Exception e){ 
	    handle exception 3 
	}
           

4、尽量指定类的final修饰符

          ■带有final修饰符的类是不可派生的。在Java核心API中,有许多应用final的例子,例如java.lang.String。其为String类指定final也防止了人们覆盖length()方法。

  另外,如果指定一个类为final,则该类所有的方法都是final。Java编译器会寻找机会内联(inline)所有的final方法(这和具体的编译器实现有关)。此举能够使性能平均提高50%。

5、尽量使用局部变量

          ■调用方法时传递的参数以及在调用中创建的临时变量都保存在栈(Stack)中,速度较快。其他变量,如静态变量、实例变量等,都在堆(Heap)中创建,速度较慢。另外,依赖于具体的编译器/JVM,局部变量还可能得到进一步优化

6、乘法和除法

for (val = 0; val < 100000; val +=5) { 
	    alterX = val * 8; 
	    myResult = val * 2;
	}
	//优化后:
	for (val = 0; val < 100000; val += 5) { 
	    alterX = val << 3; 
	    myResult = val << 1;
	}
           

这个还需要看具体情况,并不是所有的乘除法都能这样优化。

7、array(数组) 和 ArrayList的使用

    array([]):最高效;但是其容量固定且无法动态改变;

    ArrayList:容量可动态增长;但牺牲效率;

    基于效率和类型检验,应尽可能使用array,无法确定数组大小时才使用ArrayList。

    ArrayList内部封装了一个Object类型的数组,从一般的意义来说,它和数组没有本质的差别,甚至于ArrayList的许多方法,如Index、IndexOf、Contains、Sort等都是在内部数组的基础上直接调用Array的对应方法。

8、在JAVA + ORACLE的应用系统开发中,java中内嵌的SQL语句尽量使用大写的形式,以减轻ORACLE解析器的解析负担

9、由于JVM的有其自身的GC机制,不需要程序开发者的过多考虑,从一定程度上减轻了开发者负担,但同时也遗漏了隐患,过分的创建对象会消耗系统的大量内存,严重时会导致内存泄露,因此,保证过期对象的及时回收具有重要意义。JVM回收垃圾的条件是:对象不在被引用;然而,JVM的GC并非十分的机智,即使对象满足了垃圾回收的条件也不一定会被立即回收。所以,建议我们在对象使用完毕,应手动置成null,这样也可以提供性能。

10、尽量采用lazy loading 的策略

即在需要的时候才开始创建,例如:

String str = “aaa”; 
	if(i == 1) {
	    list.add(str); 
	}
	//应替换为:
	if(i == 1) 
	{
	    String str = “aaa”; 
	    list.add(str); 
	} 
           

11、在使用同步机制时,应尽量使用方法同步代替代码块同步

12、当复制大量数据时,使用System.arraycopy()命令

下一篇介绍数据库访问的一些优化原则...

继续阅读