1. 症状
1.1. Resin停止响应
1.2. Resin不停重启动
1.3. java.lang.OutOfMemoryError错误,应用程序内存溢出
1.4. 运行一会儿,服务器开始变得非常慢
1.5. CPU尖峰,高的CPU使用率
1.6. 会话(sessions)变成null,会话丢失
1.6.1. 调试日志
1.6.2. Resin会话配置
1.6.3. 应用程序重载
1.6.4. 浏览器cookie的局限
1.6.5. cookie域名的问题
1.6.6. cookie名称冲突
1.6.7. URL重写
1.7. J2EE规范,javax.servlet包规范1.3和Resin不兼容
1.8. Unsupported major.minor version 48.0
1.9. 读取POST数据的问题
2. 技巧方法
2.1. 启用调试日志
2.1.1. 服务器和所有应用程序的完全调试日志
2.1.2. 一个web应用程序的完全调试日志
2.2. 线程转储
2.2.1. 使用JDK5工具转储线程
2.2.2. 通过发送一个信号转储线程
2.2.3. 如果发送信号无效时的线程转储
2.2.4. 理解线程转储
2.3. 内存溢出和垃圾收集
2.3.1. -verbosegc
2.3.2. 使用堆转储检查内存使用
2.3.3. 理解 文件中的栈信息
2.3.4. 理解 文件中的CPU信息
2.3.5. 监视垃圾回收
2.3.6. 增加堆内存
2.4. 清空classpath
2.5. 监视HTTP传输
2.6. 使用一个外部编译器
2.7. 调整栈内存避免线程限制
2.8. 使用操作系统的 netstat 命令获得当前 TCP/IP 端口的使用
2.8.1. 连接状态
2.8.2. 端口使用
1. 症状
1.1. Resin停止响应
●可能是一个线程死锁的问题,应该进行线程转储。
●启用完全调试日志模式,检查日志最后的纪录看看发生了什么。
1.2. Resin不停重启动
●启用完全调试日志模式,检查记录看看Resin为什么不停的重启它。
1.3. java.lang.OutOfMemoryError错误,应用程序内存溢出
●使用JVM启动参数增加堆(heap)内存。
●转储堆,看看那个对象无法被垃圾回收器无法回收。
●转储线程,检查占用着对象的不能释放的线程
一个OutOfMemoryError错误通常意味着堆(heap)内存被用尽。一般是应用程序代码保持了对不在使用的对象的引用,垃圾回收器无法对其进行回收。转储堆,能够查到什么代码和什么种类的对象被占用了。如果对转储或者其它监视工具显示服务器和你的程序实际没有超出堆内存,那么 OutOfMemoryError意味着JVM超出了虚拟内存,也就是底层的malloc()调用失败。通常这种情况,通过使用操作系统工具显示内存使用,JVM自己能够显示其自己的堆内存,但是操作系统工具确显示进程占用了大量的内存。在Windows下使用任务管理器,Unix下使用top或者ps 命令。
JVM无法进行堆内存分配可能有如下原因:
●线程,特别是线程堆占用虚拟内存。
●JNI库可能调用malloc或者nmap占用虚拟内存。这包括很多数据库驱动,也包含一些Resin使用的JNI代码。
●对于.jar/.zip文件,JDK要分配虚拟内存。如果你打开了大量的jar文件,你可能会遇到
问题。可以想到用于打开jar的getResourceAsStream没有关闭将会耗尽.jar内存。
1.4. 运行一会儿,服务器开始变得非常慢
● 这可能是一个垃圾回收问题。如果你的内存缺乏,然后又创建了大量的对象,这导致垃圾回收器耗尽CPU。如果你内存溢出,JVM将会慢慢停止(连续地进行垃圾收集)直到它死亡。
  ○ 监视垃圾收集。
  ○ 转储堆,看看是否是有对象无法被回收。
  ○ 参看JVM垃圾回收参数调整的文档获得更多垃圾回收的信息。
● 可能有一个死循环的线程或者一个请求耗尽资源。回应一个请求的线程如果不能返回,Resin就没法再
次利用它,那么可用来服务的线程就会越来越少。
  ○ 进行线程转储,检查可能占用对象的无法释放的线程。
1.5. CPU尖峰,高的CPU使用率
● 转储线程,检查那些线程在无限循环。
● 检查垃圾收集的部分。
1.6. 会话(sessions)变成null,会话丢失
1.6.1. 调试日志
首先启用调试日志。特别是浏览器请求提交的头信息能够显示一个客户端的JSESSIONID状态,日志也能说明Resin什么时候识别、创建和失效一个会话。
1.6.2. Resin会话配置
另一个可能是session-max设置过低,导致当前用户建立会话的数量大于你设置的这个值。另一个可能是会话超时,你可以通过session-timeout标签来配置它。
<web-app id='/'>
  ...
  <session-config>
    <!-- timeout after 120 minutes -->
    <session-timeout>120</session-timeout>
    <!-- up to 4096 sessions at once -->
    <session-max>4096</session-max>
  </session-config>
  ...
