日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

java虚拟机 函数表_java虚拟机的基本结构如图

發(fā)布時間:2023/12/15 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java虚拟机 函数表_java虚拟机的基本结构如图 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

1 java虛擬機(jī)的基本結(jié)構(gòu)如圖:

1)類加載子系統(tǒng)負(fù)責(zé)從文件系統(tǒng)或者網(wǎng)絡(luò)中加載Class信息,加載的類信息存放于一塊稱為方法區(qū)的內(nèi)存空間。除了類的信息外,方法區(qū)中可能還會存放運(yùn)行時常量池信息,包括字符串字面量和數(shù)字常量(這部分常量信息是Class文件中常量池部分的內(nèi)存映射)。

2)java堆在虛擬機(jī)啟動的時候建立,它是java程序最主要的內(nèi)存工作區(qū)域。幾乎所有的java對象實(shí)例都存放在java堆中。堆空間是所有線程共享的,這是一塊與java應(yīng)用密切相關(guān)的內(nèi)存空間。

3)java的NIO庫允許java程序使用直接內(nèi)存。直接內(nèi)存是在java堆外的、直接向系統(tǒng)申請的內(nèi)存空間。通常訪問直接內(nèi)存的速度會優(yōu)于java堆。因此出于性能的考慮,讀寫頻繁的場合可能會考慮使用直接內(nèi)存。由于直接內(nèi)存在java堆外,因此它的大小不會直接受限于Xmx指定的最大堆大小,但是系統(tǒng)內(nèi)存是有限的,java堆和直接內(nèi)存的總和依然受限于操作系統(tǒng)能給出的最大內(nèi)存。

4)垃圾回收系統(tǒng)是java虛擬機(jī)的重要組成部分,垃圾回收器可以對方法區(qū)、java堆和直接內(nèi)存進(jìn)行回收。其中,java堆是垃圾收集器的工作重點(diǎn)。和C/C++不同,java中所有的對象空間釋放都是隱式的,也就是說,java中沒有類似free()或者delete()這樣的函數(shù)釋放指定的內(nèi)存區(qū)域。對于不再使用的垃圾對象,垃圾回收系統(tǒng)會在后臺默默工作,默默查找、標(biāo)識并釋放垃圾對象,完成包括java堆、方法區(qū)和直接內(nèi)存中的全自動化管理。

5)每一個java虛擬機(jī)線程都有一個私有的java棧,一個線程的java棧在線程創(chuàng)建的時候被創(chuàng)建,java棧中保存著幀信息,java棧中保存著局部變量、方法參數(shù),同時和java方法的調(diào)用、返回密切相關(guān)。

6)本地方法棧和java棧非常類似,最大的不同在于java棧用于方法的調(diào)用,而本地方法棧則用于本地方法的調(diào)用,作為對java虛擬機(jī)的重要擴(kuò)展,java虛擬機(jī)允許java直接調(diào)用本地方法(通常使用C編寫)

7)PC(Program Counter)寄存器也是每一個線程私有的空間,java虛擬機(jī)會為每一個java線程創(chuàng)建PC寄存器。在任意時刻,一個java線程總是在執(zhí)行一個方法,這個正在被執(zhí)行的方法稱為當(dāng)前方法。如果當(dāng)前方法不是本地方法,PC寄存器就會指向當(dāng)前正在被執(zhí)行的指令。如果當(dāng)前方法是本地方法,那么PC寄存器的值就是undefined

8)執(zhí)行引擎是java虛擬機(jī)的最核心組件之一,它負(fù)責(zé)執(zhí)行虛擬機(jī)的字節(jié)碼,現(xiàn)代虛擬機(jī)為了提高執(zhí)行效率,會使用即時編譯技術(shù)將方法編譯成機(jī)器碼后再執(zhí)行。

2 java堆

java堆是和應(yīng)用程序關(guān)系最為密切的內(nèi)存空間,幾乎所有的對象都存放在堆上。并且java堆是完全自動化管理的,通過垃圾回收機(jī)制,垃圾對象會被自動清理,而不需要顯示的釋放。

根據(jù)java回收機(jī)制的不同,java堆有可能擁有不同的結(jié)構(gòu)。最為常見的一種構(gòu)成是將整個java堆分為新生代和老年代。其中新生代存放新生對象或者年齡不大的對象,老年代則存放老年對象。新生代有可能分為eden區(qū)、s0區(qū)、s1區(qū),s0區(qū)和s1區(qū)也被稱為from和to區(qū),他們是兩塊大小相同、可以互換角色的內(nèi)存空間。

