jvm入門及理解(五)——運行時數據區(虛擬機棧)和本地方法介面
一、虛擬機棧背景
由於跨平台性的設計,java的指令都是根據棧來設計的。不同平台CPU架構不同,所以不能設計為基於暫存器的。
優點是跨平台,指令集小,編譯器容易實現,缺點是性能下降,實現同樣的功能需要更多的指令。
二、記憶體中的堆與棧
- 棧是運行時的單位,而堆是存儲的單位;即:棧解決程式的運行問題,即程式如何執行,或者說如何處理數據。堆解決的是數據存儲的問題,即數據怎麼放、放在哪兒。
- 一般來講,對象主要都是放在堆空間的,是運行時數據區比較大的一塊
- 棧空間存放 基本數據類型的局部變數,以及引用數據類型的對象的引用
- java虛擬機棧(Java Virtual Machine Stack),早期也叫Java棧。
- 每個執行緒在創建時都會創建一個虛擬機棧,其內部保存一個個的棧幀(Stack Frame),對應這個一次次的java方法調用。它是執行緒私有的
- 生命周期和執行緒是一致的
- 作用:主管java程式的運行,它保存方法的局部變數(8種基本數據類型、對象的引用地址)、部分結果,並參與方法的調用和返回。
- 局部變數:相對於成員變數(或屬性)
- 基本數據變數: 相對於引用類型變數(類,數組,介面)
三、棧的特點
- 棧是一種快速有效的分配存儲方式,訪問速度僅次於PC暫存器(程式計數器)
- JVM直接對java棧的操作只有兩個
- 每個方法執行,伴隨著進棧(入棧,壓棧)
- 執行結束後的出棧工作
- 對於棧來說不存在垃圾回收問題
四、棧中可能出現的異常
java虛擬機規範允許Java棧的大小是動態的或者是固定不變的
- 如果採用固定大小的Java虛擬機棧,那每一個執行緒的java虛擬機棧容量可以在執行緒創建的時候獨立選定。如果執行緒請求分配的棧容量超過java虛擬機棧允許的最大容量,java虛擬機將會拋出一個 StackOverFlowError異常
- 如果java虛擬機棧可以動態拓展,並且在嘗試拓展的時候無法申請到足夠的記憶體,或者在創建新的執行緒時沒有足夠的記憶體去創建對應的虛擬機棧,那java虛擬機將會拋出一個 OutOfMemoryError異常。
五、設置棧的記憶體大小
我們可以使用參數-Xss選項來設置執行緒的最大棧空間,棧的大小直接決定了函數調用的最大可達深度。 (IDEA設置方法:Run-EditConfigurations-VM options 填入指定棧的大小-Xss256k)
六、棧的存儲結構和運行原理
- 每個執行緒都有自己的棧,棧中的數據都是以棧幀(Stack Frame)的格式存在
- 在這個執行緒上正在執行的每個方法都對應各自的一個棧幀
- 棧幀是一個記憶體區塊,是一個數據集,維繫著方法執行過程中的各種數據資訊
- JVM直接對java棧的操作只有兩個,就是對棧幀的壓棧和出棧,遵循先進後出/後進先出的和原則。
- 在一條活動執行緒中,一個時間點上,只會有一個活動的棧幀。即只有當前正在執行的方法的棧幀(棧頂棧幀)是有效的,這個棧幀被稱為當前棧幀(Current Frame),與當前棧幀對應的方法就是當前方法(Current Frame)
- 執行引擎運行的所有位元組碼指令只針對當前棧幀進行操作
- 如果在該方法中調用了其他方法,對應的新的棧幀會被創建出來,放在棧的頂端,成為新的當前棧幀。
- 不同執行緒中所包含的棧幀是不允許相互引用的,即不可能在另一個棧幀中引用另外一個執行緒的棧幀
- 如果當前方法調用了其他方法,方法返回之際,當前棧幀會傳回此方法的執行結果給前一個棧幀,接著,虛擬機會丟棄當前棧幀,使得前一個棧幀重新成為當前棧幀
- Java方法有兩種返回函數的方式,一種是正常的函數返回,使用return指令;另外一種是拋出異常。不管使用哪種方式,都會導致棧幀被彈出。
七、棧幀的內部結構
每個棧幀中存儲著:
- 局部變數表(Local Variables)
- 操作數棧(Operand Stack)(或表達式棧)
- 動態鏈接(Dynamic Linking)(或執行運行時常量池的方法引用)
- 方法返回地址(Return Adress)(或方法正常退出或者異常退出的定義)
- 一些附加資訊
-
八、棧幀的內部結構
概述
- 局部變數表也被稱之為局部變數數組或本地變數表
- 定義為一個數字數組,主要用於存儲方法參數和定義在方法體內的局部變數這些數據類型包括各類基本數據類型、對象引用(reference),以及returnAddressleixing
- 由於局部變數表是建立在執行緒的棧上,是執行緒私有的數據,因此不存在數據安全問題
- 局部變數表所需的容量大小是在編譯期確定下來的,並保存在方法的Code屬性的maximum local variables數據項中。在方法運行期間是不會改變局部變數表的大小的
- 方法嵌套調用的次數由棧的大小決定。一般來說,棧越大,方法嵌套調用次數越多。對一個函數而言,他的參數和局部變數越多,使得局部變數表膨脹,它的棧幀就越大,以滿足方法調用所需傳遞的資訊增大的需求。進而函數調用就會佔用更多的棧空間,導致其嵌套調用次數就會減少。
- 局部變數表中的變數只在當前方法調用中有效。在方法執行時,虛擬機通過使用局部變數表完成參數值到參數變數列表的傳遞過程。當方法調用結束後,隨著方法棧幀的銷毀,局部變數表也會隨之銷毀。
九、變數槽slot的理解與演示
- 參數值的存放總是在局部變數數組的index0開始,到數組長度-1的索引結束
- 局部變數表,最基本的存儲單元是Slot(變數槽)
- 局部變數表中存放編譯期可知的各種基本數據類型(8種),引用類型(reference),returnAddress類型的變數。
- 在局部變數表裡,32位以內的類型只佔用一個slot(包括returnAddress類型),64位的類型(long和double)佔用兩個slot。
- byte、short、char、float在存儲前被轉換為int,boolean也被轉換為int,0表示false,非0表示true;
- long和double則佔據兩個slot。
- JVM會為局部變數表中的每一個slot都分配一個訪問索引,通過這個索引即可成功訪問到局部變數表中指定的局部變數值
- 當一個實例方法被調用的時候,它的方法參數和方法體內部定義的局部變數將會按照順序被複制到局部變數表中的每一個slot上
- 如果需要訪問局部變數表中一個64bit的局部變數值時,只需要使用簽一個索引即可。(比如:訪問long或者double類型變數)
- 如果當前幀是由構造方法或者實例方法創建的,那麼該對象引用this將會存放在index為0的slot處,其餘的參數按照參數表順序排列。
十、slot的重複利用
棧幀中的局部變數表中的槽位是可以重複利用的,如果一個局部變數過了其作用域,那麼在其作用域之後申明的新的局部變數就很有可能會復用過期局部變數的槽位,從而達到節省資源的目的。
十一、靜態變數與局部變數的對比及小結
變數的分類:
按照數據類型分:
①基本數據類型;
②引用數據類型;
按照在類中聲明的位置分:
①成員變數:在使用前,都經歷過默認初始化賦值
static修飾:類變數:類載入linking的準備階段給類變數默認賦值——>初始化階段給類變數顯式賦值即靜態程式碼塊賦值;
不被static修飾:實例變數:隨著對象的創建,會在堆空間分配實例變數空間,並進行默認賦值
②局部變數:在使用前,必須要進行顯式賦值的!否則,編譯不通過
補充:
在棧幀中,與性能調優關係最為密切的部分就是局部變數表。在方法執行時,虛擬機使用局部變數表完成方法的傳遞
局部變數表中的變數也是重要的垃圾回收根節點,只要被局部變數表中直接或間接引用的對象都不會被回收