性能问题的最明显表现是网页的响应时间变慢。在J2EE系统中,经常体现有下面更为基本的症状:
应用服务器资源的使用情况
JVM堆的使用情况
系统资源的使用情况
数据库资源的使用情况
网络活动
这些现象表明J2EE应用依赖很多外部资源,并且是运行在一个层次化的执行模式的环境中:
由于Java虚拟机和应用服务器掩盖了操作系统和硬件的特性,所以在设计软件系统时,架构工程师更应该深刻理解整个操作环境。
在设计软件系统时,架构工程师应把性能和可扩展性放在首位,然后开始寻找容易解决的问题,反应时间缓慢通常的原因是访问数据库效率低和过多地调用远程对象和方法。接下来,架构工程师可继续寻找不明显的原因,例如算法的累积影响和不必要的开销。
现在市场上的各个J2EE应用服务器有很多配置项目。这里只简单介绍一些常见的性能优化配置项目。
很多应用服务器都有一些与J2EE规范有关的操作系统配置项目或非标准的特性,这可以提高系统性能。应该化时间来理解这些性能配置。
Java虚拟机堆和垃圾回收设置
任何Java应用的性能调整基础都涉及到堆的大小和垃圾回收设置。(这里主要讨论Sun HotSpor JVM).
堆可分为三代,年轻的(新的),年老的和持久的。Hotspot JVM的内存基本配置包括最大堆大小,初始堆大小和年轻一代堆的大小。当配置最大堆大小时可参考下面一些指导:
最大大小应小于物理内存,避免虚存的页面调度。
需要减去其他进程使用的内存
在负载测试时进行优化
注意不要将最大堆大小设置得过大。堆越大,内存中保存的对象越多。内存中对象越多,回收过程时间越长。
配置初试堆大小的一般性策略包括:
将初始大小设置为最大堆大小
将初始大小设置为最大堆大小的1/4到1/2
对于年轻一代堆大小,Sun 推荐是设置为最大堆大小的1/3。
也可以选择不同的垃圾回收算法。首先是增量垃圾回收。该算法的意思是减少单个对象回收停顿时间,这样的结果是整体回收性能的下降。该算法将相互引用的对象分组,然后尝试按组回收。尝试回收的部分越小,回收处理的时间往往会越少。
1.4.1版的HotSpot JVM增加了两个垃圾回收算法:并行算法和并发算法。
在年轻一代堆中实现了并行算法。在多处理器的机器上,这种回收算法使用了多线程来提高性能。虽然这个算法会暂停所有的应用线程,但是由于利用了多个CPU使得回收时间非常快。在年轻一代堆中,该算法显著地减少了回收带来的停顿。
在年老一代堆中实现了并发算法。在应用中最大限度地执行并发。回收过程分为4个阶段,覆盖了可回收对象的标记和清除操作。前两个过程会暂停应用线程,后两阶段可与应用并发执行。并发垃圾回收算法的"最大限度并发"特点可以使JVM利用更大的堆和多个CPU。因此应关注由于采用缺省的mark-compact(标记-压缩)和stop-the-world(停顿所有处理)等垃圾回收算法所带来的延迟和吞吐量问题。
处理线程
J2EE应用服务器是多线程的应用。应用服务器的线程是一种资源池,处理请求和和应用服务器的内部功能等任务共享这些资源。
很多应用服务器允许为特定的任务或应用配置不同大小的线程池。通常需要增加这些线程池的大小以满足应用负载的需要。
架构工程师应该避免将线程池大小设置过大,这是因为会增加上下文交换的次数,从而降低应用的性能。线程池的大小通常应该能最大利用机器上的CPU,同时又不能使CPU过载。
EJB配置项目
在应用服务器中,很多不同类型的EJB是以资源池的方式实现的。通常这些池大小和初始Bean的数量会明显影响应用的性能。
架构工程师应该避免将这些池大小设置的过大,这样会导致不必要地消耗JVM和操作系统内存。另外,将初始Bean数量设置过高会使得应用服务器的启动时间长的难以接受。
在应用服务器中,缓存很多不同类型的EJB。缓存大小和超时设置通常也会对应用性能带来显著影响。
架构工程师应该避免将缓寸大小设置过大,这同样会不必要地消耗大量JVM和操作系统内存。此外,应避免设置过长的超时--例如当EJB不用时,仍被缓存---,这也会导致不必要地消耗大量内存。
数据库配置项目
J2EE规范要求应用服务器厂商必须提供数据库连接资源池功能。通常增加数据库连接池的大小会提高性能。架构工程师应该考虑不同类型的SQL操作(例如事务型和批处理型)应使用不同的连接池。如果一个消息Bean执行批处理操作,那么应该为此另创建一个连接池,而不要与事务型操作使用同一个连接池。
很多J2EE应用服务器提供了Prepared Statement 的缓存功能。创建Prepared Statement是很耗费资源的。在事务型的J2EE应用中通常执行很多同样的SQL语句,只是参数不同而已。所以在应用中应发挥数据库配置项目的作用,尽量使用Prepared Statement。
好的开始是成功的一半。对于J2EE同样如此,我们知道当开发应用时,在架构设计阶段的决定将对应用的性能和可扩展性产生深远的影响。
现在当开发一个应用项目时,我们越来越多地注意到了性能和可扩展性的问题。应用性能的问题比应用功能的不丰富问题往往更为严重,前者会影响到所有用户,而后者只会影响到碰巧使用该功能的那些用户。
作为应用系统的负责人,一直被要求"要少花钱多办事"----用更少的硬件,更少的网络带宽,以及更短的时间完成更多的任务。J2EE通过提供组件方式和通用的中间件服务是目前首选的最优方式。而要能够构建一个具有高性能和可扩展性的J2EE应用,需要遵循一些基本的架构策略。
缓存(Caching):
简单地说,缓存中存放着频繁访问的数据,在应用的整个生命周期中,这些数据存放在持久性存储器或存放在内存中。在实际环境中,典型的现象是在分布式系统中每个JVM中有一个缓存的实例或者在多个JVM中有一个缓存的实例。
缓存数据是通过避免访问持久性存储器来提高性能的,否则会导致过多的磁盘访问和过于频繁网络数据传输。
复制:
复制是通过在多台物理机器上创建指定应用服务的多个拷贝来获得整体更大吞吐效率。理论上看,如果一个服务被复制成两个服务,那么系统将可处理两倍的请求。
复制是通过单一服务的多个实例的方式从而减少每个服务的负载来提高性能的。
并行处理
并行处理将一个任务分解为更为简单的子任务,并能够同时在不同的线程中执行。
并行处理是通过利用J2EE层执行模式的多线程和多CPU特点来提高性能。与使用一个线程或CPU处理任务相比,以并行方式处理多个子任务可以使操作系统在多个线程或处理器中进行分配这些子任务。
异步处理
应用功能通常被设计为同步或串行方式。异步处理只处理那些非常重要的任务部分,然后将控制立即返回给调用者,其他任务部分将在稍后执行。
异步处理是通过缩短那些在将控制返回给用户之前必须处理的时间来提高性能的。虽然都做同样多的事情,但是用户不必等到整个过程完成就可以继续发出请求了。
资源池
资源池技术使用的是一套准备好的资源。与在请求和资源之间维持1:1的关系的不同,这些资源可被所有请求所共享。
资源池的使用是有条件的,需要衡量下面两种方式的代价:
A,维持一套可被所有请求共享资源的代价
B,为每个请求都重新创建一个资源的代价
当前者小于后者时,使用资源池才是有效率的。
构建高性能的J2EE应用不但需要了解常用的实施技巧。下面介绍最常用的10种有效方法,可帮助架构设计师们快速成为这方面的专家。
Java性能的基础----内存管理
任何Java应用,单机的或J2EE的性能基础都可归结到你的应用是如何管理内存的问题。Java的内存管理包括两个重要任务:内存的分配和内存的回收。在内存的分配中,目标是要减少需要创建的对象。 内存回收是导致性能下降的普遍原因。也就是说,内存中的对象越多,垃圾回收越困难。所以我们对创建对象的态度应该越保守越好。
在J2EE应用中常见的两个内存有关的问题是:游离的对象(也被称为内存泄露)和对象循环(指大量频繁创建和删除-在Java中体现为解除引用---对象)。
我们应注意确保所有可到达的对象实际是活的,即这些对象不但在内存中,而且也要在执行的代码中是存在的。当对象在应用中已经没有用了,而我们却忘记了删除对该对象的引用时,游离的对象就出现了。
我们知道垃圾回收会占用CPU时间。短期对象的大量创建增加了垃圾回收的频率会造成性能下降。
不要在Servlet中实现业务逻辑
在构建J2EE应用时,架构工程师通常会使用到J2EE的基本部分,Servlet。
如果架构师不使用Session Beans, Entity Beans, 或 Message Beans, 那么改进性能的方法就很少。只能采用增加CPU或更多的物理服务器等方法。EJB使用了缓存(cache)和资源池等方法可以提高性能和扩展性。
尽可能使用本地接口访问EJB
在早期的J2EE (遵循EJB1.X规范)应用中,访问EJB是`通过RMI使用远程接口实现的。随着EJB2.0的出现,可以通过本地接口访问EJB,不再使用RMI,在同一个JVM中使用远程方法已经少多了。但是现在还是有一些使用EJB1.X实现的应用和不知道使用本地接口的一些EJB新手。为说明这点,我们作个比较:
1. 客户端应用调用本地Stub
2. 该Stub装配参数
3. 该Stub传到skeleton
4. 该skeleton分解参数
5. 该skeleton调用EJB对象
6. EJB对象执行容器服务
7. EJB对象调用企业BEAN实例
8. 企业BEA执行操作
9. 执行组装/分解步骤然后返回
与远程接口处理相比较,本地接口的EJB方法是:
1. 客户端调用本地对象
2. 本地对象执行容器服务
3. 本地对象调用企业Bean实例
4. 企业Bean实例执行操作
5. 没有其他返回步骤!!
如果你不需要从远程的客户端访问一个特殊EJB,就应该使用本地方法。
在实现Session Bean的服务中封装对实体EJB的访问
从Servlet访问实体EJB不但效率低而且难于维护。使用Session Fa?ade(会话外观)模式可把对实体EJB的访问封装在会话EJB中,在该会话EJB中通过使用本地接口访问实体EJB而避免过多的远程调用。
这项技术会有额外的性能和扩展方面的好处,这是因为会话和实体EJB可以使用缓存和资源池技术来进行改进。另外,由于负载的需要,会话和实体EJB可被扩展部署到其他硬件设备上,这比将Servlet层复制扩展到其他硬件设备上要简单的多。
尽量粗粒度访问远程EJB
当访问远程EJB时,调用set/get方法将产生过多的网络请求,同时也导致远程接口处理的过载。为避免这种情况,可考虑将数据属性集中在一个对象中,这样通过一次对远程EJB的调用就可以传递所有数据。这项技术就是数据传输对象(Data Transfer Object)模式。
优化SQL
J2EE的架构设计工程师和开发人员通常不是SQL专家或经验丰富的数据库管理员。首先应该确保SQL使用了数据库提供的索引支持。在某些情况下,将数据库的索引和数据分开存放会提高性能。但要知道,增加额外的索引可以提高SELECT性能但也会降低INSERT的性能。对于某些数据库,关联表之间的排序会严重影响性能。可以多向数据库管理员咨询。
避免在实体EJB中过多执行SQL
有时候,通过实体EJB访问数据会执行多个SQL语句。根据J2EE 规范,第一步,将调用实体Bean的find(发现)方法;第二步,在第一次调用实体EJB的业务方法时,容器会调用ejbLoad()从数据库中获得信息。
很多CMP(容器管理持久性)在调用发现方法时就缓存了实体数据,所以在调用ejbLoad()时就不再访问数据库了。应该避免使用BMP(Bean管理的持久性)或者自己实现缓存算法避免二次访问数据库。
使用Fast Lane Reader 模式访问只读数据
J2EE应用经常要以只读方式访问大量长时间不变的数据,而不是访问单个实体,例如浏览在线产品目录。在这种只读情况下,使用实体EJB访问数据会导致严重过载并且实现很麻烦。实体EJB 适合于对单个实体的粗粒度访问,访问大量的列表只读数据时效率不高。不管是使用CMP还是BMP,一定需要编写代码操作多个实体EJB及其关联。这将导致访问多个数据库并存在大量的也是不必要的事务开销。
利用Java Messaging Servce(消息服务)
J2EE规范在JMS中提供了内置的异步处理服务。当涉及到系统需求时,应该了解在什么情况下应该采用JMS进行异步处理的设计。一旦确定要执行一些异步处理,那么同步处理的任务就应该越少越好,将数据库密集的操作安排在稍后的异步处理中完成。
缓存JNDI Lookup查找
很多操作在进行JNDI查找时要消耗大量资源。通常应该缓存JNDI资源避免网络调用和某些处理的过载。可以缓存的JNDI查找包括:
EJB Home Interfaces
Data Sources
JMS Connection Factories
JMS Destinations/Topics
一些JNDI包实现了缓存功能。但是调用对EJB主接口的narrow方法时,这种功能作用有限。
缓存查找的设计应该使用共享的IntialContext实例,尽管构建它很麻烦。这是因为需要访问多种数据源,包括应用资源文件JNDI.properties,系统属性的各项参数,传入到构造函数的各项参数。