如下圖:顯示了一個堆空間的一般結(jié)構(gòu):

在絕大多數(shù)情況下,對象首先分配在eden區(qū),在一次新生代回收之后,如果對象還存活,則進(jìn)入s0或者s1,每經(jīng)過一次新生代回收,對象如果存活,它的年齡就會加1。當(dāng)對象的年齡達(dá)到一定條件后,就會被認(rèn)為是老年對象,從而進(jìn)入老年代。其具體的垃圾回收算法在后面會介紹。

例1 :通過簡單的示例,展示java堆、方法區(qū)和java棧之間的關(guān)系

package com.jvm;

public class SimpleHeap {

private int id;

public SimpleHeap(int id){

this.id = id;

}

public void show(){

System.out.println("My id is "+id);

}

public static void main(String[] args) {

SimpleHeap s1 = new SimpleHeap(1);

SimpleHeap s2 = new SimpleHeap(2);

s1.show();

s2.show();

}

}

該代碼聲明了一個類,并在main函數(shù)中創(chuàng)建了兩個SimpleHeap實(shí)例。此時,各對象和局部變量的存放情況如圖:

SimpleHeap實(shí)例本身分配在堆中,描述SimpleHeap類的信息存放在方法區(qū),main函數(shù)中的s1 s2局部變量存放在java棧上,并指向堆中兩個實(shí)例。

3 java棧

java棧是一塊線程私有的內(nèi)存空間。如果說,java堆和程序數(shù)據(jù)密切相關(guān),那么java棧就是和線程執(zhí)行密切相關(guān)。線程執(zhí)行的基本行為是函數(shù)調(diào)用,每次函數(shù)調(diào)用的數(shù)據(jù)都是通過java棧傳遞的。

java棧與數(shù)據(jù)結(jié)構(gòu)上的棧有著類似的含義,它是一塊先進(jìn)后出的數(shù)據(jù)結(jié)構(gòu),只支持出棧和進(jìn)棧兩種操作,在java棧中保存的主要內(nèi)容為棧幀。每一次函數(shù)調(diào)用,都會有一個對應(yīng)的棧幀被壓入java棧,每一個函數(shù)調(diào)用結(jié)束,都會有一個棧幀被彈出java棧。如下圖:棧幀和函數(shù)調(diào)用。函數(shù)1對應(yīng)棧幀1,函數(shù)2對應(yīng)棧幀2,依次類推。函數(shù)1中調(diào)用函數(shù)2,函數(shù)2中調(diào)用函數(shù)3,函數(shù)3調(diào)用函數(shù)4.當(dāng)函數(shù)1被調(diào)用時,棧幀1入棧,當(dāng)函數(shù)2調(diào)用時,棧幀2入棧,當(dāng)函數(shù)3被調(diào)用時,棧幀3入棧,當(dāng)函數(shù)4被調(diào)用時,棧幀4入棧。當(dāng)前正在執(zhí)行的函數(shù)所對應(yīng)的幀就是當(dāng)前幀(位于棧頂),它保存著當(dāng)前函數(shù)的局部變量、中間計(jì)算結(jié)果等數(shù)據(jù)。

當(dāng)函數(shù)返回時,棧幀從java棧中被彈出,java方法區(qū)有兩種返回函數(shù)的方式,一種是正常的函數(shù)返回,使用return指令,另一種是拋出異常。不管使用哪種方式,都會導(dǎo)致棧幀被彈出。

在一個棧幀中,至少包含局部變量表、操作數(shù)棧和幀數(shù)據(jù)區(qū)幾個部分。

提示:由于每次函數(shù)調(diào)用都會產(chǎn)生對應(yīng)的棧幀,從而占用一定的棧空間,因此,如果棧空間不足,那么函數(shù)調(diào)用自然無法繼續(xù)進(jìn)行下去。當(dāng)請求的棧深度大于最大可用棧深度時,系統(tǒng)會拋出StackOverflowError棧溢出錯誤。

例2 使用遞歸,由于遞歸沒有出口,這段代碼可能會拋出棧溢出錯誤,在拋出棧溢出錯誤時,打印最大的調(diào)用深度

