java虚拟机 函数表_java虚拟机的基本结构如图
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)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 博主:P60系列是华为8GB运存末代产品
- 下一篇: java组件代码_[java]常用组件