java虛擬機的無關(guān)性java虛擬機的無關(guān)性1、JVM的運行時數(shù)據(jù)區(qū)域1.1 程序計數(shù)器(Program Counter Register)程序計數(shù)器是一塊較小的內(nèi)存空間,可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行" />

国产成人精品无码青草_亚洲国产美女精品久久久久∴_欧美人与鲁交大毛片免费_国产果冻豆传媒麻婆精东

15158846557 在線咨詢 在線咨詢
15158846557 在線咨詢
所在位置: 首頁 > 營銷資訊 > 網(wǎng)站運營 > Java虛擬機

Java虛擬機

時間:2023-07-15 02:21:01 | 來源:網(wǎng)站運營

時間:2023-07-15 02:21:01 來源:網(wǎng)站運營

Java虛擬機:參考《深入理解Java虛擬機》

java虛擬機的無關(guān)性

java虛擬機的無關(guān)性

1、JVM的運行時數(shù)據(jù)區(qū)域

1.1 程序計數(shù)器(Program Counter Register)

程序計數(shù)器是一塊較小的內(nèi)存空間,可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號指示器。

由于Java虛擬機的多線程是通過線程輪流切換并分配處理器執(zhí)行時間的方式來實現(xiàn)的,在任何一個時刻,一個處理器(對于一個多核處理器來說是一個內(nèi)核)都只會執(zhí)行一條線程中的指令。因此,為了線程切換后能恢復(fù)到正確的執(zhí)行位置,每條線程都需要一個獨立的程序計數(shù)器。

如果線程正在執(zhí)行的是一個Java方法,這個計數(shù)器記錄的是正在執(zhí)行的虛擬機字節(jié)碼指令的地址;如果線程正在執(zhí)行的是Native方法,這個計數(shù)器值為空(Undefined)。

此內(nèi)存區(qū)域是唯一一個在Java虛擬機規(guī)范中沒有規(guī)定任何OutOfMemoryError情況的區(qū)域。

1.2 Java虛擬機棧(Java Virtual Machine Stacks)

線程私有,生命周期與線程相同。

虛擬機棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個方法在執(zhí)行的同時都會創(chuàng)建一個幀棧(Stack Frame)用于存儲局部變量表、操作數(shù)棧、動態(tài)鏈表、方法出口等信息。

局部變量表存放了編譯期可知的各種基本數(shù)據(jù)類型(boolean、byte、char、short、int、float、long、double)、對象引用和returnAddress類型(指向一條字節(jié)碼指令的地址)。

其中64位長度的long和double類型的數(shù)據(jù)會占用2個局部變量空間(Slot),其余的類型只占用一個。局部變量表所需的內(nèi)存空間在編譯期間完成分配,當(dāng)進入一個方法時,這個方法需要在幀中分配多大的局部變量空間是完全確定的,在方法運行期間不會改變局部變量表的大小。

在Java虛擬機規(guī)范中,對這個區(qū)域規(guī)定了兩種異常:

1)如果線程請求的棧深度大于虛擬機所允許的深度,將拋出StackOverflowError異常;

2)如果虛擬機可以動態(tài)擴展,如果擴展時無法申請到足夠的內(nèi)存,就會拋出OutOfMemoryError的異常。

1.3 本地方法棧(Native Method Stack)

為虛擬機使用到的Native方法服務(wù)。

(Sun HotSpot虛擬機直接把方法棧和虛擬機棧合二為一)

會拋出StackOverflowError異常和OutOfMemoryError的異常。

1.4 Java堆(Java Heap)

1)對于大多數(shù)應(yīng)用來說,是Java虛擬機所管理的內(nèi)存中最大的一塊。

2)是被所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機啟動時創(chuàng)建。此內(nèi)存區(qū)域的唯一目的是存放對象實例,幾乎所有的對象實例都在這里分配內(nèi)存。

在Java虛擬機規(guī)范中:所有的對象實例以及數(shù)組都要在堆上分配。

【隨著JIT編譯期的發(fā)展和逃逸分析技術(shù)逐漸成熟,棧上分配、標(biāo)量替換優(yōu)化技術(shù)將會導(dǎo)致一些微妙的變化發(fā)生,所有的對象都分配在堆上也漸漸變得不那么“絕對”了】

3)Java堆是垃圾收集器管理的主要區(qū)域,又被稱為“GC堆”(Garbage Collected Heap)。

4)Java堆可以處于物理上不連續(xù)的內(nèi)存空間中,只要邏輯上連續(xù)即可。實現(xiàn)上既可以是固定大小,也可以是可擴展的。不過當(dāng)前主流的虛擬機都是按照可擴展來實現(xiàn)的(通過-Xmx和-Xms控制)。如果堆中沒有內(nèi)存完成實例分配,并且堆也無法再擴展,將會拋出OutOfMemoryError異常。

1.5 方法區(qū)(Method Area)

別名:Non-Heap,目的與Java堆區(qū)分

是各個線程共享的內(nèi)存區(qū)域,用于存儲已被虛擬機加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)。不需要連續(xù)內(nèi)存和可以選擇固定大小或者可擴展,還可以選擇不實現(xiàn)垃圾收集。這個區(qū)域的內(nèi)存回收主要針對常量池的回收和對類型的卸載。

當(dāng)方法區(qū)無法滿足內(nèi)存分配需求時,將拋出OutOfMemoryError異常。

1.6 運行時常量池(Runtime Constant Pool)

方法區(qū)的一部分。Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息是常量池,用于存放編譯期生成的各種字面量和符號引用,這部分內(nèi)容將在類加載后進入方法區(qū)的運行時常量池中存放。

運行時常量池相對于Class文件常量池的另外一個重要特征是具備動態(tài)性,Java語言并不要求一定只有編譯期才能產(chǎn)生,也就是并非預(yù)置入Class文件中常量池的內(nèi)容才能進入方法區(qū)運行時常量池,運行期間也可能將新的常量放入池中,比較常用的是String類的intern()方法。

當(dāng)常量池?zé)o法再申請到內(nèi)存時,會拋出OutOfMemoryError異常。

1.7直接內(nèi)存(Direct Memory)

不是虛擬機運行時數(shù)據(jù)區(qū)的一部分,也不是Java虛擬機規(guī)范中定義的內(nèi)存區(qū)域,但這部分內(nèi)存被頻繁使用。而且也可能導(dǎo)致OutOfMemoryError異常。

本機直接內(nèi)存的分配不會受到Java堆大小的限制,但是會受到本機總內(nèi)存(包括RAM以及SWAP區(qū)或者分頁文件)大小以及處理器尋址空間的限制。

注意:服務(wù)器管理員在分配虛擬機參數(shù)時,會根據(jù)實際內(nèi)存設(shè)置-Xmx等參數(shù)信息,但經(jīng)常忽略直接內(nèi)存,使得各個內(nèi)存區(qū)域總和大于物理內(nèi)存限制(包括物理的和操作系統(tǒng)級的限制),從而導(dǎo)致動態(tài)擴展時出現(xiàn)OutOfMemoryError異常。

2、HotSpot虛擬機對象

2.1、對象的創(chuàng)建

1)類加載檢查

2)為新生對象分配內(nèi)存

(1)分配方式:指針碰撞(java堆中的內(nèi)存是規(guī)整的)

(2)分配方式:空閑列表(內(nèi)存不規(guī)整)

【選擇哪種分配方式由java堆是否規(guī)整決定,java堆內(nèi)存是否規(guī)整由所采用的垃圾收集器是否帶有壓縮整理功能決定】

解決方案:

(1)對分配內(nèi)存空間的動作進行同步處理——實際上虛擬機采用CAS配上失敗重試的方式保證更新操作的原子性;

(2)TLAB本地線程分配緩沖:把內(nèi)存分配的動作按照線程劃分在不同的空間之中進行,即每個線程在java堆中預(yù)先分配一小塊內(nèi)存。通過-XX:+/-UseTLAB參數(shù)來設(shè)定。

3)需要將分配到的內(nèi)存空間都初始化為零值(不包括對象頭)

4)對對象進行必要的設(shè)置——對象頭

2.2、對象的內(nèi)存布局

1)對象頭

(1)用于存儲對象自身的運行時數(shù)據(jù);Mark Word

Mark Word被設(shè)計成一個非固定的數(shù)據(jù)結(jié)構(gòu),會根據(jù)對象的狀態(tài)復(fù)用自己的存儲空間

(2)類型指針

即對象指向它的類元數(shù)據(jù)的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例。

如果是數(shù)組,則必須有一塊記錄數(shù)組長度的數(shù)據(jù),因為java虛擬機可以通過普通java對象的元數(shù)據(jù)信息確定java對象的大小。

2)實例數(shù)據(jù)

對象真正存儲的有效信息

存儲順序:虛擬機分配策略參數(shù)和字段在java源碼中定義的順序的影響

HotSpot默認(rèn)分配策略:longs/doubles、ints、shorts/chars、bytes、booleans、oops

如果CompactFields參數(shù)值為true(默認(rèn)為true),那么子類之中較窄的變量也可能會插入到父類變量的空隙中。

3)對齊填充

不必然存在,也沒有特別含義,起占位符作用

HotSpot VM的自動內(nèi)存管理系統(tǒng)要求對象起始地址必須是8字節(jié)的整數(shù)倍。

2.3、對象的訪問定位

主流的訪問方式:

1)句柄

優(yōu)點:reference存儲的是穩(wěn)定的句柄地址,在對象被移動時,只改變句柄中的實例數(shù)據(jù)指針,而reference本身不用改變

2)直接指針(Sun HotSpot主要使用這種)

優(yōu)點:速度快,節(jié)省了一次指針定位的時間開銷

2.4實戰(zhàn):OutOfMemoryError異常

內(nèi)存映像分析工具:內(nèi)存泄露(Memory Leak) or 內(nèi)存溢出(Memory Overflow)

使用eclipse memory analyzer工具

1、官網(wǎng)下載,解壓縮后直接打開即可使用。

http://www.eclipse.org/downloads/download.php?file=/mat/1.8.1/rcp/MemoryAnalyzer-1.8.1.20180910-win32.win32.x86_64.zip

1)java堆溢出

package testJVM;import java.util.ArrayList;import java.util.List;/** * VM Args: -verbose:gc -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError * @author laibn * */public class HeapOOM { static class OOMObject{ } public static void main(String[] args) { List<OOMObject> list = new ArrayList<OOMObject>(); while(true){ list.add(new OOMObject()); } }}輸出:

分析:

如果是內(nèi)存泄露,可進一步通過工具查看泄露對象到GC Roots的引用鏈。于是就能找到泄露對象是通過怎樣的路徑與GC Roots相關(guān)聯(lián)并導(dǎo)致垃圾收集器無法自動回收它們的。掌握了泄露對象的類型信息及GC Roots引用鏈的信息,就可以比較準(zhǔn)確地定位出泄露代碼的位置。

如果不存在泄露,就是內(nèi)存中的對象確實都還必須存活著,那就應(yīng)當(dāng)檢查虛擬機的堆參數(shù)(-Xmx 與 -Xms),與機器物理內(nèi)存對比看是否還可以調(diào)大,從代碼上檢查是否存在某些對象生命周期過長、持有狀態(tài)時間過長的情況,嘗試減少程序運行期的內(nèi)存消耗。

2)虛擬機棧和本地方法棧溢出

由于HotSpot虛擬機中不區(qū)分虛擬機棧和本地方法棧,因此,對于Hotspot來說,雖然-Xoss參數(shù)(設(shè)置本地方法棧大小)存在,但實際上是無效的,棧容量只由-Xss參數(shù)設(shè)定。關(guān)于虛擬機棧和本地方法棧,在Java虛擬機規(guī)范中描述了兩種異常:

(1)如果線程請求的棧深度大于虛擬機所允許的最大深度,將拋出StackOverflowError異常。

(2)如果虛擬機在擴展棧時無法申請到足夠的內(nèi)存空間,則拋出OutOfMemoryError異常。




package testJVM;/** * VM Args: -Xss128k * @author laibn * */public class JavaVMStackSOF { private int stackLength = 1; public void stackLeak(){ stackLength++; stackLeak(); } public static void main(String[] args) throws Throwable{ JavaVMStackSOF oom = new JavaVMStackSOF(); try{ oom.stackLeak(); }catch(Throwable e){ System.out.println("stack length:"+oom.stackLength); throw e; } }}


在單個線程下,無論是由于棧幀太大還是虛擬機棧容量太小,當(dāng)內(nèi)存無法分配的時候,虛擬機拋出的都是StackOverflowError異常。

如果測試時不限于單線程,通過不斷地建立線程的方式倒是可以產(chǎn)生內(nèi)存溢出異常。

操作系統(tǒng)分配給每個進程的內(nèi)存是有限制的,譬如32位windows限制為2GB,虛擬機提供了參數(shù)來控制Java堆和方法區(qū)的這兩部分內(nèi)存的最大值。剩余的內(nèi)存為2GB(操作系統(tǒng)限制)減去Xmx(最大堆容量),再減去MaxPermSize(最大方法區(qū)容量),程序計數(shù)器消耗內(nèi)存很小,可以忽略掉。如果虛擬機進程本身耗費的內(nèi)存不計算在內(nèi),剩下的內(nèi)存就由虛擬機棧和本地方法?!肮戏帧绷?。每個線程分配到的棧容量越大,可以建立的線程數(shù)量自然就越少,建立線程時就越容易把剩下的內(nèi)存耗盡。

package testJVM;/** * VM Args: -Xss2M(我的機子跑不動,會死掉,要注意) * @author laibn * */public class JavaVMStackOOM { private void dontStop(){ while(true){ } } public void stackLeakByThread(){ while(true){ Thread thread = new Thread(new Runnable(){ public void run(){ dontStop(); } }); thread.start(); } } public static void main(String[] args) { JavaVMStackOOM oom = new JavaVMStackOOM(); oom.stackLeakByThread(); }}3)方法區(qū)和運行時常量池溢出

package testJVM;import java.util.ArrayList;import java.util.List;/** * -verbose:gc -XX:PermSize=10M -XX:MaxPermSize=10M * @author laibn * */public class RuntimeConstantPoolOOM { public static void main(String[] args) { /*List<String> list = new ArrayList<String>(); int i = 0; while(true){ list.add(String.valueOf(i++).intern()); }*/ String str1 = new StringBuilder("計算機").append("軟件").toString(); System.out.println(str1.intern() == str1); String str2 = new StringBuilder("ja").append("va").toString(); System.out.println(str2.intern() == str2); }}結(jié)果:(JDK1.7)

true

false

原因:JDK1.7中的intern()實現(xiàn)不會再復(fù)制實例,只是在常量池中記錄首次出現(xiàn)的實例引用,因此intern()返回的引用和由StringBuilder創(chuàng)建的那個字符串實例是同一個。對str2比較返回false是因為“java”這個字符串在執(zhí)行StringBuilder.toString()之前已經(jīng)出現(xiàn)過,字符串常量池中已經(jīng)有它的引用了,不符合“首次出現(xiàn)”原則,而“計算機軟件”是首次出現(xiàn),因此返回true。

String.intern()是一個Native方法,它的作用是:如果字符串常量池中已經(jīng)包含一個等于此String對象的字符串,則返回代表池中這個字符串的String對象;否則,將此String對象包含的字符串添加到常量池中,并且返回此String對象的引用。




package testJVM;import java.lang.reflect.Method;import net.sf.cglib.proxy.Enhancer;import net.sf.cglib.proxy.MethodInterceptor;import net.sf.cglib.proxy.MethodProxy;/** * -XX:PermSize=10M -XX:MaxPermSize=10M * 引入cglib-2.2.2.jar和asm-3.3.1.jar * @author laibn * */public class JavaMethodAreaOOM { public static void main(String[] args) { while(true){ Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(OOMObject.class); enhancer.setUseCache(false); enhancer.setCallback(new MethodInterceptor(){ public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable{ try{ return proxy.invokeSuper(obj, args); }catch(Throwable e){ e.printStackTrace(); throw e; } } }); enhancer.create(); } } static class OOMObject{ }}碼字時遇到的問題:

java.lang.NoClassDefFoundError: org/objectweb/asm/Type異常

需要asm的jar包

java.lang.IncompatibleClassChangeError: class net.sf.cglib.core.DebuggingClassWriter has interface org.objectweb.asm.ClassVisitor as super class

CGLib的jar包版本問題,需要將CGLib包改為2.2.2,添加對asm的引用。

我的eclipse只返回

方法區(qū)溢出也是一種常見的內(nèi)存溢出異常,一個類要被垃圾收集器回收掉,判定條件是比較苛刻的。在經(jīng)常動態(tài)生成大量Class的應(yīng)用中,需要特別注意類的回收狀況。這類場景除了上面提到的程序使用了CGLib字節(jié)碼增強和動態(tài)語言外,常見的還有:大量JSP或動態(tài)產(chǎn)生JSP文件的應(yīng)用(JSP第一次運行時需要編譯為Java類)、基于OSGi的應(yīng)用(即使是同一個類文件,被不同的加載器加載也會視為不同的類)等。

4)本機直接內(nèi)存溢出







3、垃圾搜集算法

3.1、標(biāo)記-清除

1)標(biāo)記所有需要回收的對象,在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對象。

不足:效率問題,標(biāo)記和清除兩個過程都效率不高;空間問題,會產(chǎn)生大量不連續(xù)的內(nèi)存碎片,碎片太多會導(dǎo)致以后在程序運行過程中需要分配大對象時,無法找到足夠的連續(xù)內(nèi)存而不得不觸發(fā)另一次垃圾收集動作。

3.2、復(fù)制算法

將內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中的一塊,當(dāng)這塊用完了,將還存活的對象復(fù)制到另一塊上面,然后把已使用過的內(nèi)存空間一次清理掉。

不足:將內(nèi)存縮小為原來的一半

HotSpot虛擬機:Eden 8: Survivor:1 : Survivor:1,回收時,將Eden和Surviver還存活的對象一次性復(fù)制到另一塊Servivor空間上,最后清理掉Eden和剛才用過的Survivor空間。

3.3、標(biāo)記-整理

標(biāo)記所有需要回收的對象,在標(biāo)記完成后讓所有存活的對象向一端移動,然后直接清理掉端邊界以外的內(nèi)存。

3.4、分代算法

根據(jù)對象存活周期不同,將內(nèi)存劃分為幾塊。一般把java堆分為新生代和老年代。

新生代:少量存活->復(fù)制算法;

老年代:存活率高->標(biāo)記清理或者標(biāo)記整理算法

4、Class類文件的結(jié)構(gòu)

Class文件是以8位字節(jié)為基礎(chǔ)單位的二進制流,各個數(shù)據(jù)項目嚴(yán)格按照順序緊湊排列在Class文件中,中間沒有添加任何分隔符。

不完整class截圖

4..1 數(shù)據(jù)類型:無符號數(shù)和表

無符號數(shù):基本數(shù)據(jù)類型,以u1、u2、u4、u8表示1個字節(jié)、2個字節(jié)、4個字節(jié)、8個字節(jié)的無符號數(shù),可用以描述數(shù)字、索引引用、數(shù)量值或者按照UTF-8編碼構(gòu)成字符串值。

表:由多個無符號數(shù)或者其它表構(gòu)成的復(fù)合數(shù)據(jù)類型,所有表以"_info"結(jié)尾。用以描述有層次關(guān)系的復(fù)合結(jié)構(gòu)的數(shù)據(jù),整個Class文件本質(zhì)上是一張表。

4.2 魔數(shù)

魔數(shù):Class文件的前4個字節(jié),唯一作用是確定這個文件是否為一個能被虛擬機接受的Class文件。