</web-app>
1.6.3. 应用程序重载
无论何时,一个java源文件、l或者l改变,Resin都会重启应用程序。如果这个情况发生,你当前的会话就会丢失,除非你配置了一个持久性会话存储。
1.6.4. 浏览器cookie的局限
一些用户报告,如果他们的应用程序使用大量的cookie,浏览器将会丢弃旧的cookie为新的腾出空间。这就会出现浏览器丢失了Resin 用来跟踪会话的cookie。IE浏览器用户特别容易遇到这个问题。如果你的应用程序使用大量的cookie,最好的解决方案就是减少cookie数量和 cookie数据的大小。Resin使用一个单一的cookie其存储相对很少的数据用拉跟踪用户的会话ID。应用程序存储在cookie中的信息可以使用HttpSession对象来存储。作为最后的手段,你可以配置Resin总是使用URL重写,这需要把enable-cookies设置成 false。由于安全的原因URL重写式不推荐的,因为重写URL增加了重写某些页面丢失调用的高可能性。
<web-app id='/'>
  ...
  <session-config>
    <enable-cookies>false</enable-cookies>
    <enable-url-rewriting>true</enable-url-rewriting>
  </session-config>
  ...
</web-app>
1.6.5. cookie域名的问题
如果你的cookie域名不兼容也可能丢失会
话。例如,如果你有一个服务器使用cookie域名"hogwarts",另一个使用 "qa.hogwarts",在浏览器中"hogwarts"的cookie会干扰在"qa.hogwarts"上的会话。方法是改变cookie域名"hogwarts"为"www.hogwarts"。 你可以在session-config标签中设置 cookie域名。
1.6.6. cookie名称冲突
如果你使用Resin和另一个应用服务器(例如Tomcat),你可能遇到这个冲突,因为它们使用相同的cookie名称(他通常是 JSESSIONID) 来跟踪会话。Resin提供session-cookie 和 ssl-session-cookie让你可以改变Resin使用的cookie名称。
改变用来跟踪会话的cookie名称的片断:
  <cluster>
    ...
    <session-cookie>RJESSESSIONID</session-cookie>
1.6.7. URL重写
如果你忘记了重写一个URL,一个需要重写的用户当访问到这个URL时将丢失他们的会话。Resin在一个用户浏览器和一个会话 (session)之间建立一个关联,是通过为每一个新请求返回一个惟一的id。这可通过两种方式之一来完成:使用cookie或者URL重写。 Resin首先尝试向用户浏览器发送一个包含惟一会话ID的cookie来跟踪一个用户的会话。有时Resin不能建立cookie,不是因为用户在其浏览器禁用了cookies就是因为某些浏览器不支持它们(例如一些HDML和WML浏览器)。如果cookie不能建立那么就会使用URL重写。在这种情况下,Resin重写每一个它提交给用户的URL,让其包含一个名称为_jsessionid的参数。然后为每一个新来的请求做的第一件事就是查这个参数,如果这个参数存在那么就知道一个会话已经建立,它移出参数并使用它来查用户会话对象。URL重写需要开发者的协同合作。开发者必须编码每一个URL 引用让Resin有一个合适的机会放置_jsessionid参数。
使用JSTL实现URL重写
<%@ taglib prefix='c' uri='java.sun/jstl/core' %>
Time to go <a href="<c:url _fcksavedurl="<c:url _fcksavedurl="<c:url value='home.jsp' />">Home</a>!
使用Java scriptlet实现URL重写
<%
String homeUrl = deURL("home.jsp");
%>
<%-- the presentation --%>
Time to go <a href="<%= homeUrl %>">Home</a>!
1.7. J2EE规范,javax.servlet包规范1.3和Resin不兼容
参看清除classpath环境变量。
1.8. Unsupported major.minor version 48.0
这个错误经常在发现一个冲突的jar时发生,参看清除classpath环境变量。
如果环境变量classpath被完全清除,然而一个JDK或者旧Resin的一个jar或者一些其它组件出现在的什么地方,如果你已经在那些地方添加了,在你的JAVA_HOME树里的一些jar可能有一个问题,那里可有一个和你的web程序WEB-INF/lib/目录下冲突的jar。另一种可能是你还没设置JAVA_HOME,或者你使用了一个冲突的JDK的一些组件。
如果在Windows上,检查JAVA_HOME之外的的拷贝,例如C:/或者在你PATH路
径里其它地方的。
1.9. 读取POST数据的问题
首先启用调试日志。调试日志会显示发送到Resin的请求,提供一些Resin如何处理这些数据的信息。最重要的是确保在读取POST参数之前编码设置正确。浏览器总是发回和输出页面编码相同的参数。因为请求不包含编码,应用程序代码需要确保编码匹配。因此第一件事就是确定发送到浏览器的表单的编码。你的应用程序总应该指定它。一旦你指定了它,你就知道浏览器POST使用编码。(这里UTF-8是个
自然的编码选择,我不能确信你为什么使用其它的编码)。在读取POST参数之前确保设置了正确的编码,你可以调用request.setCharacterEncoding(encoding)来设置编码。
2. 技巧方法
2.1. 启用调试日志
Resin使用JDK日志工具提供了大量的诊断信息。通过使用一个空名称(匹配所有名字)可以启用完全调试日志,调试级别为“全部”。因为将会产生大量信息,把这些信息放在一个单独的文件中比较好。
2.1.1. 服务器和所有应用程序的完全调试日志
下面的配置每天创建一个日志,当一个问题出现时用来查问题出现在什么地方。因为日志配置在l中,日志的捕捉是服务器和其上所有应用程序的。日志输出的信息在 $RESIN_HOME/log/debug.log。
<!-- l -->
<resin xmlns="caucho/ns/resin">
  <log-handler name="" level="all" path="log/debug.log"
                  timestamp="[%H:%M:%S.%s] {%{thread}} " />
  <logger name="" level="finer" />
