為什麼方法斷點那麼慢

2022-10-21 15:01:10

原文
一些IDE提供「方法斷點」的功能,可以讓斷點偵錯看起來非常簡潔,然而在偵錯過程中我們會發現偵錯反應時間很長,偵錯程式的效能大大降低。在本文中,我會簡單解釋方法斷點的實現原理,以及為何導致效能變差的原因。

為了更好的理解,我先簡單說明一下斷點是如何實現的,以及偵錯程式的工作原理。

JPDA(Java Platform Debugger Architecture)

JPDA是JAVA偵錯框架,主要用於debugger(偵錯程式)和debuggee(偵錯程式或程序)之間的通訊。JPDA主要由三個主要API構成。

  1. JVM TI(JVM Tool Interface) : 一個native介面,定義了VM提供debug的函數。
  2. Java Debug Wire Protocol(JDWP):JDWP是一個定義debugger和debuggee通訊的Api。
  3. Java Debug Interface(JDI): Java介面,用於前端和後端的通訊互動,JDI內部實現了JDWP介面。
  4. 下圖和文章中的前後端(back-end and font-end)分別指的是執行在VM上的偵錯程式(程序)和編輯器。
  5. 偵錯鏈:相關事件發生時(比如打斷點,單步偵錯,偵錯時修改引數值),VM通過回撥(JNI: java Native Interface,VM通過JNI來呼叫Native Interface)呼叫JVM TI,然後back-end傳送event給font-end。debugger通過JDI和JDWP與後端通訊。

為何要用方法斷點

如果呼叫的方法無法存取原始碼,或者方法內有多個if出口,此時用方法斷點很簡潔。

JAVA斷點原理

在編輯器打一個斷點,往往內部會進行三步

  1. 允許斷點事件:VM允許debugger啟用各種事件。font-end呼叫 SetEventNotificationMode() 方法啟用 can_generate_breakpoint_events ,當執行到斷點處,VM會觸發事件通過debugger鏈返回值。
  2. 註冊斷點:通過 SetBreakpoint 方法設定斷點,當執行緒執行到斷點處,VM會將所有active執行緒暫停,並且觸發斷點事件。
    SetBreakpoint(jvmtiEnv* env,
    
                  jmethodID method, //注意一下此變數,下文會再次提到。
    
                  jlocation location)
    
  3. 斷點事件:VM觸發的事件叫斷點事件,用於通知debugger。事件: Breakpoint(xxx)

方法斷點

實際上JDPA不提供方法斷點的功能,方法斷點是編輯器提供的。

debugger呼叫上文說的 SetEventNotificationMode()
啟用 can_generate_method_entry_eventscan_generate_method_exit_events,當VM執行進入和退出方法時,會向debugger傳送 方法進入退出事件:

MethodEntry(....,JmethodID method)
MethodExit(....,JmethodID method)

斷點實現流程:

  1. IDE將斷點新增到編輯器內建維護的一個斷點list裡。
  2. debugger呼叫上文說的SetEventNotificationMode(),啟用entry method和exit method,當VM執行程式碼進入和退出方法時,會向debugger傳送事件。
  3. 每當進入和退出方法時,VM會向font-end傳送MethodEntry或MethodExit。
  4. IDE根據事件中的jmethodID,來檢索該id是否存在於斷點list中。
  5. 如果存在,debugger則呼叫 SetBreakPoint 方法,將請求傳送到VM。
  6. VM執行程式碼到斷點處,停止活動執行緒,並且將event返回debugger

和普通斷點的區別在於:方法斷點在流程中需要先判斷該方法是否被前端標記為應該要打上斷點,然後才是註冊斷點。

偵錯方法斷點為何很慢

  1. JmethodID:JmethodID是正在執行方法的識別符號。每次VM需要返回MethodEntry和MethodExit時都需要攜帶JmethodID,然而VM查詢獲取JmethodID需要較長時間。
  2. communication:methodEntry和MethodExit導致前端和後端之間進行大量的通訊往返。
  3. VM callback is synchronization:VM觸發事件使用回撥時,經過以下幾個步驟(都是同步操作):
    1. 將context切換到back-end,back-end通知font-end
    2. font-end根據返回的jmethodID,查詢是否存在於斷點list中。
      在此期間程式碼執行是暫停的。

總結

  1. 儘量減少方法斷點的使用。
  2. 如果不必要,可以只使用methodEntry,不啟用methodExit,減少查詢以及通訊次數。