Class文件魔數(shù)值:0xCAFEBABE

魔數(shù)后的四個字節(jié)為Class文件的版本號:5、6個字節(jié)為次版本號,7、8個字節(jié)為主版本號

4.3常量池

4.4 訪問標(biāo)志

兩個字節(jié),位于常量池之后,用于識別一些類或者接口層次的訪問信息,包括:這個Class是類還是接口;是否定義為public類型;是否定義為abstract類型;如果是類,是否被聲明為final等。

4.5 類索引、父類索引和接口索引結(jié)合

4.6 字段表集合

4.7方法表集合

4.8屬性表集合

5、字節(jié)碼指令

java虛擬機的指令由一個字節(jié)(0~255)長度的、代表著某種特定操作含義的數(shù)字(操作碼)以及緊隨其后的零至多個代表此操作所需參數(shù)(操作數(shù))而構(gòu)成。

Java虛擬機采用面向操作數(shù)棧的架構(gòu),大部分指令不包括操作數(shù),只有一個操作碼

Java虛擬機的解釋器最基本的執(zhí)行模型:

do { 自動計算PC寄存器的值加1; 根據(jù)PC寄存器的指示位置,從字節(jié)碼流中取出操作碼; if(字節(jié)碼存在操作數(shù)) 從字節(jié)碼流中取出操作數(shù); 執(zhí)行操作碼所定義的操作;} while(字節(jié)碼流長度 > 0)

5.1 操作碼助記符:

i : int 類型的數(shù)據(jù)操作;

l:long;s:short;b: byte;c:char;f: float;d: double;a: reference;

5.2 加載與存儲指令:

用于將數(shù)據(jù)在棧幀中的局部變量表和操作數(shù)棧之間來回傳輸,包括:

將一個局部變量加載到操作棧:iload、iload_<n>、lload、lload_<n>、fload、fload_<n>、dload、dload_<n>、aload、aload_<n>

將一個數(shù)值從操作數(shù)棧存儲到局部變量表:istore、istore_<n>、lstore、lstore_<n>、fstore、fstore_<n>、dstore、dstore_<n>、astore、astore_<n>

將一個常量加載到操作數(shù)棧:bipush、sipush、ldc、ldc_w、ldc2_w、aconst_null、iconst_m1、iconst_<i>、lconst_<l>、fconst_<f>、dconst_<d>

擴充局部變量表的訪問索引的指令:wide

5.3 運算指令

用于對兩個操作數(shù)棧上的值進行某種特定運算,并把結(jié)果重新存入到操作棧頂。

1)對整型數(shù)據(jù)進行運算的指令

2)對浮點型數(shù)據(jù)進行運算的指令

無論哪種算術(shù)指令,都使用Java虛擬機的數(shù)據(jù)類型,由于沒有直接支持byte、short、char、boolean類型的算術(shù)指令,對這類數(shù)據(jù)的運算,應(yīng)使用操作int類型的指令代替。整數(shù)與浮點數(shù)的算術(shù)指令在溢出和被零除的時候也有各自不同的行為表現(xiàn)。

加:iadd、ladd、fadd、dadd

減:isub、lsub、fsub、dsub

乘:imul、lmul、fmul、dmul

除:idiv、ldiv、fdiv、ddiv

求余:irem、lrem、frem、drem

取反:ineg、lneg、fneg、dneg

位移:ishl、ishr、iushr、lshl、lshr、lushr

按位或指令:ior、lor

按位與指令:iand、land

按位異或指令:ixor、lxor

局部變量自增指令:iinc

比較指令:dcmpg、dcmpl、fcmpg、fcmpl、lcmp

5.4 類型轉(zhuǎn)換指令

5.5 對象創(chuàng)建與訪問指令

5.6 操作數(shù)棧管理指令

5.7 控制轉(zhuǎn)移指令

5.8 方法調(diào)用和返回指令

5.9 異常處理指令

5.10 同步指令

6、虛擬機類加載機制

