使用 VS Code 遠端 Debug Java 應用程式

簡介

在開發 Java 應用程式時,很多問題只有在特定環境才會出現。例如應用程式部署在遠端 Linux 主機、Docker container、Kubernetes Pod,或是某個測試環境中,單靠本機重現不一定能完整模擬實際狀況。這時候,遠端 Debug 就會變得很有用。

Java 本身提供 JDWP(Java Debug Wire Protocol)作為除錯器與 JVM 之間的溝通協議。只要啟動 Java 應用程式時開啟 JDWP debug port,VS Code 就可以透過 Debugger for Java 連線到該 JVM,像在本機除錯一樣設定中斷點、查看變數、逐步執行程式碼。

這篇文章會介紹如何使用 VS Code 連線到遠端 Java 應用程式進行 Debug,並說明 JDWP 啟動參數、VS Code launch.json 設定,以及常見連線問題的排查方式。


遠端 Debug 的基本概念

遠端 Debug 的流程可以簡化成三個步驟:

  1. Java 應用程式啟動時開啟 JDWP debug port。
  2. VS Code 使用 attach 模式連線到該 debug port。
  3. VS Code 本機原始碼與遠端執行中的 class 對應後,就可以進行中斷點除錯。

需要注意的是,VS Code 並不是直接連到 HTTP API port,而是連到 JVM 開出來的 JDWP port。例如 Spring Boot 應用程式可能使用 8080 提供 API,但 Debug 連線通常會使用另一個 port,例如 5005


前置準備

開始之前,請先確認本機 VS Code 已安裝 Java 除錯相關 extension。最簡單的方式是安裝:

  • Extension Pack for Java
  • Debugger for Java

如果你已經能在 VS Code 中正常開啟 Java 專案、看到語法提示並執行本機 Debug,通常就代表基本環境已經準備完成。

遠端環境則需要滿足以下條件:

  • 遠端 Java 應用程式使用 JDWP 參數啟動。
  • 本機可以連線到遠端 debug port。
  • 本機 VS Code 中的原始碼版本,應盡量與遠端正在執行的版本一致。

第三點很重要。Debug 時 VS Code 會用本機原始碼顯示中斷點位置,如果遠端部署的程式版本與本機不同,可能會出現中斷點無法命中、行號對不起來,或變數內容看起來不符合預期的情況。


啟動 Java 應用程式並開啟 JDWP

JDWP 可以透過 JVM 參數啟用。常見設定如下:

1
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 -jar app.jar

這段參數的意思如下:

參數 說明
transport=dt_socket 使用 socket 方式建立 debug 連線
server=y JVM 作為 debug server,等待除錯器連入
suspend=n 應用程式啟動後不等待 debugger,直接繼續執行
address=*:5005 5005 port 等待 debugger 連線

如果希望應用程式在啟動時先停住,等 VS Code 連上後才開始執行,可以把 suspend=n 改成 suspend=y

1
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005 -jar app.jar

這種方式很適合除錯應用程式啟動階段的問題,例如 Spring Bean 初始化失敗、設定檔載入錯誤,或啟動流程中的條件判斷。

JDK 8 的 address 寫法

如果遠端環境使用的是 JDK 8,JDWP 的 address 寫法通常會是:

1
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -jar app.jar

JDK 9 之後比較常使用 address=*:5005 來明確表示監聽所有網路介面。若你在不同 JDK 版本遇到 JDWP 參數無法啟動,可以先確認目前 Java 版本,再調整 address 的寫法。


Spring Boot 啟動範例

如果是直接執行 Spring Boot jar,可以這樣啟動:

1
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 -jar target/demo-0.0.1-SNAPSHOT.jar

如果是透過 Maven 啟動,可以使用 MAVEN_OPTS

1
MAVEN_OPTS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005" mvn spring-boot:run

如果是透過 Gradle 啟動,可以使用 JAVA_TOOL_OPTIONS

1
JAVA_TOOL_OPTIONS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005" ./gradlew bootRun

在 Windows PowerShell 中,環境變數設定方式會不一樣:

1
2
$env:JAVA_TOOL_OPTIONS="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005"
java -jar .\target\demo-0.0.1-SNAPSHOT.jar

設定 VS Code launch.json

接著在 VS Code 中建立或修改 .vscode/launch.json。遠端 Debug 會使用 attach 模式,代表 Java 應用程式已經在遠端啟動,VS Code 只是連線到那個 JVM。

基本設定如下:

1
2
3
4
5
6
7
8
9
10
11
12
{
"version": "0.2.0",
"configurations": [
{
"type": "java",
"name": "Attach to Remote Java",
"request": "attach",
"hostName": "127.0.0.1",
"port": 5005
}
]
}

如果本機可以直接連到遠端主機的 5005 port,hostName 可以改成遠端主機 IP 或網域名稱:

1
2
3
4
5
6
7
{
"type": "java",
"name": "Attach to Remote Java Server",
"request": "attach",
"hostName": "192.168.1.100",
"port": 5005
}

設定完成後,開啟 VS Code 左側的 Run and Debug 視圖,選擇剛剛建立的設定,按下 F5 就可以開始 attach。


使用 SSH Tunnel 連線到遠端 JDWP

實務上不建議把 JDWP port 直接暴露在公開網路。比較安全的方式是透過 SSH tunnel,把遠端的 5005 port 轉到本機:

1
ssh -L 5005:127.0.0.1:5005 user@remote-server