package com.jvm;

public class TestStackDeep {

private static int count =0;

public static void recursion(){

count ++;

recursion();

}

public static void main(String[] args) {

try{

recursion();

}catch(Throwable e){

System.out.println("deep of calling ="+count);

e.printStackTrace();

}

}

}

使用參數(shù)-Xss128K執(zhí)行上面代碼(在eclipse中右鍵選擇Run As-->run Configurations....設(shè)置Vm arguments),部分結(jié)果如圖:

可以看出,在進(jìn)行大約1079次調(diào)用之后,發(fā)生了棧溢出錯誤,通過增大-Xss的值,可以獲得更深的層次調(diào)用,嘗試使用參數(shù)-Xss256K執(zhí)行上述代碼,可能產(chǎn)生如下輸出,很明顯,調(diào)用層次有明顯的增加:

注意:函數(shù)嵌套調(diào)用的層次在很大程度上由棧的大小決定,棧越大,函數(shù)支持的嵌套調(diào)用次數(shù)就越多。

3.1 棧幀組成之局部變量表

局部變量表是棧幀的重要組成部分之一。它用于保存函數(shù)的參數(shù)以及局部變量,局部變量表中的變量只在當(dāng)前函數(shù)調(diào)用中有效,當(dāng)函數(shù)調(diào)用結(jié)束,隨著函數(shù)棧幀的彈出銷毀,局部變量表也會隨之銷毀。

由于局部變量表在棧幀之中,因此,如果函數(shù)的參數(shù)和局部變量很多,會使得局部變量表膨脹,從而每一次函數(shù)調(diào)用就會占用更多的棧空間,最終導(dǎo)致函數(shù)的嵌套調(diào)用次數(shù)減少。

示例3:一個recursion函數(shù)含有3個參數(shù)和10個局部變量,因此,其局部變量表含有13個變量,而第二個recursion函數(shù)不再含有任何參數(shù)和局部變量,當(dāng)這兩個函數(shù)被嵌套調(diào)用時,第二個recursion函數(shù)可以擁有更深的調(diào)用層次。

package com.jvm;

public class TestStackDeep2 {

private static int count = 0;

public static void recursion(long a,long b,long c){

long e=1,f=2,g=3,h=4,i=5,k=6,q=7,x=8,y=9,z=10;

count ++;

recursion(a,b,c);

}

public static void recursion(){

count++;

recursion();

}

public static void main(String[] args) {

try{

recursion(0L,0L,0L);

//recursion();

}catch(Throwable e){

System.out.println("deep of calling = "+count);

e.printStackTrace();

}

}

}

使用參數(shù)-Xss128K執(zhí)行上述代碼中的第一個帶參recursion(long a,long b,long c)函數(shù),輸出結(jié)果為:

使用虛擬機(jī)參數(shù)-Xss128K執(zhí)行上述代碼中第二個不帶參數(shù)的recursion()函數(shù)(當(dāng)然需要把第一個函數(shù)注釋掉),輸出結(jié)果為:

可以看出,在相同的棧容量下,局部變量少的函數(shù)可以支持更深的函數(shù)調(diào)用。

使用jclasslib工具可以查看函數(shù)的局部變量表,如下圖:最大局部變量表大小

該圖顯示了第一個帶參recursion(long a,long b,long c)的最大局部變量表的大小為26個字,因?yàn)樵摵瘮?shù)包含總共13個參數(shù)和局部變量,且都為long型,long和double在局部變量表中需要占用2個字,其他如int short byte 對象引用等占用一個字。

說明:字(word)指的是計(jì)算機(jī)內(nèi)存中占據(jù)一個單獨(dú)的內(nèi)存單元編號的一組二進(jìn)制串,一般32位計(jì)算機(jī)上一個字為4個字節(jié)長度。

通過jclasslib工具查看該類的Class文件中局部變量表的內(nèi)容,(這里說的局部變量表和上述說的局部變量表不同,這里指Class文件的一個屬性,而上述的局部變量表指java棧空間的一部分)

可以看到,在Class文件的局部變量表中,顯示了每個局部變量的作用域范圍、所在槽位的索引(index列)、變量名(name列)和數(shù)據(jù)類型(J表示long型)

