Resin 常见问题及解决方法(FAQ)

                        Resin 常见问题及解决方法(FAQ)

转载:http://blog.sina.com.cn/s/blog_9fd5b6df01010nf1.html

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. 理解 java.hprof.txt 文件中的栈信息
2.3.4. 理解 java.hprof.txt 文件中的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源文件、web.xml或者resin.xml改变,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.com",另一个使用 "qa.hogwarts.com",在浏览器中"hogwarts.com"的cookie会干扰在"qa.hogwarts.com"上的会话。方法是改变cookie域名"hogwarts.com"为"www.hogwarts.com"。 你可以在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='http://java.sun.com/jstl/core' %>
 
Time to go <a href="<c:url _fcksavedurl="<c:url _fcksavedurl="<c:url _fcksavedurl="<c:url value='home.jsp' />">Home</a>!
 
使用Java scriptlet实现URL重写
 
<%
String homeUrl = response.encodeURL("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之外的java.exe的拷贝,例如C:/WINDOWS/java.exe或者在你PATH路径里其它地方的java.exe。
 
1.9. 读取POST数据的问题
首先启用调试日志。调试日志会显示发送到Resin的请求,提供一些Resin如何处理这些数据的信息。最重要的是确保在读取POST参数之前编码设置正确。浏览器总是发回和输出页面编码相同的参数。因为请求不包含编码,应用程序代码需要确保编码匹配。因此第一件事就是确定发送到浏览器的表单的编码。你的应用程序总应该指定它。一旦你指定了它,你就知道浏览器POST使用编码。(这里UTF-8是个自然的编码选择,我不能确信你为什么使用其它的编码)。在读取POST参数之前确保设置了正确的编码,你可以调用request.setCharacterEncoding(encoding)来设置编码。
 
2. 技巧方法
 
2.1. 启用调试日志
Resin使用JDK日志工具提供了大量的诊断信息。通过使用一个空名称(匹配所有名字)可以启用完全调试日志,调试级别为“全部”。因为将会产生大量信息,把这些信息放在一个单独的文件中比较好。
 
2.1.1. 服务器和所有应用程序的完全调试日志
下面的配置每天创建一个日志,当一个问题出现时用来查找问题出现在什么地方。因为日志配置在resin.xml中,日志的捕捉是服务器和其上所有应用程序的。日志输出的信息在 $RESIN_HOME/log/debug.log。
 
<!-- resin.xml -->
<resin xmlns="http://caucho.com/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>/WEB-INF/web.xml中,那么仅这个web应用程序的日志信息被输出到日志文件中。下面的配置每天创建一个调试日志,位置<web-app-root>/WEB-INF/work/debug.log。
<!-- <web-app-root>/WEB-INF/web.xml -->
 
<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, please wait...
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来允许一个调试器附加:
      resin.xml for debugging
 
      <resin xmlns="http://caucho.com/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.
      > 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-dump.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.net.PlainSocketImpl.socketAccept(Native Method)
        at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:413)
        at java.net.ServerSocket.implAccept(ServerSocket.java:243)
        at java.net.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 java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:108)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:123)
        at java.lang.ref.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 java.lang.ref.Reference$ReferenceHandler.run(Reference.java:110)
 
"main" waiting on monitor [0xbfffd000..0xbfffd210]
        at java.lang.Thread.sleep(Native Method)
        at com.caucho.server.http.ResinServer.waitForExit(ResinServer.java:674)
        at com.caucho.server.http.ResinServer.main(ResinServer.java:821)
        at com.caucho.server.http.HttpServer.main(HttpServer.java:95)
 
每个线程都被命名了。这里有一些通用的名称:
线程名称 描述
tcp-accept-8080 在8080端口监听新连接的线程
tcpConnection-8080-3 处理从8080端口连接的servlet线程
tcp-cron Resin的run-at线程
tcp-alarm Resin的警告线程
 
