--清除耗时且低效的bug
时间:2004-12-28 作者:GVB Subrahmanyam, Shankar Itchapurapu 浏览次数: <script language="JavaScript" src="/beadevcount.jsp?d_id=161454" type="text/JavaScript"></script> 8034 本文关键字:性能, JDBC, JVM, EJB |
|
但是Java的流行并不一定使之对于日益增长的Java代码开发人员变得容易。为了在生产上达到一个新的高度,程序员们逐渐在更大的团队中工作,而在他们当中,持续缩短开发周期始终很热门。每天,那些团队都要面对一个软件开发中的不变定律:您编写的代码越多,您就会碰到越多的bug——而且是浪费时间和降低应用程序质量和性能的bug。
本文主要介绍了一个J2EE应用程序的性能调优和内存使用优化。我们设置使用 BEA WebLogic Application Server。我们将讨论以下几个方面:
- 问题域
- 调优Java虚拟机
- HTTP session管理
- 调优应用服务器
- 编码标准:为未来放弃规则
问题域
我们有一个如下设置的J2EE应用程序:
- BEA WebLogic 6.1 Service Pack 5 作为应用/Web服务器。
- 某个流行的RDBMS。对我们的讨论没有影响。
- Model I Web Architecture。
- 8个无状态EJB以及6个有状态EJB。
- HTTP session拥有对有状态EJB的引用。
- Database Connection Pool初始化为2,最大值为10。
- 在Web层有大约120个servlet。
- 基于XML/XSLT的体系结构。
问题
应用程序有一个内存问题。当服务器启动时,内存使用率大约占全部可用物理内存的7~8%。随着时间的推移和更多地使用应用程序,内存使用率会增加到接近49~53%(7~10天一个周期)。
如果用户通过点击左边菜单中的“Log off”按钮从他们的会话中注销,那么应用程序就会从服务器中删去所有的有状态bean。但是,如果一个用户仅仅只是关闭浏览器窗口,那它就不会删去那些bean,而会在容器中保留它们直到应用服务器重新启动为止。这样继续下去,内存中的EJB实例数量会增加到400个甚至更多。
当BEA WebLogic Server装载了多于400个EJB时,Hotspot虚拟机会抛出一个OutOfMemory 异常。尽管看似还有更多内存可用,但这种情况还是会发生。
调优Java虚拟机
当试图分配PermGeneration空间时,Hotspot虚拟机会抛出OutOfMemory异常。Hotspot虚拟机使用不同部分的内存。持久生成部分被用来储存类、方法以及运行的Java对象所使用的符号。持久生成部分的初始大小为1MB,最大值为64MB,1.3.1之前是32MB。
要避免这种情况,我们可以通过一个Java虚拟机开关,使用下面的命令行来设置permGeneration空间。
java -server -XX:MaxPermSize=128M.
注意,增加perm 最大值只是推迟了故障的发生。最终还是要靠您的应用程序去适当地清除无用的对象。另外,并非所有的Java虚拟机都支持XX选项。
HTTP Session 管理
当用户没有从会话中注销就关闭浏览器时,用户会话中的EJB不会被垃圾收集。这就是内存中有太多EJB的主要原因。要避免这种情况,HTTP session 管理必须注意所有可能的结合。我们可以在web.xml (Web应用程序部署描述符)中设置一个默认的会话超时周期,如下所示:
<session-config>
<session-timeout>x</session-timeout>
<session-config>
通过这个设置,用户的会话会在不活动x分钟后自动释放。
另一种方法是在创建HTTP session时用下面的代码编写会话管理
HttpSession session=new HttpSession ();
session.setmaxinactiveinternal(int timeoutSeconds);
这段代码会使不活动了timeoutSeconds时间的用户会话失效。
注意:如果您把两个步骤都做了,那么在servlet代码中的值将覆盖在web.xml中设置的值。
这两种方法惟一的不同点是第二种方法以秒作为参数,而 <session-timeout> 标记则是以分钟作为参数。通常,当会话失效后,logoff servlet/JSP 会用代码删去被特殊会话所引用的所有对象/对象图形的引用。但是当用户只是关闭浏览器时,就没有办法调用注销servlet/JSP。在这种情况下,即使会话已经失效,被封装的对象和对象图形会继续存在。当垃圾收集器试图对该会话进行垃圾收集时,它也会对所有这些封装对象进行收集。当我们有大对象时(拥有大的引用/数据的对象),我们也可以用HTTPSessionListener接口来进行同样的清除工作。
javax.servlet.http.HTTPSessionListener 接口
该接口声明了下面两种回调方法:
Public void sessionCreated(HttpSessionEvent event);
Public void sessionDestroyed(HttpSessionEven event);
这些方法在一个会话被创建/销毁前被调用。
我们可以使用一个实现了这个接口的监听器类,并用这些回调方法来控制会话的创建和销毁。我们需要像下面这样在web.xml中注册我们的监听器类:
<listener>
<listener-class>MySessionListener</listener-class>
</listener>
使用监听器类以及在web.xml文件中添加会话超时参数的好处是我们能对会话管理进行更多的控制。如果会话拥有大对象,那么在垃圾收集器清除这些对象之前,它的时间片可能会消失。在这种情况下,就需要等到下一个时间片才能清除这些对象。
注意:我们所设计的应用程序只有一个进入点是很重要的。我们需要在这个类中启动一个新的 HTTP session。所有余下的页面应该检查 HTTP session 是否存在,并且当会话为null时(Session 到期)调入一个错误页面。这样就可以实现对 HTTP session 的集中控制。
调优应用服务器
BEA WebLogic 提供了一些参数,我们能用它们优化bean池的大小,这包括设置无状态bean 池的初始大小。以下是一些使用方法:
- 用 <initial-bean-pool-size> 标记为无状态会话 bean设置 bean 池的最小值:默认情况下,这个值是1000。因为无状态会话bean可以在并发用户之间共享,所以最好把这个值设置得特别小。该标记的合适值取决于并发用户以及应用程序的峰值载入情况。
- 用 <max-beans-in-free-pool> 标记为有状态会话bean设置bean 池的最大值: 它的默认值没有被规定。该标记所设置的值会极大地影响到BEA WebLogic Server的激活与钝化机制。它的合适值取决于应用程序的流量。当池中bean的数量已经达到了阈值而又有一个对新bean实例的请求到达时,WebLogic Server会挑选池中一个或更多的bean来钝化。为钝化而挑选bean实例的算法是LRU(最近最少使用)或者NRU (最近没有使用)。
如果达到了max-beans-in-cache且高速缓存中的EJB并没有正在使用,WebLogic Server会把其中某些bean钝化。尽管没用的bean还没有达到它们的 idle-timeout-seconds极限,但这也会发生。如果达到了max-beans-in-cache且高速缓存中的EJB正在被客户端使用,那么WebLogic Server就会抛出一个CacheFullException异常。 - 用 <idle-timeout-seconds> 标记设置WebLogic Server在钝化一个空闲的有状态 会话 bean实例前所等待的时间:WebLogic Server会在从交换(swap)空间中删出bean之前等待同样长的时间。应该小心设置这个值,因为一旦从交换空间中删去钝化的实例,就没有办法重新获得该bean的状态了。
例如,考虑下面的设置
<idle-timeout-seconds>1200</idle-timeout-seconds>
这个空闲bean实例将会在静止20分钟后被钝化。再过20分钟,这个bean实例会从硬盘上删去。现在,让我们假设在第41分钟用户调用了这个bean实例的一个方法。BEA WebLogic Server将会抛出错误,如程序清单1所示。
程序清单 1
: Bean has been deleted.
at weblogic.ejb20.swap.DiskSwap.read(DiskSwap.java:156) at
weblogic.ejb20.manager.StatefulSessionManager.getBean(StatefulSession Manager.java:242) at
weblogic.ejb20.manager.StatefulSessionManager.preInvoke(StatefulSessionManager.java:313)
at weblogic.ejb20.internal.BaseEJBLocalObject.preInvoke(BaseEJBLocalObject.java:113)
at
weblogic.ejb20.internal.StatefulEJBLocalObject.preInvoke(StatefulEJBLocalObject.java:126)
WebLogic 6.1 Service Pack 5提供了一个有用的标记来避免发生这种情况。这个标记如下所示:
<!-- The stateful session beans that are passivated to the disk will stay alive for this many seconds. After this interval, the passivated beans will be removed from the disk.
Used in: stateful-session-cache
Since: WebLogic Server 6.1 sp5
Default value: 600
-->
<! ELEMENT session-timeout-seconds (#PCDATA)>
如果我们为这个<session-timeout-seconds>设置值,我们就能控制钝化的bean何时从硬盘中被删去。我们可以使用这个标记并把它设置到一个适当的值,这样我们就能总是拥有有状态EJB(或者在内存中或者在硬盘中)。这将完全消除bean被删除的错误。
使用WebLogic被管理服务器
BEA WebLogic 允许您在单独一个域中创建一个或多个服务器。一个服务器是管理服务器,所有其它服务器是被管理服务器,也就是被管理服务器管理。一个应用程序投入使用后,不应该被部署在管理服务器上。使用被管理服务器的好处是我们能从管理控制台上启动和停止它们。因此,即使服务器使应用程序停止响应(任何的原因),我们还是可以有机会从管理控制台重新启动服务器。
编码标准:为未来放弃规则
当系统的运行环境并没有在设计阶段考虑到时,管理一个开发系统会更加困难。当在设计阶段认真考虑了环境、边界、应用程序的运行环境后,系统再开发将是一项简单的任务。设计是执行路径的抽象定义。当考虑了设计中的每一个细节后再开发,解决方案会更具可伸缩性。对于系统开发并没有硬性的规定,因为这取决于您正面对的特定问题域。但是,还是有一些总会有帮助的定律。
- 对于补充类比如String 和 StringBuffer要有清晰的认识: 在合适的情况下用合适的类。例如,用String类构建一个长SQL查询是效率很低的,并加重JVM string池的负荷。
- 在您使用完继承对象后立即清除它们,比如 Hashtable。
- 在EJB引用中显式地调用 remove() 方法:这会把bean实例释放到bean池中,并减少由容器创建的bean实例数量。
- 小心地管理数据库连接:在您使用完后立即调用close()方法。不要在类的第一行就打开连接。而只在需要的时候才打开连接。
- 理解业务需求,并在您编写第一行代码之前就了解代码的执行环境。
总之一句话,”尽可能晚的创建对象而尽可能早的删除它们”。