­

「每日五分鐘,玩轉JVM」:執行緒獨佔區

  • 2019 年 10 月 3 日
  • 筆記

前言

如果我們對電腦組成有所了解,那麼我們一定會知道在電腦中有一塊兒特殊的區域,稱之為暫存器,暫存器包括了指令暫存器和程式計數器,這兩樣位於CPU中,作為程式運行的大腦來控制程式的運行和流轉。

而在JVM中,作為一種虛擬機,JVM沒有指令暫存器,它是基於棧 + 程式計數器的體系結構來完成方法的執行,之所以這麼去設計一方面是為了指令集的緊湊,一方面是有些平台上的暫存器很少或者根本沒有,而且以處理器架構的角度來說,設計一套通用的暫存器指令是很困難的,而且還有一方面的考量就是有助於運行時某些虛擬機實現的動態編譯器和即時編譯器的程式碼優化。

程式計數器

我們在使用IDE寫程式碼的時候,旁邊經常會有行數,方便我們去閱讀我們自己的程式碼,去定位我們程式碼的位置,而程式計數器是給JVM執行位元組碼過程中看的行號指示器,只不過他其中存的並非是行號,而是執行中虛擬機位元組碼指令的地址,也正是因為計數器中存儲的僅僅是一條執行中的指令地址,使用的記憶體是及其有限的,所以這個區域是唯一一個沒有OOM的區域。位元組碼解釋器通過改變程式計數器中的地址來尋找對應的指令來完成對程式的控制(這裡具體我們會在分析指令集時去詳細的深入了解,這裡僅僅點到為止)

這裡需要注意一點,如果是本地Native 方法,該計數器的值是Undefined,其實也很好理解,人家壓根不屬於JVM去管理,你憑什麼去記錄。。這裡我們使用的可以說是Native方法提供出的一個介面,具體的實現是通過C來完成的。

上節課我們說到多執行緒的實現是基於時分復用來實現的,為了每個執行緒的運行的互不干擾和有序性,程式計數器必須保證在切換時能夠回到正確的位置,所以它必須必然是執行緒獨佔區的一份子。

關鍵字:

  • 行號指示器
  • 執行中位元組碼指令地址
  • 沒有OOM
  • Native方法不歸他管
  • 執行緒獨享

虛擬機棧

虛擬機棧是Java方法運行時的記憶體模型,包括了局部變數表,操作數棧,動態連接,方法返回地址以及一些附加資訊,每個方法在被執行的時候會在虛擬機棧中創造一個棧幀

image-20190811225812846

棧幀在執行的時候創建壓入棧,在完全執行完成後就會進行彈棧,下面是一個小?去直觀的看一下過程~

carbon

image-20190811232655530

(小聲BB一句,這裡不會製作GIF圖,湊合著看吧。。)

下面簡單介紹一下棧幀中的內容,這一部分我們會在了解位元組碼執行引擎的時候會進行更為詳細的講解。

  • 局部變數表

    局部變數表(Local Variable Table)是一組變數值存儲空間,用於方法參數和方法內部定義的局部變數,包含了編譯器可知的基本數據類型(byte,short,int,long,double,float,char,boolean),對象引用(reference)以及returnAddress類型。

  • 操作數棧

    同樣是一個LIFO(Last In First Out,後入先出)的棧結構,用於計算的臨時存儲區,前面提過JVM是沒有暫存器的,而操作數棧的作用就是讓JVM的指令是從中獲取操作數。

  • 動態連接

    每個棧幀都包含一個運行時常量池中該棧幀所屬方法的引用,持有這個引用就是為了支援方法在調用過程中的動態連接

本地方法棧

和虛擬機棧相似,本地方法棧面向的對象不是JVM位元組碼,而是本地的Native方法,比如String類中的intern方法,Native方法更像是本地方法的提供的一個介面,而本地方法棧正是管理這些介面的一個區域。

public native String intern();

異常

本地方法棧和虛擬機棧同為棧結構之中,他們會面對兩種異常,當執行緒請求的棧深度大於虛擬機所允許的深度時,將拋出StackOverflowError,如果棧可以動態擴展,如果擴展時無法申請到足夠的記憶體,會拋出OOM異常

公眾號