Resin为每一个<http>和<srun>开启一个 tcp-accept-xxx  线程,tcp-accept-xxx 线程总是处于socketAccept状态。应该有一些tcpConnection-xxx-n线程,每一个都是一个servlet线程。在一个忙碌的服务器上,这些能在你代码里任何地方出现。如果几个出现在一个位置,你可能有某种死锁或者至少一个慢锁。空闲线程不是tcpAccept就是httpRequest 或者runnerRequest。对于死锁,你应查看"waiting on monitor"线程和很多线程阻塞在同一位置的任一实例。
 
2.3. 内存溢出和垃圾收集
大部分内存问题时应用程序设计上的内存漏洞。例如,一个缓存或者vector填充了过期的数据,或者一个singleton或者静态变量不能适当地侦测到web-app重启。大部分怪异的内存问题是堆内存或者虚拟内存溢出,当使用了大量的线程(〉256)。
 
追踪捕获内存问题的步骤是:
   1. 使用 resin.sh start or resin.exe -install启用 -J-verbosegc。 -verbosegc标志记录堆的垃圾收集,让你知道你是否堆内存溢出了(大部分情况是这样的)。
   2. 获得一个heap profiler或者在JVM中使用堆转储。JProfiler是一个价格便宜的商业的heap profiler.虽然JVM的堆转储不是很用户友好,但是它也是可用的。你应该使用一个heap profiler作为你开发过程的一部分,在任一产品投入使用前应该使用一个。
   3.  使用heap profiler, 找到2-3个过量消耗内存的用户并修正这些内存漏洞。
   4. 一般应用程序错误包括:
          ○ 在每一个请求(request)结束ThreadLocal变量没有正常清除。
          ○ 单一模式(Singleton) 或者静态散列影射和缓存,web-app重启要清除。
          ○ web-app重启后衍生出来的线程不能被停止。
          ○ web-app 变量 (像 "application" 变量), 被存储在一个静态变量中。
   5.如果堆没问题,例如 -verbosegc显示了一个稳定的堆,你应该看看非堆内存:
          ○ 线程栈的使用(-Xss2m). 每一个线程的消耗一些非堆内存。一些系统默认是8M。在一些32位系统上虚拟内存的限制大约是2G,256个线程,每个消耗8M,就能耗尽虚拟内存。你可以减小栈内存通过使用 -Xss指令。
          ○ Java 本地接口内存(JNI memory)。如果你使用JNI库或者使用了利用JNI的驱动,JNI分配了比可用内存更多的内存是可能的。
          ○ fork/exec 和 OS 限制. 如果操作系统没有足够的交换空间可用,例如操作系统可能拒绝一个"jikes"编译。
          ○ NIO, 内存影射, 和 .jar 文件. JDK在内存中影射jar文件。在某些情况下,大量的jar文件能够耗尽虚拟内存。这种情况也出现在NIO内存映射中。
   6. 如果所有这些情况都被排除,它可能是一个Resin的BUG。然而你应该在报告Resin BUG之前找到了这个内存漏洞,例如在报告BUG之前你已经进行了上面所有的检查。关于内存溢出的BUG报告,如果没有得到一个JDK内存转储,它更有可能是一个程序上的错误。你必须在报告任一潜在的Resin内存问题时提供一个堆转储。
 
2.3.1. -verbosegc
-verbosegc是一个JVM的调试输出。对于检查基本的内存使用和垃圾收集时间它是一个非常方便的工具。对于任一产品系统使用 -verbosegc 是一个好主意。当启动Resin时,你可以使用-J-verbosegc。
 
特定的输出依赖于JVM,一些内容看起来如下:
-verbosegc output
 
[GC 9176K->8647K(9768K), 0.0014790 secs]
[GC 9287K->8668K(9768K), 0.0011120 secs]
[GC 9308K->8668K(9768K), 0.0007810 secs]
 
"(9768K)"是非常重要的数据,表示最大可分配的堆大约是10M。其它数值显示了实际的堆使用在垃圾收集前后。
 
