你时常被客户抱怨JSP面响应速度很慢吗?你想q当客户讉Kơ数剧增Ӟ你的WEB应用能承受日益增加的讉K量吗Q?
本文讲述?jin)调整JSP和servlet的一些非常实用的Ҏ(gu)Q它可你的servlet和JSP面响应更快Q扩展性更强。而且在用h增加的情况下Q系l负载会(x)呈现出^滑上长的势。在本文中,我将通过一些实际例子和配置Ҏ(gu)使得你的应用E序的性能有出人意料的提升。其中,某些调优技术是在你的编E工作中实现的。而另一些技术是与应用服务器的配|相关的。在本文中,我们详l地描述怎样通过调整servlet和JSP面Q来提高你的应用E序的M性能。在阅读本文之前Q假设你有基本的servlet和JSP的知识?
Ҏ(gu)一Q在servlet的init()Ҏ(gu)中缓存数?/strong>
当应用服务器初始化servlet实例之后Qؓ(f)客户端请求提供服务之前,它会(x)调用q个servlet的init()Ҏ(gu)。在一个servlet的生命周期中Qinit()Ҏ(gu)只会(x)被调用一ơ。通过在init()Ҏ(gu)中缓存一些静(rn)态的数据或完成一些只需要执行一ơ的、耗时的操作,可大大地提高系l性能?
例如Q通过在init()Ҏ(gu)中徏立一个JDBCq接池是一个最佳例子,假设我们是用jdbc2.0的DataSource接口来取得数据库q接Q在通常的情况下Q我们需要通过JNDI来取得具体的数据源。我们可以想象在一个具体的应用中,如果每次SQLh都要执行一ơJNDI查询的话Q那pȝ性能会(x)急剧下降。解x法是如下代码Q它通过~存DataSourceQ得下一ơSQL调用时仍然可以l利用它Q?
|
Ҏ(gu) 2:止servlet和JSP 自动重蝲(auto-reloading)
Servlet/JSP提供?jin)一个实用的技术,卌动重载技术,它ؓ(f)开发h员提供了(jin)一个好的开发环境,当你改变servlet和JSP面后而不必重启应用服务器。然而,q种技术在产品q行阶段对系l的资源是一个极大的损耗,因ؓ(f)它会(x)lJSP引擎的类装蝲?classloader)带来极大的负担。因此关闭自动重载功能对pȝ性能的提升是一个极大的帮助?
Ҏ(gu) 3: 不要滥用HttpSession
在很多应用中Q我们的E序需要保持客L(fng)的状态,以便面之间可以怺联系。但不幸的是׃HTTPh天生无状态性,从而无法保存客L(fng)的状态。因此一般的应用服务器都提供?jin)session来保存客L(fng)状态。在JSP应用服务器中Q是通过HttpSession对像来实现session的功能的Q但在方便的同时Q它也给pȝ带来?jin)不的负担。因为每当你获得或更新sessionӞpȝ者要对它q行Ҏ(gu)的序列化操作。你可以通过对HttpSession的以下几U处理方式来提升pȝ的性能Q?
如果没有必要Q就应该关闭JSP面中对HttpSession的缺省设|:(x) 如果你没有明指定的话,每个JSP面都会(x)~省地创Z个HttpSession。如果你的JSP中不需要用session的话Q那可以通过如下的JSP面指示W来止它:(x) Q?@ page session="false"%Q?
不要在HttpSession中存攑֤的数据对像:(x)如果你在HttpSession中存攑֤的数据对像的话,每当对它q行dӞ应用服务器都对其进行序列化Q从而增加了(jin)pȝ的额外负担。你在HttpSession中存攄数据对像大Q那pȝ的性能׃降得快?
当你不需要HttpSessionӞ快地释攑֮Q当你不再需要sessionӞ你可以通过调用HttpSession.invalidate()Ҏ(gu)来释攑֮?
Ҏ(gu) 4: 页面输?gu)行压~?/strong>
压羃是解x据冗余的一个好的方法,特别是在|络带宽不够发达的今天。有的浏览器支持gzip(GNU zip)q行来对HTML文gq行压羃Q这U方法可以戏剧性地减少HTML文g的下载时间。因此,如果你将servlet或JSP面生成的HTML面q行压羃的话Q那用户׃(x)觉得面览速度?x)非常快。但不幸的是Q不是所有的览器都支持gzip压羃Q但你可以通过在你的程序中(g)查客L(fng)览器是否支持它。下面就是关于这U方法实现的一个代码片D:(x)
|
Ҏ(gu) 5: 使用U程?/strong>
应用服务器缺省地为每个不同的客户端请求创Z个线E进行处理,qؓ(f)它们分派service()Ҏ(gu)Q当service()Ҏ(gu)调用完成后,与之相应的线E也随之撤消。由于创建和撤消U程?x)耗费一定的pȝ资源Q这U缺省模式降低了(jin)pȝ的性能。但所q的是我们可以通过创徏一个线E池来改变这U状c(din)另外,我们q要个线E池讄一个最线E数和一个最大线E数。在应用服务器启动时Q它?x)创建数量等于最线E数的一个线E池Q当客户有请求时Q相应地从池从取Z个线E来q行处理Q当处理完成后,再将U程重新攑օ到池中。如果池中的U程不够地话Q系l会(x)自动地增加池中线E的数量Q但总量不能过最大线E数。通过使用U程池,当客L(fng)h急剧增加Ӟpȝ的负载就?x)呈现的qx的上升曲U,从而提高的pȝ的可伸羃性?
Ҏ(gu) 6: 选择正确的页面包含机?/strong>
在JSP中有两种Ҏ(gu)可以用来包含另一个页?、用include指示W?Q?@ includee file=”test.jsp” %Q??、用jsp指示W?Qjsp:includee page=”test.jsp” flush=”true”/Q?。在实际中我发现Q如果用第一U方法的话,可以使得pȝ性能更高?
Ҏ(gu) 7:正确地确定javabean的生命周?/strong>
JSP的一个强大的地方是对javabean的支持。通过在JSP面中用<jsp:useBeanQ标{,可以javabean直接插入C个JSP面中。它的用方法如下:(x) Qjsp:useBean id="name" scope="page|request|session|application" class= "package.className" type="typeName"Q</jsp:useBeanQ?
其中scope属性指Z(jin)q个bean的生命周期。缺省的生命周期为page。如果你没有正确地选择bean的生命周期的话,它将影响pȝ的性能?
举例来说Q如果你只想在一ơ请求中使用某个beanQ但你却这个bean的生命周期设|成?jin)sessionQ那当这ơ请求结束后Q这个bean仍然保留在内存中,除非session时或用户关闭浏览器。这样会(x)耗费一定的内存Qƈ无谓的增加了(jin)JVM垃圾攉器的工作量。因此ؓ(f)bean讄正确的生命周期,q在bean的命结束后快地清理它们,?x)用系l性能有一个提高?
其它一些有用的Ҏ(gu) ? 在字W串q接操作中尽量不使用“Q?#8221;操作W:(x)在java~程中,我们常常使用“Q?#8221;操作W来几个字W串q接hQ但你或总来没有想到过它居然会(x)对系l性能造成影响吧?׃字符串是帔RQ因此JVM?x)生一些(f)时的对像。你使用?#8220;Q?#8221;多Q生成的临时对像p多,q样也会(x)l系l性能带来一些媄(jing)响。解决的Ҏ(gu)是用StringBuffer对像来代?#8220;Q?#8221;操作W?
避免使用System.out.println()Ҏ(gu)Q由于System.out.println()是一U同步调用,卛_调用它时Q磁盘I(y)/O操作必须{待它的完成Q因此我们要量避免对它的调用。但我们在调试程序时它又是一个必不可的方便工具Qؓ(f)?jin)解册个矛盾,我徏议你最好用Log4j工具(http://Jakarta.apache.org ; )Q它既可以方便调试,而不?x)生System.out.println()q样的方法?
ServletOutputStream ?PrintWriter的权?使用PrintWriter可能?x)带来一些小的开销Q因为它?yu)所有的原始输出都{换ؓ(f)字符来输出Q因此如果用它来作为页面输出的话,pȝ要负担一个{换过E。而用ServletOutputStream作ؓ(f)面输出的话׃存在一个问题,但它是以二进制进行输出的。因此在实际应用中要权衡两者的利弊?
ȝ
本文的目的是通过对servlet和JSP的一些调优技术来极大地提高你的应用程序的性能Qƈ因此提升整个J2EE应用的性能。通过q些调优技术,你可以发现其实ƈ不是某种技术^収ͼ比如J2EE?NET之争Q决定了(jin)你的应用E序的性能Q重要是你要对这U^台有一个较为深入的?jin)解Q这样你才能从根本上对自q应用E序做一个优化!
Java提供?jin)两cM要的异常:runtime exception和checked exception。所有的checked exception是从java.lang.Exceptionc衍生出来的Q而runtime exception则是?span class="hilite1">java.lang.RuntimeException?span class="hilite1">java.lang.Errorc衍生出来的?
它们的不同之处表现在两方?机制上和逻辑上?br />
一、机制上
它们在机制上的不同表现在两点:1.如何定义Ҏ(gu);2. 如何处理抛出的异常。请看下面CheckedException的定?
public class CheckedException extends Exception
{
public CheckedException() {}
public CheckedException( String message )
{
super( message );
}
}
以及(qing)一个用exception的例?
public class ExceptionalClass
{
public void method1()
throws CheckedException
{
// ... throw new CheckedException( “...出错?#8220; );
}
public void method2( String arg )
{
if( arg == null )
{
throw new NullPointerException( “method2的参数arg是null!” );
}
}
public void method3() throws CheckedException
{
method1();
}
}
你可能已l注意到?jin),两个?gu)method1()和method2()都会(x)抛出exceptionQ可是只有method1()做了(jin)声明。另外,method3()本nq不?x)抛出exceptionQ可是它却声明会(x)抛出CheckedException。在向你解释之前Q让我们先来看看q个cȝmain()Ҏ(gu):
public static void main( String[] args )
{
ExceptionalClass example = new ExceptionalClass();
try
{
example.method1();
example.method3();
}
catch( CheckedException ex ) { } example.method2( null );
}
在main()Ҏ(gu)中,如果要调用method1()Q你必须把这个调用放在try/catchE序块当中,因ؓ(f)它会(x)抛出Checked exception?br />
相比之下Q当你调用method2()Ӟ则不需要把它放在try/catchE序块当中,因ؓ(f)它会(x)抛出的exception不是checked exceptionQ而是runtime exception。会(x)抛出runtime exception的方法在定义时不必声明它?x)抛出exception?br />
现在Q让我们再来看看method3()。它调用?jin)method1()却没有把q个调用攑֜try/catchE序块当中。它是通过声明它会(x)抛出method1()?x)抛出的exception来避免这样做的。它没有捕获q个exceptionQ而是把它传递下厅R实际上main()Ҏ(gu)也可以这样做Q通过声明它会(x)抛出Checked exception来避免用try/catchE序?当然我们反对q种做法)?br />
结一?
* Runtime exceptions:
在定义方法时不需要声明会(x)抛出runtime exception;
在调用这个方法时不需要捕莯个runtime exception;
runtime exception是从java.lang.RuntimeException?span class="hilite1">java.lang.Errorc衍生出来的?br />
* Checked exceptions:
定义Ҏ(gu)时必d明所有可能会(x)抛出的checked exception;
在调用这个方法时Q必L获它的checked exceptionQ不然就得把它的exception传递下?
checked exception是从java.lang.Exceptionc衍生出来的?/p>
二、逻辑?br />
从逻辑的角度来_(d)checked exceptions和runtime exception是有不同的用目的的。checked exception用来指示一U调用方能够直接处理的异常情c(din)而runtime exception则用来指CZU调用方本n无法处理或恢复的E序错误?br />
checked exceptionq你捕获它q处理这U异常情c(din)以java.net.URLcȝ构徏?constructor)ZQ它的每一个构建器都会(x)抛出MalformedURLException。MalformedURLException是一Uchecked exception。设想一下,你有一个简单的E序Q用来提C用戯入一个URLQ然后通过q个URLM载一个网c(din)如果用戯入的URL有错误,构徏器就?x)抛Z个exception。既然这个exception是checked exceptionQ你的程序就可以捕获它ƈ正确处理:比如说提C用户重新输入?br />
再看下面q个例子:
public void method()
{
int [] numbers = { 1, 2, 3 };
int sum = numbers[0] numbers[3];
}
在运行方法method()时会(x)遇到ArrayIndexOutOfBoundsException(因ؓ(f)数组numbers的成员是??)。对于这个异常,调用Ҏ(gu)法处?U正。这个方法method()和上面的method2()一P都是runtime exception的情形。上面我已经提到Qruntime exception用来指示一U调用方本n无法处理/恢复的程序错误。而程序错误通常是无法在q行q程中处理的Q必L正程序代码?br />
总而言之,在程序的q行q程中一个checked exception被抛出的时候,只有能够适当处理q个异常的调用方才应该用try/catch来捕获它。而对于runtime exceptionQ则不应当在E序中捕获它。如果你要捕获它的话Q你׃(x)冒这样一个风?E序代码的错?bug)被掩盖在q行当中无法被察觉。因为在E序试q程中,pȝ打印出来的调用堆栈\?StackTrace)往往使你更快扑ֈq修改代码中的错误。有些程序员捕获runtime exceptionq纪录在log中,我反对这样做。这样做的坏处是你必通过览log来找出问题,而用来测试程序的试pȝ(比如Unit Test)却无法直接捕获问题ƈ报告出来?br />
在程序中捕获runtime exceptionq会(x)带来更多的问?要捕获哪些runtime exception?什么时候捕?runtime exception是不需要声明的Q你怎样知道有没有runtime exception要捕?你想看到在程序中每一ơ调用方法时Q都使用try/catchE序块吗?
当用java -jar yourJarExe.jar来运行一个经q打包的应用E序的时候,你会(x)发现如何讄-classpath参数应用E序都找不到相应的第三方c,报ClassNotFound错误。实际上q是׃当?jar参数q行的时候,java VM?x)屏蔽所有的外部classpath,而只以本wyourJarExe.jar的内部class作ؓ(f)cȝL范围?/p>
**解决Ҏ(gu)**
一 BootStrap class扩展Ҏ(gu)
Java 命o(h)行提供了(jin)如何扩展bootStrap U别class的简单方?
-Xbootclasspath: 完全取代基本核心(j)的Java class 搜烦(ch)路径.
不常?否则要重新写所有Java 核心(j)class
-Xbootclasspath/a: 后缀在核?j)class搜烦(ch)路径后面.常用!!
-Xbootclasspath/p: 前缀在核?j)class搜烦(ch)路径前面.不常?避免
引v不必要的冲突.
语法如下:
(分隔W与classpath参数cMQunix使用:?windows使用;Pq里以unixZ)
java -Xbootclasspath/a:/usrhome/thirdlib.jar: -jar yourJarExe.jar
?extend class 扩展Ҏ(gu)
Java exten class 存放在{Java_home}\jre\lib\ext目录?当调用Java?Ҏ(gu)展class路径的搜索是自动?M(x)搜烦(ch)?q样,解决的方案就很简单了(jin),所有要使用的第三方的jar包都复制到ext 目录?
?User class扩展Ҏ(gu)
当?jar执行可执行Jar包时,JVMJar包所在目录设|ؓ(f)codebase目录,所有的class搜烦(ch)都在q个目录下开?所以如果用了(jin)其他W三方的jar?一个比较可以接受的可配|方?是利用jar包的Manifest扩展机制.
步骤如下:
1.需要的W三方的jar?复制在同可执行jar所在的目录或某个子目录? 比如:jar 包在 /usrhome/yourJarExe.jar 那么你可以把所有jar包复制到/usrhome目录下或/usrhome/lib {类似的子目录下.
2.修改Manifest 文g
在Manifest.mf文g里加入如下行
Class-Path:classes12.jar lib/thirdlib.jar
Class-Path 是可执行jar包运行依赖的关键?详细内容可以参?nbsp;http://java.sun.com/docs/books/tutorial/deployment/jar/downman.html 。要注意的是 Class-Path 只是作ؓ(f)你本地机器的CLASSPATH环境变量的一个羃写,也就是说用这个前~表示在你的jar包执行机器上所有的CLASSPATH目录下寻扄应的W三方类/cd。你q不能通过 Class-Path 来加载位于你本n的jar包里面(或者网l上Q的jar文g。因Z理论上来Ԍ你的jar发布包不应该再去包含其他的第三方cdQ而应该通过使用说明来提醒用户去获取相应的支持类库)(j)。如果由于特D需要必L其他的第三方cdQjar, zip, class{)(j)直接打包在你自己的jar包里面一起发布,你就必须通过实现自定义的ClassLoader来按照自q意图加蝲q些W三方类库?/p>
Java的安全机刉不同的JDK版本有不同的变化,?x)?jing)响很多核?j)CLASS,比如Thread,所以很多大型商业Y?要求JDK的版本很严格.部分原因也在?q也要求在发布自q写的应用时?不管大小,都要说明开发和试的JDK版本. ---------------------------------------------------------------------------------------------- ?背景知识 自JDK 1.2以后,JVM采用?jin)委?delegate)模式来蝲入classQ采用这U设计的原因可以参?a target="_blank">http://java.sun.com/docs/books/tutorial/ext/basics/load.html 归纳来讲:是基于JVM sandbox(沙盒)安装模型上提供应用层的可定制的安全机? 1. Bootstrap classes 属于Java q_核心(j)的class,比如java.lang.String{??qing)rt.jar{重要的核心(j)U别的class.q是由JVM Bootstrap class loader来蝲入的.一般是攄在{java_home}\jre\lib目录?/p>
2. Extension classes ZJava扩展机制,用来扩展Java核心(j)功能模块.比如Java串口通讯模块comm.jar.一般放|在{Java_home}\jre\lib\ext目录?/p>
3. User classes 开发h员或其他W三方开发的JavaE序?通过命o(h)行的-classpath?cp,或者通过讄CLASSPATH环境变量来引?JVM通过攄在{java_home}\lib\tools.jar来寻扑֒调用用户U的class.常用的javac也是通过调用tools.jar来寻扄h定的路径来编译Java源程?q样引Z(jin)User class路径搜烦(ch)的顺序或优先U别的问? 3.1 ~省?调用Java或javawa的当前\?.),是开发的class所存在的当前目?br />
3.2 CLASSPATH环境变量讄的\?如果讄?jin)CLASSPATH,则C(j)LASSPATH的g(x)覆盖~省?br />
3.3 执行Java的命令行-classpath?cp的?如果制定?jin)这两个命o(h)行参C一,它的g(x)覆盖环境变量CLASSPATH的?br />
3.4 -jar 选项:如果通过java -jar 来运行一个可执行的jar?q当前jar包会(x)覆盖上面所有的?换句话说,-jar 后面所跟的jar包的优先U别最?如果指定?jar选项,所有环境变量和命o(h)行制定的搜烦(ch)路径都将被忽?JVM APPClassloader只?x)以jar包ؓ(f)搜烦(ch)范围.
以上三种Ҏ(gu)推荐W一U?/strong>Q扩展性好Q操作v来也最方便.
另外~写自己的ClassLoader,来动态蝲入class,是更加复杂和高技?限于幅,不赘q?有兴了(jin)解可以去google一下custom classloaderQ或者参考我的另一日志:(x)让classpath参数走开?/p>
本文所q方法测试基于j2sdk 1.4.2_04-b05
Java虚拟?JVM)LClass的顺?/p>
有关可执行jar有许多相关的安全斚w的描q?可以参?a target="_blank">http://java.sun.com/docs/books/tutorial/jar/ 来全面了(jin)?