java性能优化:Java性能优化技巧集锦



=
摘要:
=
可供利用资源(内存、CPU时间、网络带宽等)是有限优化就是让用尽可能少资源完成预定任务优化通常包含两方面内容:减小代码体积提高代码运行效率本文讨论主要是如何提高代码效率
=
提纲:
=
、通用篇
  1.1 不用关键词创建类例子
  1.2 使用非阻塞I/O
  1.3 慎用异常
  1.4 不要重复化变量
  1.5 尽量指定类final修饰符
  1.6 尽量使用局部变量
  1.7 乘法和除法
2、J2EE篇
  2.1 使用缓冲标记
  2.2 始终通过会话Bean访问实体Bean
  2.3 选择合适引用机制
  2.4 在部署描述器中设置只读属性
  2.5 缓冲对EJB Home访问
  2.6 为EJB实现本地接口
  2.7 生成主键
  2.8 及时清除不再需要会话
  2.9 在JSP页面中关闭无用会话
  2.10 Servlet和内存使用
  2.11 HTTP Keep-Alive
  2.12 JDBC和Unicode
  2.13 JDBC和I/O
  1.14 内存数据库
3、GUI篇
  3.1 用JAR压缩类文件
  3.2 提示Applet装入进程
  3.3 在画出图形的前预先装入它
  3.4 覆盖update思路方法
  3.5 延迟重画操作
  3.6 使用双缓冲区
  3.7 使用BufferedImage
  3.8 使用VolatileImage
  3.9 使用Window Blitting
4、补充资料
=
正文:
=
、通用篇

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

1.1 不用关键词创建类例子

关键词创建类例子时构造链中所有构造都会被自动但如果个对象实现了Cloneable接口我们可以clone思路方法clone思路方法不会任何类构造

在使用设计模式(Design Pattern)场合如果用Factory模式创建对象则改用clone思路方法创建新对象例子非常简单例如下面是Factory模式个典型实现:


public Credit getNewCredit {
Credit;
}

改进后代码使用clone思路方法如下所示:

private Credit BaseCredit = Credit;
public Credit getNewCredit {
(Credit) BaseCredit.clone;
}

上面思路对于处理同样很有用

1.2 使用非阻塞I/O

版本较低JDK不支持非阻塞I/O API为避免I/O阻塞些应用采用了创建大量线程办法(在较好情况下会使用个缓冲池)这种技术可以在许多必须支持并发I/O流应用中见到如Web服务器、报价和拍卖应用等然而创建Java线程需要相当可观开销

JDK 1.4引入了非阻塞I/O库(java.nio)如果应用要求使用版本较早JDK在这里有个支持非阻塞I/O软件Software包

请参见Sun中国网站WebSite调整JavaI/O性能

1.3 慎用异常

异常对性能不利抛出异常首先要创建个新对象Throwable接口构造名为fillInStackTrace本地(Native)思路方法fillInStackTrace思路方法检查堆栈收集跟踪信息只要有异常被抛出VM就必须调整堆栈在处理过程中创建了个新对象

异常只能用于处理不应该用来控制流程

1.4 不要重复化变量

默认情况下构造 Java会把变量化成确定值:所有对象被设置成null整数变量(、long)设置成0float和double变量设置成0.0逻辑值设置成false个类从另个类派生时点尤其应该注意关键词创建个对象时构造链中所有构造都会被自动

1.5 尽量指定类final修饰符

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

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

1.6 尽量使用局部变量

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

1.7 乘法和除法

考虑下面代码:

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值得虽然移位操作速度快但可能使代码比较难于理解所以最好加上些注释

2、J2EE篇

前面介绍改善性能窍门技巧适合于大多数Java应用接下来要讨论问题适合于使用JSP、EJB或JDBC应用

2.1 使用缓冲标记

些应用服务器加入了面向JSP缓冲标记功能例如BEAWebLogic Server从6.0版本开始支持这个功能Open Symphony工程也同样支持这个功能JSP缓冲标记既能够缓冲页面片断也能够缓冲整个页面当JSP页面执行时如果目标片断已经在缓冲的中则生成该片断代码就不用再执行页面级缓冲捕获对指定URL请求并缓冲整个结果页面对于购物篮、目录以及门户网站WebSite主页来说这个功能极其有用对于这类应用页面级缓冲能够保存页面执行结果供后继请求使用



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

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

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

