﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-WOLF--执着-文章分类-WEB SERVER</title><link>http://www.blogjava.net/sutao/category/24293.html</link><description>用文字记录学习的体验！</description><language>zh-cn</language><lastBuildDate>Thu, 22 Nov 2007 09:21:14 GMT</lastBuildDate><pubDate>Thu, 22 Nov 2007 09:21:14 GMT</pubDate><ttl>60</ttl><item><title>java heap详细介绍</title><link>http://www.blogjava.net/sutao/articles/162288.html</link><dc:creator>苏醄</dc:creator><author>苏醄</author><pubDate>Thu, 22 Nov 2007 02:20:00 GMT</pubDate><guid>http://www.blogjava.net/sutao/articles/162288.html</guid><wfw:comment>http://www.blogjava.net/sutao/comments/162288.html</wfw:comment><comments>http://www.blogjava.net/sutao/articles/162288.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/sutao/comments/commentRss/162288.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/sutao/services/trackbacks/162288.html</trackback:ping><description><![CDATA[<div class="postText">
<p><span style="color: #0000ff">1、<br />
</span></p>
<p><span style="color: #0000ff">PermGen space的全称是Permanent Generation space,是指内存的永久保存区域OutOfMemoryError: PermGen space从表面上看就是内存益出，解决方法也一定是加大内存。说说为什么会内存益出：这一部分用于存放Class和Meta的信息,Class在被 Load的时候被放入PermGen space区域，它和和存放Instance的Heap区域不同,GC(Garbage Collection)不会在主程序运行期对PermGen space进行清理，所以如果你的APP会LOAD很多CLASS的话,就很可能出现PermGen space错误。这种错误常见在web服务器对JSP进行pre compile的时候。</span></p>
<span style="color: #0000ff">改正方法：-Xms256m -Xmx256m -XX:MaxNewSize=256m -XX:MaxPermSize=256m </span></div>
<div class="entry-more" id="more">
<p><span style="color: #0000ff">2、</span></p>
<p><span style="color: #0000ff">在tomcat中redeploy时出现outofmemory的错误.</span></p>
<p><span style="color: #0000ff">可以有以下几个方面的原因:</span></p>
<p><span style="color: #0000ff">１,使用了<u><font color="#800080">proxool,因为proxool内部包含了一个老版本的cglib.</font></u></span></p>
<p><u><span style="color: #0000ff">2, log4j,最好不用,只用common-logging</span></u></p>
<p><u><span style="color: #0000ff">3, 老版本的cglib,快点更新到最新版。</span></u></p>
<p><span style="color: #0000ff"><u><font color="#800080">４，更新到最新的hibernate3.2</font></u><br />
</span></p>
<span style="color: #0000ff">3、<br />
<br />
这里以tomcat环境为例，其它WEB服务器如jboss,weblogic等是同一个道理。<br />
一、java.lang.OutOfMemoryError: PermGen space </span>
<p><span style="color: #0000ff">PermGen space的全称是Permanent Generation space,是指内存的永久保存区域,<br />
这块内存主要是被JVM存放Class和Meta信息的,Class在被Loader时就会被放到PermGen space中,<br />
它和存放类实例(Instance)的Heap区域不同,GC(Garbage Collection)不会在主程序运行期对<br />
PermGen space进行清理，所以如果你的应用中有很多CLASS的话,就很可能出现PermGen space错误,<br />
这种错误常见在web服务器对JSP进行pre compile的时候。如果你的WEB APP下都用了大量的第三方jar, 其大小<br />
超过了jvm默认的大小(4M)那么就会产生此错误信息了。<br />
解决方法： 手动设置MaxPermSize大小</span></p>
<p><span style="color: #0000ff">修改TOMCAT_HOME/bin/catalina.sh<br />
在&#8220;echo "Using CATALINA_BASE:&nbsp;&nbsp; $CATALINA_BASE"&#8221;上面加入以下行：<br />
JAVA_OPTS="-server -XX:PermSize=64M -XX:MaxPermSize=128m<br />
建议：将相同的第三方jar文件移置到tomcat/shared/lib目录下，这样可以达到减少jar 文档重复占用内存的目的。</span></p>
<p><span style="color: #0000ff">二、java.lang.OutOfMemoryError: Java heap space<br />
Heap size 设置<br />
JVM堆的设置是指java程序运行过程中JVM可以调配使用的内存空间的设置.JVM在启动的时候会自动设置Heap size的值，<br />
其初始空间(即-Xms)是物理内存的1/64，最大空间(-Xmx)是物理内存的1/4。可以利用JVM提供的-Xmn -Xms -Xmx等选项可<br />
进行设置。Heap size 的大小是Young Generation 和Tenured Generaion 之和。<br />
提示：在JVM中如果98％的时间是用于GC且可用的Heap size 不足2％的时候将抛出此异常信息。<br />
提示：Heap Size 最大不要超过可用物理内存的80％，一般的要将-Xms和-Xmx选项设置为相同，而-Xmn为1/4的-Xmx值。 <br />
解决方法：手动设置Heap size<br />
修改TOMCAT_HOME/bin/catalina.sh<br />
在&#8220;echo "Using CATALINA_BASE:&nbsp;&nbsp; $CATALINA_BASE"&#8221;上面加入以下行：<br />
JAVA_OPTS="-server -Xms800m -Xmx800m&nbsp;&nbsp; -XX:MaxNewSize=256m"</span></p>
<p><span style="color: #0000ff">三、实例，以下给出1G内存环境下java jvm 的参数设置参考：</span></p>
<p><span style="color: #0000ff">JAVA_OPTS="-server -Xms800m -Xmx800m&nbsp; -XX:PermSize=64M -XX:MaxNewSize=256m -XX:MaxPermSize=128m -Djava.awt.headless=true "</span></p>
<p><br />
<span style="color: #0000ff">三、相关资料</span></p>
<p><a href="http://www.tot.name/show/3/7/20061112220131.htm"><span style="color: #0000ff">/show/3/7/20061112220131.htm</span></a></p>
<p><a href="http://www.tot.name/show/3/7/20061112220054.htm"><span style="color: #0000ff">/show/3/7/20061112220054.htm</span></a></p>
<p><a href="http://www.tot.name/show/3/7/20061112220201.htm"><span style="color: #0000ff">/show/3/7/20061112220201.htm</span></a></p>
<span style="color: #0000ff">题外话：经常看到网友抱怨tomcat的性能不如...，不稳定等，其实根据笔者几年的经验，从"互联星空&#8220;到现在的房产门户网，我们<br />
均使用tomcat作为WEB服务器，每天访问量百万多，tomcat仍然运行良好。建议大家有问题多从自己程序入手，多看看java的DOC文档<br />
并详细了解JVM的知识。这样开发的程序才会健壮。<br />
<br />
<font style="font-weight: bold" size="4"><span style="color: rgb(153,204,0)">延伸阅读：</span></font> </span>
<h3 class="entry-header"><a href="http://www.wujianrong.com/archives/2007/02/jvm_1.html#more"><span style="color: #0000ff">JVM 性能调整的一些基本概念</span></a></h3>
<h2 class="r"></h2>
<h2 class="r"><a href="http://www.wujianrong.com/archives/2006/11/apachetomcat.html"><span class="l"><span style="color: #0000ff">apache+Tomcat负载平衡设置详解[转]</span></span></a></h2>
<h1><span style="color: #0000ff"><a href="http://java.sun.com/j2se/1.3/docs/tooldocs/linux/java.html">java - the Java application launcher</a></span><br />
</h1>
<h3 class="entry-header"><a href="http://www.wujianrong.com/archives/2006/11/jvm.html"><span style="color: #0000ff">JVM调优[转]</span></a></h3>
</div>
<img src ="http://www.blogjava.net/sutao/aggbug/162288.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/sutao/" target="_blank">苏醄</a> 2007-11-22 10:20 <a href="http://www.blogjava.net/sutao/articles/162288.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>struts异常处理</title><link>http://www.blogjava.net/sutao/articles/161519.html</link><dc:creator>苏醄</dc:creator><author>苏醄</author><pubDate>Mon, 19 Nov 2007 02:06:00 GMT</pubDate><guid>http://www.blogjava.net/sutao/articles/161519.html</guid><wfw:comment>http://www.blogjava.net/sutao/comments/161519.html</wfw:comment><comments>http://www.blogjava.net/sutao/articles/161519.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/sutao/comments/commentRss/161519.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/sutao/services/trackbacks/161519.html</trackback:ping><description><![CDATA[<span style="color: #2708ff">Struts异常处理(原创) 异 常是当JVM(JAVA虚拟机)在执行应用程序的某个方法的时候遇到的非正常现象,JVM就会生成一个异常对象,抛给客户以便客户进行异常处理. Struts框架的异常处理机制建立在java异常处理的基础之上.在研究Struts异常处理之前,先回顾一下java异常处理原理,理解java虚拟 机JVM的异常处理过程有助于应用设计正确的异常处理方法.处理异常需要JVM付出不小的开销,所以用于必须慎重使用. JAVA 异常是在java程序运行的时候遇到非正常的情况而创建的对象,它封装了异常信息,java异常的根类为java.lang.Throwable,整个类 有两个直接子类java.lang.Error和java.lang.Exception.Error是程序本身无法恢复的严重错误.Exception 则表示可以被程序捕获并处理的异常错误.JVM用方法调用栈来跟踪每个线程中一系列的方法调用过程,该栈保存了每个调用方法的本地信息.对于独立的 JAVA程序,可以一直到该程序的main方法.当一个新方法被调用的时候,JVM把描述该方法的栈结构置入栈顶,位于栈顶的方法为正确执行的方法.当一 个JAVA方法正常执行完毕,JVM回从调用栈中弹处该方法的栈结构,然后继续处理前一个方法.如果java方法在执行代码的过程中抛出异常,JVM必须 找到能捕获异常的catch块代码.它首先查看当前方法是否贼这样的catch代码块,如果存在就执行该catch代码块,否则JVM回调用栈中弹处该方 法的栈结构,继续到前一个方法中查找合适的catch代码块.最后如果JVM向上追到了main()方法,也就是一直把异常抛给了main()方法,仍然 没有找到该异常处理的代码块,该线程就会异常终止,如果该线程是主线程,应用程序也随之终止,此时JVM将把异常直接抛给用户,在用户终端上会看到原始的 异常信息.回顾完了JAVA的异常处理机制,就要开始研究Struts的异常处理了. Struts 框架在视图层和控制层提供了对异常处理的支持.Struts的控制器负责捕获各种异常,包括控制器运行中本身抛出的异常,以及调用模型的业务方法的异常. 当异常被控制器捕获的时候,在异常处理代码块中,创建描述异常信息的ActionMessage对象把它保存在ActionMessages或者它的子类 ActionErrors对象中,然后把它保存在特定的范围内,比如request或session.接下来在视图层
<HTML: error>
标签检索特定范围的ActionMessages对象,把本地化错误消息输出到页面上.这种异常处理机制可以避免用户看到原始的 java异常信息.可以更友好的把错误信息展示给用户. Struts 框架处理异常是以JVM的异常处理机制为基础的,尽管它提供了强大的通用错误处理机制,但是不能保证捕获到所有的异常或者错误.当错误发生的时候,如果 Struts不能处理这种异常或者错误,就把错误抛给JavaWeb容器.容器先查看是否在Web应用发布描述文件中配置了<error- page>元素,如果存在该元素就返回元素的子元素<location>指定的错误页面,否则就会把错误直接抛给用户.下面简单介绍一 Struts几个重要的类的异常处理机制,如果想了解这几个类的更深的异常机制,建议查看Struts源代码. 1) ActionServlet类的peocess()方法不捕获任何异常,仅仅声明向上层调用方法抛出异常. 2) RequestProcessor类是Struts框架处理异常的核心组件. 3) ExceptionHandler类是默认的异常处理类,它的execute()方法负责处理异常. 根据打造技术Blog,寻求JAVA精髓! Servlet规范,当容器捕获到异常的时候,将查看是否在Web.xml中配置了相应的<error-page>元素,如果存在,就会返回其<location>子元素指定的错误页面.举个简单明了的例子如下: <error-page><error-code>500</error> <location>/error.jsp</location> </error-page><error-page><exception-type>javax.servlet.ServletException</error> <location>/error.jsp</location> </error-page>Struts框架也允许以配置的方式来处理异 常,配置方法可以避免在Action类中通过硬编码来处理异常,提高应用的灵活性,可重用性和可维护性.对于Action类的execute()方法抛出 的异常会先查找异常处理元素<exception>,如果是被嵌套在<global-exception>元素中就表示是全局异常处理元素,对所有的Action都适用.如果是嵌套在<action>元素中,就表示局部的异常处理元素,仅对当前的Action适用.阐 述一下<exception>元素的属性: 1) type:指定待处理的异常类 2) handler:指定异常处理类.默认是ExceptionHandler.如果自己要定义必须继承它 3) path:指定转发路径 4) key:指定错误消息key.根据这个key到ResourceBundle中寻找匹配的消息文本 5) bundle:指定ResourceBundle,如果没设置将使用哦么人的ResourceBundle 6) scope:指定ActionMessages对象的存放范围,默认request,也可以设置session Struts框架提供了强大的异常处理功能,Struts控制器负责捕获异常,并把异常包装与 ResourceBundle绑定的 ActionMessages对象,在视图层<html:error>
标签能够显示出来,主要可以通过配置的方式和编程的方式实现,这里不推 荐用编程的方法,可以增强程序的灵活性,可重用性,可维护性. (网友们的支持,是我继续写技术文章的动力!) Struts的异常处理机制总的来说，在struts新的版本中加入了对异常的处理，称之为：Exception Handling，标志着作为一个整体的解决框架，struts原来越趋于成熟。 通 常来说，以前在用struts开发的过程中，对于异常的处理，主要是采用手动处理的方式：如通过try/catch等等捕获异常，然后定制个性化的比较详 细的错误信息放进ActionError中，然后在具体的返回页面中把这些错误信息反馈给用户（包括开发员）。异常原始的信息不管是最终用户还是开发员都是不希望看到的。 下面着重讲一下在struts中是如何通过配置文件来解决异常。 Struts中的Exception Handleing不难，简单高效是业内给其的一个比较好的评价。 通过配置文件（主要是struts-config.xml）来定制异常处理，就象定义formbean一样，定制异常也有两种方法，姑且把它分为：&#8220;全局异常&#8221;和&#8220;局部异常&#8221;。 全局异常，定义方法如下： &#8230;&#8230;&#8230;&#8230; <global-exceptions><exception path="error.jsp" scope="request" type="com.iplateau.jshop.common.waf.&#13;&#10;exceptions.InvalidiItemsCatalogNameException" key="expired.InvalidItemsCatalogName"  /></global-exceptions>&#8230;&#8230;&#8230;&#8230; 上 述代码在struts-config.xml中定义了一个全局异常，它的作用是抛出InvalidiItemsCatalogNameException （本处的意思是当在添加商品分类的时候发现该类别已经存在）异常的时候返回到error.jsp中，并且携带自定的比较规范的异常信息 expired.InvalidItemsCatalogName，expired.InvalidItemsCatalogName可以在应用程序的资源配置文件中找到,如： expired.InvalidItemsCatalogName=你要添加的商品类别已经存在，请添加新的类别！ 局部异常，定义方法如下： &#8230;&#8230;&#8230;&#8230; <action-mappings><action path="&#8221;/addItemsCatalogAction&#8221;" type="&#8221;com.iplateau.jsop.action.ItemsCatalogAction&#8221;" name="&#8221;itemsCatalogForm&#8221;"><exception path="&#8221;/error.jsp&#8221;/" type="com.iplateau.jshop.common.waf.&#13;&#10;exceptions.InvalidiItemsCatalogNameException" key="&#8221;"  expired.InvalidItemsCatalogName&#8221;><forward path="***Layout(此处采用Tiles进行辅助开发)" name="success"  /></ation></action-mappings>&#8230;&#8230;&#8230;&#8230; （关于Tiles的内容参看我的另一篇文章&#8220;Struts使用Tiles辅助开发&#8221;） 下面我们把关注的目光放在具体action里，看看struts是如何进行异常处理的 //ItemsCatalogAction.java package com.iplateau.jshop.action.ItemsCatalogAction import com.iplateau.jshop.business.ItemsCatalogMap; import com.iplateau.jshop.action.ItemsCatalogForm; import ***; public class ItemsCatalogAction extends BaseAction { public ActionForward execute( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { String flg=&#8221;error&#8221;; String act=request.getParameter(&#8220;act&#8221;); ItemsCatalogMap map=new ItemsCatalogMap(); ItemsCatalogForm thisform=( ItemsCatalogForm)form; // ItemsCatalogMap为具体的处理商品类别的类 if(act.equals(&#8220;create&#8221;)) { map.validateNameOfCatalog(thisform); map.create(thisform); flg=&#8221;success&#8221;; return mapping.findForward(flg); } else{ return mapping.findForward(flg); } } } 上便是一个简单的action，其中用act的具体内容来判断此时的action要处理的操作，如act为cteate的时候，处理的是商品类别的添加等等。 看了上边的代码，可以了解，在调用具体的业务类进行添加商品类别以前先要判断此时要添加的商品类别的合法性，如： 调用map.validateNameOfCatalog(thisform); 其中在ItemsCatalogMap定义的validateNameOfCatalog 方法抛出InvalidiItemsCatalogNameException。 下面让我们运行一下添加商品类别的例子，如果此时数据库中已经存在&#8220;牛奶制品&#8221;这个商品类别，而我们又要添加此类别的时候,程序如我们预想的一样转到error.jsp并且显示了我们要先是的错误信息：你要添加的商品类别已经存在，请添加新的类别！ 其实就这么简单，我们的定制的异常在程序中并不需要用try/catch来捕获，一旦出现了我们已经定义的异常那么就会转到相应得页面，并且携带定制的信息。 还记得在struts先前的版本中我们的请求都是通过action的perform来处理，可是现在都要通过execute来处理，就其原因一个很重要的就是&#8220;成全&#8221;Exception Handling。为什么呢？ 因为perform在声明的时候仅仅抛出IOException 和ServletException，这远远不能满足Exception Handling的要求,那么让我们看看execute是怎样的：它笼统的抛出Exception（所有异常的父类） 上 面的阐述只是一个引子，而且默认struts的异常是通过org.apache.struts.action. ExceptionHandler来处理的，你可以定义自己的处理方式，只要继承它并实现其中的execute方法，这个方法在ExceptionHandler的定义如下： public ActionForward execute (Exception ex, ExceptionConfig ae, ActionMapping mapping, ActionForm formInstance, HttpServletRequest request, HttpServletResponse response) throws ServletException 具体而且专业的分析Exception Handling的内容，请参看《Programming Jakarta Struts》第10章??这本书的电子版网上很多地方可以下载。 原文地址 http://tech.ccidnet.com/pub/article/c322_a74029_p1.html
</html:error>
</span>
<img src ="http://www.blogjava.net/sutao/aggbug/161519.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/sutao/" target="_blank">苏醄</a> 2007-11-19 10:06 <a href="http://www.blogjava.net/sutao/articles/161519.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Jive论坛系统详细介绍</title><link>http://www.blogjava.net/sutao/articles/158764.html</link><dc:creator>苏醄</dc:creator><author>苏醄</author><pubDate>Wed, 07 Nov 2007 03:08:00 GMT</pubDate><guid>http://www.blogjava.net/sutao/articles/158764.html</guid><wfw:comment>http://www.blogjava.net/sutao/comments/158764.html</wfw:comment><comments>http://www.blogjava.net/sutao/articles/158764.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/sutao/comments/commentRss/158764.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/sutao/services/trackbacks/158764.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: Jive论坛系统Jive是基于Web结构的一套论坛系统。Jive的早期版本是基于开放源代码开发的，由于其出色的代码设计水平以及优越的性能，被广泛地应用在很多网站系统中。市场的成功促使Jive从2.1版本以后不再开放其源代码。因此，本章讨论的Jive论坛系统是基于Jive 1.2.4 Yazd（http://yazd.yasna.com/）的修改版Jdon论坛。Java系统开发学习的一个不可...&nbsp;&nbsp;<a href='http://www.blogjava.net/sutao/articles/158764.html'>阅读全文</a><img src ="http://www.blogjava.net/sutao/aggbug/158764.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/sutao/" target="_blank">苏醄</a> 2007-11-07 11:08 <a href="http://www.blogjava.net/sutao/articles/158764.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>用Apache做负载均衡时HTTP长连接每5分钟断线  </title><link>http://www.blogjava.net/sutao/articles/138857.html</link><dc:creator>苏醄</dc:creator><author>苏醄</author><pubDate>Thu, 23 Aug 2007 07:41:00 GMT</pubDate><guid>http://www.blogjava.net/sutao/articles/138857.html</guid><wfw:comment>http://www.blogjava.net/sutao/comments/138857.html</wfw:comment><comments>http://www.blogjava.net/sutao/articles/138857.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/sutao/comments/commentRss/138857.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/sutao/services/trackbacks/138857.html</trackback:ping><description><![CDATA[<a style="color: #3518ff;" href="http://www.searchfull.net/blog/2007/07/31/1185878655515.html" name="a1185878655515" title="http://www.searchfull.net:80/blog/2007/07/31/1185878655515.html">用Apache做负载均衡时HTTP长连接每5分钟断线</a><span style="color: #3518ff;">
</span><br style="color: #3518ff;">
<p style="color: #3518ff;"><font face="Arial">Apache
被常用来做Java应用系统的负载均衡软件，Tomcat、Websphere、Weblogic等都有相应的插件支持Apache集成负载均衡。但是最
近遇到这样的问题，对于大数据量的长HTTP连接的业务每过5分钟就断线了，而且每5分钟就重发同一个http请求到应用服务器。<br>
Apache的httpd.conf有下列两个配置参数，类似地Array、F5等负载均衡硬件也应该有这些配置：<br>
#<br>
# Timeout: The number of seconds before receives and sends time out.<br>
#<br>
Timeout 300</font></p>
<p style="color: #3518ff;"><font face="Arial">#<br>
# KeepAlive: Whether or not to allow persistent connections (more than<br>
# one request per connection). Set to "Off" to deactivate.<br>
#<br>
KeepAlive On</font></p>
<font style="color: #3518ff;" face="Arial">这两个参数会导致那些HTTP长连接，如对大数据量操作的请求每过300秒就会断线，并且会每300秒重试发送请求给Weblogic等应用服务器，导致应用异常。<br>
虽然应用程序应该尽量避免每次http请求别太长，但是万一有这样的业务，那么只能调整Timeout参数。</font><span style="color: #3518ff;">
</span><br><img src ="http://www.blogjava.net/sutao/aggbug/138857.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/sutao/" target="_blank">苏醄</a> 2007-08-23 15:41 <a href="http://www.blogjava.net/sutao/articles/138857.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Apache 实现禁止图片盗链</title><link>http://www.blogjava.net/sutao/articles/136033.html</link><dc:creator>苏醄</dc:creator><author>苏醄</author><pubDate>Sat, 11 Aug 2007 08:01:00 GMT</pubDate><guid>http://www.blogjava.net/sutao/articles/136033.html</guid><wfw:comment>http://www.blogjava.net/sutao/comments/136033.html</wfw:comment><comments>http://www.blogjava.net/sutao/articles/136033.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/sutao/comments/commentRss/136033.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/sutao/services/trackbacks/136033.html</trackback:ping><description><![CDATA[<p style="color: #101fff;"><strong> <a href="http://www.webdn.com/web_file/Apache/fwq0608025/" _base_href="http://www.webdn.com/web_file/program/php/0602080455/"><strong style="background-color: #ffff66;">Apache</strong></a> 实现禁止<strong style="background-color: #a0ffff;">图片</strong>盗链</strong> </p>
<p style="color: #101fff;">1、假设充许连结<strong style="background-color: #a0ffff;">图片</strong>的主机域名为：www.gongyelu.cn <br><br>2、修改httpd.conf<br><br>Code: <br>SetEnvIfNoCase Referer "^http://www.gongyelu.cn/" local_ref=1 <br>&lt;FilesMatch ".(gif|jpg)"&gt; <br>Order Allow,Deny <br>Allow from env=local_ref <br>&lt;/FilesMatch&gt; <br><br><br>这个简单的应用不光可以解决<strong style="background-color: #a0ffff;">图片</strong>盗链的问题，稍加修改还可以防止任意文件盗链下载的问题。 <br><br>使用以上的方法当从非指定的主机连结<strong style="background-color: #a0ffff;">图片</strong>时，<strong style="background-color: #a0ffff;">图片</strong>将无法显示，如果希望显示一张&#8220;禁止盗链&#8221;的<strong style="background-color: #a0ffff;">图片</strong>，我们可以用mod_rewrite 来实现。 <br><br>首先在安装 <strong style="background-color: #ffff66;">apache</strong> 时要加上 --enable-rewrite 参数加载 mod_rewrite 模组。 <br><br>假设&#8220;禁止盗链&#8221;的<strong style="background-color: #a0ffff;">图片</strong>为abc.gif，我们在 httpd.conf 中可以这样配置：<br><br><br>Code:<br>RewriteEngine on <br>RewriteCond %{HTTP_REFERER} !^$ <br>RewriteCond %{HTTP_REFERER} !^http://(www\.)?demo.com.cn /.*$ [NC] <br>RewriteRule \.(gif|jpg)$ http://www.demo.com.cn/abc.gif [R,L] </p>
<p style="color: #101fff;">＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋＋</p>
<p style="color: #101fff;">DocumentRoot "/usr/local/<strong style="background-color: #ffff66;">apache</strong>/htdocs" <br>#设置<a name="baidusnap2" _base_href="http://www.webdn.com/web_file/program/php/0602080455/" href_cetemp="http://www.webdn.com/web_file/program/php/0602080455/"></a>
<!-- end 主体内容 -->
<!-- 网络广告投放 -->
<br><img src ="http://www.blogjava.net/sutao/aggbug/136033.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/sutao/" target="_blank">苏醄</a> 2007-08-11 16:01 <a href="http://www.blogjava.net/sutao/articles/136033.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Tomcat安全域设置大全</title><link>http://www.blogjava.net/sutao/articles/134194.html</link><dc:creator>苏醄</dc:creator><author>苏醄</author><pubDate>Fri, 03 Aug 2007 03:20:00 GMT</pubDate><guid>http://www.blogjava.net/sutao/articles/134194.html</guid><wfw:comment>http://www.blogjava.net/sutao/comments/134194.html</wfw:comment><comments>http://www.blogjava.net/sutao/articles/134194.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/sutao/comments/commentRss/134194.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/sutao/services/trackbacks/134194.html</trackback:ping><description><![CDATA[<p>安全域是tomcat内置的功能，在org.apache.catalina.Realm接口中声明了把一组用户名，口令及所关联的角色集成到Tomcat的方法中，Tomcat5提供了4个实现这一接口的类，它们代表了4种安全域类型。</p>
<p>　　下面我一一介绍其相关配置，及其应用方法。</p>
<p>　　运行环境：windows2000，tomcat5.0.28，jdk1.5，jdbc3.0，sqlserver2000， <br>　　测试目录：%tomcat%\webapps\Area目录。</p>
<p>　　1. 内存域：类名，MemoryRealm；在初始化阶段，从xml文件中读取安全验证信息，并把它们以一组对象的形式放在内存中。 <br>　　对于资源访问它有三种方式：BASIC、DIGEST、FORM。使用Basic Authentication通过被认为是不安全的，因为它没有强健的加密方法，除非在客户端和服务器端都使用HTTPS或者其他密码加密码方式（比如，在一个虚拟私人网络中）。 <br>　　配置文件%tomcat%\conf\Catalina\localhost\Area.xml为： <br>　　&lt;?xml version='1.0' encoding='utf-8'?&gt; <br>　　&lt;Context docBase="D:\jakarta-tomcat-5.0.28\webapps\Area" path="/Area" reloadable="true" <br>　　　workDir="work\Catalina\localhost\Area"&gt; <br>　　　　&lt;Realm className="org.apache.catalina.realm.MemoryRealm"/&gt; <br>　　&lt;/Context&gt; <br>　　web应用所在的WEB-INF\web.xml为： <br>　　&lt;?xml version="1.0" encoding="GB2312"?&gt; <br>　　&lt;!DOCTYPE web-app <br>　　 PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"&gt; <br>　　　&lt;web-app&gt; <br>　　　　&lt;security-constraint&gt; <br>　　　　&lt;display-name&gt;sessiontest secruity constraint&lt;/display-name&gt; <br>　　　　&lt;web-resource-collection&gt; <br>　　　　&lt;web-resource-name&gt;Protected Area&lt;/web-resource-name&gt; <br>　　　　&lt;url-pattern&gt;/test/*&lt;/url-pattern&gt; <br>　　　　&lt;/web-resource-collection&gt;</p>
<p>　　　&lt;auth-constraint&gt; <br>　　　　&lt;role-name&gt;cool&lt;/role-name&gt; <br>　　　&lt;/auth-constraint&gt; <br>　　　&lt;/security-constraint&gt;</p>
<p>&lt;!--以下是基于BASIC验证--&gt; <br>　　　 &lt;login-config&gt; <br>　　　&lt;auth-method&gt;BASIC&lt;/auth-method&gt; <br>　　 　&lt;realm-name&gt;Sessiontest Realm&lt;/realm-name&gt; <br>　　　&lt;/login-config&gt;</p>
<p>&lt;!--以下是基于DIGEST验证--&gt; <br>&lt;!--　 &lt;login-config&gt; <br>　　　&lt;auth-method&gt;DIGEST&lt;/auth-method&gt; <br>　　 　&lt;realm-name&gt;Sessiontest Realm&lt;/realm-name&gt; <br>　　　&lt;/login-config&gt; <br>--&gt;</p>
<p>&lt;!--以下是基于FORM验证--&gt; <br>&lt;!--　&lt;login-config&gt; <br>　　　　&lt;auth-method&gt;FORM&lt;/auth-method&gt; <br>　　　　&lt;realm-name&gt;Sessiontest Realm&lt;/realm-name&gt; <br>　　　　&lt;form-login-config&gt; <br>　　　　　&lt;form-login-page&gt;/usercheck.jsp&lt;/form-login-page&gt; <br>　　　　　&lt;form-error-page&gt;/error.jsp&lt;/form-error-page&gt; <br>　　　　&lt;/form-login-config&gt; <br>　　　&lt;/login-config&gt; <br>若采用form验证，usercheck.jsp，主要参数(不要去更改它)设置如下： <br>&lt;form method="post" action="j_security_check"&gt; <br>&lt;input type="text" name="j_username"&gt;&lt;br&gt; <br>&lt;br&gt; <br>&lt;input type="password" name="j_password"&gt;&lt;br&gt; <br>&lt;br&gt; <br>&lt;input type="submit" value="login"&gt; <br>&lt;input type="reset" value="reset"&gt; <br>&lt;/form&gt; <br>--&gt;</p>
<p>　　　 &lt;security-role&gt; <br>　　　&lt;description&gt; session jdbctest &lt;/description&gt; <br>　　　&lt;role-name&gt;cool&lt;/role-name&gt; <br>　　　&lt;/security-role&gt; <br>　　&lt;/web-app&gt; <br>
基中&lt;url-pattern&gt;/test/*&lt;/url-pattern&gt;表示受保护的资源为：http:
//localhost:8080/Area/test/下的所有资源。role角色cool在%tomcat%conf中的tomcat-
users.xml中添加： <br>　　 &lt;role rolename="cool"/&gt; <br>　　&lt;user username="zxj1" password="zxj2" roles="cool"/&gt;</p>
<p>　　2. JDBC域：类名，JDBCRealm，通过jdbc驱动程序访问存在数据库中的安全验证。</p>
<p>　　3. 数据源域：类名，DataSourceRealm，通过JNDI数据源访问存在数据库中的安全验证信息。</p>
4. JNDI域：类名，JNDIRealm，通过JNDIproveider访问存放在基于LDAP的目录服务器中的安全验证信息<img src ="http://www.blogjava.net/sutao/aggbug/134194.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/sutao/" target="_blank">苏醄</a> 2007-08-03 11:20 <a href="http://www.blogjava.net/sutao/articles/134194.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>tomcat5集群中的SESSON复制</title><link>http://www.blogjava.net/sutao/articles/134191.html</link><dc:creator>苏醄</dc:creator><author>苏醄</author><pubDate>Fri, 03 Aug 2007 03:10:00 GMT</pubDate><guid>http://www.blogjava.net/sutao/articles/134191.html</guid><wfw:comment>http://www.blogjava.net/sutao/comments/134191.html</wfw:comment><comments>http://www.blogjava.net/sutao/articles/134191.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/sutao/comments/commentRss/134191.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/sutao/services/trackbacks/134191.html</trackback:ping><description><![CDATA[【IT168技术文档】Tomcat
5服务器为集群和SESSION复制提供了集成的支持。本系列的第一篇文章将为大家提供SESSION持久性以及TOMCAT集群中SESSION复制的
内在工作机制一个概要认识。我将会讨论SESSION复制在TOMCAT5中是怎样进行的以及跨越多集群节点的SESSION持久性的复制机制。在第2部
分，我会详细讨论一个带有SESSION复制功能的TOMCAT集群的安装例子，并且比较不同的复制情形。<br>　　<br>　　<strong>集群</strong><br>　　<br>　　传统独立服务器（非集群的）不提供任何失效无缝转移以及负载平衡能力。当服务器失败的时候，就无法获取整个网站的内容，除非服务器被重新唤起。由于服务器失效，任何存储在服务器内存中的SESSION都会丢失，用户必须重新登陆并且输入所有由于服务器失效丢失的数据。<br>　　<br>
不同的是，作为集群一部分的服务器则提供了可测性以及失效无缝转移能力。一个集群就是一组同步运行并且协同工作，能提供高可靠性，高稳定性以及高可测性
的多服务器例程。服务端集群对客户端表现出来似乎就是一个单独的服务器例程。从客户端的视角来看，集群的客户端和单独的服务器没多大不同，但是他们通过提
供实效无缝转移和SESSION复制做到了不间断服务以及SESSION数据持久性。<br>　　<br>　　<strong>集群中的服务器通讯</strong><br>　　<br>　　集群中的应用程序服务器通过诸如IP多点传送（IP multicast）和IP sockets这样的技术和其他服务器共享信息<br>　　<br>　　●IP多点传送：主要用于1对多的服务器通讯，通过广播服务和 heartbeats消息的可用来显示服务器的有效<br>　　<br>　　●IP sockets：主要用于在集群的服务器例程中进行P2P服务器通讯<br>　　<br>　　<strong>使用IP多点传送进行一对多通讯</strong><br>　　<br>
TOMCAT服务器使用IP多点传送在集群中的服务器例程间进行一对多的通讯，IP多点传送是一种能够让多服务器向指定IP地址和端口号进行订阅并且监
听消息的广播技术（多点传送IP地址范围从224.0.0.0 到239.255.255.255）。在集群中的每个服务器都使用多点传送广播特定的
heartbeat消息，通过监视这些
heartbeat消息，在集群中的服务器例程判断什么时候服务器例程失效。在服务器通讯中使用IP多点传送的一个缺点是他不能保证这些消息被确实接收到
了。例如，一个应用持续的本地多点传送缓存满了，就不能写入新的多点传送消息，等消息过了之后该应用程序就没有被通知到。<br>　　<br>　　<strong>使用IP Sockets进行服务器通讯</strong><br>　　<br>
IP sockets 同样也通过了一套在集群中的服务器间进行发送消息和数据的机制。服务器例程使用IP sockets
在集群节点间进行HTTP
SESSION状态的复制。正确的SOKET配制对于集群的性能是至关重要的，基于SOCKET的通讯的效率取决于SOCKET的实现类别（例如：系统使
用本地的或者纯JAVA SOCKET读取器实现），如果服务器使用纯JAVA
SOCKET读取器则要看服务器例程是否注册使用了足够的SOCKET读取器线程。<br>　　<br>　　如果想要有最佳的SOCKET性能，系统应该
注册使用本地的SOCEKT而不是纯JAVA实现。这是因为相对于基于JAVA的SOCKET实现，本地SOCKET消耗更少的系统资源。虽然
SOCKET读取器的JAVA实现是P2P通信中一种可靠而且可移动的方法，可是他不能为集群中的重型SOCKET使用提供最好的性能。当判断从
SOCKET是否有数据读取的时候本地SOCKET读取器使用了更有效率的方法。使用本地SOCKET读取器实现，读取器线程不需要去统计静止的
SOCKET：他们仅仅为活动的SOCKET服务，并且在一个给定的SOCKET开始活跃起来时他们可以立刻捕捉到。而使用纯JAVA
SOCKET读取器，线程必须动态的统计所有打开的SOCKET，判断他们是否包含可读取的数据。换句话说，SOCKET读取器总是忙于统计
SOCKET，即使这些SOCKET没有数据可读。这些本不应该的系统开销降低了性能。<br>　　<br>　　<strong>TOMCAT 5中的集群</strong><br>　　<br>
虽然在TOMCAT5的早些版本中也有集群的功能，但是在稍后的版本中（5。0。19或者更高），集群变的更加模块组件化。在 server.xml
中集群元素已经被重构，这样我们可以替换集群的不同部分而不会影响其他元素。例如，当前配置中把成员服务设置为多点传送发现。这里可以轻易地把成员服务修
改替换为使用TCP或者 Unicast ，而不会改变集类逻辑的其他部分。<br>　　<br>　　其他一些集群元素，例如SESSION管理器，复制发送端，复制接受端也可以被自定义的实现取代而不影响集群配置的其他部分。同样，在TOMCAT集群中的任何服务器组件可以使用集类API向集群中的所有成员发送消息。<br>　　<br>　　<strong>SESSION复制</strong><br>　　<br>
服务器集群通常操纵两种SESSION： sticky sessions和 replicated sessions 。sticky
sessions就是存在单机服务器中的接受网络请求的SESSION，其他集群成员对该服务器的SESSION状态完全不清楚，如果存有SESSION
的服务器失败的话，用户必须再次登陆网站，重新输入所有存储在SESSION中的数据。<br>　　<br>　　另一种SESSION类型是，在一台服务
器中SESSION状态被复制到集群中的其他所有服务器上，无论何时，只要SESSION 被改变，SESSION数据都要重新被复制。这就是
replicated session 。 sticky 和 replicated sessions都有他们的优缺点， Sticky
sessions简单而又容易操作，因为我们不必复制任何SESSION数据到其他服务器上。这样就会减少系统消耗，提高性能。但是如果服务器失败，所有
存储在该服务器内存中的SESSION数据也同样会消失。如果SESSION数据没有被复制到其他服务器，这些SESSION就完全丢失了。当我们在进行
一个查询事务当中的时候，丢失所有已经输入的数据，就会导致很多问题。<br>　　<br>　　为了支持 JSP HTTP session 状态的自动失效无缝转移，TOMCAT服务器复制了在内存中的SESSION状态。这是通过复制存储在一台服务器上的SESSION数据到集群中其他成员上防止数据丢失以及允许失效无缝转移。<br>　　<br>　　<strong>对象的状态管理</strong><br>　　<br>　　通过在服务器上的保存状态可以区分出4种对象：<br>　　<br>
●无状态：一个无状态对象在调用的时候不会在内存中保存任何状态，因为客户端和服务器端没必要保存任何有关对方的信息。在这种情况下，客户端会在每次请
求服务器时都会发送数据给服务器。SESSION状态被在客户端和服务器端来回发送。这种方法不总是可行和理想的，特别是当传输的数据比较大或者一些安全
信息我们不想保存在客户端的时候；<br>　　<br>　　●会话：一个会话对象在一个SESSION中只被用于特定的某个客户端。在SESSION中，
他可以为所有来自该客户端的请求服务，并且仅仅是这个客户端的请求。贯穿一个SESSION，两个请求间的状态信息必须保存。会话服务通常在内存中保存短
暂的状态，当在服务器失败的时候可能会丢失。SESSION状态通常被保存在请求间的服务器的内存中。为了清空内存，SESSION状态也可以被从内存中
释放（就像在一个对象CACHE）。在该对象中，性能和可量测性都有待提高，因为更新并不是被单独的写到磁盘上，并且服务器失败的时候数据也没办法抢救。<br>　　<br>　　●缓存：缓存对象在内存中保存状态，并且使用这个去处理从多客户端来的请求。缓存服务的实现可以扩展到他们把缓存的是数据备份保存在后端存储器中（通常是一个关系数据库）。<br>　　<br>
●独立的：一个独立的对象在一个时间内只活跃在集群中的一台服务器上，处理来自多客户端的请求。他通常由那些私有的，持久的，在内存中缓寸的数据支持。
他同样也在内存中保持短暂状态，在服务器失败的时候要重建或者丢失。当失败的时候，独立对象必须在同一个服务器上重起或者移植到另一台服务器上。<br>　　<br>　　(来源: "Using WebLogic Server Clusters")<br>　　<br>　　<strong>SESSION复制的设计考虑事项<br>　　<br>　　网络考虑事项</strong><br>　　<br>
把集群的多点传送地址和其他应用程序隔离是至关重要的。我们不希望集群配置或者网络布局干扰到多点传送服务器通信。和其他应用程序共享集群多点传送地址
将迫使集群的服务器例程处理不应该的消息，消耗系统内存。共享多点传送地址可能也会使IP多点传送缓冲过载，延迟服务器 heartbeat
消息传输。这样的延迟可能导致一个服务器例程被标识为死亡，仅仅因为他的 heartbeat 消息没有被及时接收。<br>　　<br>　　<strong>编程考虑事项</strong><br>　　<br>　　除了上面提到的网络相关因素，还有些和我们写 J2EE 网络应用程序有关的设计考虑也会影响SESSION复制。以下列出了一些编程方面的考虑：<br>　　<br>　　●SESSION数据必须被序列化：为了支持HTTP session 状态的内存内复制，所有的 servlet 和 JSP session 数据必须被序列化，对象中的每个域都必须被序列化，这样对象被可靠的序列化。<br>　　<br>
●把应用程序设计为幂等的：幂等的的意思就是一个操做不会修改状态信息，并且每次操作的时候都返回同样的结果（换句话说就是：做多次和做一次的效果是一
样的），通常，WEB请求，特别是 HTML forms
都被发送多次（当用户点击发送按纽两次，重载页面多次），导致多次HTTP请求。设计SERVLET和其他WEB对象为
幂等的，可以容忍多次请求。详细可以去参考设计模式&#8220;Synchronized Token &#8221;和&#8220;Idempotent Receiver
&#8221;关于怎样设计幂等的的应用程序。<br>　　<br>　　●在BUSINESS层存储状态：会话状态应该使用有状态的SESSION
BEANS存储在EJB层，而不是存储在WEB层的HttpSession。因为企业应用程序要支持各种类型客户端（WEB客户端，JAVA应用程序，其
他EJB），存储数据在WEB层会导致在客户端的双数据存储。因此，有状态的SESSION
BEAN在这些情况下就被用于存储SESSION状态。无状态的SESSION
BEAN要为每次的调用重构造会话状态。这些状态可能必须从数据库中恢复的数据中重编译。这些缺点失去了使用无状态SESSION
BEAN去提高性能和可测量性的目的，严重的减低了性能。<br>　　<br>　　●序列化系统消耗：序列化SESSION数据在复制SESSION状态的时候回会些系统消耗。随着序列化对象大小的增长消耗也越多。最好是保持SE<br><br><br>
<p>实践中整理出tomcat集群和负载均衡<br>(一)环境说明<br>(1)服务器有4台，一台安装apache,三台安装tomcat<br>(2)apache2.0.55、tomcat5.5.15、jk2.0.4、jdk1.5.6或jdk1.4.2<br>(3)ip配置,一台安装apache的ip为192.168.0.88,三台安装tomcat的服务器ip分别为192.168.0.1/2/4<br>(二)安装过程<br>(1)在三台要安装tomcat的服务器上先安装jdk<br>(2)配置jdk的安装路径,在环境变量path中加入jdk的bin路径,新建环境变量JAVA_HOME指向jdk的安装路径<br>(3)在三台要安装tomcat的服务器上分别安装tomcat,调试三个tomcat到能够正常启动<br>(4)tomcat的默认WEB服务端口是8080,默认的模式是单独服务,我的三个tomcat的WEB服务端口修改为7080/8888/9999<br>修改位置为tomcat的安装目录下的conf/server.xml<br>修改前的配置为<br>&nbsp;&nbsp;&nbsp;&nbsp;&lt;Connector port="8080" maxHttpHeaderSize="8192"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; maxThreads="150" minSpareThreads="25" maxSpareThreads="75"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; enableLookups="false" redirectPort="8443" acceptCount="100"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; connectionTimeout="20000" disableUploadTimeout="true" /&gt;<br>修改后的配置为<br>&nbsp;&nbsp;&nbsp;&nbsp;&lt;Connector port="7080" maxHttpHeaderSize="8192"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; maxThreads="150" minSpareThreads="25" maxSpareThreads="75"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; enableLookups="false" redirectPort="8443" acceptCount="100"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; connectionTimeout="20000" disableUploadTimeout="true" /&gt;<br>依次修改每个tomcat的监听端口(7080/8888/9999)<br><br>(5)分别测试每个tomcat的启动是否正常<br>http://192.168.0.1:7080<br>http://192.168.0.2:8888<br>http://192.168.0.4:9999<br>(三)负载均衡配置过程<br>(1)在那台要安装apache的服务器上安装apache2.0.55,我的安装路径为默认C:\Program Files\Apache Group\Apache2<br>(2)安装后测试apache能否正常启动，调试到能够正常启动http://192.168.0.88<br>(3)下载jk2.0.4后解压缩文件<br>(4)将解压缩后的目录中的modules目录中的mod_jk2.so文件复制到apache的安装目录下的modules目录中,我的为C:\Program Files\Apache Group\Apache2\modules<br>(5)修改apache的安装目录中的conf目录的配置文件httpd.conf，在文件中加LoadModule模块配置信息的最后加上一句LoadModule jk2_module modules/mod_jk2.so<br>(6)分别修改三个tomcat的配置文件conf/server.xml，修改内容如下<br>修改前<br>&nbsp;&nbsp;&nbsp;&nbsp;&lt;!-- An Engine represents the entry point (within Catalina) that processes<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; every request.&nbsp;&nbsp;The Engine implementation for Tomcat stand alone<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; analyzes the HTTP headers included with the request, and passes them<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; on to the appropriate Host (virtual host). --&gt;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&lt;!-- You should set jvmRoute to support load-balancing via AJP ie :<br>&nbsp;&nbsp;&nbsp;&nbsp;&lt;Engine name="Standalone" defaultHost="localhost" jvmRoute="jvm1"&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;--&gt; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&lt;!-- Define the top level container in our container hierarchy --&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&lt;Engine name="Catalina" defaultHost="localhost"&gt;<br>修改后<br>&nbsp;&nbsp;&nbsp;&nbsp;&lt;!-- An Engine represents the entry point (within Catalina) that processes<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; every request.&nbsp;&nbsp;The Engine implementation for Tomcat stand alone<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; analyzes the HTTP headers included with the request, and passes them<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; on to the appropriate Host (virtual host). --&gt;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&lt;!-- You should set jvmRoute to support load-balancing via AJP ie :--&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&lt;Engine name="Standalone" defaultHost="localhost" jvmRoute="tomcat1"&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&lt;!-- Define the top level container in our container hierarchy <br>&nbsp;&nbsp;&nbsp;&nbsp;&lt;Engine name="Catalina" defaultHost="localhost"&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;--&gt;<br>将其中的jvmRoute="jvm1"分别修改为jvmRoute="tomcat1"和jvmRoute="tomcat2"和jvmRoute="tomcat3"<br><br>(7)然后重启三个tomcat，调试能够正常启动。<br>(8)在apache的安装目录中的conf目录下创建文件workers2.propertie，写入文件内容如下<br><br># fine the communication channel <br>[channel.socket:192.168.0.1:8009] <br>info=Ajp13 forwarding over socket<br>#配置第一个服务器 <br>tomcatId=tomcat1 #要和tomcat的配置文件server.xml中的jvmRoute="tomcat1"名称一致<br>debug=0 <br>lb_factor=1 #负载平衡因子，数字越大请求被分配的几率越高<br><br># Define the communication channel <br>[channel.socket:192.168.0.2:8009] <br>info=Ajp13 forwarding over socket<br>tomcatId=tomcat2 <br>debug=0 <br>lb_factor=1 <br><br># Define the communication channel <br>[channel.socket:192.168.0.4:8009] <br>info=Ajp13 forwarding over socket<br>tomcatId=tomcat3 <br>debug=0 <br>lb_factor=1 <br><br>[status:] <br>info=Status worker, displays runtime information.&nbsp;&nbsp;<br><br>[uri:/jkstatus.jsp] <br>info=Display status information and checks the config file for changes. <br>group=status: <br><br>[uri:/*] <br>info=Map the whole webapp <br>debug=0<br>(9)在三个tomcat的安装目录中的webapps建立相同的应用,我和应用目录名为TomcatDemo,在三个应用目录中建立相同 WEB-INF目录和页面index.jsp,index.jsp的页面内容如下<br>&lt;%@ page contentType="text/html; charset=GBK" %&gt;<br>&lt;%@ page import="java.util.*" %&gt;<br>&lt;html&gt;&lt;head&gt;&lt;title&gt;Cluster App Test&lt;/title&gt;&lt;/head&gt;<br>&lt;body&gt;<br>Server Info:<br>&lt;%<br>out.println(request.getLocalAddr() + " : " + request.getLocalPort()+"&lt;br&gt;");%&gt;<br>&lt;%<br>&nbsp;&nbsp;out.println("&lt;br&gt; ID " + session.getId()+"&lt;br&gt;");<br><br>&nbsp;&nbsp;// 如果有新的 Session 属性设置<br>&nbsp;&nbsp;String dataName = request.getParameter("dataName");<br>&nbsp;&nbsp;if (dataName != null &amp;&amp; dataName.length() &gt; 0) {<br>&nbsp;&nbsp;&nbsp;&nbsp; String dataValue = request.getParameter("dataValue");<br>&nbsp;&nbsp;&nbsp;&nbsp; session.setAttribute(dataName, dataValue);<br>&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;out.print("&lt;b&gt;Session 列表&lt;/b&gt;");<br><br>&nbsp;&nbsp;Enumeration e = session.getAttributeNames();<br>&nbsp;&nbsp;while (e.hasMoreElements()) {<br>&nbsp;&nbsp;&nbsp;&nbsp; String name = (String)e.nextElement();<br>&nbsp;&nbsp;&nbsp;&nbsp; String value = session.getAttribute(name).toString();<br>&nbsp;&nbsp;&nbsp;&nbsp; out.println( name + " = " + value+"&lt;br&gt;");<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; System.out.println( name + " = " + value);<br>&nbsp;&nbsp; }<br>%&gt;<br>&nbsp;&nbsp;&lt;form action="index.jsp" method="POST"&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;名称:&lt;input type=text size=20 name="dataName"&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp; &lt;br&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;值:&lt;input type=text size=20 name="dataValue"&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp; &lt;br&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&lt;input type=submit&gt;<br>&nbsp;&nbsp; &lt;/form&gt;<br>&lt;/body&gt;<br>&lt;/html&gt;<br>(10)重启apache服务器和三个tomcat服务器,到此负载 均衡已配置完成。测试负载均衡先测试apache,访问http://192.168.0.88/jkstatus.jsp<br>能否正常访问，并查询其中的内容，有三个tomcat的相关配置信息和负载说明,访问http://192.168.0.88/TomcatDemo/index.jsp看能够运行,<br>能运行，则已建立负载均衡。<br>(四)tomcat集群配置<br>(1)负载均衡配置的条件下配置tomcat集群<br>(2)分别修改三个tomcat的配置文件conf/server.xml,修改内容如下<br>修改前<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;!-- <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;Cluster className="org.apache.catalina.cluster.tcp.SimpleTcpCluster"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; managerClassName="org.apache.catalina.cluster.session.DeltaManager"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; expireSessionsOnShutdown="false"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; useDirtyFlag="true"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; notifyListenersOnReplication="true"&gt;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;Membership <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;className="org.apache.catalina.cluster.mcast.McastService"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mcastAddr="228.0.0.4"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mcastPort="45564"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mcastFrequency="500"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mcastDropTime="3000"/&gt;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;Receiver <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;className="org.apache.catalina.cluster.tcp.ReplicationListener"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tcpListenAddress="auto"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tcpListenPort="4001"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tcpSelectorTimeout="100"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tcpThreadCount="6"/&gt;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;Sender<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;className="org.apache.catalina.cluster.tcp.ReplicationTransmitter"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;replicationMode="pooled"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ackTimeout="5000"/&gt;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;Valve className="org.apache.catalina.cluster.tcp.ReplicationValve"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; filter=".*\.gif;.*\.js;.*\.jpg;.*\.png;.*\.htm;.*\.html;.*\.css;.*\.txt;"/&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;Deployer className="org.apache.catalina.cluster.deploy.FarmWarDeployer"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tempDir="/tmp/war-temp/"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;deployDir="/tmp/war-deploy/"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;watchDir="/tmp/war-listen/"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;watchEnabled="false"/&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;ClusterListener className="org.apache.catalina.cluster.session.ClusterSessionListener"/&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;/Cluster&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;--&gt;&nbsp;&nbsp; <br>修改后<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;!-- modify by whh --&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;Cluster className="org.apache.catalina.cluster.tcp.SimpleTcpCluster"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; managerClassName="org.apache.catalina.cluster.session.DeltaManager"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; expireSessionsOnShutdown="false"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; useDirtyFlag="true"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; notifyListenersOnReplication="true"&gt;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;Membership <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;className="org.apache.catalina.cluster.mcast.McastService"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mcastAddr="228.0.0.4"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mcastPort="45564"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mcastFrequency="500"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mcastDropTime="3000"/&gt;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;Receiver <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;className="org.apache.catalina.cluster.tcp.ReplicationListener"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tcpListenAddress="auto"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tcpListenPort="4001"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tcpSelectorTimeout="100"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tcpThreadCount="6"/&gt;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;Sender<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;className="org.apache.catalina.cluster.tcp.ReplicationTransmitter"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;replicationMode="pooled"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ackTimeout="5000"/&gt;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;Valve className="org.apache.catalina.cluster.tcp.ReplicationValve"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; filter=".*\.gif;.*\.js;.*\.jpg;.*\.png;.*\.htm;.*\.html;.*\.css;.*\.txt;"/&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;Deployer className="org.apache.catalina.cluster.deploy.FarmWarDeployer"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;tempDir="/tmp/war-temp/"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;deployDir="/tmp/war-deploy/"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;watchDir="/tmp/war-listen/"<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;watchEnabled="false"/&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;ClusterListener className="org.apache.catalina.cluster.session.ClusterSessionListener"/&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;/Cluster&gt;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &lt;!-- modify by whh --&gt;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>将集群配置选项的注释放开即可，如上。<br>(3)重启三个tomcat。到此tomcat的集群已配置完成。</p>
<p>&nbsp;</p>
<p>(五)应用配置<br>对于要进行负载和集群的的tomcat目录下的webapps中的应用中的WEB-INF中的web.xml文件要添加如下一句配置<br>&lt;distributable/&gt;<br>配置前<br>&lt;?xml version="1.0" encoding="UTF-8"?&gt;<br>&lt;web-app
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4"&gt;<br>&nbsp;&nbsp;&lt;display-name&gt;TomcatDemo&lt;/display-name&gt;<br>&lt;/web-app&gt;<br>配置后<br>&lt;?xml version="1.0" encoding="UTF-8"?&gt;<br>&lt;web-app
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4"&gt;<br>&nbsp;&nbsp;&lt;display-name&gt;TomcatDemo&lt;/display-name&gt;<br>&nbsp;&nbsp; &lt;distributable/&gt;<br>&lt;/web-app&gt;<br><!-- the post be hidden --></p>
<p>问:tomcat集群是怎么处理session的阿</p>
答:在tomcat做集群之后，每个tomcat之间自动根据tomcat的配置文件中的参数进行session复制,<br>对于一个客户端对说，只要是同一个IP，那它每次上传的sessionID就是一样的,<br><img src ="http://www.blogjava.net/sutao/aggbug/134191.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/sutao/" target="_blank">苏醄</a> 2007-08-03 11:10 <a href="http://www.blogjava.net/sutao/articles/134191.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Hibernate二级缓存全攻略</title><link>http://www.blogjava.net/sutao/articles/134086.html</link><dc:creator>苏醄</dc:creator><author>苏醄</author><pubDate>Thu, 02 Aug 2007 10:06:00 GMT</pubDate><guid>http://www.blogjava.net/sutao/articles/134086.html</guid><wfw:comment>http://www.blogjava.net/sutao/comments/134086.html</wfw:comment><comments>http://www.blogjava.net/sutao/articles/134086.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/sutao/comments/commentRss/134086.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/sutao/services/trackbacks/134086.html</trackback:ping><description><![CDATA[<font size="2">&nbsp;&nbsp; 【IT168 技术文档】很多人对二级缓存都不太了解，或者是有错误的认识，我一直想写一篇文章介绍一下hibernate的二级缓存的，今天终于忍不住了。 <br>我的经验主要来自hibernate2.1版本，基本原理和3.0、3.1是一样的，请原谅我的顽固不化。</font>
<p><font size="2">&nbsp;&nbsp;&nbsp; hibernate的session提供了一级缓存，每个session，对同一个id进行两次load，不会发送两条sql给数据库，但是session关闭的时候，一级缓存就失效了。</font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp; 二级缓存是SessionFactory级别的全局缓存，它底下可以使用不同的缓存类库，比如ehcache、oscache等，需要设置hibernate.cache.provider_class，我们这里用ehcache，在2.1中就是 <br>hibernate.cache.provider_class=net.sf.hibernate.cache.EhCacheProvider <br>如果使用查询缓存，加上 <br>hibernate.cache.use_query_cache=true</font></p>
<p><font size="2">缓存可以简单的看成一个Map，通过key在缓存里面找value。</font></p>
<p><font size="2"><strong>Class的缓存</strong><br>&nbsp;&nbsp;&nbsp;
对于一条记录，也就是一个PO来说，是根据ID来找的，缓存的key就是ID，value是POJO。无论list，load还是
iterate，只要读出一个对象，都会填充缓存。但是list不会使用缓存，而iterate会先取数据库select
id出来，然后一个id一个id的load，如果在缓存里面有，就从缓存取，没有的话就去数据库load。假设是读写缓存，需要设置： <br><br>&lt;cache usage="read-write"/&gt; <br><br>&nbsp;&nbsp;&nbsp; 如果你使用的二级缓存实现是ehcache的话，需要配置ehcache.xml <br>&lt;cache
name="com.xxx.pojo.Foo" maxElementsInMemory="500" eternal="false"
timeToLiveSeconds="7200" timeToIdleSeconds="3600" overflowToDisk="true"
/&gt; <br><br>&nbsp;&nbsp;&nbsp;
其中eternal表示缓存是不是永远不超时，timeToLiveSeconds是缓存中每个元素（这里也就是一个POJO）的超时时间，如果
eternal="false"，超过指定的时间，这个元素就被移走了。timeToIdleSeconds是发呆时间，是可选的。当往缓存里面put
的元素超过500个时，如果overflowToDisk="true"，就会把缓存中的部分数据保存在硬盘上的临时文件里面。 <br><br>&nbsp;&nbsp;&nbsp; 每个需要缓存的class都要这样配置。如果你没有配置，hibernate会在启动的时候警告你，然后使用defaultCache的配置，这样多个class会共享一个配置。 <br>当某个ID通过hibernate修改时，hibernate会知道，于是移除缓存。 <br><br>&nbsp;&nbsp;&nbsp;
这样大家可能会想，同样的查询条件，第一次先list，第二次再iterate，就可以使用到缓存了。实际上这是很难的，因为你无法判断什么时候是第一
次，而且每次查询的条件通常是不一样的，假如数据库里面有100条记录，id从1到100，第一次list的时候出了前50个id，第二次
iterate的时候却查询到30至70号id，那么30-50是从缓存里面取的，51到70是从数据库取的，共发送1+20条sql。所以我一直认为
iterate没有什么用，总是会有1+N的问题。 <br><br>&nbsp;&nbsp;&nbsp;
（题外话：有说法说大型查询用list会把整个结果集装入内存，很慢，而iterate只select
id比较好，但是大型查询总是要分页查的，谁也不会真的把整个结果集装进来，假如一页20条的话，iterate共需要执行21条语句，list虽然选择
若干字段，比iterate第一条select
id语句慢一些，但只有一条语句，不装入整个结果集hibernate还会根据数据库方言做优化，比如使用mysql的limit，整体看来应该还是
list快。） <br><br>&nbsp;&nbsp;&nbsp; 如果想要对list或者iterate查询的结果缓存，就要用到查询缓存了</font></p>
<p><br></p>
<p><font size="2"><br></font>
</p>
<p><font size="2"><strong>查询缓存<br></strong><br>&nbsp;&nbsp;&nbsp; 首先需要配置hibernate.cache.use_query_cache=true <br><br>&nbsp;&nbsp;&nbsp; 如果用ehcache，配置ehcache.xml，注意hibernate3.0以后不是net.sf的包名了 <br>&lt;cache name="net.sf.hibernate.cache.StandardQueryCache" <br>maxElementsInMemory="50" eternal="false" timeToIdleSeconds="3600" <br>timeToLiveSeconds="7200" overflowToDisk="true"/&gt; <br>&lt;cache name="net.sf.hibernate.cache.UpdateTimestampsCache" <br>maxElementsInMemory="5000" eternal="true" overflowToDisk="true"/&gt; <br>然后 <br>query.setCacheable(true);//激活查询缓存 <br>query.setCacheRegion("myCacheRegion");//指定要使用的cacheRegion，可选 <br>第二行指定要使用的cacheRegion是myCacheRegion，即你可以给每个查询缓存做一个单独的配置，使用setCacheRegion来做这个指定，需要在ehcache.xml里面配置它： <br><br>&lt;cache
name="myCacheRegion" maxElementsInMemory="10" eternal="false"
timeToIdleSeconds="3600" timeToLiveSeconds="7200" overflowToDisk="true"
/&gt; <br><br>&nbsp;&nbsp;&nbsp; 如果省略第二行，不设置cacheRegion的话，那么会使用上面提到的标准查询缓存的配置，也就是net.sf.hibernate.cache.StandardQueryCache</font><font size="2">对于查询缓存来说，缓存的key是根据hql生成的sql，再加上参数，分页等信息（可以通过日志输出看到，不过它的输出不是很可读，最好改一下它的代码）。 <br>比如hql： <br>from Cat c where c.name like ? <br>生成大致如下的sql： <br>select * from cat c where c.name like ? <br><br>&nbsp;&nbsp;&nbsp; 参数是"tiger%"，那么查询缓存的key*大约*是这样的字符串（我是凭记忆写的，并不精确，不过看了也该明白了）： <br><br>select * from cat c where c.name like ? , parameter:tiger% <br><br>&nbsp;&nbsp;&nbsp; 这样，保证了同样的查询、同样的参数等条件下具有一样的key。 <br>现
在说说缓存的value，如果是list方式的话，value在这里并不是整个结果集，而是查询出来的这一串ID。也就是说，不管是list方法还是
iterate方法，第一次查询的时候，它们的查询方式很它们平时的方式是一样的，list执行一条sql，iterate执行1+N条，多出来的行为是
它们填充了缓存。但是到同样条件第二次查询的时候，就都和iterate的行为一样了，根据缓存的key去缓存里面查到了value，value是一串
id，然后在到class的缓存里面去一个一个的load出来。这样做是为了节约内存。 <br><br>&nbsp;&nbsp;&nbsp; 可以看出来，查询缓存需要打开相关类的class缓存。list和iterate方法第一次执行的时候，都是既填充查询缓存又填充class缓存的。 <br><br><strong>&nbsp;&nbsp;&nbsp; 这里还有一个很容易被忽视的重要问题，即打开查询缓存以后，即使是list方法也可能遇到1+N的问题！</strong>相
同条件第一次list的时候，因为查询缓存中找不到，不管class缓存是否存在数据，总是发送一条sql语句到数据库获取全部数据，然后填充查询缓存和
class缓存。但是第二次执行的时候，问题就来了，如果你的class缓存的超时时间比较短，现在class缓存都超时了，但是查询缓存还在，那么
list方法在获取id串以后，将会一个一个去数据库load！因此，class缓存的超时时间一定不能短于查询缓存设置的超时时间！如果还设置了发呆时
间的话，保证class缓存的发呆时间也大于查询的缓存的生存时间。这里还有其他情况，比如class缓存被程序强制evict了，这种情况就请自己注意
了。</font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp; 另外，如果hql查询包含select字句，那么查询缓存里面的value就是整个结果集了。</font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp; 当hibernate更新数据库的时候，它怎么知道更新哪些查询缓存呢？ <br>hibernate在一个地方维护每个表的最后更新时间，其实也就是放在上面net.sf.hibernate.cache.UpdateTimestampsCache所指定的缓存配置里面。 <br>当
通过hibernate更新的时候，hibernate会知道这次更新影响了哪些表。然后它更新这些表的最后更新时间。每个缓存都有一个生成时间和这个缓
存所查询的表，当hibernate查询一个缓存是否存在的时候，如果缓存存在，它还要取出缓存的生成时间和这个缓存所查询的表，然后去查找这些表的最后
更新时间，如果有一个表在生成时间后更新过了，那么这个缓存是无效的。 <br>可以看出，只要更新过一个表，那么凡是涉及到这个表的查询缓存就失效了，因此查询缓存的命中率可能会比较低。</font></p>
<p><font size="2"><strong>Collection缓存<br></strong><br>&nbsp;&nbsp;&nbsp; 需要在hbm的collection里面设置 <br>&lt;cache usage="read-write"/&gt; <br>假如class是Cat，collection叫children，那么ehcache里面配置 <br>&lt;cache name="com.xxx.pojo.Cat.children" <br>maxElementsInMemory="20" eternal="false" timeToIdleSeconds="3600" timeToLiveSeconds="7200" <br>overflowToDisk="true" /&gt; <br>Collection的缓存和前面查询缓存的list一样，也是只保持一串id，但它不会因为这个表更新过就失效，一个collection缓存仅在这个collection里面的元素有增删时才失效。<br><br>&nbsp;&nbsp;&nbsp; 这样有一个问题，如果你的collection是根据某个字段排序的，当其中一个元素更新了该字段时，导致顺序改变时，collection缓存里面的顺序没有做更新。<br>
<p><strong>缓存策略<br></strong><br>&nbsp;&nbsp;&nbsp; 只读缓存（read-only）：没有什么好说的 <br>&nbsp;&nbsp;&nbsp; 读/写缓存（read-write）:程序可能要的更新数据 <br>&nbsp;&nbsp;&nbsp; 不严格的读/写缓存（nonstrict-read-write）：需要更新数据，但是两个事务更新同一条记录的可能性很小，性能比读写缓存好 <br>&nbsp;&nbsp;&nbsp; 事务缓存（transactional）：缓存支持事务，发生异常的时候，缓存也能够回滚，只支持jta环境，这个我没有怎么研究过</p>
<p>&nbsp;&nbsp;&nbsp; 读写缓存和不严格读写缓存在实现上的区别在于，读写缓存更新缓存的时候会把缓存里面的数据换成一个锁，其他事务如果去取相应的缓存数据，发现被锁住了，然后就直接取数据库查询。 <br>在hibernate2.1的ehcache实现中，如果锁住部分缓存的事务发生了异常，那么缓存会一直被锁住，直到60秒后超时。 <br>不严格读写缓存不锁定缓存中的数据。</p>
</font></p>
<p>&nbsp;</p>
<p><font size="2"><strong>使用二级缓存的前置条件<br></strong><br>&nbsp;&nbsp;&nbsp;
你的hibernate程序对数据库有独占的写访问权，其他的进程更新了数据库，hibernate是不可能知道的。你操作数据库必需直接通过
hibernate，如果你调用存储过程，或者自己使用jdbc更新数据库，hibernate也是不知道的。hibernate3.0的大批量更新和删
除是不更新二级缓存的，但是据说3.1已经解决了这个问题。 <br>这个限制相当的棘手，有时候hibernate做批量更新、删除很慢，但是你却不能自己写jdbc来优化，很郁闷吧。 <br><br>&nbsp;&nbsp;&nbsp; SessionFactory也提供了移除缓存的方法，你一定要自己写一些JDBC的话，可以调用这些方法移除缓存，这些方法是： <br>void evict(Class persistentClass) <br>Evict all entries from the second-level cache. <br>void evict(Class persistentClass, Serializable id) <br>Evict an entry from the second-level cache. <br>void evictCollection(String roleName) <br>Evict all entries from the second-level cache. <br>void evictCollection(String roleName, Serializable id) <br>Evict an entry from the second-level cache. <br>void evictQueries() <br>Evict any query result sets cached in the default query cache region. <br>void evictQueries(String cacheRegion) <br>Evict any query result sets cached in the named query cache region. <br><br>&nbsp;&nbsp;&nbsp;
不过我不建议这样做，因为这样很难维护。比如你现在用JDBC批量更新了某个表，有3个查询缓存会用到这个表，用evictQueries
(String cacheRegion)移除了3个查询缓存，然后用evict(Class
persistentClass)移除了class缓存，看上去好像完整了。不过哪天你添加了一个相关查询缓存，可能会忘记更新这里的移除代码。如果你的
jdbc代码到处都是，在你添加一个查询缓存的时候，还知道其他什么地方也要做相应的改动吗？</font></p>
<p><font size="2">----------------------------------------------------</font></p>
<p><font size="2"><strong>总结：</strong><br>&nbsp;&nbsp;&nbsp;
不要想当然的以为缓存一定能提高性能，仅仅在你能够驾驭它并且条件合适的情况下才是这样的。hibernate的二级缓存限制还是比较多的，不方便用
jdbc可能会大大的降低更新性能。在不了解原理的情况下乱用，可能会有1+N的问题。不当的使用还可能导致读出脏数据。 <br>如果受不了hibernate的诸多限制，那么还是自己在应用程序的层面上做缓存吧。 <br>&nbsp;&nbsp;&nbsp;
在越高的层面上做缓存，效果就会越好。就好像尽管磁盘有缓存，数据库还是要实现自己的缓存，尽管数据库有缓存，咱们的应用程序还是要做缓存。因为底层的缓
存它并不知道高层要用这些数据干什么，只能做的比较通用，而高层可以有针对性的实现缓存，所以在更高的级别上做缓存，效果也要好些吧。</font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp; 终于写完了，好累&#8230;&#8230;</font></p><img src ="http://www.blogjava.net/sutao/aggbug/134086.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/sutao/" target="_blank">苏醄</a> 2007-08-02 18:06 <a href="http://www.blogjava.net/sutao/articles/134086.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>IBM WebSphere Application Server诊断和调优</title><link>http://www.blogjava.net/sutao/articles/134085.html</link><dc:creator>苏醄</dc:creator><author>苏醄</author><pubDate>Thu, 02 Aug 2007 10:04:00 GMT</pubDate><guid>http://www.blogjava.net/sutao/articles/134085.html</guid><wfw:comment>http://www.blogjava.net/sutao/comments/134085.html</wfw:comment><comments>http://www.blogjava.net/sutao/articles/134085.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/sutao/comments/commentRss/134085.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/sutao/services/trackbacks/134085.html</trackback:ping><description><![CDATA[<div class="w_t1">
<div class="w_c2">
<div class="l2 f16">
<h1>IBM WebSphere Application Server诊断和调优（二）</h1>
</div>
<div class="r2 top8">[<a  href="javascript:d=document;t=d.selection?(d.selection.type!='None'?d.selection.createRange().text:''):(d.getSelection?d.getSelection():'');void(keyit=window.open('http://www.365key.com/storeit.aspx?t='+escape(d.title)+'&amp;u='+escape(d.location.href)+'&amp;c='+escape(t),'keyit','scrollbars=no,width=475,height=575,left=75,top=20,status=no,resizable=yes'));keyit.focus();">收藏此页</a>] [<a  href="javascript:window.print();">打印</a>]</div>
</div>
<div class="top11">
</div>
<div class="w_c2">
<div class="l2">
作者：<strong class="red">佚名</strong>&nbsp;&nbsp;2007-06-25
</div>
<div class="r2">
<div><strong>内容导航：</strong></div>
<a  href="javascript:;" id="Nav1" class="sel">
第1页
</a>
<div class="pp">
<div id="Nav1Menu" class="p">
<a  href="http://tech.it168.com/j/2007-06-25/200706251020109.shtml" title="第1页" class="hov1">第1页：<span>
第1页
</span></a>
<a  href="http://tech.it168.com/j/2007-06-25/200706251020109_1.shtml" title="第2页">第2页：<span>
第2页
</span></a>
<a  href="http://tech.it168.com/j/2007-06-25/200706251020109_2.shtml" title="第3页">第3页：<span>
第3页
</span></a>
<a  href="http://tech.it168.com/j/2007-06-25/200706251020109_3.shtml" title="第4页">第4页：<span>
第4页
</span></a>
</div>
</div>
</div>
<script>init_Nav();</script>
</div>
</div>
<div id="1"><font size="2">IT168
技术文档】近段时间，我们项目中用到的WebSphere应用服务器(WAS)，但在客户的production环境下极不稳定，经常宕机。给客户造成非
常不好的影响，同时，也给项目组很大压力。为此，我们花了近一个月时间对其诊断，现在基本上稳定了，需要继续观察一段时间。现在我主要将工作做一个阶段性
的总结。 <br>我们的产品环境是：WAS6.0＋DB2 8.1＋AIX5.3＋RS/6000。在该产品环境下，出现的问题非常多，现象如下： <br>WAS经常不稳定、宕机几乎一天一次，经常报告OutOfMemory(内存泄漏吗？NO)。 <br>DB2连接数过大，有时把DB2撑死，有时也把AIX撑死。 <br>AIX虚拟内存报错、分页报错、IO也报错、还有很多其它莫名奇妙的错。</font>
<p><font size="2">&nbsp;&nbsp;&nbsp; 总是，每次问题发生的现象和理论上的总是不一致，导致我们不知道从何入手，也无从检测自己的优化参数。咨询过多次IBM技术支持，只解决了某些局部问题。 <br>虽然问题依然存在，但我想，解决问题的思路、特别是理论基础，还是有一些规律和原则。</font></p>
<p><font size="2">对于WAS这块，我近段时间的主要时间集中在以下几个方面(时间顺序)： <br>1、Java性能监测工具：Jprofiler，也用到Jprobe。后来发现Jprofiler在AIX下几乎不可用。 <br>2、IBM Java虚拟机和WAS技术细节，特别是IBM JVM的GC原理，我发现它和sun、bea的差别很大。 <br>3、IBM的heap分析器Heap Analyzer、GCCollector。这两个事后监测工具非常实用，特别是我们的产品运行环境，非测试环境。 <br>4、某些Application的怀疑和诊断。 <br>5、AIX诊断，我几乎没有这个能力，只能常规监测一下，需另请高人。</font></p>
<p><font size="2">我打算将本文分成以下几个部分总结： <br>JVM原理、IBM JVM的GC策略和调优。 <br>Jprofiler和IBM工具的实际体会 <br>WAS的诊断体会和AIX调优</font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp; 下面开始主题吧，可能比较零碎，另外，开始的理论篇基本上看书都可以，我只是总结一下，再添加一些自己的理解。</font></p>
<p><font size="2">以下是我参考的最重要的两本电子书和一些网站： <br>《Inside Java Vrtual Machine》:半部分有约四章我认为非常棒，其它章节可能意义不大。 <br>《The Java Virtual Machine Specification, 2nd》：前半部分有两三章很不错，不过可以对照上一本书看。 <br>sun的hotspot虚拟机技术：</font><a  href="http://java.sun.com/javase/technologies/hotspot/" target="blank"><font size="2">http://java.sun.com/javase/technologies/hotspot/ </font></a><br><font size="2">BEA的JRockit虚拟机技术：</font><a  href="http://edocs.bea.com/jrockit/geninfo/genintro/index.html" target="blank"><font size="2">http://edocs.bea.com/jrockit/geninfo/genintro/index.html </font></a><font size="2">JVM技术文档入口，虚拟机理论，内存泄漏诊断等的索引页。 <br>IBM诊断资料：</font><a  href="http://www-128.ibm.com/developerworks/java/jdk/diagnosis/" target="blank"><font size="2">http://www-128.ibm.com/developerworks/java/jdk/diagnosis/</font></a><font size="2"> 上面有一个500多页的pdf文档，对IBM JVM技术和诊断讲解很深入。</font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp;
我不得不提的是，在查资料这块，BEA和Sun都有很好的官方文档和论坛支持，并且官方文档导航非常好。虽然IBM的诊断资料也不少，但需要搜索，其搜索
是很痛苦的。而且，IBM官方论坛很差。如果用IBM的产品出问题，切记：找IBM技术支持，千万不要蒙着头搞！反正它们的产品很少免费。说实话，它们的
技术支持还是挺负责的，一般会为你推荐很多support资料，而该资料往往都在developerworks网站上，属于support那个频道，但你
就是搜不着。</font></p>
<p><font size="2"><strong>Java虚拟机规范概要</strong> <br><br>&nbsp;&nbsp;&nbsp;
研究Java虚拟机，首先要了解Sun的Java虚拟机规范。现在，该实现版本很多，如比较有名的Sun、IBM、BEA、Apple、HP、MS、
Apache
Harmony。它们都实现了JVM规范，但有各自扩展。譬如，针对IBM虚拟机的堆碎片导致OutOfMemory（OOM），在Sun的虚拟机上就不
会发生。Sun的JVM有maxPermSize的概念，IBM就没有，如果你设置这个参数，虚拟机根本就启动不了。<br> <br>&nbsp;&nbsp;&nbsp;
比较有意思的是，学Java，就一定要了解各种规范，这和MS的风格很不一样。Sun总是在定义一些规范，实现都留给各厂商。我们除了理解规范本身外，一
定要理解规范和实现之间的关系，譬如JDBC规范和JDBC驱动的关系，它们是怎么组合到一起的。要是你用过php的xml解析库，或db函数，就会体会
深刻，它们可没有什么规范可言，所以每个数据库厂商的db函数用法都不一样。我推荐大家研读一下HSQLDB的jdbc和Tomcat的servlet相
关实现，因为我认为它们还是比较好懂的。 <br><br>&nbsp;&nbsp;&nbsp;
JVM规范只是定义一个虚拟机该做什么，但它并没有要求你该怎么做。例如我们最常见的Servlet规范，在该规范中，有
HttpServletRequest、HttpServletResponse，HttpSession等接口，但它们的实现都留给了各个容器厂商。遗
憾的是，规范留下的空白，会把我们这些开发人员给整惨了：容器间移植有时候就是恶梦。譬如J2EE并没有SSO规范，但它很重要，我以前专门针对它做过
WebSphere AppServer和Weblogic
AppServer的SSO项目，差别还是不小，不过还是有点共通，那就是都遵循JAAS规范。</font></p>
<p><br></p>
<p><font size="2"><strong>JVM的结构</strong> <br><br>&nbsp;&nbsp;&nbsp; 从功能上分，Java虚拟机主要由六个部分组成，可以分成三类： <br>第一类：JVM API：就是我们最常用的Java API，它是开发人员和Java交互的入口，它主要是JAVA_HOME/jre/lib下的运行时类库rt.jar和编译相关的tools.jar</font></p>
<p><font size="2">第二类：JVM内部组件 <br><strong>类装载器</strong>(ClassLoader)：将Byte Array的 .class文件装载、链接和初始化。 <br><strong>内存管理</strong>(Memory Managent)：为对象分配内存，以及释放内存。后者就是垃圾回收Garbage Collector（GC）。由于JVM最复杂的、最影响性能的就是GC，所以内存管理一般就指垃圾回收。 <br><strong>诊断接口</strong>(Diagostics Interface)：这主要体现在JVMTI(jdk1.4下的JVMPI和JVMDI)，它主要用来诊断程序的问题和性能，一般提供给工具厂商实现。如eclispe IDE下的debug功能，Jprofiler性能调优工具。 <br><strong>类解释器</strong>(Interpreter)：解释装载进虚拟机的class对象，包括JIT等特性相关。</font></p>
<p><font size="2">第三类：平台相关接口(Platform Interface)：主要为了跨操作系统平台重用JVM代码，不过，它和我们开发人员关系不大。</font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp; 在以上六个组件中，我们开发人员最关心的是<strong>ClassLoader</strong>和<strong>GC</strong>，
用Java做系统框架、容器和它们密切相关。做业务系统时一些基础代码也和它们打交道，譬如最常用的Class.forName(),
Thread.currentThread.getContextClassLoader()。我们仔细想想，为什么是上面两个问题？因为，它和我们
class的整个生命周期最为相关：怎么将一个class和相关class加载进来，class实例什么时候创建，什么时候被销毁？ <br>所以，下面的部分我们要专门讨论这些问题。</font></p>
<p><font size="2"><strong>ClassLoader</strong> <br><br>&nbsp;&nbsp;&nbsp; JVM主要有三类ClassLoader：Bootstrap、Extention、Application，该三类ClassLoader从上到下是分级(hierarchy)结构，遵循代理模型(Delegation Model)。 <br>Tip：大家可以看看sun.misc.Launcher的源码，Bootstrap和Extention就在该文件里。该src可以在sun的网站上下载该压缩包，约60M(jdk-1_5_0-src-scsl.zip)，它不在jdk自带的那个src.zip里。</font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp; Bootstrap ClassLoader：也称为primordial(root) class
loader。主要是负责装载jre/lib下的jar文件，当然，你也可以通过-Xbootclasspath参数定义。该ClassLoader不能
被Java代码实例化，因为它是JVM本身的一部分。</font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp; Extention ClassLoader：该ClassLoader是Bootstrap
classLoader的子class
loader。它主要负责加载jre/lib/ext/下的所有jar文件。只要jar包放置这个位置，就会被虚拟机加载。一个常见的、类似的问题是，你
将mysql的低版本驱动不小心放置在这儿，但你的Web应用程序的lib下有一个新的jdbc驱动，但怎么都报错，譬如不支持JDBC2.0的
DataSource，这时你就要当心你的新jdbc可能并没有被加载。这就是ClassLoader的delegate现象。常见的有log4j、
common-log、dbcp会出现问题，因为它们很容易被人塞到这个ext目录，或是Tomcat下的common/lib目录。</font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp; Application ClassLoader：也称为System
ClassLoaer。它负责加载CLASSPATH环境变量下的classes。缺省情况下，它是用户创建的任何ClassLoader的父
ClassLoader，我们创建的standalone应用的main
class缺省情况下也是由它加载(通过Thread.currentThread().getContextClassLoader()查看)。 <br>我们实际开发中，用ClassLoader更多时候是用其加载classpath下的资源，特别是配置文件，如ClassLoader.getResource()，比FileInputStream直接。</font></p>
<p><font size="2">ClassLoader是一种分级(<strong>hierarchy</strong>)的代理(<strong>delegation</strong>)模型。 <br>Delegation：
其实是Parent
Delegation，当需要加载一个class时，当前线程的ClassLoader首先会将请求代理到其父classLoader，递归向上，如果该
class已经被父classLoader加载，那么直接拿来用，譬如典型的ArrayList，它最终由Bootstrap
ClassLoader加载。并且，每个ClassLoader只有一个父ClassLoader。 <br>Class查找的位置和顺序依次是：Cache、parent、self。 <br>Hierarchy：
上面的delegation已经暗示了一种分级结构，同时它也说明：一个ClassLoader只能看到被它自己加载的classes，或是看到其父
(parent) ClassLoader或祖先(ancestor) ClassLoader加载的Classes。 <br>在一个单虚拟机环境下，标识一个类有两个因素：class的全路径名、该类的ClassLoader。</font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp; 我碰到的一个典型的例子是：在做WAS的SSO开发时，由于我们的类是由WAS在启动时加载，该ClassLoader比下面的部署的Applicaton的ClassLoader的级别高。所以，在我们自己的类中没法用到应用程序的连接池，必须自建。 <br>代
理模型是Java安全模型的保证。譬如，我们自己写一个String.java，并且编译、package到自己的java.lang包下。按照代理模
型，当前线程的ClassLoader会将其代理到父ClassLoader，父ClassLoader(最终会是Bootstrap)会找到
rt.jar下的String.class，也就是说我们的String.class不会捣乱。</font></p>
<p><font size="2"><strong>自定义ClassLoader</strong> <br>&nbsp;&nbsp;&nbsp; 我们前面说过，自定义ClassLoader的缺省父ClassLoader是Application ClassLoader。一般的应用开发用不到它，但我们最好理解。因为在内存泄漏查找、应用程序部署出问题时，很多都和它有关。 <br>譬
如，内存泄漏是怎么产生的？这就涉及到ClassLoader和Class的生命周期。我曾经碰到这样一个问题：我们的程序用到了Webwork和
Spring框架，当部署到Tomcat下时没有任何问题，但部署到WAS下，报告找不到Webwork的xml的DTD文件，而且Spring的日志也
总是失效。Why？因为解析xml
dtd时，用的是IBM的Xerces，不是我们的。而Spring日志问题是因为应用程序用的是WAS的Common-log.jar，而不是我们的。
将应用的ClassLoader从默认的Parent-First，改成Parent-Last就可以解决，不过我们项目中用到其它库，又发生了其它问
题。</font></p>
<p><font size="2">一般来说，用到自定义ClassLoader有三种情况： <br>1、应用框架可以自己控制Classes的目录，并且自动部署。 <br>我读过Jive公司的Wildfire(著名的即时通讯服务器)，它自己有一套应用框架，非常灵活，遵循该框架插件规范的的第三方的plug-in放置在指定目录可以自动部署，实现某些扩展功能，如文件传输、语音聊天。 <br>2、区分用户代码 <br>这被广泛应用在Servlet容器和类似容器，譬如EJB Container设计中，大家看到Tomcat下有common、server、share三个目录吧(ClassLoader顺序从左到有)，另外也有用户应用的WEB-INF目录，它是我们自己开发的。 <br>3、允许Classes卸载 <br>如
果没有自定义的ClassLoader，那么我们自己应用中的classes永远都不能被卸载，因为这些类被Application
ClassLoader加载后cache起来了，我们的classes一直对该ClassLoader有引用，而该系统级的ClassLoader永远都
不会被卸载，除非JVM shutdown了。JSP和Servlet的动态部署就用到这个特性。</font></p>
<p><font size="2">待续.......</font></p>
<p><font size="2">Note: 还有JVM运行时(Runtime)架构，ClassLoader加载class过程没有总结，这两部分我觉得太重要了，但内容太多，写不完啊。 <br>这部分内容，《Inside Java Virtual Machine》讲解非常清楚，BEA的官方网站这部分也非常不错，要理解深刻，我建议结合JProfiler工具，非常直观。</font></p>
<p><font size="2">待续.......</font></p>
<br><font size="2"><br><br><br>【IT168 技术文档】<br><br></font>
<p><font size="2">&nbsp;&nbsp;&nbsp; 续写这篇文章，已经过去一个半月了。直到现在，系统一直运行平稳。 <br>先说说我接手这项工作的经历
吧：该项目大部分是06年10月就部署在客户那边了，到07年3月份，WAS宕机问题实在无法忍受，我才加入进来，前半年有另外一位同事断断续续处理，但
对问题一直都无可奈何，而且项目负责人也没有引起足够的重视。可想而知，最后付出的代价是非常惨重的。在这近半年的时间内，服务器宕机63次。每次宕机
时，WAS的JVM会dump出一个heapdump.phd文件(heap快照)，然后JVM就死掉了，当然，此时WAS也停止了响应。一般我们的做法
是重启，最后是干脆AIX每天晚上定时重启。有时候一天还死多次。大家见附件的截图（all-GC.png）。这是我接手后，用IBM的分析工具得到的截
图。对截图的分析，留给后面对应的部分吧。 <br>服务器不稳定、宕机问题，拖延到最后，客户愤怒了，公司高层也害怕了，部门还专门成立了八人攻关组。当然了，我当时的压力也非常大，因为我是技术负责人，也就是实实在在干活、想主意的。 <br>服务器诊断那段时间，从前到后，我们也是沿着一条线走下来，虽然最后发现很多路都走不通。现在就按这个思路，也就是时间先后一步步叙述吧。我想，大家如果也碰到类似应用服务器诊断，应该思路差不多。</font></p>
<p><font size="2">术语说明： <br>IBM Websphere Application Server：WAS，WebSphere本身是一个平台，产品家族 <br>OutOfMemoryError：OOM，内存泄漏，内存溢出 <br>Gabage Collection：GC，自动垃圾回收 <br>Content Management System：CMS，就是给新闻类门户网站编辑们用的系统</font></p>
<p><font size="2">我们诊断大体上经历了以下几个阶段： <br>1、按Job调度线程池引起内存泄漏诊断：因为很多次OOM是发生在某个特定时候，譬如14：30、22：40左右。 <br>2、按应用程序引起内存泄漏诊断：用JProfiler等工具探测：因为总是发生OOM。 <br>3、分离WAS怀疑有OOM的应用：因为每个WAS应用太多，20来个，混一起没法定位。 <br>4、用IBM官方heap、GC分析工具。以及和IBM技术支持联系。WAS、AIX参数优化。 <br>5、隔离出WAS超级恶魔程序：一个CMS产品。 <br>6、WAS、AIX参数优化、设置。</font></p>
<p><font size="2">我们走到第5步时，才出现效果。计算一下，那时已经过去一个月了。服务器宕机、系统不稳定，在这个验收的时候，客户已经忍无可忍，以致后来的每一次行动都得胆战心惊得去做。</font></p>
<p><font size="2"><strong>一、按Job调度线程池导致内存泄漏诊断</strong> <br>&nbsp;&nbsp;&nbsp; 因为从我们WAS的日志(默认是native_stderr.log)来看，最近半年的宕机时间都有一个明显时间规律。见附件截图Job1-1.png。 <br>&nbsp;&nbsp;&nbsp;
我想，做过Java服务器性能调优的朋友，都知道在Web容器里面启线程池是个不太好的做法，因为Web容器本身有一个线程池，譬如Servlet线程池
（Tomcat默认起25个），而自启的线程池很容易导致Servlet线程管理混乱，最终导致GC问题。我们的现象似乎和那很符合。如果我们沿着这个思
路做下去，具体怎样实施呢？</font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp;
我们的WAS上部署了20个左右的Web应用，譬如Lucene全文检索、B2B行业数据同步等，都是通过Quartz的Job调度做的，当然还有很多其
它调度。当时，由我负责，通知相关负责人，将定时调度暂时去掉。观察了几天，后来发现问题依然存在，不过时间有点随机了。 <br>不过，最后还是发现OOM不是由Job调度引起的。 <br>也
就是说，我们这个方案是失败的。而且，我们的很多想法都是臆测的，没有可靠的根据，也没有方向，再加上我是第一次处理这种问题，这导致后来查找问题的艰
难。但是，仔细想想，我们又能拿什么做依据呢？出现OOM错误，我想大多数人想到的，除了JVM参数设置，就是内存泄漏。其实，OOM发生有很多种情况，
在IBM、Sun、BEA等不同虚拟机上，因为GC机制不一样，所以原因一般都不同，容易定位难度也不一样。下文会谈到。 <br>于是，我们干脆釜底抽薪分析问题吧：用JProfiler检测。</font></p>
<p><br></p>
<p><font size="2"><strong>二、按应用程序导致内存泄漏诊断，JProfiler检测</strong> <br><br>&nbsp;&nbsp;&nbsp; 如果遇到OOM问题，我想大家都会想到内存检测工具，现在最可靠的还是下面三种分析工具：Borland 的Optimizeit Suite，Quest的JProbe，ej-technologies的JProfiler。但面临三个问题： <br>1、三个都是商业产品，公司暂时没有买，必须自己下载，而且要找序列号。 <br>2、工具必须支持AIX5.3＋JDK1.42＋WAS6.0，不是Windows平台。 <br>3、工具必须在客户真实环境下部署，对客户的业务不能有冲击，也就是说部署测试工具前，必须做个大量测试，对工具非常熟练，遇到意外可以立即恢复现场。 <br>Note:项目上线后，而不是测试或试运行阶段遇到此类问题，非常考验人；另外一个，就是性能和可伸缩性问题，很可能把整个项目给毁了。</font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp;
当我决定要这么做后，就立即动手查阅这些工具的官方文档，用emule下载，最终都下载到了。试用后，最终选择了JProfiler4.03，比起其它工
具，它界面美观、清晰、功能强大、集成度高(Heap,Memory,CPU,Thread都统一了)。另外，JProbe没有AIX版本，这也是放弃它
的一个原因。</font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp; JVM的Profiler原理，都是通过JVM内置的的标准C语言Profiler
接口收集数据，然后通过Profiler工具的客户端展现。也就是说各厂商的Profiler工具，都有两个部分，一个部分是Profiler
Agent，和JVM绑定，负责收集JVM内部数据，譬如方法调用次数、耗费时间等；另外一个部分就是Profiler
front-end。通过Profiler工具的自定义local或remote协议传输到front-end，其实，我们最常用的JavaIDE的
debug功能就是在此基础上的（JPDA）。（JProfiler的截图</font><a  href="http://www.ej-technologies.com/products/jprofiler/screenshots.html" target="blank"><font size="2">http://www.ej-technologies.com/products/jprofiler/screenshots.html</font></a><font size="2"> ）。 <br>下面是Sun官方文档： <br>JDK1.42及以前是JVM PI：</font><a  href="http://java.sun.com/j2se/1.4.2/docs/guide/jvmpi/jvmpi.html" target="blank"><font size="2">http://java.sun.com/j2se/1.4.2/docs/guide/jvmpi/jvmpi.html</font></a><font size="2"> <br>JDK1.5是JVM TI：</font><a  href="http://java.sun.com/j2se/1.5.0/docs/guide/jvmti/jvmti.html" target="blank"><font size="2">http://java.sun.com/j2se/1.5.0/docs/guide/jvmti/jvmti.html</font></a><font size="2"> <br>具体到JProfiler的配置上，专门针对JDK1.4和1.5的JVM配置差别很大。 <br><br>我用的JProfiler是4.31版本，透露给大家一个万能序列号吧(这东西不太好找)，对各版本应该都支持。深入了解Java，这类工具是不可少的： <br>Name: License for You <br>Lincese Code: A-G667#42616F-10kg1r134fq9m#2217</font></p>
<p><font size="2">为了保证真实环境的检测成功，我做了大量的试验，譬如： <br>1、Windows系列的本地、远程测试。 <br>2、AIX的远程测试。 <br>3、Tomcat5.0、WebLogic8.14、WebSphere6.02，以及上述两种方式的组合测试，排列组合，场景不下10个。 <br>当时也参阅了大量JVM文档，JProfiler官方几百页英文文档，辅助的JProbe对照。而且也制造过内存泄漏造成的OOM场景。 <br>当然，要是在几个月前，在客户那边部署的测试环境时，就进行测试该多好啊。</font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp;
在公司内部，我用JProfiler测试了我们当时部署的几个应用，没有发现内存泄漏，所以，我们最怀疑的是就是CMS系统。因为出问题的那个WAS上它
占去了90％的负荷（我们有多台AIX、WAS服务器）。该CMS超级庞大，感觉著名的赛迪网就用它，当时该CMS厂商给我们部署都花了快一个月。所以再
重新部署一套测试环境也挺困难。另外，CMS提供商不给lisence。现在测试，客户早就对我们恼火了，当然不怎么配合，这对我们工作的开展就有很大的
挑战。</font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp; 在大致可以确定万无一失的情况下，我们最终决定在客户的真实环境下测试。也就是让JProfiler的agent端直接在WAS的JVM里面启动（北京IDC），然后远程（大连）监控。 <br>本
来该模式在另外几个应用的测试都通过了（因为北京IDC那边好几台AIX服务器）。但一部署上，客户的一些编辑用CMS时就感觉超级慢，尽管我们用了
JProfiler的最小负载模式。半个小时后，客户实在无法忍受，打电话过来，又把我们部长和经理训了一顿，还要写书面报告。我们被迫马山中止测试，恢
复现场。</font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp;
虽然JProfiler也支持客户那边的环境，但还是有bug，至少负载一高就有严重的性能问题，几乎导致系统挂起，这是我当时没有料到的。
JProfiler一启动，WAS的启动时间就由原来的3分钟降到10分钟，而且系统响应明显变慢，我们具体的环境如下（排列组合恐怕不下20种）： <br>1、AIX5.3，Power PC64位（不是32位） <br>2、WebSphere6.0 <br>3、IBM JVM1.42 <br>4、Remote 模式</font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp; 我后来仔细读了一下JProfiler的changeLog，发现对上面的环境支持不够也很正常，因为官方在最近的4.3.x版本下陆续有对IBM JVM和Websphere6.0的features和bug fix：</font><a  href="http://www.ej-technologies.com/download/jprofiler/changelog.html" target="blank"><font size="2">http://www.ej-technologies.com/download/jprofiler/changelog.html</font></a><font size="2"> </font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp; 进行到这一步，我忽然觉得无计可施了<img  src="http://www.javaeye.com/images/forum/smiles/icon_sad.gif" alt=""> ，此时已经过了三周。 <br>上面的策略，我认为是很正统的处理方法。谁怪我们当初项目上线时没有进行充分的测试呢？其实，这类测试没做也正常，OOM这类问题谁都无法预测。</font></p>
<p><font size="2">到这个时候，我想肯定有人会问我？你知道导致JVM的OOM有几种情况吗？在当时，我想到了以下五种： <br>1、JVM的heap最小、最大值没有设，或不合理。 <br>2、JVM的maxPermSize没有设置（这个IBM的JVM没有，一设置JVM就起不来）。 <br>对于Sun和BEA的JVM，以上两种参数设置，可以排除90％以上的非程序内存溢出导致的OOM。 <br>3、程序内存泄漏 <br>4、有的JVM，当在80%的CPU时间内，不能GC出2%的heap时，也发生OOM（IBM的JVM就是，但我没有验证） <br>5、JVM本身内存泄漏（JVM有bug不是不可能）</font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp;
现在的难题是，如果是那个可怕的CMS程序本身有内存溢出，在产品环境下，我们怎么去验证？我们自己开发的10来个web应用，测试并不是很难，在公司测
试都可以。但是，我现在最想解决的，就是CMS测试的问题。而且，在我们那种环境下，该CMS产品供应商并没有透露成功案例。</font></p>
<p><font size="2">其实，最后发现，并不是内存泄漏造成的，因为我们的heap走势都很平稳。纳闷的是，有1000M的heap，每次在heap只被占用400来M时就发生OOM，没有任何预兆。大家猜猜，会是什么原因<img  src="http://www.javaeye.com/images/forum/smiles/icon_rolleyes.gif" alt=""> ？这个问题我到后面相关章节再说吧。</font></p>
<p><font size="2">既然我们所有的矛头都指向那个可怕的CMS系统，现在就是考虑隔离的问题。如果我们验证这个问题是CMS造成的， 那么大部分责任就应该由CMS厂商承担<img  src="http://www.javaeye.com/images/forum/smiles/icon_wink.gif" alt=""> 。 <br>既然CMS我们不敢移（费劲，而且客户在正式用），那我就移我们开发的10来个web系统吧。</font></p>
<p><font size="2"><strong>三、移出除CMS系统以外的所有应用</strong> <br>说起来容易啊，做呢？ 隔离（移动）工作由我负责，具体涉及到10来个相关负责人。 <br>转移工作，必须处理好很多问题，就说几个印象最深的吧： <br>1、某些应用，如Blog和BBS，都涉及到文件、图片上传目录和产品本身的环境，如 JDBC连接池、Cache位置。 <br>2、目标服务器本身的环境，WAS安装环境、网络等。 <br>3、移植时的先后顺序、调度，各应用内部本身的约束关系。 <br>4、移植后的测试。 <br>当然，还有一个最严峻的问题，客户允许我们这么做吗？对它们目前运行的系统有多大影响？风险如何评估？</font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp; 这个工作持续了一天，已经完成了80％的工作，到第二天，客户又恼火了：WAS又宕机了。 <br>为什
么？这确实是WAS的一个bug：WAS的后台随便一操作，heap就会突然上升几百M，导致JVM内存不够。不过WAS撑住的话，过半小时后就会降下
来，我估计是WAS后台对用户操作状态、文件都缓存到Session里面。你们可以检查类似这样的一个文件夹：d:\IBM\WebSphere\
AppServer\profiles\AppSrv01\wstemp，我不知道为什么WAS不主动去清除它，它偷偷的就上升到几个G，系统硬盘可能不
久就后就会空间不足，WAS莫名迟缓、最后死掉。听过WAS6.0以前这个目录更夸张。大家见我附件的截图WAS_Console.png那个尖峰。</font></p>
<p><font size="2">咋办？经理也已经不敢让我们继续铤而走险了。这个方案最终又以失败告终<img  src="http://www.javaeye.com/images/forum/smiles/icon_sad.gif" alt=""> 。</font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp; 不过，最后我们还是发现问题出在CMS上。我们以前把这个问题向CMS技术支持反映，有大量依据和现象，并且把相关日志都给它们。过了两天，他们最后竟然只回了一句话&#8220;从给我的两个日志来看，没有找到任何与XXX有关的东西....&#8221;。TMD！我真的很生气<img  src="http://www.javaeye.com/images/forum/smiles/icon_twisted.gif" alt=""> ，它们的产品都折磨我们半年之久了。不过，看他们对IBM的WAS和JVM也不懂，我也就不想再说什么了。下面是我的邮件，公司机密部分都隐去了：</font></p>
<div class="quote_title"><font size="2">引用</font></div>
<div class="quote_div"><font size="2">&nbsp;&nbsp;&nbsp; 附件是我们这段时间服务器宕机的日志。我们用IBM
Pattern Modeling and Analysis Tool for Java Garbage Collector Version
1.3.2分析了一下虚拟机日志，没有发现是内存泄漏导致；用IBM HeapAnalyzer Version 1.4.4
分析heap文件，也没有发现很可疑的内存泄漏。 <br><br>&nbsp;&nbsp;&nbsp;
我想以前你们也这样做过，现在我们分析错误日志，发现有一个现象，在宕机时，总是找不到文件，我看就是Websphere或是AIX
IO资源不够，不知道是什么导致的。但是，我们自己的应用，基本上没有什么IO，除了一次load几个配置文件。不过，我觉得你们WCM的IO操作挺多
的，不知道你对日志有什么新的发现。 <br>客观的说，这几个月来，宕机那台服务器，除了你们的XXX，就以论坛和blog为主，而且他们都是开源的。在频繁宕机的06年11月份，我们的论坛和blog还没有上线。现在我们不得不每天晚上11点定时重启，但这也不是长久之计。 <br>现在，我们进行分离遇到很大阻力，原来想把你们的XXX单独分离出来，在当前的环境下，不是很现实，如安装、测试（负载、定时服务），所以现在分离我们自己的应用，但当前在产品环境下，客户方阻力也很大。 <br>希望尽快能够得到你们的问题建议和方案。</font></div>
<p><font size="2">&nbsp;&nbsp;&nbsp; 文中说到了IBM的两个分析工具，这也是我们后来的救星：我们就是需要这种<strong>离线分析</strong>工具，因为<strong>实时检测</strong>已经证明不现实。但我始终对该分析出来的结果抱怀疑态度，直到我去深入IBM的JVM以及和IBM的技术支持交流......</font></p>
<p><font size="2">柳暗花明啊<img  src="http://www.javaeye.com/images/forum/smiles/icon_smile.gif" alt=""> ，至少看到了一点希望，不过最后我们还是失望而返。</font></p>
<p><br></p>
<p><strong><font size="2">四、用IBM的HeapAnalyzer和GarbageCollector检测</font></strong></p>
<p><font size="2">&nbsp;&nbsp;&nbsp; 找到这两个工具，已经是够费劲了，因为以前找的IBM
HeapRoot工具，让我对这类工具很失望。而且，这两个工具，只有在IBM的Techinical
Support网站能够搜索到，但很不容易，因为那两个工具，并不是象IBM的Websphere产品那样宣传，它只在IBM Techinical
Support文章的某些角落里出现。要知道，Techinical
Support是IBM很重要的收入来源，这类文档，他们并不会让你很轻易就拿到，比起BEA WLS的支持网站dev2dev差远了。 <br>具体
诊断细节我就不详述了。我认为，IBM的WAS或JVM出了性能和OOM问题，这两个工具是最有效的，而且是离线分析工具，比起那些实时Profiler
工具，某些场合有绝对的优势：譬如我们目前的产品环境，你只能分析宕机后的日志，实时分析前面已经验证是不可行的。 <br>从日志分析，我们最终得出结论，我们购买的CMS系统有严重的碎片（大对象）问题，而该问题是OOM的罪魁祸首，而且IBM工程师也得出了同一结论。不过，在起先我们得出这一结论一周后，我还始终不相信heap碎片会导致OOM，直到IBM工程师总是向我强调。</font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp;
我想很多人也是不太相信，因为大多数人用的都是Sun的JVM，譬如Windows、Solaris上的hotspot。而且，Sun
JVM出问题，如果是配置的问题，一般通过配置heap最大最小值，以及maxPermSize都可以解决。Heap碎片导致的OOM，只有BEA的
JRockit和IBM JVM上发生，不过JRockit有专门文档说明，而且很容易找到（就在jdk的文档里面）。</font></p>
<p><font size="2"><strong>&nbsp;&nbsp;&nbsp; 配置heap最小最大值</strong>，我想大多数人都有经验。对于Sun的JVM来
说，一般可以设置heap最大最小值一致，也是推荐的做法。因为它的GC策略默认是复制、分代算法。也就是说，它会将heap分成不同的几个区，譬如
Solaris
JVM中最上面有两个大小相等的区。GC时刻，将一个区的存活对象复制到另外一个对等区，垃圾对象就算遗弃了。这样在heap里面，就不存在碎片问题。另
外，根据Java对象的存活期不同，将之转移到不同的区（Tenured区），存活最长的在最底部（火车算法），这也就是分代模型。具体请参考官方文档：</font><a  href="http://java.sun.com/docs/hotspot/gc1.4.2/" target="blank"><font size="2">http://java.sun.com/docs/hotspot/gc1.4.2/</font></a><font size="2"> </font></p>
<p><font size="2"><strong>&nbsp;&nbsp;&nbsp; 对于maxPermSize（Permanent Generation），</strong>主要和那些加载到JVM里面的Java Class对象相关，它的空间不是在Java Heap里面分配。如果你当前的heap有1000M，permSize是200M，那么JVM至少占用1200M。 <br>在
这个空间内的对象的生存期和JVM是一样的，譬如JDK的核心类库，它们被System Classloader加载到JVM的Method
Area（方法区）后，就不会被GC掉的，这些对象一般是Class对象，而不是普通的实例对象，也就是JVM的元数据。我们在用反射时经常用到它们。所
以，对于现在象Spring、Hibernate这些框架经常通过反射创建实例，可能对maxPermSize要求就大了，缺省的64M很多时候是不够
的，特别是对于应用服务器里的应用，象JSP就会产生和加载很多classes。不过，如果是它导致的OOM，一般会有类似 perm size提示。</font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp;
但是，对于IBM的JVM，情况就完全不一样。它的默认GC策略并没有采取复制、分代。这个可以从GC日志分析出来。它不像Sun的JVM那样，有个单独
的方法区，它的方法区就放在Java Heap里面。JVM规范里面并没有要求方法区的必须存放的位置，因为它只是一个JVM实现问题。</font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp;
在IBM的JVM里面，这些对象一般分配在称为k-cluster和p-cluster里（cluster又是属于Heap），而后者一般是临时在
heap里面申请。并且，这些cluster是不能GC，或是被移动重排的（Compact过程）。这就导致Java
Heap里面就如同马蜂窝，但不同的蜂孔又不能合并，于是，当我们程序里面产生一个大对象，譬如2M的数组(数组必须分配在连续的内存区)时，就没有可分
配空间了，于是就报告OOM。这些不能被移动的cluster就称为所谓的碎片。此时，JVM的Heap利用率可能不到50%。 <br>当然，通过一定时期的GC日志，可以计算出cluster的合理大小（专门在Java Heap的底部），另外，还可以为这些大对象专门分配大对象区的（超过64k的对象）。</font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp;
通过上面的理论介绍，我想大家一定知道了为什么IBM的JVM里面不推荐heap的最大最小值相同，因为这样碎片问题会非常严重：如果我们每次大对象申请
内存时，heap都扩展5%，譬如50M，碎片问题很大程度上可以避开，程序性能也高些（寻找可用空隙和分配耗时，以及每次GC时间拉长）。 <br>以上的具体阐述，请参考我在上文推荐的几个URL，另外再推荐三个宝贵的链接： <br></font><a  href="http://www-1.ibm.com/support/docview.wss?rs=180&amp;context=SSEQTP&amp;q1=fragmentation&amp;uid=swg21176363&amp;loc=en_US&amp;cs=utf-8&amp;lang=en" target="blank"><font size="2">http://www-1.ibm.com/support/docview.wss?rs=180&amp;context=SSEQTP&amp;q1=fragmentation&amp;uid=swg21176363&amp;loc=en_US&amp;cs=utf-8&amp;lang=en</font></a><font size="2"> <br></font><a  href="http://www-900.ibm.com/cn/support/viewdoc/detail?DocId=2447476A10000" target="blank"><font size="2">http://www-900.ibm.com/cn/support/viewdoc/detail?DocId=2447476A10000</font></a><font size="2">（IBM 技术支持告诉我的，太重要了！） <br></font><a  href="http://www-900.ibm.com/cn/support/viewdoc/detail?DocId=2847476B08000" target="blank"><font size="2">http://www-900.ibm.com/cn/support/viewdoc/detail?DocId=2847476B08000</font></a></p>
<p><font size="2">&nbsp;&nbsp;&nbsp; 我想大家应该会问：<strong>我怎么能够肯定我的OOM问题是heap碎片造成的呢</strong>？下面的方法可以验证。 <br>在OOM
发生时，JVM会产生一个heapdump文件。然后用GarbageCollector分析出该OOM发生时刻，JVM去申请的空间，譬如约235k。
此时，你再用HeapAnalyzer去分析此时的heap快照里面的gap
size大小（空隙大小）和各自的可用数目。你会发现，大于235k的空隙个数为0。这就是碎片导致OOM的证据。</font></p>
<p><font size="2">另外，有人会问：<strong>我怀疑我的OOM是因为程序内存泄漏造成的，怎么去验证</strong>？</font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp;
你可以用HeapAnalyzer分析发生OOM时刻的heap快照，工具会罗列出哪些对象怀疑有内存泄漏，譬如Cache对象都非常大（但你可以确定它
不是内存泄漏）。另外，分析这次宕机（从这次虚拟机启动到宕机这段时间）的heap走势，如果曲线明显是向上倾斜，也就是那种典型的内存泄漏图，就有可能
是内存泄漏。当然，还必须结合heap快照。 <br>内存持续上升在JVM开始一段时间很正常，因为JVM对第一次访问到的Class
对象，譬如一个典型的Web应用，就有jdk的class、Spring或Hibernate的class对象，它们都会被缓存下来
(ClassLoader原理)，一般均不会被GC。当大多数class对象缓存差不多（当然还可能有一些Singleton对象，不过不怎么占分量），
JVM的Heap就平稳了，呈一水平波浪或锯齿线。 <br>如果可以用JProfiler这类工具实时监控，就更容易确诊了。</font></p>
<p><font size="2">经过一番周折，我们终于看到了一线希望了<img  src="http://www.javaeye.com/images/forum/smiles/icon_smile.gif" alt=""> 。</font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp; 在一定的准备后，我们决定对WAS进行性能调优了。WAS的调优参数，可以分为两个部分：JVM级别和WAS级别： <br>JVM：主要是GC和Heap。 <br>WAS：Thread Pool，JDBC DataSource。 <br>当然要调节，你需要明白你的目标是什么，调节依据是什么，怎么计算，绝对不是凭空想象的，譬如heap最小值1024M，日志证明，该参数非常不适合我们的环境。具体细节，留给后文吧。</font></p>
<p><font size="2">战战兢兢地，中午12:00，我们给产品环境下的WAS调节参数、重启，同时优化了AIX的IO相关参数。</font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp;
我试着设置了一下JVM的k-cluster和p-cluster。下午15:00左右，WAS挂了，AIX也挂了。这下麻烦可大了。我们都慌了，马山客
户的老总就来电话了，一阵哗哗啦啦。实在无奈，让客户那边工作人员通知机房（服务器托管处）工作人员重启AIX。我也不得不强行更改刚才的参数，立即设为
另外一个值。 <br>其实，我把那个两个cluster值确实设置太大了，我把它们设置为推荐值的5倍，譬如p-cluster是65k&#215;110%&#215;5。另外一个愚蠢的设置就是把最小heap设置为2048M(AIX有4G内存)。 <br>后来我恢复到约正常的值，也就是去掉那个cluster的5，另外分配了一个30%的大对象区（如果1000M的heap，就是700M＋300M）。</font></p>
<p><font size="2">就这样，系统持续正常运行了三天，以前可是一天一down。当在三天后再次宕机时，我们都没有自信了<img  src="http://www.javaeye.com/images/forum/smiles/icon_evil.gif" alt=""> 。不得不通过AIX的cron，继续每天深夜11点的WAS定时重启。 <br>不过，那次宕机，包括以后的几次宕机，<strong>再也没有出现OOM错误了</strong>，但系统依然不稳定。虽然我可以说OOM问题解决了，但领导和客户需要的并不是这种结果。</font></p>
<p><font size="2">其实，在这个时候，我们已经发现我们系统的四大问题： <br>1、WAS和JVM参数：OOM问题 <br>2、AIX的IO和Paging Spacing不足：AIX日志后来显示错误 <br>3、AIX的WAS分区空间不够：WAS的日志膨胀一周就把那个opt分区塞满了。 <br>4、应用程序的JDBC连接池：我们20来个应用，一个20 connections，DB2数据库有时被撑死。</font></p>
<p><font size="2">也就是说，我们最初在客户那儿部署时，用的默认值根本不行。而且，部署涉及多人，人员之间出现断层。如果我们只是按OOM，无疑是走入死胡同，必须全局考虑！ <br>但
是，项目组实力薄弱，公司范围内就没有对AIX精通的。不过项目组原来有一个搞银行系统，在AIX下开发，就他熟悉些。我当时对AIX也比较陌生，你们从
Linux转到AIX，你就知道它有多别扭了。命令都自定一套（也许因为是Unix元老吧），那个shell也超级别扭，而且参考书特少。不是自诩，我两
年前负责一个高负载的Linux服务器管理一年多，也是玩得很转的。</font></p>
<p><font size="2">就这样，他负责AIX的相关问题，我负责WAS相关的。 <br>但是，现实环境，已经不允许我们再试验下去了<img  src="http://www.javaeye.com/images/forum/smiles/icon_sad.gif" alt=""> 。我们必须找到一条绝对可靠的对策！ <br>这就是下文的CMS系统大迁移，服务器再次优化。</font></p>
<p><br></p>
<p><font size="2"><strong>五、隔离CMS系统，服务器优化</strong> <br>&nbsp;&nbsp;&nbsp; 从前面的介绍，大家应该记得，我们开始是固定CMS，分离其它应用，但遭遇失败。现在是反过来，干脆把CMS系统赶出WAS平台<img  src="http://www.javaeye.com/images/forum/smiles/icon_question.gif" alt=""> 。</font></p>
<p><font size="2">说实话，项目经理做这个决定，我认为已经是鼓出很大勇气了<img  src="http://www.javaeye.com/images/forum/smiles/icon_idea.gif" alt=""> 。</font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp; 当时我们想在一个备用AIX机上安装CMS产品测试，但最后还是没有做成： <br>CMS这类文章发布系统很难安装，也不好测试，又没有liscence，而且还有一堆准备工作。绝对没有著名的openCMS安装那么简单，当然功能远远比它复杂。而且，我们当时也低估了后来的工作，总觉得问题好解决。</font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp;
在很遥远的06年中期，CMS厂商在客户那边一台AIX的Tomcat上部署了一套CMS产品。但当时客户执意要求将其跑在WAS上，也就是现在的情况。
最开始，客户还要求我们必须用WAS的集群(我们买的就是WAS的ND版)，无奈该CMS不支持。要是集群，又是死伤一遍。其实，现在想想，我们当时太被
动，CMS这种东西，就供公司的几十个编辑用，一个普通Tomcat就完全够用。而且，把它和面向公网的Internet应用混在一起，完全没有必要。也
许，被动是因为我的实力造成的。</font></p>
<p><font size="2">我们决定背水一战时，已经做过周密的计划：某年某月某日晚上8:00...... <br>CMS产品负责人现场切换 <br>xx（我）负责WAS相关参数调整 <br>yy负责AIX参数。 <br>zz负责应用的测试 <br>&#8230;..</font></p>
<p><font size="2">总之，该行动涉及到客户方、产品提供商、公司高层、项目组。每个人都密切关注，不下20人。每个人都守在电脑前，随时听候调遣，当天晚上，我们都没有准备回家睡觉，大家齐心协力。</font></p>
<p><font size="2">真没想到，整个式切换工作，一个小时就顺利完成<img  src="http://www.javaeye.com/images/forum/smiles/icon_surprised.gif" alt=""> ！第二天，客户编辑打开浏览器，她们一定想不到昨晚大家准备经历一场厮杀&#8230;.</font></p>
<p><font size="2">系统持续平稳地运行了一周，然后是漫长的五一，我回湖北黄冈老家休息了八天。回来时，一切依旧。</font></p>
<p><font size="2">当天晚上，我们这边主要做了两项工作： <br>1、JVM的Heap参数，共五个。 <br>2、AIX的IO、Paging Space等共六个。 <br>当然还有其他人的工作，譬如测试、监控。</font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp;
还有一个非常重要的方面：JDBC连接池。我们原来是在每个Web应用里面独立设置，这样20来个应用就有几百个DB连接，一不小心DB就给撑死。现在统
一交由WAS内置DataSource处理，总共连接不到30个。其实，我们项目开始部署时，就是这样做的，但当时WAS内置的DataSource对
JTA(XA)支持有bug （这个和IBM技术支持确认过，但他们没有给予很好的解决方案），不过Datasource还是配好的。</font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp; 但是这个工作已经属于WAS性能优化的主题了，而且优化值必须持续观察一段时间，通过专门的分析工具来计算。 <br>优化本身，是一项很考验人的工作，我就简单说一下最实用方法吧，也许是专门针对IBM的产品。 <br>1、清理归零WAS日志。然后启动WAS，生成日志（-verbose:gc默认是开的） <br>2、让WAS持续运行约两周，让JVM Heap占用曲线平稳一段时间即可（用IBM的Garbage Collector分析观察）。 <br>3、
在AIX的shell里，产生heapdump.phd文件，也就是heap快照。命令：kill -3 pid (pid是WAS的PID，通过ps
&#8211;ef查看)，观察heap当时的碎片情况，是否需要单独分大对象区（一般不需要设置），特别是那个方法区Class对象大小（p-cluster参
数）。 <br>4、通过GC工具，观察GC平均时间、Heap实际占用情况。Note: GC是一个Stop The
World过程，也就是说GC时系统对外不响应，多CPU也不例外。看你的应用实际需求了，GC持续时间和频率是矛盾的，另外还有性能考虑。一般Web应
用，我想让GC持续时间（Pause time）调节到合理值就ok了，譬如0.2到0.4s。 <br>5、根据3可以算出k-cluster值，它是工具推荐值的110%。 <br>6、Heap的最小值是程序刚启动不久的占用值，譬如320M。切记：IBM JVM初始值太大非常不好。 <br>7、
Heap的最大值是系统平稳后的100/70。也就是说如果最大值是1000M，那么应该平稳时是700M，还有30%的空余。IBM的JVM默认情下的
碎片问题，WAS控制台下操作Heap猛增这种bug，你不得不堤防。Heap最大值不设，AIX下的WAS肯定OOM。</font></p>
<p><font size="2">当然啦，我没有考虑到大对象区的计算（虽然我们的应用设置了专门的大对象区），包括IBM JVM支持的分代GC、并行GC，Heap每次expand百分比等。那些情况我们一般不常用，譬如，你的AIX平台一般不是16CPU吧？</font></p>
<p><font size="2">一口气写到现在，我忽然觉得该收尾了。下面就说说我对这类工作的整体看法吧。 <br>1、尽量在项目测试和试运行的时候就进行压力、性能测试，当正式投入使用后，如果发现类似问题，代价非常大。躲过算你运气好，一般来说，可能你们系统没多少人用，也不是核心业务系统，譬如一般的电子政务。 <br>2、千万不要低估了技术风险，用IBM的系列产品尤其要慎重，出问题一定不要忘了技术支持。而且，查资料时，建议用google English，因为象WebSphere这类问题，很少有中文资料。 <br>3、程序部署环境建立时，就要考虑到日后的正式环境，譬如AIX的Paging Space、IO、分区大小，默认值往往是不行的，而且在产品环境下改这些值，往往非常难。 <br>4、
在项目开发初期，就考虑到日志的问题，因为它分散到每个方法内，必须慎重定义好debug、info、warn、error级别，不要随便忽视异常
（catch里面不记录），到真正程序出问题时，它就是我们的最重要的依据之一。当然这主要是功能性问题诊断。另外对于高负载网站，日志文件往往非常大，
各级别日志千万不要混在一起，否则找问题就很困难了。 <br>5、怎么说呢，别死扣技术，以为什么都可以通过技术解决。你看我们最大的问题就是把
CMS给移到Tomcat下。你要是问我，为什么CMS产品会导致系统这么多的问题，我也不知道，到那时候，我确实也不想深入。我只要知道，赶出你这个应
用，我的系统就好控制多了。而且，那个CMS系统，在Tomcat下，就是跑得服服帖帖的，非常稳定。难度是可恶的WAS？不过那CMS，据IBM工程
师，包括我们二次开发，都觉得够烂了，每个jsp页面都打开、关闭DB connection（7年前的jsp开发模式），还有那么严重的大对象问题。</font></p>
<p><font size="2">&nbsp;&nbsp;&nbsp; 好了，以上总结的几点可能也不充分、深入，但如果你仔细读我这篇文章，应该有自己的想法。毕竟，只有经过思考的东西，才会属于自己。</font></p>
</div><img src ="http://www.blogjava.net/sutao/aggbug/134085.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/sutao/" target="_blank">苏醄</a> 2007-08-02 18:04 <a href="http://www.blogjava.net/sutao/articles/134085.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>