- 浏览: 114888 次
- 性别:
- 来自: 广州
最新评论
-
焦德江:
非常感谢楼主啦!网上下载其他精简版的都不能用
安装oracle9i客户端精简版 -
fuyonglei:
这个标准应该严重过时了。。。。。。。。
我刚毕业一年,除了没带 ...
程序员 -
marc0658:
非常好。。
安装oracle9i客户端精简版 -
李智明:
和没说一样
IETester+DebugBar+Companion.JS -
JustDoNow:
哥们,谢啦
解决问题了
log4j日志文件乱码问题
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
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
发表评论
-
容器和队列
2014-12-05 11:26 743同步容器 主要代表有Vector和Hashtable,以及C ... -
tfs安装
2014-10-23 17:06 673yum install automake.noarch yum ... -
分析函数
2010-12-06 16:22 821row_number() over(partition by ... -
十个免费的Web压力测试工具
2010-11-03 10:12 884Grinder – Grinder是一个开源的JVM负载测试 ... -
Web服务器性能压力测试
2010-11-03 10:11 1020一、http_load 程序非常小,解压后也不到100K ... -
整理用SQL进行表单查询
2010-10-19 15:22 1315显示数据表的结构 desc 数据表名 查询所有记录 selec ... -
spring3 mvc 注解
2010-09-19 14:59 1011@Autowired @Component @Contro ... -
resin泛域名
2010-09-15 10:05 0Resin: <host id="ho ... -
memcache
2010-09-01 21:16 752http://www.chineselinuxuniversi ... -
需要掌握的技术【转】
2010-09-01 20:55 7541、主流框架要掌握如(ssh),没事可以了解下jsf,shal ... -
获取spring bean
2010-08-26 16:29 983某个线程处理回调时,需要对数据库进行操作,本来可以这样: ... -
转 spring mvc图
2010-08-12 09:02 825... -
sql函数大全
2010-06-11 10:01 9001、内部合计函数 1)COUNT(*) 返回行数 2)COUN ... -
Oracle复制表
2010-05-27 08:23 2211如下,表a是数据库中已经存在的表,b是准备根据表a进行复制创建 ... -
Spring2.5注解实现AOP(转)
2010-05-26 09:00 898这个例子非常的简单, ... -
Spring2.5 注解 Aspect AOP (转)
2010-05-26 08:46 858在低版本Spring中定义一个切面是比较麻烦的,需要实现特定的 ... -
Java remote
2010-05-24 10:34 731在处理Remote调用时,通常思路如下: 1. WebSer ... -
创建session学习-request.getSession()
2010-05-15 15:12 1145在 HttpServlet中,HttpSessio ... -
JVM垃圾回收机制与GC调整
2010-02-20 15:56 777JVM垃圾回收机制与GC调 ... -
关于spring包的详解
2010-01-27 12:59 719[转http://www.iteye.com/topic/57 ...
相关推荐
### 运维-resin常见问题解析 #### 1. Resin服务器基础问题 ##### 1.1 Resin服务器概述 Resin是一款高性能的企业级Java应用服务器,它支持Servlet、JSP以及各种Java EE规范,被广泛应用于Web应用程序的部署与运行...
【Resin常见有关问题详解】 Resin是一款由CAUCHO公司开发的高性能Web服务器和应用服务器,它专门支持Servlets和JSP,并以其快速的处理能力而受到青睐。Resin内建了HTTP/1.1协议的支持,不仅擅长处理动态内容,而且...
#### 四、常见问题及解决方案 1. **无法启动Resin服务**:检查端口是否被占用,尝试更改端口号。 2. **管理员账户无法登录**:确认用户名和密码是否正确,密码是否经过正确的加密。 3. **项目部署失败**:确保...
本篇文章将围绕Resin的配置方法进行详细介绍。 #### 二、JDK与Resin的集成配置 1. **JDK环境变量设置**: - 首先确保系统中已正确安装JDK,并设置了`JAVA_HOME`环境变量。这一步至关重要,因为Resin会依赖于`JAVA...
通过阅读提供的"resin-2.1.12"文档,可以了解详细的安装步骤、配置参数、API使用以及常见问题的解决方案。 对于初学者,建议从以下几个方面入手学习Resin 2.1.2: 1. **安装与配置**:学习如何在不同的操作系统...
#### 四、常见问题及解决方法 - **启动失败**:检查resin.conf中的配置是否正确,例如端口冲突、路径错误等问题。 - **应用部署失败**:确认应用文件是否完整无误,检查Eclipse中Deployment配置是否正确。 通过...
### 泛微e-cology常见问题解决及性能调优 #### 概述 泛微e-cology是一款功能强大的办公自动化(OA)系统软件,广泛应用于各类企事业单位的日常办公环境中。由于其高度定制化和复杂的功能特性,在部署、运行过程中...
9. **与其他技术的集成**:Resin可以与各种数据库系统(如MySQL、Oracle、PostgreSQL等)、缓存系统(如Memcached、Redis)和消息队列(如ActiveMQ)无缝集成,以构建复杂的企业级解决方案。 10. **开发工具兼容性*...
通过以上方案的实施,可以在一定程度上缓解甚至解决Resin服务器遇到内存溢出问题时的自动重启需求。值得注意的是,虽然自动重启可以在短时间内恢复服务,但从长远来看,还是需要深入分析问题的根本原因,并针对性地...
8. **日志和诊断**:详细的日志记录和故障排查工具对于诊断和解决性能问题至关重要。4.0.50版本可能提供了更强大的日志系统和故障检测机制。 在解压后的`resin-4.0.50`文件中,通常会包含以下组件: - `bin`: 存放...
用户可以通过IIS7的图形化界面管理站点,而应用的执行则交由Resin来处理,特别是对于需要运行Java应用的Windows环境,这是一种常见的解决方案。 需要注意的是,配置过程中可能出现的安全问题和性能优化需要根据实际...
总结来说,Nginx+Resin的组合是一种常见的Web服务架构,利用Nginx的高效性能和Resin的强大Java应用支持,为大型网站和企业应用提供可靠且高效的解决方案。理解并熟练掌握这两个工具的配置和协作,对于任何IT专业人士...
5. **日志分析与调试**:在配置过程中,日志文件(如Apache的`error_log`和Resin的`access.log`、`server.log`)是非常有用的工具,它们能帮助识别和解决出现的问题。 6. **安全性考虑**:在生产环境中,确保Web...
在互联网服务领域,Resin和Apache是两种常见的Web服务器。Resin,作为Java应用服务器,以其高效的性能和对Servlet、JSP的支持而受到青睐;而Apache,作为开源HTTP服务器,以其稳定性和丰富的模块扩展性闻名。当我们...
Java 使用 Apache+Resin 实现负载均衡设置是一种常见的解决方案,旨在提高系统的可用性和可扩展性。下面是该方案的详细说明: 一、为什么需要负载均衡? 在实际应用中,系统可能会面临高并发访问的情况,这时系统...
针对常见的性能瓶颈问题,文档中列举了一些典型场景及其解决方案,帮助用户快速解决问题。 #### 九、结论 通过对Resin部分核心功能的深入解析,我们不仅了解了其内部的工作原理,同时也掌握了如何利用这些特性来...
"IDEA2020 1.1中Plugins加载不出来的问题及解决方法" 本文将详细介绍IDEA2020 1.1中Plugins加载不出来的问题,并提供相应的解决方法。 IDEA2020 1.1中Plugins加载不出来的问题 IDEA2020 1.1中Plugins加载不出来的...
- 配置错误,可通过查看日志文件`Resin/log/stdout.log`及`stderr.log`来定位问题。 - 数据库连接失败。 - **解决方案**: - 在Windows环境中,检查日志文件以获取详细的配置错误提示。 - 在Linux环境下,可以...
5. 常见问题解决: - 安装过程中可能会遇到的问题可能包括路径权限问题、配置文件设置错误、端口冲突等。解决这些问题通常需要检查文件系统权限、仔细核对配置文件设置、使用系统命令查看端口使用状态。 - 启动过程...