一、運行時資料區的認識
執行緒共享和執行緒私有的區域
名詞解釋:PC程式計數器 VMS 虛擬機堆疊 NMS 本地方法堆疊
從上圖可以大致的了解到執行緒私有區域的構成,接下來就對里面的每一個部分展開描述一下:
(1)程式計數器(PC)
記憶體空間小,執行緒私有,位元組碼解釋器作業是就是通過改變這個計數器的值來選取下一條需要執行指令的位元組碼指令,分支、回圈、跳轉、例外處理、執行緒恢復等基礎功能都需要依賴計數器完成,
程式計數器執行緒私有的理解:由于Java 虛擬機的多執行緒是通過執行緒輪流切換并分配處理器執行時間的方式來實作的,在任何一個確定的時刻,一個處理器(對于多核處理器來說是一個內核)只會執行一條執行緒中的指令,因此,為了執行緒切換后能恢復到正確的執行位置,每條執行緒都需要有一個獨立的程式計數器,各條執行緒之間的計數器互不影響,獨立存盤,
如果執行緒正在執行的是一個Java 方法,這個計數器記錄的是正在執行的虛擬機位元組碼指令的地址;如果正在執行的是Native 方法,這個計數器值則為空(Undefined),
此記憶體區域是唯一一個在Java 虛擬機規范中沒有規定任何OutOfMemoryError(OOM) 情況的區域,
(2)虛擬機堆疊(VM Stack)
描述的是 Java 方法執行的記憶體模型:每個方法在執行時都會床創建一個堆疊幀(Stack Frame)用于存盤區域變數表、運算元堆疊、動態鏈接、方法出口等資訊,每一個方法從呼叫直至執行結束,就對應著一個堆疊幀從虛擬機堆疊中入堆疊到出堆疊的程序,
虛擬機堆疊中可能出現的例外:
- StackOverflowError:執行緒請求的堆疊深度大于虛擬機所允許的深度,
- OutOfMemoryError:如果虛擬機堆疊可以動態擴展,而擴展時無法申請到足夠的記憶體,
1、堆疊幀(Stack Frame)
堆疊幀(Stack Frame)是用于支持虛擬機進行方法呼叫和方法執行的資料結構,它是虛擬機堆疊的堆疊元素,堆疊幀存盤了方法的區域變數表、運算元堆疊、動態連接和方法回傳地址等資訊,每一個方法從呼叫開始至執行完成的程序,都對應著一個堆疊幀在虛擬機里面從入堆疊到出堆疊的程序,
在編譯程式代碼的時候,堆疊幀中需要多大的區域變數表記憶體,多深的運算元堆疊都已經完全確定了,因此一個堆疊幀需要分配多少記憶體,不會受到程式運行期變數資料的影響,而僅僅取決于具體的虛擬機實作,

如上圖所示,當前堆疊楨總是指向堆疊頂【在活動執行緒中,只有位于堆疊頂的堆疊幀才是有效的,稱為當前堆疊幀,與這個堆疊幀相關聯的方法稱為當前方法,】執行引擎運行的所有位元組碼指令都只針對當前堆疊幀進行操作,

2、區域變數表(Local Variable Table)
存放了編譯期可知的各種基本資料型別(boolean、byte、char、short、int、float、long、double)、物件參考(reference 型別,它不等同于物件本身,根據不同的虛擬機實作,它可能是一個指向物件起始地址的參考指標,也可能指向一個代表物件的句柄或者其他與此物件相關的位置)和returnAddress 型別(指向了一條位元組碼指令的地址),其中64 位長度的long 和double 型別的資料會占用2 個區域變數空間(槽 Slot),其余的資料型別只占用1 個槽,【從這里也反映了一個槽的大小是4位元組】區域變數表所需的記憶體空間在編譯期間完成分配,當進入一個方法時,這個方法需要在幀中分配多大的區域變數空間是完全確定的,在方法運行期間不會改變區域變數表的大小,由于區域變數表是建立在執行緒的堆疊上,是執行緒的私有資料,因此不存在資料安全問題,【這當然可以說是廢話】
【需要了解什么是編譯期和運行期的可以看一下下面這篇博客:Java 編譯期與運行期,別傻傻分不清楚! - Java技術堆疊 - 博客園】

區域變數表中的變數只在當前方法呼叫中有效,在方法執行時,虛擬機通過使用區域變數表完成引數值到引數變數串列的傳遞程序,當方法呼叫結束后,隨著方法堆疊幀的銷毀,區域變數表也會隨之銷毀,
最后提一些區域變數表中的拓展知識:
1、Slot(槽)復用
為了盡可能節省堆疊幀空間,區域變數表中的Slot是可以重用的,也就是說當PC計數器的指令指向已經超出了某個變數的作用域(執行完畢),那這個變數對應的Slot就可以交給其他變數使用,【如果當前位元組碼PC計數器的值已經超出了某個變數的作用域,那這個變數對應的Slot就可以交給其他變數使用,】
- 優點 : 節省堆疊幀空間,
- 缺點 : 影響到系統的垃圾收集行為,(如大方法占用較多的Slot,執行完該方法的作用域后沒有對Slot賦值或者清空設定null值,垃圾回收器便不能及時的回收該記憶體,) -> 注意:垃圾回收是在堆和方法區的,堆疊中沒有垃圾回收
public class A {
public static void main(String[] args) {
int a = 0;
{
int b = 0;
b = a + 1;
}
// 變數c使用之前已經銷毀的變數b的slot
int c = a + 1;
}
}
2.擴展
如果當前堆疊幀是由構造方法或者實體方法創建的,那么該物件參考this將會存放在index為0的slot處,其余的引數按照引數表順序繼續排列,如果需要訪問區域變數表中一個64bit的區域變數值時,只需要使用前一個索引即可,(比如:訪問long或double型別變數)【下面是一段簡單的測驗代碼】
public class A {
public void test(){
int a = 10;
String b = "hello";
}
}

