1. 癥狀
1.1. Resin停止響應(yīng)
●可能是一個線程死鎖的問題,應(yīng)該進行線程轉(zhuǎn)儲。
●啟用完全調(diào)試日志模式,檢查日志最后的紀(jì)錄看看發(fā)生了什么。
1.2. Resin不停重啟動
●啟用完全調(diào)試日志模式,檢查記錄看看Resin為什么不停的重啟它。
1.3. java.lang.OutOfMemoryError錯誤,應(yīng)用程序內(nèi)存溢出
●使用JVM啟動參數(shù)增加堆(heap)內(nèi)存。
●轉(zhuǎn)儲堆,看看那個對象無法被垃圾回收器無法回收。
●轉(zhuǎn)儲線程,檢查占用著對象的不能釋放的線程
一個OutOfMemoryError錯誤通常意味著堆(heap)內(nèi)存被用盡。一般是應(yīng)用程序代碼保持了對不在使用的對象的引用,垃圾回收器無法對其進行回收。轉(zhuǎn)儲堆,能夠查到什么代碼和什么種類的對象被占用了。如果對轉(zhuǎn)儲或者其它監(jiān)視工具顯示服務(wù)器和你的程序?qū)嶋H沒有超出堆內(nèi)存,那么 OutOfMemoryError意味著JVM超出了虛擬內(nèi)存,也就是底層的malloc()調(diào)用失敗。通常這種情況,通過使用操作系統(tǒng)工具顯示內(nèi)存使用,JVM自己能夠顯示其自己的堆內(nèi)存,但是操作系統(tǒng)工具確顯示進程占用了大量的內(nèi)存。在Windows下使用任務(wù)管理器,Unix下使用top或者ps 命令。
JVM無法進行堆內(nèi)存分配可能有如下原因:
●線程,特別是線程堆占用虛擬內(nèi)存。
●JNI庫可能調(diào)用malloc或者nmap占用虛擬內(nèi)存。這包括很多數(shù)據(jù)庫驅(qū)動,也包含一些Resin使用的JNI代碼。
●對于.jar/.zip文件,JDK要分配虛擬內(nèi)存。如果你打開了大量的jar文件,你可能會遇到問題??梢韵氲接糜诖蜷_jar的getResourceAsStream沒有關(guān)閉將會耗盡.jar內(nèi)存。
1.4. 運行一會兒,服務(wù)器開始變得非常慢
● 這可能是一個垃圾回收問題。如果你的內(nèi)存缺乏,然后又創(chuàng)建了大量的對象,這導(dǎo)致垃圾回收器耗盡CPU。如果你內(nèi)存溢出,JVM將會慢慢停止(連續(xù)地進行垃圾收集)直到它死亡。
○ 監(jiān)視垃圾收集。
○ 轉(zhuǎn)儲堆,看看是否是有對象無法被回收。
○ 參看JVM垃圾回收參數(shù)調(diào)整的文檔獲得更多垃圾回收的信息。
● 可能有一個死循環(huán)的線程或者一個請求耗盡資源?;貞?yīng)一個請求的線程如果不能返回,Resin就沒法再次利用它,那么可用來服務(wù)的線程就會越來越少。
○ 進行線程轉(zhuǎn)儲,檢查可能占用對象的無法釋放的線程。
1.5. CPU尖峰,高的CPU使用率
● 轉(zhuǎn)儲線程,檢查那些線程在無限循環(huán)。
● 檢查垃圾收集的部分。
1.6. 會話(sessions)變成null,會話丟失
1.6.1. 調(diào)試日志
首先啟用調(diào)試日志。特別是瀏覽器請求提交的頭信息能夠顯示一個客戶端的JSESSIONID狀態(tài),日志也能說明Resin什么時候識別、創(chuàng)建和失效一個會話。
1.6.2. Resin會話配置
另一個可能是session-max設(shè)置過低,導(dǎo)致當(dāng)前用戶建立會話的數(shù)量大于你設(shè)置的這個值。另一個可能是會話超時,你可以通過session-timeout標(biāo)簽來配置它。
<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. 應(yīng)用程序重載
無論何時,一個java源文件、web.xml或者resin.xml改變,Resin都會重啟應(yīng)用程序。如果這個情況發(fā)生,你當(dāng)前的會話就會丟失,除非你配置了一個持久性會話存儲。
1.6.4. 瀏覽器cookie的局限
一些用戶報告,如果他們的應(yīng)用程序使用大量的cookie,瀏覽器將會丟棄舊的cookie為新的騰出空間。這就會出現(xiàn)瀏覽器丟失了Resin 用來跟蹤會話的cookie。IE瀏覽器用戶特別容易遇到這個問題。如果你的應(yīng)用程序使用大量的cookie,最好的解決方案就是減少cookie數(shù)量和 cookie數(shù)據(jù)的大小。Resin使用一個單一的cookie其存儲相對很少的數(shù)據(jù)用拉跟蹤用戶的會話ID。應(yīng)用程序存儲在cookie中的信息可以使用HttpSession對象來存儲。作為最后的手段,你可以配置Resin總是使用URL重寫,這需要把enable-cookies設(shè)置成 false。由于安全的原因URL重寫式不推薦的,因為重寫URL增加了重寫某些頁面丟失調(diào)用的高可能性。
<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域名不兼容也可能丟失會話。例如,如果你有一個服務(wù)器使用cookie域名"hogwarts.com",另一個使用 "qa.hogwarts.com",在瀏覽器中"hogwarts.com"的cookie會干擾在"qa.hogwarts.com"上的會話。方法是改變cookie域名"hogwarts.com"為"www.hogwarts.com"。 你可以在session-config標(biāo)簽中設(shè)置 cookie域名。
1.6.6. cookie名稱沖突
如果你使用Resin和另一個應(yīng)用服務(wù)器(例如Tomcat),你可能遇到這個沖突,因為它們使用相同的cookie名稱(他通常是 JSESSIONID) 來跟蹤會話。Resin提供session-cookie 和 ssl-session-cookie讓你可以改變Resin使用的cookie名稱。
改變用來跟蹤會話的cookie名稱的片斷:
<cluster>
...
<session-cookie>RJESSESSIONID</session-cookie>
1.6.7. URL重寫
如果你忘記了重寫一個URL,一個需要重寫的用戶當(dāng)訪問到這個URL時將丟失他們的會話。Resin在一個用戶瀏覽器和一個會話 (session)之間建立一個關(guān)聯(lián),是通過為每一個新請求返回一個惟一的id。這可通過兩種方式之一來完成:使用cookie或者URL重寫。 Resin首先嘗試向用戶瀏覽器發(fā)送一個包含惟一會話ID的cookie來跟蹤一個用戶的會話。有時Resin不能建立cookie,不是因為用戶在其瀏覽器禁用了cookies就是因為某些瀏覽器不支持它們(例如一些HDML和WML瀏覽器)。如果cookie不能建立那么就會使用URL重寫。在這種情況下,Resin重寫每一個它提交給用戶的URL,讓其包含一個名稱為_jsessionid的參數(shù)。然后為每一個新來的請求做的第一件事就是查找這個參數(shù),如果這個參數(shù)存在那么就知道一個會話已經(jīng)建立,它移出參數(shù)并使用它來查找用戶會話對象。URL重寫需要開發(fā)者的協(xié)同合作。開發(fā)者必須編碼每一個URL 引用讓Resin有一個合適的機會放置_jsessionid參數(shù)。
使用JSTL實現(xiàn)URL重寫
<%@ taglib prefix='c' uri='http://java.sun.com/jstl/core' %>
Time to go <a href="<c:url _fcksavedurl="<c:url _fcksavedurl="<c:url value='home.jsp' />">Home</a>!
使用Java scriptlet實現(xiàn)URL重寫
<%
String homeUrl = response.encodeURL("home.jsp");
%>
<%-- the presentation --%>
Time to go <a href="<%= homeUrl %>">Home</a>!
1.7. J2EE規(guī)范,javax.servlet包規(guī)范1.3和Resin不兼容
參看清除classpath環(huán)境變量。
1.8. Unsupported major.minor version 48.0
這個錯誤經(jīng)常在發(fā)現(xiàn)一個沖突的jar時發(fā)生,參看清除classpath環(huán)境變量。
如果環(huán)境變量classpath被完全清除,然而一個JDK或者舊Resin的一個jar或者一些其它組件出現(xiàn)在的什么地方,如果你已經(jīng)在那些地方添加了,在你的JAVA_HOME樹里的一些jar可能有一個問題,那里可有一個和你的web程序WEB-INF/lib/目錄下沖突的jar。另一種可能是你還沒設(shè)置JAVA_HOME,或者你使用了一個沖突的JDK的一些組件。
如果在Windows上,檢查JAVA_HOME之外的java.exe的拷貝,例如C:/WINDOWS/java.exe或者在你PATH路徑里其它地方的java.exe。
1.9. 讀取POST數(shù)據(jù)的問題
首先啟用調(diào)試日志。調(diào)試日志會顯示發(fā)送到Resin的請求,提供一些Resin如何處理這些數(shù)據(jù)的信息。最重要的是確保在讀取POST參數(shù)之前編碼設(shè)置正確。瀏覽器總是發(fā)回和輸出頁面編碼相同的參數(shù)。因為請求不包含編碼,應(yīng)用程序代碼需要確保編碼匹配。因此第一件事就是確定發(fā)送到瀏覽器的表單的編碼。你的應(yīng)用程序總應(yīng)該指定它。一旦你指定了它,你就知道瀏覽器POST使用編碼。(這里UTF-8是個自然的編碼選擇,我不能確信你為什么使用其它的編碼)。在讀取POST參數(shù)之前確保設(shè)置了正確的編碼,你可以調(diào)用request.setCharacterEncoding(encoding)來設(shè)置編碼。
2. 技巧方法
2.1. 啟用調(diào)試日志
Resin使用JDK日志工具提供了大量的診斷信息。通過使用一個空名稱(匹配所有名字)可以啟用完全調(diào)試日志,調(diào)試級別為“全部”。因為將會產(chǎn)生大量信息,把這些信息放在一個單獨的文件中比較好。
2.1.1. 服務(wù)器和所有應(yīng)用程序的完全調(diào)試日志
下面的配置每天創(chuàng)建一個日志,當(dāng)一個問題出現(xiàn)時用來查找問題出現(xiàn)在什么地方。因為日志配置在resin.xml中,日志的捕捉是服務(wù)器和其上所有應(yīng)用程序的。日志輸出的信息在 $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應(yīng)用程序的完全調(diào)試日志
通常你一般僅需要一個程序輸出的調(diào)試日志。日志配置記錄放在<web-app-root>/WEB-INF/web.xml中,那么僅這個web應(yīng)用程序的日志信息被輸出到日志文件中。下面的配置每天創(chuàng)建一個調(diào)試日志,位置<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. 線程轉(zhuǎn)儲
如果應(yīng)用程序好像有問題或者超出資源泄露,線程轉(zhuǎn)儲能夠顯示服務(wù)器的狀態(tài)。對于服務(wù)器調(diào)試Java的縣城轉(zhuǎn)儲是一個重要的工具。因為 Servlet是多線程的,沒有處理好的話很可能出現(xiàn)死鎖,或者出現(xiàn)死循環(huán)和導(dǎo)致內(nèi)存溢出錯誤。特別是你使用了第三方軟件例如數(shù)據(jù)庫、EJB和Corba ORBs。
2.2.1. 使用JDK5工具轉(zhuǎn)儲線程
在JDK5里可以使用jps和jstack,一個快捷的命令行方法獲得當(dāng)前所有線程的堆棧跟蹤信息。
# 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. 通過發(fā)送一個信號轉(zhuǎn)儲線程
在 Windows, ctrl-break會產(chǎn)生線程轉(zhuǎn)儲。
在Unix, "kill -QUIT" 會產(chǎn)生線程轉(zhuǎn)儲。
2.2.3. 如果發(fā)送信號無效時的線程轉(zhuǎn)儲
你可以在啟動JVM時指定附加的參數(shù)允許附加一個調(diào)試器而不是發(fā)送信號來轉(zhuǎn)儲線程。你然后在任何時候附加調(diào)試器來得到線程轉(zhuǎn)儲。 這種方法在所有的操作系統(tǒng)上得到支持。
下面是是逐步的指導(dǎo):
1. 使用附加的參數(shù)啟動Resin來允許一個調(diào)試器附加:
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. 等待,直到你認(rèn)為應(yīng)用程序出現(xiàn)了死鎖或者失去控制。
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"命令獲得一個線程轉(zhuǎn)儲:
例子: 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" 命令來恢復(fù)進程
> 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
雖然沒有進行過嚴(yán)格基準(zhǔn)測試,好像使用線程轉(zhuǎn)儲參數(shù)啟動的JVM在性能上影響不大。
2.2.4. 理解線程轉(zhuǎn)儲
在任何情況下,你會最終得到類似如下的跟蹤調(diào)試信息(不同的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端口監(jiān)聽新連接的線程
tcpConnection-8080-3 處理從8080端口連接的servlet線程
tcp-cron Resin的run-at線程
tcp-alarm Resin的警告線程
Resin為每一個<http>和<srun>開啟一個 tcp-accept-xxx 線程,tcp-accept-xxx 線程總是處于socketAccept狀態(tài)。應(yīng)該有一些tcpConnection-xxx-n線程,每一個都是一個servlet線程。在一個忙碌的服務(wù)器上,這些能在你代碼里任何地方出現(xiàn)。如果幾個出現(xiàn)在一個位置,你可能有某種死鎖或者至少一個慢鎖。空閑線程不是tcpAccept就是 httpRequest 或者runnerRequest。對于死鎖,你應(yīng)查看"waiting on monitor"線程和很多線程阻塞在同一位置的任一實例。
2.3. 內(nèi)存溢出和垃圾收集
大部分內(nèi)存問題時應(yīng)用程序設(shè)計上的內(nèi)存漏洞。例如,一個緩存或者vector填充了過期的數(shù)據(jù),或者一個singleton或者靜態(tài)變量不能適當(dāng)?shù)貍蓽y到web-app重啟。大部分怪異的內(nèi)存問題是堆內(nèi)存或者虛擬內(nèi)存溢出,當(dāng)使用了大量的線程(〉256)。
追蹤捕獲內(nèi)存問題的步驟是:
1. 使用 resin.sh start or resin.exe -install啟用 -J-verbosegc。 -verbosegc標(biāo)志記錄堆的垃圾收集,讓你知道你是否堆內(nèi)存溢出了(大部分情況是這樣的)。
2. 獲得一個heap profiler或者在JVM中使用堆轉(zhuǎn)儲。JProfiler是一個價格便宜的商業(yè)的heap profiler.雖然JVM的堆轉(zhuǎn)儲不是很用戶友好,但是它也是可用的。你應(yīng)該使用一個heap profiler作為你開發(fā)過程的一部分,在任一產(chǎn)品投入使用前應(yīng)該使用一個。
3. 使用heap profiler, 找到2-3個過量消耗內(nèi)存的用戶并修正這些內(nèi)存漏洞。
4. 一般應(yīng)用程序錯誤包括:
○ 在每一個請求(request)結(jié)束ThreadLocal變量沒有正常清除。
○ 單一模式(Singleton) 或者靜態(tài)散列影射和緩存,web-app重啟要清除。
○ web-app重啟后衍生出來的線程不能被停止。
○ web-app 變量 (像 "application" 變量), 被存儲在一個靜態(tài)變量中。
5.如果堆沒問題,例如 -verbosegc顯示了一個穩(wěn)定的堆,你應(yīng)該看看非堆內(nèi)存:
○ 線程棧的使用(-Xss2m). 每一個線程的消耗一些非堆內(nèi)存。一些系統(tǒng)默認(rèn)是8M。在一些32位系統(tǒng)上虛擬內(nèi)存的限制大約是2G,256個線程,每個消耗8M,就能耗盡虛擬內(nèi)存。你可以減小棧內(nèi)存通過使用 -Xss指令。
○ Java 本地接口內(nèi)存(JNI memory)。如果你使用JNI庫或者使用了利用JNI的驅(qū)動,JNI分配了比可用內(nèi)存更多的內(nèi)存是可能的。
○ fork/exec 和 OS 限制. 如果操作系統(tǒng)沒有足夠的交換空間可用,例如操作系統(tǒng)可能拒絕一個"jikes"編譯。
○ NIO, 內(nèi)存影射, 和 .jar 文件. JDK在內(nèi)存中影射jar文件。在某些情況下,大量的jar文件能夠耗盡虛擬內(nèi)存。這種情況也出現(xiàn)在NIO內(nèi)存映射中。
6. 如果所有這些情況都被排除,它可能是一個Resin的BUG。然而你應(yīng)該在報告Resin BUG之前找到了這個內(nèi)存漏洞,例如在報告BUG之前你已經(jīng)進行了上面所有的檢查。關(guān)于內(nèi)存溢出的BUG報告,如果沒有得到一個JDK內(nèi)存轉(zhuǎn)儲,它更有可能是一個程序上的錯誤。你必須在報告任一潛在的Resin內(nèi)存問題時提供一個堆轉(zhuǎn)儲。
2.3.1. -verbosegc
-verbosegc是一個JVM的調(diào)試輸出。對于檢查基本的內(nèi)存使用和垃圾收集時間它是一個非常方便的工具。對于任一產(chǎn)品系統(tǒng)使用 -verbosegc 是一個好主意。當(dāng)啟動Resin時,你可以使用-J-verbosegc。
特定的輸出依賴于JVM,一些內(nèi)容看起來如下:
-verbosegc output
[GC 9176K->8647K(9768K), 0.0014790 secs]
[GC 9287K->8668K(9768K), 0.0011120 secs]
[GC 9308K->8668K(9768K), 0.0007810 secs]
"(9768K)"是非常重要的數(shù)據(jù),表示最大可分配的堆大約是10M。其它數(shù)值顯示了實際的堆使用在垃圾收集前后。
2.3.2. 使用堆轉(zhuǎn)儲檢查內(nèi)存使用
如果一個應(yīng)用程序過分地消耗內(nèi)存直到拋出內(nèi)存溢出錯誤,或者好像在垃圾收集上消耗了大量的時間,一個堆轉(zhuǎn)儲能夠幫助你找到問題的根源。真正需要你去做的是有一個CPU和堆調(diào)試程序(profile)。JDK自帶一個簡單的(界面不是很用戶友好),因此不必一定需要買一個profile。 jvmstat就是一個簡單的堆監(jiān)視器。它是一個標(biāo)準(zhǔn)的java參數(shù),因此"java -Xrunhprof:help"會告訴你如何啟動它。例如你可以如下啟動Resin
> resin.sh -J-Xrunhprof:heap=sites,cpu=samples
(在Unix上, Resin啟動腳本有個 -cpuprof-ascii 參數(shù)被自動設(shè)置.)
運行一個負(fù)載一定的時間(你可以運行類似Apache "ab"工具10分鐘時間),然后正常停止服務(wù)器,你不應(yīng)使用crtl-C殺死它,你需要一個正常的退出。它會轉(zhuǎn)儲一個 java.hprof.txt 文件。在這個文件的尾部查看跟蹤信息。
2.3.3. 理解 java.hprof.txt 文件中的棧信息
假設(shè)你采用廉價方案,使用JDK的堆調(diào)試器而不是購買一個,你就需要幫助來解釋它。下面是一個運行中的Resin堆轉(zhuǎn)儲的一個例子。在這個例子中你要跳到"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"列數(shù)值大,你需要分析它。 那可能有內(nèi)存漏洞。第二,如果一些類在"alloc'ed objs"列數(shù)值大,這可能浪費了大量的垃圾收集時間,你可以使用緩存來解決它。
在類名稱中的 [C 意味著一個字符數(shù)組。要知道它到底表示什么,你需要查看棧跟蹤 (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"參數(shù)能得到更長的信息。(或者在-Xrunhprof指定相應(yīng)的深度)。那會通常會給出更多的信息。
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行的一些信息,因為它們僅僅是等待一個用戶的回應(yīng)。SocketInputStream.socketRead是一個例子。你可使用跟蹤號萊調(diào)用跟蹤信息:
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. 監(jiān)視垃圾回收
使用附加參數(shù)-Xloggc:gc.log 運行Resin, "gc.log" 是日志文件的名稱,其將會在Resin根目錄創(chuàng)建,例如 /resin/gc.log。一旦服務(wù)器在一定負(fù)載下運行一定時間,或者開始出現(xiàn)了問題,查看 gc.log文件,并搜索"Full"。開始它出現(xiàn)的不是很頻繁,往底部查看,將會變得越來越頻繁知道連續(xù)出現(xiàn)。注意在第一列的"timestamp"是進程已運行的秒數(shù)。垃圾收集日志會對性能有輕微的影響,但是它對診斷與垃圾收集的相關(guān)問題是很重要的。過多的垃圾收集的可能原因是內(nèi)存泄露和不充足的堆內(nèi)存。
2.3.6. 增加堆內(nèi)存
參看JVM微調(diào)中有關(guān)內(nèi)存部分的內(nèi)容。
2.4. 清空classpath
舊的或者不兼容的類版本經(jīng)常引起沖突。摒除這些類的第一個步驟是使用一個空的CLASSPATH環(huán)境變量來啟動Resin。
win> set CLASSPATH=
win> bin/resin.exe
unix.sh> export CLASSPATH=""
unix.sh> bin/resin.sh
如果你已經(jīng)在$RESIN_HOME/lib目錄或者你的JDK目錄放置了一些jar文件,也同樣可能引起沖突。
如果在一個舊版本的Resin上安裝一個新的Resin(例如安裝在相同目錄),一些舊的jar可能殘留。最好給每一版本獨立的目錄。
如果RESIN_HOME環(huán)境變量沒有設(shè)置,Resin可能采用一個舊版本的。
你可以使用 -verbose 選項運行resin.sh/resin.exe來查看當(dāng)Resin啟動時使用的CLASSPATH。
2.5. 監(jiān)視HTTP傳輸
要監(jiān)視HTTP頭信息,在$RESIN_HOME/resin.xml文件中啟用如下調(diào)試日志:
<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>
偵聽和監(jiān)視一個web瀏覽器和Resin之間傳遞的原始數(shù)據(jù)能夠提供很有價值的信息。這個原始數(shù)據(jù)包括瀏覽器提交的信息頭和內(nèi)容,及Resin返回給瀏覽器的信息頭和內(nèi)容。 Apache Axis jar包含了一個工具"tcpmon",它可以用來偵聽和監(jiān)視瀏覽器和Resin之間的傳輸。使用tcpmon, 你要指定一個"listen port" 、一個 "target host" 、一個"target port"。例如,如果你通常運行Resin在8080端口上,你可以啟動tcpmon使用"listen port"9090端口,一個localhost的目標(biāo)主機和一個目標(biāo)端口8080。現(xiàn)在你可以在瀏覽器中使用一個url de>http://localhost:9090。這時瀏覽器就會使用tcpmon。tcpmon會紀(jì)錄發(fā)送的請求,同時轉(zhuǎn)發(fā)內(nèi)容到8080端口上的Resin,也會紀(jì)錄Resin返回的數(shù)據(jù)并把它也發(fā)送回瀏覽器。de>
2.6. 使用一個外部編譯器
Resin默認(rèn)使用內(nèi)部(internal)編譯器,因為它是很容易使用的。有時內(nèi)部編譯器會導(dǎo)致錯誤,拋出錯誤或者簡單掛起和占用一個線程。解決方法是在resin.xml中改變編譯器為"javac"。
<javac compiler="javac" args="" />
當(dāng)然也可以使用Jikes等編譯器。
2.7. 調(diào)整棧內(nèi)存避免線程限制
每一個線程分配了一個棧,如果棧尺寸太大,當(dāng)線程數(shù)量增大時可能內(nèi)存溢出。請參看JVM參數(shù)調(diào)整的文章。
2.8. 使用操作系統(tǒng)的 netstat 命令獲得當(dāng)前 TCP/IP 端口的使用
netstat命令可用來獲取當(dāng)前系統(tǒng)的網(wǎng)絡(luò)狀態(tài)。
unix$ netstat -anp
win> netstat -an
-a 指示偵聽的和非偵聽的套接字都顯示。-n 指示顯示端口號而不是端口名(例 http)。-p 顯示正在使用套接字的進程。因為Windows下的netstat命令和UNIX下的有些不同,-p選項在Winodws系統(tǒng)上無效。
2.8.1. 連接狀態(tài)
連接狀態(tài)可能是最重要的信息。可查看netstat命令幫助獲得相關(guān)狀態(tài)的詳細(xì)描述。
"LISTEN" or "LISTENING" 表示,進程在套接字上等待連接。
"TIME_WAIT"表示包處理結(jié)束之后套接字仍在等待的狀態(tài)。連接關(guān)閉之后,套接字會被操作系統(tǒng)保持在打開狀態(tài)一個短期的時間。即使連接完全關(guān)閉了,在網(wǎng)絡(luò)上也可能有些偏離的包需要連接。TIME_WAIT就是保持套接字足夠長的打開時間來捕捉這些偏離的包,以至于這些偏離的包不會傳輸?shù)皆谕粋€套接字上的新連接上。
2.8.2. 端口使用
如果Resin顯示不能綁定到一個端口,這意味著可能令一個其它進程在使用這個端口,netstat可以查出那個程序在使用這個端口。因為netstat產(chǎn)生了很多信息,應(yīng)該濾掉那些沒用的信息。下面的例子是查找使用80端口的程序: