1|0虛擬機棧
1|1內(nèi)存中的棧和堆
棧是運行時的單位,堆是存儲的單位。
即:棧解決程序的運行問題,即程序如何執(zhí)行,或者說如何處理數(shù)據(jù)。堆解決的是數(shù)據(jù)存儲的問題,即數(shù)據(jù)怎么放、放在哪兒。
1|2虛擬機棧的基本內(nèi)容
Java虛擬機,早期也叫Java棧。每個線程在創(chuàng)建時都會創(chuàng)建一個虛擬機棧,其內(nèi)部保存一個個的棧楨,對應著一次次的Java方法調(diào)用。
生命周期和線程一致。
主管Java程序的運行,它保存方法的局部變量(8種基本類型、對象的引用地址)、部分結(jié)果,并參與方法的調(diào)用和返回。
- 局部變量、成員變量
- 基本數(shù)據(jù)變量、引用類型變量(類、數(shù)組、接口)
- 棧是一種快速有效的分配存儲方式,訪問速度僅次于程序計數(shù)器。
- JVM直接對Java棧的操作只有兩個:
- 每個方法執(zhí)行,伴隨著進棧(入棧、壓棧)
- 執(zhí)行結(jié)束后的出棧工作
- 對于棧來說不存在垃圾回收的問題
Java虛擬機規(guī)范允許Java棧的大小是動態(tài)的或者固定不變的。
- 如果采用固定大小的Java虛擬機棧,那每一個線程的Java虛擬機棧容量可以在線程創(chuàng)建的時候獨立選定。如果線程請求分配的棧容量超過Java虛擬機棧允許的最大容量,Java虛擬機將會拋出一個StackOverflowError異常。
- 如果Java虛擬機??梢詣討B(tài)擴展,并且嘗試擴展的時候無法申請到足夠的內(nèi)存,或者在創(chuàng)建新的線程時沒有足夠的內(nèi)存去創(chuàng)建對應的虛擬機棧,那Java虛擬機將會拋出一個OutOfMemoryError異常。
1|3棧的存儲單位
1|0棧中存儲什么?
- 每個線程都有自己的棧,棧中的數(shù)據(jù)都是以棧楨格式存在
- 在這個線程上正在執(zhí)行的每個方法都各自對應一個棧楨(Stack Frame)
- 棧楨是一個內(nèi)存區(qū)塊,是一個數(shù)據(jù)集,維系著方法執(zhí)行過程中的各種數(shù)據(jù)信息。
1|4棧運行原理
- JVM直接對Java棧的操作只有兩個,就是對棧楨的壓棧和出棧,遵循“先進后出”、“后進先出”原則。
- 在一條活動線程中,一個時間點上,只會有一個活動的棧楨。即只有當前正在執(zhí)行的方法的棧楨(棧頂棧楨)是有效的,這個棧楨被稱為當前棧楨,與當前棧楨相對應的方法就是當前方法,定義這個方法的類就是當前類。
- 執(zhí)行引擎運行的所有字節(jié)碼指令只針對當前棧楨進行操作。
- 如果在這個該方法中調(diào)用了其他方法,對應的新的棧楨會被創(chuàng)建出來,放在棧的頂端,稱為當前棧楨。
- 不同線程中所包含的棧楨是不允許存在相互引用的,即不可能在在一個棧楨之中引用另一個線程的棧楨。
- 如果當前方法調(diào)用了其他方法,方法返回之際,當前棧楨會傳回此方法的執(zhí)行結(jié)果給前一個棧楨,接著虛擬機會丟棄當前棧楨,使前一個棧楨重新成為當前棧楨。
- Java方法有兩種返回函數(shù)的方式,一種是正常的函數(shù)返回,使用return指令;另一種是拋出異常。不管使用哪種方式,都會導致棧楨被彈出。
1|5棧楨的內(nèi)部結(jié)構(gòu)
每個棧楨中都存儲著:
- 局部變量表
- 操作數(shù)棧(或表達式棧)
- 動態(tài)鏈接(或指向運行時常量池的方法引用)
- 方法返回地址(方法正常退出或者異常退出的定義)
- 一些附加信息
1|0局部變量表
- 局部變量表被稱為局部變量數(shù)組或本地變量表
- 定義為一個數(shù)字數(shù)組,主要用于存儲方法參數(shù)和定義在方法體內(nèi)的局部變量,這些數(shù)據(jù)類型包括各類基本數(shù)據(jù)類型、對象引用,以及returnAddress類型。
- 由于局部變量表是建立在線程的棧上,是線程的私有數(shù)據(jù),因此不存在數(shù)據(jù)安全問題。
- 局部變量表所需的容量大小在編譯期確定下來的,并保存在方法的Code屬性的maximum lock variables數(shù)據(jù)項中。在方法運行期間是不會改變局部變量表的大小的。
- 方法嵌套調(diào)用的次數(shù)由棧的大小決定。一般來說,棧越大,方法嵌套調(diào)用次數(shù)越多。對于一個函數(shù)而言,它的參數(shù)和局部變量越多,使得局部變量表膨脹,它的棧楨就越大,以滿足方法調(diào)用所需傳遞的信息增大的需求。進而函數(shù)調(diào)用就會占用更多的??臻g,導致其嵌套調(diào)用次數(shù)就會越少。
- 局部變量表中的變量只在當前方法調(diào)用中有效。在方法執(zhí)行時,虛擬機通過使用局部變量表完成參數(shù)值到參數(shù)變量列表的傳遞過程。當方法調(diào)用結(jié)束后,隨著方法棧楨的銷毀,局部變量表也會隨之銷毀。
1|6字節(jié)碼中方法內(nèi)部結(jié)構(gòu)的剖析
1|0關(guān)于Slot的理解
- 參數(shù)值的存放總是在局部變量數(shù)組的index0開始,到數(shù)組長度-1的索引結(jié)束。
- 局部變量表,最基本的存儲單元是Slot(變量槽)
- 局部變量表存放編譯器可知的各種基本數(shù)據(jù)類型(8種),引用類型(reference),returnAddress類型的變量。
- 在局部變量表,32位以內(nèi)的類型只占用一個slot(包括returnAddress類型),64位的類型(long和double)占用兩個slot。
- byte、short、char在存儲前被轉(zhuǎn)換為int,boolean也被轉(zhuǎn)換為int,0 表示false,非0表示true。
- long和double則占據(jù)兩個slot。
- JVM會為局部變量表中的每一個Slot都分配一個訪問索引,通過這個索引即可成功訪問到局部變量表中指定的局部變量值。
- 當一個實例方法被調(diào)用的時候,它的方法參數(shù)和方法體內(nèi)部定義的局部變量將會按照順序被復制到局部變量表中的每一個Slot上。
- 如果需要訪問局部變量表中一個64bit的局部變量值時,只需要使用前一個索引即可。(比如:long和double類型變量)
- 如果當前幀是由構(gòu)造方法或者實例方法創(chuàng)建的,那么該對象引用this將會存放在index為0的slot處,其余的參數(shù)按照參數(shù)表順序繼續(xù)排列。
1|0Slot的重復利用
棧楨中的局部變量表中的槽位是可以重復利用的,如果一個局部變量過了其作用域,那么在其作用域之后申明的新的局部變量就很有可能會復用過期局部變量的槽位,從而達到節(jié)省資源的目的。
public class SlotTest{
public void localVar(){
{
int a = 0;
}
//此時變量b就會復用a的槽位。
int b = 0;
}
}
1|0靜態(tài)變量與局部變量的對比
- 按照數(shù)據(jù)類型分: (1)基本數(shù)據(jù)類型;(2)引用數(shù)據(jù)類型;
- 按照在類中聲明的位置分:
(1)成員變量:在使用前都經(jīng)歷過默認初始化賦值
類變量:連接的準備階段:給類變量默認賦值 -> 初始化階段: 給類變量顯式賦值即靜態(tài)代碼塊賦值;
實例變量:隨著對象的創(chuàng)建,會在堆空間中分配實例變量空間,并進行默認賦值;
(2)局部變量:在使用前,必須要進行顯式賦值的,否則編譯不通過;
- 參數(shù)表分配完畢之后,再根據(jù)方法體內(nèi)定義的變量的順序和作用域分配。
- 我們知道類變量表有兩次初始化的機會,第一次是在“準備階段”,執(zhí)行系統(tǒng)初始化,對類變量設(shè)置零值,另一次則是在“初始化”階段,賦予程序員在代碼中定義的初始值。
- 和類變量初始化不同的是,局部變量表不存在系統(tǒng)初始化的過程,這意味著一旦定義了局部變量則必須認為的初始化,否則無法使用。
- 在棧楨中,與性能調(diào)優(yōu)關(guān)系最為密切的部分就是前面提到的局部變量表。在方法執(zhí)行時,虛擬機使用局部變量表完成方法的傳遞。
- 局部變量表中的變量也是重要的垃圾回收根節(jié)點,只要被局部變量表中直接或間接引用的對象都不會被回收。
__EOF__
|