3、動態鏈接(Dynamic Linking)
每個堆疊幀都包含一個指向運行時常量池中該堆疊幀所屬方法的參考,持有這個參考是為了支持方法呼叫程序中的動態連接(Dynamic Linking),在類加載階段中的決議階段會將符號參考轉為直接參考,這種轉化也稱為靜態決議,另外的一部分將在每一次運行時期轉化為直接參考,這部分稱為動態連接,【比如: invokedynamic指令,】
在Java源檔案被編譯到位元組碼檔案中時,所有的變數和方法參考都作為符號參考(symbolic Reference)保存在class檔案的常量池里,比如:描述一個方法呼叫了另外的其他方法時,就是通過常量池中指向方法的符號參考來表示的,那么動態鏈接的作用就是為了將這些符號參考轉換為呼叫方法的直接參考,

4、方法回傳地址(Return Address)
方法回傳地址(Return Address)(方法正常退出或者例外退出的定義)---- 存放呼叫該方法的pc暫存器的值,
當一個方法開始執行后,只有2種方式可以退出這個方法 :
- ? 方法回傳指令 (return): 執行引擎遇到一個方法回傳的位元組碼指令,這時候有可能會有回傳值傳遞給上層的方法呼叫者,這種退出方式稱為正常完成出口,
- 例外退出 : 在方法執行程序中遇到了例外,并且沒有處理這個例外,就會導致方法退出,
一般來說,方法正常退出時,呼叫者的PC計數器的值可以作為回傳地址,堆疊幀中會保存這個計數器值,而方法例外退出時,回傳地址是要通過例外處理器表來確定的,堆疊幀中一般不會保存這部分資訊,
5、運算元堆疊(Operand Stack):(運算式堆疊)
運算元堆疊,主要用于保存計算程序的中間結果,同時作為計算程序中變數臨時的存盤空間,運算元堆疊就是JVM執行引擎的一個作業區,當一個方法剛開始執行的時候,一個新的堆疊幀也會隨之被創建出來,這時這個方法的運算元堆疊是空的,
某些位元組碼指令將值壓入運算元堆疊,其余的位元組碼指令將運算元取出堆疊,使用它們后再把結果壓入堆疊,比如:執行復制、交換、求和等操作,
【下面是一個簡單的代碼講解以及位元組碼的具體分析】
public class A {
/*
0 bipush 15 運算元 byte 15 入堆疊
2 istore_1 運算元出堆疊,區域變數表索引為 1的位置存盤 int 15
3 bipush 8 運算元入堆疊 byte 8
5 istore_2 運算元出堆疊,區域變數表索引為 2的位置存盤 int 8
6 iload_1 從區域變數表對應索引處獲取值,入堆疊
7 iload_2 從區域變數表對應索引處獲取值,入堆疊
8 iadd 15 和 8 出堆疊求和,再入堆疊
9 istore_3 將計算結果存盤再區域變數表索引為 3位置 int 23
10 sipush 800
13 istore 4
15 return
*/
public static void main(String[] args) {
byte i = 15;
int j = 8;
int k = i + j;
int m = 800;
}
}

簡單的拓展問題
1、方法中定義的區域變數是否執行緒安全?
如果只有一個執行緒可以操作此資料,則必是執行緒安全的,如果有多個執行緒操作此資料,則此資料是共享資料,如果不考慮同步機制的話,會存在執行緒安全問題,
2、分配的堆疊記憶體越大越好么?
不是,一定時間內降低了OOM概率,但是會擠占其它的執行緒空間,因為整個虛擬機的記憶體空間是有限的,
(3)本地方法堆疊(Native Method Stack)
本地方法堆疊(Native Method Stacks)與虛擬機堆疊所發揮的作用是非常相似的,其區別不過是虛擬機堆疊為虛擬機執行Java 方法(也就是位元組碼)服務,而本地方法堆疊則是為虛擬機使用到的Native (本地)方法服務,虛擬機規范中對本地方法堆疊中的方法使用的語言、使用方式與資料結構并沒有強制規定,因此具體的虛擬機可以自由實作它,甚至有的虛擬機(譬如Sun HotSpot 虛擬機)直接就把本地方法堆疊和虛擬機堆疊合二為一,與虛擬機堆疊一樣,本地方法堆疊區域也會拋出StackOverflowError 和OutOfMemoryError例外,
【方法區與堆記憶體在下一篇博客再進行講解,】本篇主要記錄博主在學習java虛擬機程序的筆記以及自己的總結,內容及配圖來源《深入理解java虛擬機(第三版)-JVM高級特性與最佳實踐 》,書的作者是:周志明老師,文章中的圖片主要是尚硅谷JVM課程學習的圖片,有興趣的可以去 blibli 搜索學習,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/332199.html
標籤:其他
下一篇:嵌入式Linux學習筆記
