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