java进出栈_JVM函数调用:Java出入栈
JVM函數調用:Java出入棧
JVM函數調用:Java出入棧
目錄
局部變量表
索引復用
垃圾回收
棧數據區
棧上分配
線程作為系統運算調度的最小單位,在JVM中線程的行為體現就是函數調用,函數調用中數據的傳遞就是通過Java棧,Java棧顧名思義有著和數據結構中“棧”相似的屬性,后進先出,出棧入棧,棧中保存的是棧幀,當JVM發生函數調用時,就會有一個棧幀被壓入Java棧,當函數調用結束后,再從棧中彈出棧幀,當前正在執行的函數其對應的棧幀位于棧頂處,且保存有當前函數的局部變量表和棧數據區(保存一些中間結果等數據)。在函數返回,也就是有棧幀要從Java棧中彈出時,正常的情況是函數通過return返回,此時棧幀正常彈出,如果函數調用出現問題無法正常返回,則拋出異常,舉個例子:我們每一次函數調用時都會對Java棧進行入棧操作,棧空間是一定的,隨著不斷入棧操作,例如遞歸函數調用,棧空間變得越來越小,最后達到最大可用深度時,就會拋出棧溢出異常,所以有時我們遞歸函數調用過程中出現的“StackOverflowError”,就是棧空間因為某些原因被占滿了導致的。
局部變量表
函數對應的棧幀中有一個局部變量表,里面保存了調用函數的局部變量,參數等,這些參數和變量是跟著函數走的,只在當前函數調用中有效,函數調用結束后,棧幀就會彈出Java棧,局部變量表也就隨之被銷毀。來看一個簡單的例子:
draw()方法中有3個入參和3個局部變量,它們都是int數據類型,一個占用24個字節內存空間,在32位操作系統中每4個字節為一個字,所以在局部變量表中,函數draw()的局部變量一共占6個字。
從上圖可以看到,Maximum local variables表示最大局部變量表大小為6個字。
索引復用
詳細點開draw()函數中的局部變量表,可以看到一些更詳細的屬性,例如每一個變量的索引index,變量名name和數據類型descriptor等。
局部變量表中的索引是可以進行復用的,以次來節省Java棧空間,具體的復用方式是這樣,假設我們定義了一個局部變量i,當程序運行到i離開其作用域后,再定義的其他變量可能就會復用i變量的索引,具體看個例子:
package cell;
public class Cell {
private static boolean tag = true;
public void example1() {
int i = 9527;
System.out.println("i = " + i);
int s = 9527;
}
public void example2() {
if (tag) {
int i = 9527;
System.out.println("i = " + i);
}
int s = 9527;
}
}
程序中有兩個方法,example1()中定義的變量i和變量s作用域都是一樣的,直到example1()方法的結束,所以兩者的索引沒有辦法復用。example2()方法中,局部變量i在第16行后就離開了作用域范圍,那么后續定義的局部變量s可以復用它的索引,從局部變量表里看,的確是這樣的:
可以看到,方法example1()中局部變量i和s的所有不同,分別是1和2,而到了方法example2()中,局部變量i和s的所有都一樣是1。
垃圾回收
索引復用有時也會對JVM的垃圾回收產生影響,例如某一個變量i,雖然離開了自己的作用域,但是它之前指向了某一對象,使得變量i仍然存在于局部變量表中,導致變量i指向的對象無法被回收如果變量i指向了某一對象,i離開了作用域后,其索引被后面定義的局部變量所服用了,那么變量i也會被銷毀掉,其指向的對象也就能被正常GC,看一個例子:
public void example1() {
byte[] buffer = new byte[2*1024*1024];
System.gc();
}
public void example2() {
byte[] buffer = new byte[2*1024*1024];
buffer = null;
System.gc();
}
public void example3() {
boolean tag = true;
if (tag) {
byte[] buffer = new byte[2*1024*1024];
}
System.gc();
}
public void example4() {
boolean tag = true;
if (tag) {
byte[] buffer = new byte[2*1024*1024];
}
int i = 9527;
System.gc();
}
上面的程序中,4個方法,第一個example1()中,首先定義byte數組,申請2MB大小的空間,之后立刻進行GC,回收該數組對象,但是注意,此時因為局部變量buffer強引用了這塊內存空間,所以gc暫時無法對其進行回收。到example2(),在為byte數組申請空間,并用局部變量buffer引用這塊區域后,顯示將buffer置為null,這樣buffer就不會強引用這塊內存空間,之后再進行GC就可以成功回收。example3()中,我們讓局部變量buffer作用在if語句中,當if語句結束,buffer離開了它的作用域后,我們再進行GC,此時因為變量buffer還存在于局部變量表中,所以它的引用還是有效的,GC無法對其引用的空間進行回收,解決的辦法來看example4()方法。在example4()中,buffer離開其作用域后,我們再聲明另一個局部變量i,讓它來復用變量buffer的索引,,這樣buffer變量就真正被替代銷毀了,并且沒有其他變量引用這片內存區域,之后再進行GC,可以成功進行回收。
棧數據區
Java棧中局部變量表上面簡單總結了一下,除了局部便變量表,還有棧數據區,分為虛擬機棧和本地方法棧,虛擬機棧存放就是棧幀,Java方法運行時所需要的數據,本地方法棧存放的是JVM調用的本地方法。
棧上分配
上面說的棧數據區中,虛擬機棧和本地方法棧都是線程獨占的,對于一些線程私有的,不能被其他線程訪問的對象,JVM可以把它們分配在棧上,這樣當函數調用結束后就會自行出棧銷毀,不需要GC來進行回收,好處就是提高了性能。
private static People p;
public static void initPeople() {
p = new People();
p.name = "Alex";
p.age = 20;
}
上面代碼中People類對象p是一個逃逸的對象,因為它是類的成員變量,可能會被其他線程訪問到,所以虛擬機會把它分配到堆上,而不是線程私有的棧數據區中。如果我們把它改成非逃逸的對象:
public static void initPeople() {
People p = new People();
p.name = "Alex";
p.age = 20;
}
把對象p改成局部變量的方式,且initPeople()方法也沒有將其返回出去,那么該對象p沒有發生逃逸,虛擬機就會將它分配到棧數據區中。
JVM函數調用:Java出入棧相關教程
總結
以上是生活随笔為你收集整理的java进出栈_JVM函数调用:Java出入栈的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 冒号运算 java_java 8 双冒号
- 下一篇: magento mysql4-insta