操作篇
這部分主要講,如何開啟tomcat遠程調試,並佐以實例。本文方式適用於windows和linux。
假設有兩台機器,A是tomcat服務器所在機器,B是IDE安裝機器。A和B可以是同一台機器,通常A是測試環境,B是開發環境。
簡潔版本
在A機器的tomcat/bin/文件夾中,新建文件setenv.bat(或者setenv.sh,根據你的操作系統),輸入:
export JAVA_OPTS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005"
保存文件。啟動startup.bat(或者startup.sh)。
在B機器上,啟動IDE Intellij Idea。
在run中打開edit configurations
點擊左邊綠色的加號,選擇remote
輸入IP地址和端口。
在程序中設置斷點,刷新web頁面,即可觸發斷點進行調試。[end]
簡潔版本解釋
在測試環境啟用Tomcat Remote Debug模式
Tomcat啟動遠程調試非常簡單,只需要在JVM啟動參數中設置下變量即可。
上面所做的事情,也就是在設置下JVM的啟動參數。
根據Intellij Idea的提示:
對於JDK1.5以上的版本,JVM參數是:
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
對於JDK1.4版本,使用:
-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005
再老的版本,就不用再列出,實際中也很少遇到了。
為什么Intellij給出不同版本的JDK,需要使用不同的JVM參數,stackoverflow上是這么解釋的:
Before Java 5.0, use -Xdebug and -Xrunjdwp arguments. These options will still work in later versions, but but it will run in interpreted mode instead of JIT, which will be slower.
From Java 5.0, it is better to use the -agentlib:jdwp single option:
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=1044
Options on -Xrunjdwp or agentlib:jdwp arguments are :
- transport=dt_socket : means the way used to connect to JVM (socket is a good choice, it can be used to debug a distant computer)
- address=8000 : TCP/IP port exposed, to connect from the debugger,
- suspend=y : if 'y', tell the JVM to wait until debugger is attached to begin execution, otherwise (if 'n'), starts execution right away.
該答案解釋了,使用-Xdebug方式,主要是在交互模式下適用,它在性能上會弱於使用-agentlib方式。
同時,該答案還解釋了相應的三個參數:
transport:有兩種形式,分別是socket和shared memory,需要跨機器,只能用socket
address:端口號,這里采用的是tcp協議。我們可以使用
cat /etc/services | grep '8000'
來查看該端口是否開啟
suspend:如果是y,則需要等B機器上的debugger開啟后,程序才會開始運行。否則,程序啟動時候不會掛起,直接運行。
-Xdebug -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n
也就是Intellij Idea給出的JDK1.4的版本。不過tomcat官方文檔中,也給出了
catalina jpda start
后面這種方式,其實是觸發了tomcat中的catalina腳本:
if [ "$1" = "jpda" ] ; then if [ -z "$JPDA_TRANSPORT" ]; then JPDA_TRANSPORT="dt_socket" fi if [ -z "$JPDA_ADDRESS" ]; then JPDA_ADDRESS="localhost:8000" fi if [ -z "$JPDA_SUSPEND" ]; then JPDA_SUSPEND="n" fi if [ -z "$JPDA_OPTS" ]; then JPDA_OPTS="-agentlib:jdwp=transport=$JPDA_TRANSPORT,address=$JPDA_ADDRESS,server=y,suspend=$JPDA_SUSPEND" fi CATALINA_OPTS="$CATALINA_OPTS $JPDA_OPTS" shift fi
該腳本會讀取自定義參數(如果存在),作為啟動JPDA的參數。默認的端口號是8000.
設置tomcat的JVM參數
設置參數有多種方式,其本質是修改catalina中的JAVA_OPTS值,也就是JVM的啟動參數。
一種方式是,直接在catalina中設置該值。不過這種方法,hardcode了catalina文件,屬於not recommend方式。
推薦的方式是,在tomcat/bin目錄下,新建一個setenv.bat(或者startup.sh,根據你的操作系統),並寫入需要設置的參數。
這種推薦的方式,反應在catalina中的代碼是:
# Ensure that any user defined CLASSPATH variables are not used on startup, # but allow them to be specified in setenv.sh, in rare case when it is needed. CLASSPATH= if [ -r "$CATALINA_BASE/bin/setenv.sh" ]; then . "$CATALINA_BASE/bin/setenv.sh" elif [ -r "$CATALINA_HOME/bin/setenv.sh" ]; then . "$CATALINA_HOME/bin/setenv.sh" fi
catalina腳本會去先從setenv中讀取環境變量。
最后,啟動startup即可。
原理篇
核心是JPDA(Java Platform Debugger Architecture)框架。IBM有非常詳細的介紹:
https://www.ibm.com/developerworks/cn/java/j-lo-jpda1/
https://www.ibm.com/developerworks/cn/java/j-lo-jpda2/
https://www.ibm.com/developerworks/cn/java/j-lo-jpda3/
https://www.ibm.com/developerworks/cn/java/j-lo-jpda4/
關鍵摘抄如下:
- JPDA是JVM的調試標准,任何JDK都必須實現。
- 調試Java程序,本質是向JVM請求當前狀態。這需要對JVM發送一定指令,設置一定回調。
- JPDA的三個層次:Java 虛擬機工具接口(JVMTI),Java 調試線協議(JDWP)以及 Java 調試接口(JDI)。它們之間的角色圖:
- JVMTI(Java Virtual Machine Tool Interface):通向JVM的native的接口,處於體系最底層。所有的調試功能都需要該接口提供。是debug和profiler的接口,是線程分析、監聽和代碼覆蓋率檢查的基礎。會有個緊耦合的Agent來實現JVMTI。該Agent的加載時間是出於JVM初始化早期或者運行時加載(對應於上文中的suspend參數)。
- JDWP(Java Debug Wire Protocol):交互的通訊協議。定義了交互中的命令、回應數據和錯誤代碼。不包含傳輸層具體實現。主要分為握手和應答兩個階段。
- JDI(Java Debug Interface):對JDWP進行解析,為JDWP提供隊列、緩存等服務。分為數據模塊,將虛擬機上的所有數據、狀態映射為Java的數據對象;鏈接模塊,調試器向虛擬機發起鏈接,以獲得各種狀態,分為主動和被動形式。事件請求和處理模塊。
- 由於JVM神一般的存在,使得Java的運行是先天可控的,因此其調試程序的開發,也遠容易與C++。
- 類似工具:Apache harmony