這個指令會把本機 127.0.0.1:5005 轉發到遠端主機的 127.0.0.1:5005。完成後,VS Code 的 launch.json 可以維持:

1
2
3
4
5
6
7
{
"type": "java",
"name": "Attach through SSH Tunnel",
"request": "attach",
"hostName": "127.0.0.1",
"port": 5005
}

這樣 VS Code 看起來是在連本機 port,但實際上會透過 SSH tunnel 連到遠端 JVM。


Docker 環境中的遠端 Debug

如果 Java 應用程式跑在 Docker container 中,需要同時處理兩件事:

  1. Container 內的 JVM 開啟 JDWP port。
  2. Docker 將該 port 映射到 host。

例如:

1
2
3
4
docker run --rm \
-p 8080:8080 \
-p 5005:5005 \
my-java-app

應用程式啟動時仍需要帶上 JDWP 參數:

1
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 -jar app.jar

如果使用 docker-compose.yml,可以在 service 中加入 port 映射與環境變數:

1
2
3
4
5
6
7
8
services:
app:
image: my-java-app
ports:
- "8080:8080"
- "5005:5005"
environment:
JAVA_TOOL_OPTIONS: "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005"

完成後,VS Code 同樣連到 127.0.0.1:5005 即可。


Kubernetes 環境中的遠端 Debug

如果 Java 應用程式部署在 Kubernetes,可以先讓 Pod 內的 JVM 開啟 JDWP port,再使用 kubectl port-forward 將 port 轉到本機。

假設 Pod 內的 Java 應用程式已經監聽 5005,可以執行:

1
kubectl port-forward pod/my-java-app-xxxxx 5005:5005

如果是透過 Deployment,可以使用:

1
kubectl port-forward deployment/my-java-app 5005:5005

接著在 VS Code 中使用 127.0.0.1:5005 attach。這種方式不需要把 JDWP port 暴露成 Service,適合短時間排查測試環境中的問題。


設定中斷點與開始除錯

VS Code attach 成功後,就可以在本機程式碼中設定中斷點。常用操作如下:

操作 快捷鍵 說明
設定或取消中斷點 F9 在目前行切換 breakpoint
繼續執行 F5 執行到下一個中斷點
Step Over F10 執行目前行,不進入 method
Step Into F11 進入目前呼叫的 method
Step Out Shift + F11 離開目前 method

如果中斷點沒有被命中,可以先確認以下幾件事:

  • 目前請求是否真的有執行到該段程式。
  • 本機原始碼版本是否與遠端部署版本一致。
  • 遠端應用程式是否有重新部署到包含該段程式的版本。
  • 中斷點是否設定在可執行的程式碼行上。

常見問題排查

VS Code 無法連線到 debug port

如果 attach 時出現連線失敗,通常可以從網路與 port 狀態開始查:

1
nc -vz 127.0.0.1 5005

或在 Windows PowerShell 中使用:

1
Test-NetConnection 127.0.0.1 -Port 5005

如果 port 無法連線,請確認 Java 應用程式是否真的有帶 JDWP 參數啟動,以及防火牆、Docker port mapping、SSH tunnel 或 kubectl port-forward 是否設定正確。

Address already in use

如果啟動 Java 應用程式時出現 Address already in use,代表 5005 已經被其他程序使用。可以換一個 debug port,例如 5006

1
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5006 -jar app.jar

同時也要把 VS Code 的 launch.json 改成相同 port。

中斷點顯示未綁定

中斷點未綁定通常和 class 尚未載入、原始碼版本不一致,或編譯後的 class 與本機檔案對應不上有關。可以先觸發相關功能讓 class 被載入,再確認遠端應用程式使用的 jar、image 或部署版本是否正確。

suspend=y 後應用程式看起來沒有啟動

suspend=y 時,JVM 會在 main class 執行前等待 debugger 連線。因此應用程式看起來像是卡住,其實是在等 VS Code attach。只要 VS Code 連線成功,應用程式就會繼續啟動。


安全性注意事項

JDWP port 不應該暴露在未受信任的網路中。只要有人可以連到該 debug port,就可能控制 JVM 執行流程、讀取記憶體中的資料,甚至造成應用程式停止。

建議遵守以下原則:

  • 不要在正式環境長時間開啟 JDWP。
  • 不要把 JDWP port 直接暴露到公開網路。
  • 優先使用 SSH tunnel 或 kubectl port-forward 進行短時間除錯。
  • Debug 結束後,移除 JDWP 啟動參數並重新部署應用程式。

遠端 Debug 很方便,但它也等於開了一個高權限的操作入口。使用時務必控制連線來源與開啟時間。


總結

VS Code 搭配 Debugger for Java,可以透過 JDWP attach 到遠端 Java 應用程式進行除錯。整個流程的關鍵在於:啟動 JVM 時開啟 JDWP、確保本機能連到 debug port,並讓本機原始碼與遠端執行版本保持一致。

在一般遠端主機上,可以使用 SSH tunnel 保護 JDWP 連線;在 Docker 或 Kubernetes 環境中,則可以透過 port mapping 或 kubectl port-forward 將 debug port 暫時轉到本機。只要掌握這些設定,就能在 VS Code 中用熟悉的中斷點、變數檢查與單步執行功能,排查遠端 Java 應用程式中的問題。


實際 Demo

如果想看完整操作流程,可以參考以下影片 Demo: