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