虛擬機的類加載機制:虛擬機把類的數(shù)據(jù)從Class文件加載到內(nèi)存,并對數(shù)據(jù)進行校驗、轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機直接使用的Java類型。

6.1類加載時機

類的生命周期
類的加載過程中,加載、驗證、準(zhǔn)備、初始化和卸載這5個階段順序是確定的,必須按照此順序按部就班地開始(并非“進行”或“完成”,通常互相交叉混合進行,通常會在一個階段執(zhí)行的過程中調(diào)用、激活另一階段),而解析在某些情況下,可以在初始化階段之后再開始,這是為了支持Java語言的運行時綁定(動態(tài)綁定或晚期綁定)。

虛擬機規(guī)范中規(guī)定,有且只有 5 種情況必須立即對類進行初始化:【對類的主動引用】

1)遇到new 、getstatic、putstatic、invokestatic這4條字節(jié)碼指令時,若類沒有進行過初始化,則需要先觸發(fā)其初始化。場景比如:

2)使用java.lang.reflect包的方法對類進行反射調(diào)用的時候,如果類沒有進行過初始化,則需要先觸發(fā)其初始化。

3)當(dāng)初始化一個類的時候,如果發(fā)現(xiàn)其父類還沒進行過初始化,則需要先觸發(fā)其父類的初始化。

4)當(dāng)虛擬機啟動時,用戶需要指定一個要執(zhí)行的主類(包含main()方法的那個類),虛擬機會先初始化這個主類

5)當(dāng)使用JDK1.7的動態(tài)語言支持時,如果一個java.lang.invoke.MethodHandle實例最后的解析結(jié)果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且這個方法句柄對應(yīng)的類沒有進行過初始化,則需要先觸發(fā)其初始化。

6.2 類加載過程

1)加載

虛擬機操作:

讀取二進制字節(jié)流的渠道:




2)驗證

為了確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機的要求,并且不會危害虛擬機自身的安全。

非常重要但非必要,可使用 -Xverify:none參數(shù)來關(guān)閉大部分的類驗證措施,以縮短虛擬機類加載的時間。

i. 文件格式驗證

第一階段要驗證字節(jié)流是否符合Class文件格式的規(guī)范,并且能被當(dāng)前版本的虛擬機處理。

該階段目的是保證輸入的字節(jié)流能正確地解析并存儲于方法區(qū)之內(nèi),格式上符合描述一個Java類型信息的要求。該階段的驗證是基于二進制字節(jié)流進行的,只有通過了這個階段的驗證后,字節(jié)流才會進入內(nèi)存的方法區(qū)中進行存儲,所以后面3個階段驗證全部是基于方法區(qū)的存儲結(jié)構(gòu)進行的,不會直接操作字節(jié)流。

比如:

ii. 元數(shù)據(jù)驗證

第二階段是對字節(jié)碼描述的信息進行語義分析,以保證其描述的信息符合Java語言規(guī)范的要求。

該階段主要目的是對類的元數(shù)據(jù)信息進行語義校驗,保證不存在不符合Java語言規(guī)范的元數(shù)據(jù)信息

比如:

iii. 字節(jié)碼驗證

第三個階段主要目的是通過數(shù)據(jù)流和控制流分析,確定程序語義是合法的、符合邏輯的。主要對類的方法體進行校驗分析,保證被校驗類在運行時不會做出危害虛擬機安全的事件

iv. 符號引用驗證

第四個階段的校驗主要發(fā)生在虛擬機將符號引用轉(zhuǎn)化為直接引用的時候,這個轉(zhuǎn)化動作在“解析”階段中發(fā)生。符號引用驗證可看作是對類自身以外(常量池中的各種符合引用)的信息進行匹配性校驗。

該階段的目的是確保解析動作能正常執(zhí)行,若無法通過驗證則拋出“java.lang.IncompatibleClassChangeError”異常的子類,如:java.lang.IllegalAccessError、java.lang.NoSuchFieldError、java.lang.NoSuchMethodError等。

如:




3)準(zhǔn)備

正式為類變量分配內(nèi)存并設(shè)置類變量初始值的階段,所使用的內(nèi)存都在方法區(qū)中分配。