2.3.2. 使用堆转储检查内存使用
如果一个应用程序过分地消耗内存直到抛出内存溢出错误,或者好像在垃圾收集上消耗了大量的时间,一个堆转储能够帮助你找到问题的根源。真正需要你去做的是有一个CPU和堆调试程序(profile)。JDK自带一个简单的(界面不是很用户友好),因此不必一定需要买一个profile。jvmstat就是一个简单的堆监视器。它是一个标准的java参数,因此"java -Xrunhprof:help"会告诉你如何启动它。例如你可以如下启动Resin
 
> resin.sh -J-Xrunhprof:heap=sites,cpu=samples
 
(在Unix上, Resin启动脚本有个 -cpuprof-ascii 参数被自动设置.)
 运行一个负载一定的时间(你可以运行类似Apache "ab"工具10分钟时间),然后正常停止服务器,你不应使用crtl-C杀死它,你需要一个正常的退出。它会转储一个 java.hprof.txt 文件。在这个文件的尾部查看跟踪信息。
 
2.3.3. 理解 java.hprof.txt 文件中的栈信息
假设你采用廉价方案,使用JDK的堆调试器而不是购买一个,你就需要帮助来解释它。下面是一个运行中的Resin堆转储的一个例子。在这个例子中你要跳到"SITES BEGIN" 开始的段落。对于这大部分信息,你仅需要注意上面的20行。别的其它的都是杂乱信息,忽略它。
 