直接访问实体Bean不利于性能当客户远程访问实体Bean时个get思路方法都是个远程访问实体Bean会话Bean是本地能够把所有数据组织成个结构然后返回它

用会话Bean封装对实体Bean访问能够改进事务管理会话Bean只有在到达事务边界时才会提交个对get思路方法直接产生个事务容器将在每个实体Bean事务的后执行个“装入-读取”操作

些时候使用实体Bean会导致性能不佳如果实体Bean用途就是提取和更新数据改成在会话Bean的内利用JDBC访问数据库可以得到更好性能

2.3 选择合适引用机制

在典型JSP应用系统中页头、页脚部分往往被抽取出来然后根据需要引入页头、页脚当前在JSP页面中引入外部资源思路方法主要有两种:指令以及动作

指令:例如<%@ file="copyright.html" %>该指令在编译时引入指定资源在编译的前带有指令页面和指定资源被合并成个文件被引用外部资源在编译时就确定比运行时才确定资源更高效
动作:例如<jsp: page="copyright.jsp" />该动作引入指定页面执行后生成结果由于它在运行时完成因此对输出结果控制更加灵活但时只有当被引用内容频繁地改变时或者在对主页面请求没有出现的前被引用页面无法确定时使用动作才合算
2.4 在部署描述器中设置只读属性

实体Bean部署描述器允许把所有get思路方法设置成“只读”当某个事务单元工作只包含执行读取操作思路方法时设置只读属性有利于提高性能容器不必再执行存储操作

2.5 缓冲对EJB Home访问

EJB Home接口通过JNDI名称查找获得这个操作需要相当可观开销JNDI查找最好放入Servletinit思路方法里面如果应用中多处频繁地出现EJB访问最好创建个EJBHomeCache类EJBHomeCache类般应该作为singleton实现

2.6 为EJB实现本地接口

本地接口是EJB 2.0规范标准新增内容它使得Bean能够避免远程开销请考虑下面代码

PayBeanHome home = (PayBeanHome)
javax.rmi.PortableRemoteObject.narrow
(ctx.lookup ("PayBeanHome"), PayBeanHome.);
PayBean bean = (PayBean)
javax.rmi.PortableRemoteObject.narrow
(home.create, PayBean.);

个语句表示我们要寻找BeanHome接口这个查找通过JNDI进行它是个RMI然后我们定位远程对象返回代理引用这也是个RMI第 2个语句示范了如何创建个例子涉及了创建IIOP请求并在网络上传输请求stub它也是个RMI

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

思路方法不能再抛出java.rmi.RemoteException异常包括从RemoteException派生异常比如TransactionRequiredException、TransactionRolledBackException和NoSuchObjectExceptionEJB提供了等价本地异常如TransactionRequiredLocalException、TransactionRolledBackLocalException和NoSuchObjectLocalException
所有数据和返回值都通过引用方式传递而不是传递值
本地接口必须在EJB部署机器上使用简而言的客户和提供服务组件必须在同个JVM上运行
如果Bean实现了本地接口则其引用不可串行化
请参见用本地引用提高EJB访问效率

2.7 生成主键

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

利用数据库内建标识机制(SQL ServerIDENTITY或OracleSEQUENCE)这种思路方法缺点是EJB可移植性差

由实体Bean自己计算主键值(比如做增量操作)缺点是要求事务可串行化而且速度也较慢

利用NTP的类时钟服务这要求有面向特定平台本地代码从而把Bean固定到了特定OS的上另外它还导致了这样种可能即在多CPU服务器上个毫秒的内生成了两个主键

借鉴Microsoft思路在Bean中创建个GUID然而如果不求助于JNIJava不能确定网卡MAC地址;如果使用JNI就要依赖于特定OS

还有其他几种办法但这些办法同样都有各自局限似乎只有个答案比较理想:结合运用RMI和JNDI先通过RMI注册把RMI远程对象绑定到JNDI树客户通过JNDI进行查找下面是个例子:


