1. 程式人生 > >Resin 常見問題及解決方法(FAQ)

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