</resin>
有其它一些的日志配置选项,请参看Resin日志文档。
2.1.2. 一个web应用程序的完全调试日志
通常你一般仅需要一个程序输出的调试日志。日志配置记录放在<web-app-root>/l中,那么仅这个web应用程序的日志信息被输出到日志文件中。下面的配置每天创建一个调试日志,位置<web-app-root>/WEB-INF /work/debug.log。
<!-- <web-app-root>/l -->
<web-app>
  ...
  <log name="" path="WEB-INF/work/debug.log" timestamp="[%H:%M:%S.%s] {%{thread}} " />
  <logger name="" level="finer" />
  ...
</web-app>
2.2. 线程转储
如果应用程序好像有问题或者超出资源泄露,线程转储能够显示服务器的状态。对于服务器调试Java的县城转储是一个重要的工具。因为 Servlet是多线程的,没有处理好的话很可能出现死锁,或者出现死循环和导致内存溢出错误。特别是你使用了第三方软件例如数据库、EJB和Corba ORBs。
2.2.1. 使用JDK5工具转储线程
在JDK5里可以使用jps和jstack,一个快捷的命令行方法获得当前所有线程的堆栈跟踪信息。
# jps
12903 Jps
20087 Resin
# jstack 20087
Attaching to process ID 20087,
Debugger attached successfully.
Client compiler detected.
JVM version is 1.5.0-beta2-b51
Thread 12691: (state
= BLOCKED)
- java.lang.Object.wait(long) (Compiled frame; information may be imprecise)
- com.caucho.util.ThreadPool.runTasks() @bci=111, line=474 (Compiled frame)
- com.caucho.util.ThreadPool.run() @bci=85, line=423 (Interpreted frame)
- java.lang.Thread.run() @bci=11, line=595 (Interpreted frame)
Thread 12689: (state = BLOCKED)
- java.lang.Object.wait(long) (Compiled frame; information may be imprecise)
- com.caucho.util.ThreadPool.runTasks() @bci=111, line=474 (Compiled frame)
- com.caucho.util.ThreadPool.run() @bci=85, line=423 (Interpreted frame)
- java.lang.Thread.run() @bci=11, line=595 (Interpreted frame)
...
2.2.2. 通过发送一个信号转储线程
在 Windows, ctrl-break会产生线程转储。
在Unix, "kill -QUIT" 会产生线程转储。
2.2.3. 如果发送信号无效时的线程转储
你可以在启动JVM时指定附加的参数允许附加一个调试器而不是发送信号来转储线程。你然后在任何时候附加调试器来得到线程转储。 这种方法在所有的操作系统上得到支持。
下面是是逐步的指导:
  1. 使用附加的参数启动Resin来允许一个调试器附加:
      l for debugging
      <resin xmlns="caucho/ns/resin">
      <cluster id="">
        <server-default>
          <jvm-arg>-Xdebug</jvm-arg>
          <jvm-arg>-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5432</jvm-arg>
        </server-default>
        <server id="" address="127.0.0.1" port="6800" />
      </cluster>
      </resin>
  2. 等待,直到你认为应用程序出现了死锁或者失去控制。
  3. 打开另一个终端 (window), 使用jdb连接正在运行的Resin实例:
      $JAVA_HOME/bin/jdb -connect com.sun.jdi.SocketAttach:hostname=localhost,port=5432
      jdb会显示类似如下信息:
      Set uncaught java.lang.Throwable
      Set deferred uncaught java.lang.Throwable
      Initializing jdb ...
      >
  4. 使用 "suspend" 命令, 然后 "where all"命令获得一个线程转储:
      例子: jdbc suspend
      > suspend
      All threads suspended.
jvm调优参数      > where all
      tcpConnection-6862-3:
      [1] java.lang.Object.wait (native method)
      [2] com.caucho.server.TcpServer.accept (TcpServer.java:650)
      [3] com.caucho.server.TcpConnection.accept
      (TcpConnection.java:208)
      [4] com.caucho.server.TcpConnection.run (TcpConnection.java:131)
      [5] java.lang.Thread.run (Thread.java:536)
      tcpConnection-543-2:
      [1] java.lang.Object.wait (native method)
      [2] com.caucho.server.TcpServer.accept (TcpServer.java:650)
      [3] com.caucho.server.TcpConnection.accept
      (TcpConnection.java:208)
      [4] com.caucho.server.TcpConnection.run (TcpConnection.java:131)
      [5] java.lang.Thread.run (Thread.java:536)
      ..
  5. 使用 "resume" 命令来恢复进程
      > resume
Unix 用户(和Windows上的Cygwin用户)可以使用一个脚本:
resin-thread-du
mp.sh
#!/bin/sh
echo -e "suspend\nwhere all\nresume\nquit" | $JAVA_HOME/bin/jdb -connect \
  com.sun.jdi.SocketAttach:hostname=localhost,port=5432
虽然没有进行过严格基准测试,好像使用线程转储参数启动的JVM在性能上影响不大。
2.2.4. 理解线程转储
在任何情况下,你会最终得到类似如下的跟踪调试信息(不同的JDK有稍微的差别):
Full thread dump:
"tcpConnection-8080-2" daemon waiting on monitor [0xbddff000..0xbddff8c4]
        at java.lang.Object.wait(Native Method)
        at com.caucho.server.TcpServer.accept(TcpServer.java:525)
        at com.caucho.server.TcpConnection.accept(TcpConnection.java:190)
        at com.caucho.server.TcpConnection.run(TcpConnection.java:136)
        at java.lang.Thread.run(Thread.java:484)
"tcpConnection-8080-1" daemon waiting on monitor [0xbdfff000..0xbdfff8c4]
        at java.lang.Object.wait(Native Method)
        at com.caucho.server.TcpServer.accept(TcpServer.java:525)
        at com.caucho.server.TcpConnection.accept(TcpConnection.java:190)
        at com.caucho.server.TcpConnection.run(TcpConnection.java:136)
        at java.lang.Thread.run(Thread.java:484)
"tcpConnection-8080-0" daemon waiting on monitor [0xbe1ff000..0xbe1ff8c4]
        at java.lang.Object.wait(Native Method)
        at com.caucho.server.TcpServer.accept(TcpServer.java:525)
        at com.caucho.server.TcpConnection.accept(TcpConnection.java:190)
        at com.caucho.server.TcpConnection.run(TcpConnection.java:136)
        at java.lang.Thread.run(Thread.java:484)
"tcp-accept-8080" runnable [0xbe7ff000..0xbe7ff8c4]
        at java.PlainSocketImpl.socketAccept(Native Method)
        at java.PlainSocketImpl.accept(PlainSocketImpl.java:413)
        at java.ServerSocket.implAccept(ServerSocket.java:243)
        at java.ServerSocket.accept(ServerSocket.java:222)
        at com.caucho.server.TcpServer.run(TcpServer.java:415)
        at java.lang.Thread.run(Thread.java:484)
"resin-cron" daemon waiting on monitor [0xbe9ff000..0xbe9ff8c4]
        at java.lang.Thread.sleep(Native Method)
        at com.caucho.util.Cron$CronThread.run(Cron.java:195)
"resin-alarm" daemon waiting on monitor [0xbebff000..0xbebff8c4]
        at java.lang.Thread.sleep(Native Method)
        at com.caucho.util.Alarm$AlarmThread.run(Alarm.java:268)
"Signal Dispatcher" runnable [0..0]
"Finalizer" daemon waiting on monitor [0xbf3ff000..0xbf3ff8c4]
        at java.lang.Object.wait(Native Method)
        at ve(ReferenceQueue.java:108)
        at ve(ReferenceQueue.java:123)
        at f.Finalizer$FinalizerThread.run(Finalizer.java:162)
"Reference Handler" daemon waiting on monitor [0xbf5ff000..0xbf5ff8c4]
        at java.lang.Object.wait(Native Method)
        at java.lang.Object.wait(Object.java:420)
        at f.Reference$ReferenceHandler.run(Reference.java:110)
"main" waiting on monitor [