僅包括類變量(被static修飾的變量)【實例變量會在類對象實例化時隨著對象一起分配在java堆中】

“初始值“通常情況下指數(shù)據(jù)類型的零值【final修飾的常量ConstantValue除外】

基本數(shù)據(jù)類型的零值



4)解析

虛擬機將常量池內(nèi)的符號引用替換為直接引用的過程

符號引用:以一組符號來描述所引用的目標(biāo),符號可以是任何形式的字面量,只要使用時能無歧義地定位到目標(biāo)即可。符號引用的字面量定義在Java虛擬機規(guī)范的Class文件格式中。與虛擬機的內(nèi)存布局無關(guān),引用的目標(biāo)不一定已經(jīng)加載到內(nèi)存中。

直接引用:可以是直接指向目標(biāo)的指針、相對偏移量或是一個能間接定位到目標(biāo)的句柄。與虛擬機內(nèi)存布局有關(guān),同一個符號引用在不同虛擬機實例上翻譯出來的直接引用一般不會相同,如果有了直接引用,那引用的目標(biāo)必定是已經(jīng)存在于內(nèi)存中。

虛擬機規(guī)范要求在執(zhí)行 new、anewarray、multianewarray、checkcast、getfield、getstatic、instanceof、invokedynamic、invokeinterface、invokespecial、invokestatic、invokevirtual、ldc、lcd_w、putfield、putstatic這16個操作符號引用的字節(jié)碼指令之前,先對它們所使用的符號引用進行解析。

除了invokedynamic指令以外,虛擬機實現(xiàn)可以對第一次解析的結(jié)果進行緩存,避免重復(fù)解析。

invokedynamic,必須等到程序?qū)嶋H運行到這條指令時,解析動作才能進行。

解析動作主要針對類或接口、字段、類方法、接口方法、方法類型、方法句柄和調(diào)用點限定符7類符號引用進行,分別對應(yīng)于常量池:CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info、CONSTANT_InterfaceMethodref_info、CONSTANT_MethodType_info、CONSTANT_MethodHandle_info、CONSTANT_InvokeDynamic_info。




5)初始化

初始化階段是執(zhí)行類構(gòu)造器<clinit>()方法的過程

<clinit>()方法是由編譯器自動收集類中的所有類變量的賦值動作和靜態(tài)語句塊(static{}塊)中的語句合并產(chǎn)生的。不需要顯式調(diào)用父類構(gòu)造器,虛擬機會保證在子類的<clinit>()執(zhí)行之前,父類的<clinit>已經(jīng)執(zhí)行完畢,因此虛擬機中第一個被執(zhí)行的<clinit>()的類是java.lang.Object。該方法非必需,若類沒有靜態(tài)語句塊和變量初始化賦值操作,則不會生成此方法。執(zhí)行接口的<clinit>()不需要先執(zhí)行父接口的,只有當(dāng)父接口定義的變量被初始化時,父接口才會初始化。接口的實現(xiàn)類初始化時,也不會執(zhí)行接口的<clinit>()。虛擬機會保證多線程環(huán)境中,只有一個線程執(zhí)行<clinit>(),其他線程阻塞等待直到執(zhí)行完畢,但此時其他線程也不會再次進行<clinit>(),同一個類加載器下,一個類只會初始化一次。




7、類加載器

類加載器:實現(xiàn)“通過一個類的全限定名來獲取描述此類的二進制字節(jié)流”,即實現(xiàn)類的加載動作。每一個類加載器都有一個獨立的類名稱空間。詳見另一篇文章

Bonnie Lai:JAVA 類加載器

8、字節(jié)碼執(zhí)行引擎




9、內(nèi)存模型



關(guān)鍵詞:虛擬

74
73
25
news

版權(quán)所有? 億企邦 1997-2025 保留一切法律許可權(quán)利。

為了最佳展示效果,本站不支持IE9及以下版本的瀏覽器,建議您使用谷歌Chrome瀏覽器。 點擊下載Chrome瀏覽器
關(guān)閉