1.虛擬機堆疊
1.1概述
作用:主管java程式的執行,存盤的是堆疊幀(稍后介紹),堆疊幀主要存盤區域變數,部分結果,以及方法的呼叫和回傳
特點:
- 堆疊是一種快速有效的分配存盤方式,訪問速度僅次于程式計數器
- jvm對虛擬機堆疊的操作只有兩個,伴隨著方法的執行入堆疊、結束出堆疊
- 不存在垃圾回收問題
堆疊中可能會出現的問題
根據java虛擬機規范,虛擬機堆疊的大小是動態或者固定不變的
- 如果采用固定的堆疊,每個執行緒的虛擬機堆疊可以在執行緒創建的時候獨立完成,如果執行緒請求分配的堆疊容量超過了虛擬機堆疊允許的最大容量,會拋出StackOverflowError(堆疊溢位)
- 如果采用動態的堆疊,在嘗試擴展的時候無法申請足夠的記憶體,或者在創建新執行緒時沒有足夠的記憶體創建對應的虛擬機堆疊,就會拋出OutOfMemoryError(記憶體溢位)
1.2堆疊幀的存盤單位
- 每個執行緒都有自己的堆疊,堆疊中的資料都以**堆疊幀(Stack Frame)**的形式存在
- 在當前執行緒上正在執行的方法都有各自對應的堆疊幀
- 堆疊幀是一塊記憶體區域、資料集,維護著方法執行程序中的各種資料資訊
1.3堆疊運行原理
- jvm對堆疊的操作只有兩個,壓堆疊\出堆疊,遵循”FILO“原則
- 一個正在運行的執行緒,只會有一個運行的堆疊幀,就是當前方法執行的當前堆疊幀,也叫堆疊頂堆疊幀,與當前堆疊幀對應的就是當前方法,與當前方法對應的就是當前類
- 執行引擎運行的所以位元組碼檔案只對當前堆疊幀進行操作
- 不同執行緒的堆疊幀是不允許相互參考的,所以不可能在一個堆疊幀中參考另一個執行緒的堆疊幀
- 如果方法中呼叫了其他方法,對應的堆疊幀也會創建出來,成為新的當前堆疊幀,放在堆疊頂
- 如果方法中呼叫了其他方法,方法回傳的時候,當前堆疊幀會回傳一個執行結果給前一個堆疊幀,隨后虛擬機會丟棄當前堆疊幀,讓前一個堆疊幀成為當前堆疊幀
- java中有兩種方法回傳方式:1,return,2,拋例外,兩種方式都會導致堆疊幀被彈出
- idea在執行debug的時候,可以在Frames視窗各個堆疊幀的壓堆疊、出堆疊

1.4堆疊幀的內部結構
- 區域變數表
- 運算元堆疊
- 動態鏈接
- 方法回傳地址
- 其他附加資訊

1.4.1區域變數表
主要用于存盤方法引數和方法體中的區域變數
可以存盤的型別有:
- java基本資料型別
- 物件參考(可能是指向物件地址的參考指標,或是指向代表物件的句柄或相關地址)
- returnAddress型別(指向方法回傳的位元組碼指令,已被例外表取代)
由于區域變數表是存在執行緒的堆疊上的,是執行緒私有的資料,所以不存在資料安全問題
區域變數表的容量大小在編譯期就確定下來的,并保存在方法的Code屬性的maxmum local variables資料項中,運行期是不會改變表的大小的
方法嵌套呼叫的次數由堆疊的大小決定,一般情況下,堆疊越大,方法嵌套呼叫越多,
1.4.1.1槽 slot
- 區域變數表的基本單位是slot(變數槽)
- 在區域變數表中,32位的資料型別占用一個slot,64位則占用兩個slot
注:
byte,short,char,boolean(0表示false,1表示true)在存盤之前會轉為int
long和double占兩個slot
- jvm會為區域變數表中的每個slot分配一個索引,通過索引訪問對應的slot
- 如果需要訪問一個64位的區域變數時,只需要前一個索引即可,如(long型別的變數占用了a1,a2兩個slot,此時需要訪問a1即可訪問到該變數)
- 如果當前堆疊幀是由構造方法或實體方法創建的,那么this將會存放在index為0的slot中(此處引出一個問題:靜態方法為什么不能參考this?就是因為this變數不存在當前方法的區域變數表中)
- 在堆疊幀中,與性能調優關系最為密切的就是區域變數表,在方法執行時,虛擬機會使用區域變數表來完成方法的傳遞
- 區域變數表中的變數也是重要的垃圾回收根節點,只要被區域變數表中直接或間接參考的物件都不會被回收
- slot是可以重用的