public keyGenerator extends UnicastRemoteObject implements Remote {
private long KeyValue = .currentTimeMillis;
public synchronized long getKey throws RemoteException { KeyValue; }

2.8 及时清除不再需要会话

为了清除不再活动会话许多应用服务器都有默认会话超时时间般为30分钟当应用服务器需要保存更多会话时如果内存容量不足操作系统会把部分内存数据转移到磁盘应用服务器也可能根据“最近最频繁使用”(Most Recently Used)算法把部分不活跃会话转储到磁盘甚至可能抛出“内存不足”异常在大规模系统中串行化会话代价是很昂贵当会话不再需要时应当及时HttpSession.invalidate思路方法清除会话HttpSession.invalidate思路方法通常可以在应用退出页面

2.9 在JSP页面中关闭无用会话

对于那些无需跟踪会话状态页面关闭自动创建会话可以节省些资源使用如下page指令:

<%@ page session="false"%>


2.10 Servlet和内存使用

许多开发者随意地把大量信息保存到用户会话的中些时候保存在会话中对象没有及时地被垃圾回收机制回收从性能上看典型症状是用户感到系统周期性地变慢却又不能把原因归于任何个具体组件如果监视JVM堆空间表现是内存占用不正常地大起大落

解决这类内存问题主要有 2种办法种办法是在所有作用范围为会话Bean中实现HttpSessionBindingListener接口这样只要实现valueUnbound思路方法就可以显式地释放Bean使用资源

另外种办法就是尽快地把会话作废大多数应用服务器都有设置会话作废间隔时间选项另外也可以用编程方式会话MaxInactiveInterval思路方法该思路方法用来设定在作废会话的前Servlet容器允许客户请求最大间隔时间以秒计

2.11 HTTP Keep-Alive

Keep-Alive功能使客户端到服务器端连接持续有效当出现对服务器后继请求时Keep-Alive功能避免了建立或者重新建立连接市场上大部分Web服务器包括iPlanet、IIS和Apache都支持HTTP Keep-Alive对于提供静态内容网站WebSite来说这个功能通常很有用但是对于负担较重网站WebSite来说这里存在另外个问题:虽然为客户保留打开连接有好处但它同样影响了性能在处理暂停期间本来可以释放资源仍旧被占用当Web服务器和应用服务器在同台机器上运行时Keep-Alive功能对资源利用影响尤其突出

2.12 JDBC和Unicode

想必你已经了解些使用JDBC时提高性能措施比如利用连接池、正确地选择存储过程和直接执行SQL、从结果集删除多余列、预先编译SQL语句等等

除了这些显而易见选择的外个提高性能好选择可能就是把所有数据都保存为Unicode(代码页13488)Java以Unicode形式处理所有数据因此数据库驱动不必再执行转换过程但应该记住:如果采用这种方式数据库会变得更大每个Unicode需要2个字节存储空间另外如果有其他非Unicode访问数据库性能问题仍旧会出现这时数据库驱动仍旧必须执行转换过程

2.13 JDBC和I/O

如果应用需要访问个规模很大数据集则应当考虑使用块提取方式默认情况下JDBC每次提取32行数据举例来说假设我们要遍历个5000行记录集JDBC必须数据库157次才能提取到全部数据如果把块大小改成512数据库次数将减少到10次

些情形下这种技术无效例如如果使用可滚动记录集或者在查询中指定了FOR UPDATE则块操作方式不再有效

1.14 内存数据库

许多应用需要以用户为单位在会话对象中保存相当数量数据典型应用如购物篮和目录等由于这类数据可以按照行/列形式组织因此许多应用创建了庞大Vector或HashMap在会话中保存这类数据极大地限制了应用可伸缩性服务器拥有内存至少必须达到每个会话占用内存数量乘以并发用户最大数量它不仅使服务器价格昂贵而且垃圾收集时间间隔也可能延长到难以忍受程度

些人把购物篮/目录功能转移到数据库层定程度上提高了可伸缩性然而把这部分功能放到数据库层也存在问题且问题根源和大多数关系数据库系统体系结构有关对于关系数据库来说运行时重要原则的是确保所有写入操作稳定、可靠因而所有性能问题都和物理上把数据写入磁盘能力有关关系数据库力图减少I/O操作特别是对于读操作但实现该目标主要途径只是执行套实现缓冲机制复杂算法而这正是数据库层第号性能瓶颈通常总是CPU主要原因

种替代传统关系数据库方案是使用在内存中运行数据库(In-memory Database)例如TimesTen内存数据库出发点是允许数据临时地写入但这些数据不必永久地保存到磁盘上所有操作都在内存中进行这样内存数据库不需要复杂算法来减少I/O操作而且可以采用比较简单加锁机制因而速度很快

Tags:  性能优化 为提高性能而优化 java性能优化

延伸阅读

最新评论

发表评论