SITES BEGIN (ordered by live bytes) Tue Jan  9 17:44:33 2001
          percent         live       alloc'ed  stack class
 rank   self  accum    bytes objs   bytes objs trace name
    1 11.87% 11.87%   983520  120 1393320  170  2553 [B
    2  9.89% 21.76%   819600  100 1286772  157  4070 [B
    3  9.09% 30.85%   753756   23 3539376  108  4970 [L<Unknown>;
    4  5.83% 36.68%   483564   59  778620   95  7180 [B
    5  5.74% 42.42%   475368   58  745836   91  7178 [B
    6  4.35% 46.77%   360624   44  696660   85  7182 [B
    7  2.97% 49.74%   245880   30  450780   55  7176 [B
    8  2.37% 52.11%   196704   24  352428   43  7254 [B
    9  1.88% 53.99%   155724   19  262272   32  7174 [B
   10  1.78% 55.77%   147528   18  245880   30  7137 [B
   11  1.53% 57.30%   126988 1063 16973092 129113  3271 [C
   12  1.34% 58.64%   110684 3953 20362832 727244  1213 sun/io/CharToByteISO8859_1
   13  1.25% 59.88%   103320  738  141820 1013  5942 java/lang/Class
   14  1.21% 61.10%   100548   49  221616  108  5003 [L<Unknown>;
   15  1.21% 62.31%   100548   49  221616  108  5005 [L<Unknown>;
   16  1.07% 63.38%    89080 1532 18393580 317347  1340 [B
   17  0.79% 64.18%    65568    8   81960   10  8408 [B
   18  0.79% 64.97%    65552    4   65552    4 27630 [C
   19  0.70% 65.67%    58232   24 1110128  386  5038 [C
   20  0.68% 66.35%    56200  450  116816  980  7186 [C
 
有两个需要查找的。首先,如果任何一个类在"live objs"列数值大,你需要分析它。 那可能有内存漏洞。第二,如果一些类在"alloc'ed objs"列数值大,这可能浪费了大量的垃圾收集时间,你可以使用缓存来解决它。
 
 
在类名称中的 [C 意味着一个字符数组。要知道它到底表示什么,你需要查看栈跟踪 (3271):
 
TRACE 3271:
java/lang/String.<init>(String.java:244)
com/caucho/util/CharBuffer.close(CharBuffer.java:714)
com/caucho/vfs/FilesystemPath.normalizePath(FilesystemPath.java:162)
com/caucho/vfs/FilesystemPath.schemeWalk(FilesystemPath.java:127)
 
那是 Resin的VFS代码部分。也许在将来会尽力减少它。你使用 "-prof-depth 10"参数能得到更长的信息。(或者在-Xrunhprof指定相应的深度)。那会通常会给出更多的信息。
 
2.3.4. 理解 java.hprof.txt 文件中的CPU信息
 
CPU信息比较容易理解。在一些JDK,你需要禁用JIT来运行它。
 
CPU SAMPLES BEGIN (total = 424614) Tue Jan  9 17:44:33 2001
rank   self  accum   count trace method
   1 21.36% 21.36%   90704  7266 com/caucho/server/http/VirtualHost.logAccess
   2 10.84% 32.20%   46041  7269 java/net/SocketInputStream.socketRead
   3  5.99% 38.19%   25428  1213 java/lang/Class.newInstance0
   4  5.11% 43.31%   21715  7896 com/caucho/util/CharBuffer.toString
   5  4.82% 48.13%   20463  1286 sun/io/CharToByteISO8859_1.convert
   6  3.54% 51.66%   15018  1242 sun/io/CharToByteConverter.<init>
   7  2.68% 54.35%   11388  7241 java/io/PrintWriter.<init>
   8  2.47% 56.82%   10508  7748 com/caucho/server/http/Request.fillCookies
   9  2.27% 59.09%    9650  1214 sun/io/ByteToCharConverter.<init>
  10  1.85% 60.94%    7857  5097 java/lang/String.<init>
  11  1.59% 62.53%    6754  1341 java/lang/String.substring
  12  1.57% 64.10%    6650  1340 java/lang/String.getBytes
  13  0.92% 65.02%    3907  7897 java/lang/String.<init>
  14  0.76% 65.78%    3227  3259 com/caucho/vfs/FilePath.fsWalk
  15  0.75% 66.53%    3195  7895 com/caucho/server/http/Request.fillCookie
  16  0.71% 67.25%    3031  7321 java/lang/String.getBytes
  17  0.71% 67.95%    2996  3270 com/caucho/util/CharBuffer.close
  18  0.68% 68.63%    2892  3271 java/lang/String.<init>
  19  0.66% 69.29%    2782  7318 com/caucho/vfs/FilePath.openWriteImpl
  20  0.61% 69.90%    2604  7320 java/io/FileOutputStream.<init>
 
你仅需要注意顶部的20行。你可能需要忽略顶部10行的一些信息,因为它们仅仅是等待一个用户的回应。SocketInputStream.socketRead是一个例子。你可使用跟踪号莱调用跟踪信息:
 
TRACE 7266:
com/caucho/server/http/VirtualHost.logAccess(VirtualHost.java:487)
com/caucho/server/http/Application.logAccess(Application.java:1846)
com/caucho/server/http/Response.finish(Response.java:1345)
com/caucho/server/http/Request.finish(Request.java:416)
 
2.3.5. 监视垃圾回收
使用附加参数-Xloggc:gc.log 运行Resin, "gc.log" 是日志文件的名称,其将会在Resin根目录创建,例如 /resin/gc.log。一旦服务器在一定负载下运行一定时间,或者开始出现了问题,查看gc.log文件,并搜索"Full"。开始它出现的不是很频繁,往底部查看,将会变得越来越频繁知道连续出现。注意在第一列的"timestamp"是进程已运行的秒数。垃圾收集日志会对性能有轻微的影响,但是它对诊断与垃圾收集的相关问题是很重要的。过多的垃圾收集的可能原因是内存泄露和不充足的堆内存。
 
2.3.6. 增加堆内存
参看JVM微调中有关内存部分的内容。
 
2.4. 清空classpath
旧的或者不兼容的类版本经常引起冲突。摒除这些类的第一个步骤是使用一个空的CLASSPATH环境变量来启动Resin。
win> set CLASSPATH=
win> bin/resin.exe
 
unix.sh> export CLASSPATH=""
unix.sh> bin/resin.sh
 
如果你已经在$RESIN_HOME/lib目录或者你的JDK目录放置了一些jar文件,也同样可能引起冲突。
如果在一个旧版本的Resin上安装一个新的Resin(例如安装在相同目录),一些旧的jar可能残留。最好给每一版本独立的目录。
如果RESIN_HOME环境变量没有设置,Resin可能采用一个旧版本的。
 
你可以使用 -verbose 选项运行resin.sh/resin.exe来查看当Resin启动时使用的CLASSPATH。
 
2.5. 监视HTTP传输
要监视HTTP头信息,在$RESIN_HOME/resin.xml文件中启用如下调试日志:
<resin xmlns="http://caucho.com/ns/resin">
  ...
  <log-handler name='com.caucho.server.http' level='finer'
                  path='log/http.log' />
  <log-handler name='com.caucho.server.connection' level='finer'
                  path='log/http.log' />
  ...
</resin>
侦听和监视一个web浏览器和Resin之间传递的原始数据能够提供很有价值的信息。这个原始数据包括浏览器提交的信息头和内容,及Resin返回给浏览器的信息头和内容。 Apache Axis jar包含了一个工具"tcpmon",它可以用来侦听和监视浏览器和Resin之间的传输。使用tcpmon, 你要指定一个"listen port" 、一个 "target host" 、一个"target port"。例如,如果你通常运行Resin在8080端口上,你可以启动tcpmon使用"listen port"9090端口,一个localhost的目标主机和一个目标端口8080。现在你可以在浏览器中使用一个url http://localhost:9090。这时浏览器就会使用tcpmon。tcpmon会纪录发送的请求,同时转发内容到8080端口上的Resin,也会纪录Resin返回的数据并把它也发送回浏览器。
 
2.6. 使用一个外部编译器
Resin默认使用内部(internal)编译器,因为它是很容易使用的。有时内部编译器会导致错误,抛出错误或者简单挂起和占用一个线程。解决方法是在resin.xml中改变编译器为"javac"。
 
  <javac compiler="javac" args="" />
 
当然也可以使用Jikes等编译器。
 
2.7. 调整栈内存避免线程限制
每一个线程分配了一个栈,如果栈尺寸太大,当线程数量增大时可能内存溢出。请参看JVM参数调整的文章。
 
2.8. 使用操作系统的 netstat 命令获得当前 TCP/IP 端口的使用
 
netstat命令可用来获取当前系统的网络状态。
 
unix$ netstat -anp
win> netstat -an
 
-a 指示侦听的和非侦听的套接字都显示。-n 指示显示端口号而不是端口名(例 http)。-p 显示正在使用套接字的进程。因为Windows下的netstat命令和UNIX下的有些不同,-p选项在Winodws系统上无效。
 
2.8.1. 连接状态
连接状态可能是最重要的信息。可查看netstat命令帮助获得相关状态的详细描述。
 
"LISTEN" or "LISTENING" 表示,进程在套接字上等待连接。
 
"TIME_WAIT"表示包处理结束之后套接字仍在等待的状态。连接关闭之后,套接字会被操作系统保持在打开状态一个短期的时间。即使连接完全关闭了,在网络上也可能有些偏离的包需要连接。TIME_WAIT就是保持套接字足够长的打开时间来捕捉这些偏离的包,以至于这些偏离的包不会传输到在同一个套接字上的新连接上。
 
2.8.2. 端口使用
如果Resin显示不能绑定到一个端口,这意味着可能令一个其它进程在使用这个端口,netstat可以查出那个程序在使用这个端口。因为netstat产生了很多信息,应该滤掉那些没用的信息。下面的例子是查找使用80端口的程序:
 
unix$ netstat -anp | grep ":80" | less

猜你喜欢

转载自blog.csdn.net/xiao__jia__jia/article/details/84062658