棧幀中局部變量表中的槽位是可以重用的,如果一個局部變量過了其作用域,那么在其作用域之后申明的新的局部變量就很有可能會復(fù)用過期局部變量的槽位,從而達(dá)到節(jié)省資源的目的。

示例4 :顯示局部變量表的復(fù)用,在localvar1函數(shù)中,局部變量a和b都作用到了函數(shù)的末尾,故b無法復(fù)用a所在的位置。而在localvar2()函數(shù)中,局部變量a在第?行不再有效,故局部變量b可以復(fù)用a的槽位(1個字)

package com.jvm;

public class TestReuse {

public static void localvar1(){

int a=0;

System.out.println(a);

int b=0;

}

public static void localvar2(){

{

int a=0;

System.out.println(a);

}

int b=0;

}

}

如圖顯示localvar1()函數(shù)的局部變量表,該函數(shù)局部變量大小為2個字,(最大局部變量表中一般第一個局部變量槽位是this引用)第一個槽位是變量a,第二個槽位是變量b,每個變量占一個字。

而localvar2()函數(shù)的局部變量表信息如下圖,雖然和localvar1()一樣,但是b復(fù)用了a的槽位,(從他們都占用同一個槽位index都是0可以看出),因此在整個函數(shù)執(zhí)行中,同時存在的局部變量為1字。

局部變量表中的變量也是垃圾回收根節(jié)點(diǎn),只要被局部變量表中直接或者間接引用的對象都是不會被回收的。

示例5:通過一個簡單示例,展示局部變量對垃圾回收的影響

package com.jvm;

public class LocalvarGC {

public void localvarGc1(){

byte[] a = new byte[6*1024*1024];//6M

System.gc();

}

public void localvarGc2(){

byte[] a = new byte[6*1024*1024];

a = null;

System.gc();

}

public void localvarGc3(){

{

byte[] a = new byte[6*1024*1024];

}

System.gc();

}

public void localvarGc4(){

{

byte[] a = new byte[6*1024*1024];

}

int c = 10;

System.gc();

}

public void localvarGc5(){

localvarGc1();

System.gc();

}

public static void main(String[] args) {

LocalvarGC ins = new LocalvarGC();

ins.localvarGc1();

}

}

每一個localvarGcN()函數(shù)都分配了一塊6M的堆內(nèi)存,并使用局部變量引用這塊空間。

在localvarGc1()中,在申請空間后,立即進(jìn)行垃圾回收,很明顯由于byte數(shù)組被變量a引用,因此無法回收這塊空間。

在localvarGc2()中,在垃圾回收前,先將變量a置為null,使得byte數(shù)組失去強(qiáng)引用,故垃圾回收可以順利回收byte數(shù)組。

在localvarGc3()中,在進(jìn)行垃圾回收前,先使局部變量a失效,雖然變量a已經(jīng)離開了作用域,但是變量a依然存在于局部變量表中,并且也指向這塊byte數(shù)組,故byte數(shù)組依然無法被回收。

對于localvarGc4(),在垃圾回收之前,不僅使變量a失效,更是聲明了變量c,使變量c復(fù)用了變量a的字,由于變量a此時被銷毀,故垃圾回收器可以順利回收數(shù)組byte

對于localvarGc5(),它首先調(diào)用了localvarGc1(),很明顯,在localvarGc1()中并沒有釋放byte數(shù)組,但在localvarGc1()返回后,它的棧幀被銷毀,自然也包含了棧幀中的所有局部變量,故byte數(shù)組失去了引用,在localvarGc5()的垃圾回收中被回收。

可以使用-XX:+printGC執(zhí)行上述幾個函數(shù),在輸出日志里,可以看到垃圾回收前后堆的大小,進(jìn)而推斷出byte數(shù)組是否被回收。

下面的輸出是函數(shù)localvarGc4()的運(yùn)行結(jié)果:

[GC (System.gc())7618K->624K(94208K), 0.0015613 secs]

[Full GC (System.gc()) 624K->526K(94208K), 0.0070718 secs]

從日志中可以看出,堆空間從回收前的7618K變?yōu)榛厥蘸蟮?24K,釋放了>6M的空間,byte數(shù)組已經(jīng)被回收釋放。

參考:https://www.cnblogs.com/zwbg/p/6194470.html

總結(jié)

以上是生活随笔為你收集整理的java虚拟机 函数表_java虚拟机的基本结构如图的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。