java性能优化技巧

一、通用篇 


    “通用篇”讨论的问题适合于大多数 Java应用。 


    1.1     new 
    1.1     new 
    11..11 不用 nneeww关键词创建类的实例 


    用new 关键词创建类的实例时,构造函数链中的所有构造函数都会被自动调用。但如 


果一个对象实现了Cloneable 接口,我们可以调用它的clone()方法。clone()方法不会调用任 


何类构造函数。 


    在使用设计模式(Design Pattern)的场合,如果用 Factory模式创建对象,则改用clone() 


方法创建新的对象实例非常简单。例如,下面是Factory 模式的一个典型实现: 


public static Credit getNewCredit(){ 


returnnew Credit(); 





    改进后的代码使用clone()方法,如下所示: 


private static Credit BaseCredit =new Credit(); 


public static Credit getNewCredit(){ 


return(Credit) BaseCredit.clone(); 





    上面的思路对于数组处理同样很有用。 


    1.2           I/O 
    1.2           I/O 
    11..22 使用非阻塞 II//OO 


    版本较低的JDK 不支持非阻塞I/OAPI。为避免 I/O阻塞,一些应用采用了创建大量线 


程的办法(在较好的情况下,会使用一个缓冲池)。这种技术可以在许多必须支持并发I/O 


流的应用中见到,如Web服务器、报价和拍卖应用等。然而,创建Java 线程需要相当可观 


的开销。 


    JDK1.4引入了非阻塞的I/O库(java.nio)。如果应用要求使用版本较早的 JDK,在这里 


有一个支持非阻塞I/O 的软件包。 


    请参见Sun 中国网站的《调整Java 的I/O性能》。 


    1.3 
    1.3 
    11..33 慎用异常 


    异常对性能不利。抛出异常首先要创建一个新的对象。Throwable 接口的构造函数调用 


名为fillInStackTrace()的本地(Native)方法,fillInStackTrace()方法检查堆栈,收集调用跟
踪信息。只要有异常被抛出,VM就必须调整调用堆栈,因为在处理过程中创建了一个新的 


对象。 


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


   1.4 
   1.4 
    11..44 不要重复初始化变量 


    默认情况下,调用类的构造函数时, Java会把变量初始化成确定的值:所有的对象被 


设置成null,整数变量(byte、short、int、long)设置成0,float 和 double 变量设置成0.0, 


逻辑值设置成false。当一个类从另一个类派生时,这一点尤其应该注意,因为用new 关键 


词创建一个对象时,构造函数链中的所有构造函数都会被自动调用。 


   1.5             final 
   1.5              final 
    11..55 尽量指定类的 ffiinnaall修饰符 


    带有final 修饰符的类是不可派生的。在 Java核心 API 中,有许多应用 final 的例子,例 


如java.lang.String。为 String 类指定 final 防止了人们覆盖length()方法。 


    另外,如果指定一个类为final,则该类所有的方法都是 final。Java 编译器会寻找机会 


内联(inline)所有的final 方法(这和具体的编译器实现有关)。此举能够使性能平均提高 


50%。 


   1.6 
   1.6 
    11..66 尽量使用局部变量 


    调用方法时传递的参数以及在调用中创建的临时变量都保存在栈(Stack)中,速度较 


快。其他变量,如静态变量、实例变量等,都在堆(Heap)中创建,速度较慢。另外,依 


赖于具体的编译器/JVM,局部变量还可能得到进一步优化。请参见《尽可能使用堆栈变量》。 


   1.7 
   1.7 
    11..77 乘法和除法 


    考虑下面的代码: 


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; } 


    修改后的代码不再做乘以8的操作,而是改用等价的左移3位操作,每左移1位相当于乘 


以2。相应地,右移1位操作相当于除以2。值得一提的是,虽然移位操作速度快,但可能使 


代码比较难于理解,所以最好加上一些注释。 




    J2EE 
    J2EE 
二、JJ22EEEE篇 


    前面介绍的改善性能技巧适合于大多数Java 应用,接下来要讨论的问题适合于使用 


JSP、EJB 或JDBC 的应用。 


    2.1 
    2.1 
    22..11 使用缓冲标记 


    一些应用服务器加入了面向JSP的缓冲标记功能。例如,BEA 的 WebLogic Server从6.0 


版本开始支持这个功能,OpenSymphony 工程也同样支持这个功能。JSP缓冲标记既能够缓 


冲页面片断,也能够缓冲整个页面。当JSP页面执行时,如果目标片断已经在缓冲之中,则 


生成该片断的代码就不用再执行。页面级缓冲捕获对指定URL 的请求,并缓冲整个结果页 


面。对于购物篮、目录以及门户网站的主页来说,这个功能极其有用。对于这类应用,页面 


级缓冲能够保存页面执行的结果,供后继请求使用。 


    对于代码逻辑复杂的页面,利用缓冲标记提高性能的效果比较明显;反之,效果可能略 


逊一筹。 


    请参见《用缓冲技术提高JSP应用的性能和稳定性》。 


    2.2             Bean         Bean 
    2.2             Bean         Bean 
    22..22 始终通过会话 BBeeaann访问实体 BBeeaann 


    直接访问实体Bean 不利于性能。当客户程序远程访问实体Bean 时,每一个get 方法都 


是一个远程调用。访问实体Bean 的会话 Bean 是本地的,能够把所有数据组织成一个结构, 


然后返回它的值。 


    用会话Bean 封装对实体Bean 的访问能够改进事务管理,因为会话Bean 只有在到达事 


务边界时才会提交。每一个对get 方法的直接调用产生一个事务,容器将在每一个实体 Bean 


的事务之后执行一个“装入-读取”操作。 


    一些时候,使用实体Bean 会导致程序性能不佳。如果实体Bean 的唯一用途就是提取 


和更新数据,改成在会话Bean 之内利用JDBC 访问数据库可以得到更好的性能。 


    2.3 
    2.3 
    22..33 选择合适的引用机制 


    在典型的JSP应用系统中,页头、页脚部分往往被抽取出来,然后根据需要引入页头、 


页脚。当前,在 JSP页面中引入外部资源的方法主要有两种:include 指令,以及 include 动 


作。 


    include 指令:例如<%@include file="copyright.html" %>。该指令在编译时引入指定的 


资源。在编译之前,带有include 指令的页面和指定的资源被合并成一个文件。被引用的外 


部资源在编译时就确定,比运行时才确定资源更高效。 


include 动作:例如<jsp:include page="copyright.jsp"/>。该动作引入指定页面执行后生成的结 
果。由于它在运行时完成,因此对输出结果的控制更加灵活。但时,只有当被引用的内容频 


繁地改变时,或者在对主页面的请求没有出现之前,被引用的页面无法确定时,使用include 


动作才合算。 


    2.4 
    2.4 
    22..44 在部署描述器中设置只读属性 


    实体Bean 的部署描述器允许把所有get 方法设置成“只读”。当某个事务单元的工作只 


包含执行读取操作的方法时,设置只读属性有利于提高性能,因为容器不必再执行存储操作。 


    2.5       EJB Home 
    2.5       EJB Home 
    22..55 缓冲对 EEJJBBHHoommee 的访问 


    EJB Home 接口通过JNDI名称查找获得。这个操作需要相当可观的开销。JNDI查找最 


好放入Servlet 的init()方法里面。如果应用中多处频繁地出现 EJB访问,最好创建一个 


EJBHomeCache类。EJBHomeCache类一般应该作为 singleton 实现。 


    2.6   EJB 
    2.6   EJB 
    22..66 为 EEJJBB实现本地接口 


    本地接口是EJB 2.0规范新增的内容,它使得Bean 能够避免远程调用的开销。请考虑下 


面的代码。 


PayBeanHome home =(PayBeanHome) 


javax.rmi.PortableRemoteObject.narrow 


(ctx.lookup ("PayBeanHome"),PayBeanHome.class); 


PayBean bean = (PayBean) 


javax.rmi.PortableRemoteObject.narrow 


(home.create(),PayBean.class); 


    第一个语句表示我们要寻找Bean 的Home 接口。这个查找通过JNDI进行,它是一个 


RMI 调用。然后,我们定位远程对象,返回代理引用,这也是一个 RMI 调用。第二个语句 


示范了如何创建一个实例,涉及了创建IIOP请求并在网络上传输请求的stub程序,它也是 


一个RMI 调用。 


    要实现本地接口,我们必须作如下修改: 


    方法不能再抛出java.rmi.RemoteException 异常,包括从 RemoteException 派生的异常, 


比如 TransactionRequiredException、TransactionRolledBackException 和 


NoSuchObjectException。EJB 提供了等价的本地异常,如TransactionRequiredLocalException、 


TransactionRolledBackLocalException 和NoSuchObjectLocalException。 


    所有数据和返回值都通过引用的方式传递,而不是传递值。 


    本地接口必须在EJB 部署的机器上使用。简而言之,客户程序和提供服务的组件必须 


在同一个JVM 上运行。 
  如果Bean 实现了本地接口,则其引用不可串行化。 


    请参见《用本地引用提高EJB 访问效率》。 


2.7 
2.7 
22..77 生成主键 


    在EJB 之内生成主键有许多途径,下面分析了几种常见的办法以及它们的特点。 


    利用数据库内建的标识机制(SQLServer的 IDENTITY或Oracle 的 SEQUENCE)。这 


种方法的缺点是EJB 可移植性差。 


    由实体Bean 自己计算主键值(比如做增量操作)。它的缺点是要求事务可串行化,而且 


速度也较慢。 


    利用NTP之类的时钟服务。这要求有面向特定平台的本地代码,从而把Bean 固定到了 


特定的OS 之上。另外,它还导致了这样一种可能,即在多CPU 的服务器上,同一个毫秒 


之内生成了两个主键。 


    借鉴Microsoft的思路,在 Bean 中创建一个GUID。然而,如果不求助于 JNI,Java 不 


能确定网卡的MAC 地址;如果使用JNI,则程序就要依赖于特定的OS。 


    还有其他几种办法,但这些办法同样都有各自的局限。似乎只有一个答案比较理想:结 


合运用RMI和JNDI。先通过RMI注册把RMI远程对象绑定到JNDI树。客户程序通过JNDI 


进行查找。下面是一个例子: 


public classkeyGenerator extendsUnicastRemoteObjectimplements Remote { 


private static long KeyValue = System.currentTimeMillis(); 


public static synchronized long getKey()throwsRemoteException {returnKeyValue++;} 


    2.8 
    2.8 
    22..88 及时清除不再需要的会话 


    为了清除不再活动的会话,许多应用服务器都有默认的会话超时时间,一般为30分钟。 


当应用服务器需要保存更多会话时,如果内存容量不足,操作系统会把部分内存数据转移到 


磁盘,应用服务器也可能根据“最近最频繁使用”(MostRecently Used)算法把部分不活跃的 


会话转储到磁盘,甚至可能抛出“内存不足”异常。在大规模系统中,串行化会话的代价是很 


昂贵的。当会话不再需要时,应当及时调用HttpSession.invalidate()方法清除会话。 


HttpSession.invalidate()方法通常可以在应用的退出页面调用。 


    2.9   JSP 
    2.9   JSP 
    22..99 在 JJSSPP页面中关闭无用的会话 


    对于那些无需跟踪会话状态的页面,关闭自动创建的会话可以节省一些资源。使用如下 


page 指令: 


<%@page session="false"%> 
  2.10Servlet 
    2.10Servlet 
    22..1100SSeerrvvlleett与内存使用 


    许多开发者随意地把大量信息保存到用户会话之中。一些时候,保存在会话中的对象没 


有及时地被垃圾回收机制回收。从性能上看,典型的症状是用户感到系统周期性地变慢,却 


又不能把原因归于任何一个具体的组件。如果监视JVM 的堆空间,它的表现是内存占用不 


正常地大起大落。 


    解决这类内存问题主要有二种办法。第一种办法是,在所有作用范围为会话的Bean 中 


实现HttpSessionBindingListener 接口。这样,只要实现valueUnbound()方法,就可以显式地 


释放Bean 使用的资源。 


    另外一种办法就是尽快地把会话作废。大多数应用服务器都有设置会话作废间隔时间的 


选项。另外,也可以用编程的方式调用会话的 setMaxInactiveInterval()方法,该方法用来设 


定在作废会话之前,Servlet 容器允许的客户请求的最大间隔时间,以秒计。 


    2.11HTTP Keep-Alive 
    2.11HTTP Keep-Alive 
    22..1111HHTTTTPP KKeeeepp--AAlliivvee 


    Keep-Alive 功能使客户端到服务器端的连接持续有效,当出现对服务器的后继请求时, 


Keep-Alive 功能避免了建立或者重新建立连接。市场上的大部分 Web 服务器,包括 iPlanet、 


IIS和Apache,都支持HTTPKeep-Alive。对于提供静态内容的网站来说,这个功能通常很 


有用。但是,对于负担较重的网站来说,这里存在另外一个问题:虽然为客户保留打开的连 


接有一定的好处,但它同样影响了性能,因为在处理暂停期间,本来可以释放的资源仍旧被 


占用。当Web服务器和应用服务器在同一台机器上运行时,Keep-Alive 功能对资源利用的 


影响尤其突出。 


    2.12JDBC    Unicode 
    2.12JDBC    Unicode 
    22..1122JJDDBBCC与 UUnniiccooddee 


    想必你已经了解一些使用JDBC 时提高性能的措施,比如利用连接池、正确地选择存储 


过程和直接执行的SQL、从结果集删除多余的列、预先编译SQL语句,等等。 


    除了这些显而易见的选择之外,另一个提高性能的好选择可能就是把所有的字符数据都 


保存为Unicode(代码页13488)。Java以 Unicode 形式处理所有数据,因此,数据库驱动程 


序不必再执行转换过程。但应该记住:如果采用这种方式,数据库会变得更大,因为每个 


Unicode字符需要2个字节存储空间。另外,如果有其他非 Unicode的程序访问数据库,性能 


问题仍旧会出现,因为这时数据库驱动程序仍旧必须执行转换过程。 


    2.13JDBC    I/O 
    2.13JDBC    I/O 
    22..1133JJDDBBCC与 II//OO 


    如果应用程序需要访问一个规模很大的数据集,则应当考虑使用块提取方式。默认情况 


下,JDBC 每次提取32行数据。举例来说,假设我们要遍历一个5000 行的记录集,JDBC 必 


须调用数据库157次才能提取到全部数据。如果把块大小改成512,则调用数据库的次数将减 


少到10次。 
 在一些情形下这种技术无效。例如,如果使用可滚动的记录集,或者在查询中指定了 


FOR UPDATE,则块操作方式不再有效。 


    2.14 
    2.14 
    22..1144 内存数据库 


    许多应用需要以用户为单位在会话对象中保存相当数量的数据,典型的应用如购物篮和 


目录等。由于这类数据可以按照行/列的形式组织,因此,许多应用创建了庞大的Vector或 


HashMap。在会话中保存这类数据极大地限制了应用的可伸缩性,因为服务器拥有的内存至 


少必须达到每个会话占用的内存数量乘以并发用户最大数量,它不仅使服务器价格昂贵,而 


且垃圾收集的时间间隔也可能延长到难以忍受的程度。 


    一些人把购物篮/目录功能转移到数据库层,在一定程度上提高了可伸缩性。然而,把 


这部分功能放到数据库层也存在问题,且问题的根源与大多数关系数据库系统的体系结构有 


关。对于关系数据库来说,运行时的重要原则之一是确保所有的写入操作稳定、可靠,因而, 


所有的性能问题都与物理上把数据写入磁盘的能力有关。关系数据库力图减少I/O 操作,特 


别是对于读操作,但实现该目标的主要途径只是执行一套实现缓冲机制的复杂算法,而这正 


是数据库层第一号性能瓶颈通常总是 CPU 的主要原因。 


    一种替代传统关系数据库的方案是,使用在内存中运行的数据库(In-memoryDatabase), 


例如TimesTen。内存数据库的出发点是允许数据临时地写入,但这些数据不必永久地保存 


到磁盘上,所有的操作都在内存中进行。这样,内存数据库不需要复杂的算法来减少 I/O操 


作,而且可以采用比较简单的加锁机制,因而速度很快。 

java性能优化技巧,布布扣,bubuko.com

java性能优化技巧

上一篇:微信公众号支付出现:“当前页面的URL未注册”


下一篇:微信红包的随机算法是怎样实现的?