一、虛拟機棧的背景
基于Java的語言的
跨平台性
設計,由因為CPU的架構不同,是以JVM不能設計為基于寄存器結構
根據棧設計
優點:1. 跨平台 2.指令集小,編譯器容易實作
缺點 :1.性能下降,效率低 2.實作同樣功能需要更多的指令
JVM的堆與棧
棧是運作時的機關,堆是儲存機關:棧管運作,堆管儲存
- 棧解決程式的運作問題,即程式如何執行,或者說如何處理資料。堆解決的是資料存儲的問題,即資料怎麼放、放在哪兒
- 一般來講,對象主要都是放在堆空間的,是運作時資料區占用記憶體較大的一部分
- 棧存放基本
和資料類型的局部變量
引用資料類型對象的引用
二、Java虛拟機棧的特點與作用
1. Java虛拟機棧的特點
1.Java虛拟棧(Java Virtual Machine Stack),起初也叫Java棧,每個線程在建立是都會建立一個虛拟機棧,其内部儲存一個個的棧幀(Stack Frame),對應一個線程中每個方法的調用,
它是線程私有
2.聲明周期與建立它的線程一緻
3.棧是一種快速有效的配置設定存儲方式,通路速度僅次于
程式計數器
4.JVM對Java虛拟機棧的操作隻有兩個
- 每個方法執行前,伴随的入棧
- 方法執行完畢後的出棧
5.Java虛拟機棧沒有GC問題,但存在OutOfMemoryError與StackOverflowError
2. Java虛拟機棧的作用
負責Java程式的運作,它儲存方法的局部變量、8種基本資料類型、對象的引用位址、部分結果,并參與方法的調用和傳回
局部變量是相對于成員變量(屬性):如不清楚,可以參考:
https://www.cnblogs.com/battlecry/p/9373993.html三、Java虛拟機棧異常
Java虛拟機規範允許Java棧的大小是動态的或者是固定不變的
Java棧的大小是固定不變導緻:StackOverflowError
如果采用固定大小的Java虛拟機棧,那每一個線程的Java虛拟機棧容量可以線上程建立的時候獨立標明。如果線程請求配置設定的棧容量超過Java虛拟機棧允許的最大容量,Java虛拟機将會抛出一個 StackOverFlowError異常
/**
* 示範棧中的異常:StackOverflowError
*/
public class StackErrorTest {
public static void main(String[] args) {
main(args);
}
}
解決辦法:我們可以使用參數-Xss選項來設定線程的最大棧空間,棧的大小直接決定了函數調用的最大可達深度(IDEA設定方法:Run-EditConfigurations-VM options 填入指定棧的大小-Xss256k)
/**
* 示範棧中的異常
*
* 預設情況下:count 10818
* 設定棧的大小: -Xss256k count 1872
*/
public class StackErrorTest {
private static int count = 1;
public static void main(String[] args) {
System.out.println(count);
count++;
main(args);
}
}
Java棧的大小是動态導緻:OOM(OutOfMemoryError)
如果Java虛拟機棧可以動态拓展,并且在嘗試拓展的時候無法申請到足夠的記憶體,或者在建立新的線程時沒有足夠的記憶體去建立對應的虛拟機棧,那Java虛拟機将會抛出一個 OutOfMemoryError異常
四、Java虛拟棧的存儲結構和運作原理
1.每個線程都有自己的棧,棧中的資料都是以棧幀(Stack Frame)的格式存在
2.在這個線程上正在執行的每個方法都對應各自的一個棧幀
3.棧幀是一個記憶體區塊,是一個資料集,維系着方法執行過程中的各種資料資訊
4.JVM直接對Java棧的操作隻有兩個,就是對棧幀的壓棧和出棧,遵循先進後出(FILO)的和原則
5.在一條活動線程中,一個時間點上,隻會有一個活動的棧幀。即隻有目前正在執行的方法的棧幀(棧頂棧幀)是有效的,這個棧幀被稱為目前棧幀(Current Frame),與目前棧幀對應的方法就是目前方法(Current Frame)
6.執行引擎運作的所有位元組碼指令隻針對目前棧幀進行操作
7.如果在該方法中調用了其他方法,對應的新的棧幀會被建立出來,放在棧的頂端,成為新的目前棧幀。
8.不同線程中所包含的棧幀是不允許互相引用的,即不可能在另一個棧幀中引用另外一個線程的棧幀
9.如果目前方法調用了其他方法,方法傳回之際,目前棧幀會傳回此方法的執行結果給前一個棧幀,接着,虛拟機會丢棄目前棧幀,使得前一個棧幀重新成為目前棧幀
10.Java方法有兩種傳回函數的方式,一種是正常的函數傳回,使用return指令;另外一種是抛出異常,不管使用哪種方式,都會導緻棧幀被彈出
/**
* 棧幀執行順序示範
*/
public class StackFrameTest {
public static void main(String[] args) {
StackFrameTest test = new StackFrameTest();
test.method1();
// 輸出 method1()和method2()都作為目前棧幀出現了兩次,method3()一次
// method1()開始執行。。。
// method2()開始執行。。。
// method3()開始執行。。。
// method3()執行結束。。。
// method2()執行結束。。。
// method1()執行結束。。。
}
public void method1(){
System.out.println("method1()開始執行。。。");
method2();
System.out.println("method1()執行結束。。。");
}
public int method2(){
System.out.println("method2()開始執行。。。");
int i = 10;
int m = (int) method3();
System.out.println("method2()執行結束。。。");
return i+m;
}
public double method3(){
System.out.println("method3()開始執行。。。");
double j = 20.0;
System.out.println("method3()執行結束。。。");
return j;
}
}
五、Java虛拟棧相關面試題
1.舉例棧溢出的情況?(StackOverflowError)
- 遞歸調用等,通過-Xss設定棧的大小;
2.調整棧的大小,就能保證不出現溢出麼?
- 不能 如遞歸無限次數肯定會溢出,調整棧大小隻能保證溢出的時間晚一些,極限情況會導緻OOM記憶體溢出(Out Of Memery Error)
3.配置設定的棧記憶體越大越好麼?
- 不是 會擠占其他線程的空間
4.垃圾回收是否會涉及到虛拟機棧?
- 不會
-
5.方法中定義的局部變量是否線程安全?
- 要分析具體情況
/** * 面試題: * 方法中定義的局部變量是否線程安全?具體情況具體分析 * * 何為線程安全? * 如果隻有一個線程可以操作此資料,則必定是線程安全的。 * 如果有多個線程操作此資料,則此資料是共享資料。如果不考慮同步機制的話,會存線上程安全問題 * * 我們知道StringBuffer是線程安全的源碼中實作synchronized,StringBuilder源碼未實作synchronized,在多線程情況下是不安全的 **/ public class StringBuilderTest { //s1的聲明方式是線程安全的,s1在方法method1内部消亡了 public static void method1(){ StringBuilder s1 = new StringBuilder(); s1.append("a"); s1.append("b"); } //stringBuilder的操作過程:是不安全的,因為method2可以被多個線程調用 public static void method2(StringBuilder stringBuilder){ stringBuilder.append("a"); stringBuilder.append("b"); } //s1的操作:是線程不安全的,将自己傳回,可能被其他線程共享 public static StringBuilder method3(){ StringBuilder s1 = new StringBuilder(); s1.append("a"); s1.append("b"); return s1; } //s1的操作:是線程安全的 ,StringBuilder的toString方法是建立了一個新的String,s1在内部消亡了 public static String method4(){ StringBuilder s1 = new StringBuilder(); s1.append("a"); s1.append("b"); return s1.toString(); } public static void main(String[] args) { StringBuilder s = new StringBuilder(); new Thread(()->{ s.append("a"); s.append("b"); }).start(); method2(s); } }