上圖:區域變更表中有四個變數
注:至于這里為什么this沒有在第一個位置,是因為在執行構造方法之前會先執行非靜態代碼塊

上圖:但是區域變數最大槽數只有3,因為代碼塊中變數b使用完就銷毀了,但是b的slot還存在,所以就被變數c重用了
拓展:圖片中的方法為構造方法,在位元組碼檔案中構造方法叫init,靜態代碼塊叫clinit,而非靜態代碼塊就沒別的名字,因為它里面的區域變數會被存在init方法對于堆疊幀中
1.4.2運算元堆疊(LIFO)
- 在方法執行程序中,根據位元組碼指令,對運算元堆疊寫入(入堆疊push)和提取(出堆疊pop)資料,
- 某些位元組碼指令將值寫入運算元堆疊,某些指令將值取出堆疊,再進行相關操作,比如加減乘除,結果值也會放入運算元堆疊,
概述:
- 運算元堆疊,主要用于保存計算程序的中間結果,同時作為變數計算后的臨時的存盤空間
- 運算元堆疊是jvm執行引擎的一個作業區,當一個方法執行時,新的堆疊幀出現,此時這個運算元堆疊是空的
- 運算元堆疊都會有一個明確的大小來用于存盤數值,所需要的最大容量再編譯器就定義好了,在方法的Code屬性的max_stack中
- 堆疊中的元素可以是java中的任意資料型別,與slot一樣(32位占一個單位,64占兩個單位)
- 如果被呼叫的方法有回傳值,那回傳值會被壓到當前堆疊幀的運算元堆疊中,并通知程式計數器下一條需要執行的位元組碼指令
- jvm的解釋引擎是基于堆疊的執行引擎,堆疊指的是運算元堆疊
1.4.3動態鏈接(指向常量池的方法參考)
靜態系結:當一個位元組碼檔案被加載到虛擬機時,如果其呼叫的方法在編譯器可知,并且在運行期保持不變
動態系結:相反,如果在編譯器不能確定下來,只能在運行期將符號參考轉為直接參考
- 每個堆疊幀中都包含一個指向常量池中該堆疊幀所屬方法的參考,目的就是為了實作動態鏈接
- 在java檔案被編譯成位元組碼檔案時,所以變數和方法參考都會作為符號參考保存在Class檔案的常量池中
- 比如:描述一個方法呼叫其他方法時,就是通過常量池中指向方法的符號參考來表示,動態鏈接的作用就是將這些符號參考轉換為呼叫該方法的直接參考

1.4.3.1jvm時如何呼叫方法的
- 方法呼叫階段的唯一任務就是找到需要呼叫發方法,暫時還不涉及內部運行程序
- 方法在Class檔案中存盤的是符號參考,而不是在運行時的直接參考,也就是需要在類加載階段,甚至是運行期才能確定方法的直接參考
1.4.3.1.1在jvm中方法的符號參考轉化為直接參考跟系結機制有關
系結機制有早期系結(靜態系結)、晚期系結(動態系結),系結是一個字,方法或類的符號參考轉為直接參考的程序,只會發生一次
1.4.3.1.2虛方法和非虛方法
- 在編譯期可以確定下來,在運行期不變,稱為非虛方法,比如,靜態方法、final方法、私有方法、父類方法,構造方法都是非虛方法
- 其他方法叫虛方法
1.4.3.1.3虛方法表
- 為了提高性能,jvm在類的方法區建立了一個虛方法表,用于存放虛方法,因為在面向物件編程中,會頻繁設計動態分配,每次動態分配都在類的方法元資料中查找合適的目標,
- 每個類都會有一個虛方法表,存放著各個方法的實際入口
- 虛方法表會在類加載的連接(驗證、準備、決議)階段開始初始化,類的變數初始值準備完成之后,虛方法表也初始化完畢
1.4.4f方法回傳地址
-
用來存放呼叫該方法的程式計數器值
-
方法的兩種回傳方式,不管是那種,在方法推出后都會回傳到呼叫該方法的位置
1.正常回傳時:呼叫者的程式計數器的值作為回傳地址,就是呼叫該方法的指令的下一條指令地址 2.例外回傳時:通過例外表來確定的,堆疊幀中一般不會保存這部分資訊 -
實際上,方法的退出就是當前堆疊幀出戰的程序,此時需要回到上一個方法,讓方法執行下去
-
正常回傳與例外回傳的區別在于:例外回傳不會給上層呼叫者任何的回傳資訊
1.4.5附帶資訊
堆疊幀中還可以攜帶一些jvm相關資訊,比如,對程式除錯支持的資訊,但是這些資訊取決于虛擬機的實作
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/293844.html
標籤:其他
上一篇:基于pytest的介面測驗
