2021-03-15 分類: 網(wǎng)站建設
聲明:本文只是做一個總結,有關jvm的詳細知識可以參考本人之前的系列文章,尤其是那篇:Java虛擬機詳解04—-GC算法和種類。那篇文章和本文是面試時的重點。
面試必問關鍵詞:JVM垃圾回收、類加載機制。
先把本文的目錄畫一個思維導圖:(圖的源文件在本文末尾)
一、Java引用的四種狀態(tài):
強引用:
用的最廣。我們平時寫代碼時,new一個Object存放在堆內(nèi)存,然后用一個引用指向它,這就是強引用。
* 如果一個對象具有強引用,那垃圾回收器絕不會回收它*。當內(nèi)存空間不足,Java虛擬機寧愿拋出OutOfMemoryError錯誤,使程序異常終止,也不會靠隨意回收具有強引用的對象來解決內(nèi)存不足的問題。
軟引用:
如果一個對象只具有軟引用,則內(nèi)存空間足夠時,垃圾回收器就不會回收它;如果內(nèi)存空間不足了,就會回收這些對象的內(nèi)存。(備注:如果內(nèi)存不足,隨時有可能被回收。)
只要垃圾回收器沒有回收它,該對象就可以被程序使用。軟引用可用來實現(xiàn)內(nèi)存敏感的高速緩存。
弱引用:
弱引用與軟引用的區(qū)別在于:只具有弱引用的對象擁有更短暫的生命周期。
* 每次執(zhí)行GC的時候,一旦發(fā)現(xiàn)了只具有弱引用的對象,不管當前內(nèi)存空間足夠與否,都會回收它的內(nèi)存。不過,由于垃圾回收器是一個優(yōu)先級很低的線程,因此不一定會很快發(fā)現(xiàn)那些只具有弱引用的對象*。
虛引用:
“虛引用”顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用并不會決定對象的生命周期。如果一個對象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。
虛引用主要用來跟蹤對象被垃圾回收器回收的活動。
二、Java中的內(nèi)存劃分:
Java程序在運行時,需要在內(nèi)存中的分配空間。為了提高運算效率,就對數(shù)據(jù)進行了不同空間的劃分,因為每一片區(qū)域都有特定的處理數(shù)據(jù)方式和內(nèi)存管理方式。
上面這張圖就是jvm運行時的狀態(tài)。具體劃分為如下5個內(nèi)存空間:(非常重要)
- 程序計數(shù)器:保證線程切換后能恢復到原來的執(zhí)行位置
- 虛擬機棧:(棧內(nèi)存)為虛擬機執(zhí)行java方法服務:方法被調(diào)用時創(chuàng)建棧幀–>局部變量表->局部變量、對象引用
- 本地方法棧:為虛擬機執(zhí)使用到的Native方法服務
- 堆內(nèi)存:存放所有new出來的東西
- 方法區(qū):存儲被虛擬機加載的類信息、常量、靜態(tài)常量、靜態(tài)方法等。
- 運行時常量池(方法區(qū)的一部分)
GC對它們的回收:
內(nèi)存區(qū)域中的程序計數(shù)器、虛擬機棧、本地方法棧這3個區(qū)域隨著線程而生,線程而滅;棧中的棧幀隨著方法的進入和退出而有條不紊地執(zhí)行著出棧和入棧的操作,每個棧幀中分配多少內(nèi)存基本是在類結構確定下來時就已知的。在這幾個區(qū)域不需要過多考慮回收的問題,因為方法結束或者線程結束時,內(nèi)存自然就跟著回收了。
GC回收的主要對象:而J**ava堆和方法區(qū)**則不同,一個接口中的多個實現(xiàn)類需要的內(nèi)存可能不同,一個方法中的多個分支需要的內(nèi)存也可能不一樣,我們只有在程序處于運行期間時才能知道會創(chuàng)建哪些對象,這部分內(nèi)存的分配和回收都是動態(tài)的,GC關注的也是這部分內(nèi)存,后面的文章中如果涉及到“內(nèi)存”分配與回收也僅指著一部分內(nèi)存。
1、程序計數(shù)器:(線程私有)
每個線程擁有一個程序計數(shù)器,在線程創(chuàng)建時創(chuàng)建,
指向下一條指令的地址
執(zhí)行本地方法時,其值為undefined
說的通俗一點,我們知道,Java是支持多線程的,程序先去執(zhí)行A線程,執(zhí)行到一半,然后就去執(zhí)行B線程,然后又跑回來接著執(zhí)行A線程,那程序是怎么記住A線程已經(jīng)執(zhí)行到哪里了呢?這就需要程序計數(shù)器了。因此,為了線程切換后能夠恢復到正確的執(zhí)行位置,每條線程都有一個獨立的程序計數(shù)器,這塊兒屬于“線程私有”的內(nèi)存。
2、Java虛擬機棧:(線程私有)
每個方法被調(diào)用的時候都會創(chuàng)建一個棧幀,用于存儲局部變量表、操作棧、動態(tài)鏈接、方法出口等信息。局部變量表存放的是:編譯期可知的基本數(shù)據(jù)類型、對象引用類型。
每個方法被調(diào)用直到執(zhí)行完成的過程,就對應著一個棧幀在虛擬機中從入棧到出棧的過程。
在Java虛擬機規(guī)范中,對這個區(qū)域規(guī)定了兩種異常情況:
?。?)如果線程請求的棧深度太深,超出了虛擬機所允許的深度,就會出現(xiàn)StackOverFlowError(比如無限遞歸。因為每一層棧幀都占用一定空間,而 Xss 規(guī)定了棧的大空間,超出這個值就會報錯)
(2)虛擬機??梢詣討B(tài)擴展,如果擴展到無法申請足夠的內(nèi)存空間,會出現(xiàn)OOM
3、本地方法棧:
(1)本地方法棧與java虛擬機棧作用非常類似,其區(qū)別是:java虛擬機棧是為虛擬機執(zhí)行java方法服務的,而本地方法棧則為虛擬機執(zhí)使用到的Native方法服務。
(2)Java虛擬機沒有對本地方法棧的使用和數(shù)據(jù)結構做強制規(guī)定,Sun HotSpot虛擬機就把java虛擬機棧和本地方法棧合二為一。
(3)本地方法棧也會拋出StackOverFlowError和OutOfMemoryError。
4、Java堆:即堆內(nèi)存(線程共享)
(1)堆是java虛擬機所管理的內(nèi)存區(qū)域中大的一塊,java堆是被所有線程共享的內(nèi)存區(qū)域,在java虛擬機啟動時創(chuàng)建,堆內(nèi)存的唯一目的就是存放對象實例幾乎所有的對象實例都在堆內(nèi)存分配。
(2)堆是GC管理的主要區(qū)域,從垃圾回收的角度看,由于現(xiàn)在的垃圾收集器都是采用的分代收集算法,因此java堆還可以初步細分為新生代和老年代。
(3)Java虛擬機規(guī)定,堆可以處于物理上不連續(xù)的內(nèi)存空間中,只要邏輯上連續(xù)的即可。在實現(xiàn)上既可以是固定的,也可以是可動態(tài)擴展的。如果在堆內(nèi)存沒有完成實例分配,并且堆大小也無法擴展,就會拋出OutOfMemoryError異常。
5、方法區(qū):(線程共享)
(1)用于存儲已被虛擬機加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)。
(2)Sun HotSpot虛擬機把方法區(qū)叫做永久代(Permanent Generation),方法區(qū)中最終要的部分是運行時常量池。
6、運行時常量池:
(1)運行時常量池是方法區(qū)的一部分,自然受到方法區(qū)內(nèi)存的限制,當常量池無法再申請到內(nèi)存時就會拋出OutOfMemoryError異常。
三、Java對象在內(nèi)存中的狀態(tài):
可達的/可觸及的:
Java對象被創(chuàng)建后,如果被一個或多個變量引用,那就是可達的。即從根節(jié)點可以觸及到這個對象。
其實就是從根節(jié)點掃描,只要這個對象在引用鏈中,那就是可觸及的。
可恢復的:
Java對象不再被任何變量引用就進入了可恢復狀態(tài)。
在回收該對象之前,該對象的finalize()方法進行資源清理。如果在finalize()方法中重新讓變量引用該對象,則該對象再次變?yōu)榭蛇_狀態(tài),否則該對象進入不可達狀態(tài)
不可達的:
Java對象不被任何變量引用,且系統(tǒng)在調(diào)用對象的finalize()方法后依然沒有使該對象變成可達狀態(tài)(該對象依然沒有被變量引用),那么該對象將變成不可達狀態(tài)。
當Java對象處于不可達狀態(tài)時,系統(tǒng)才會真正回收該對象所占有的資源。
四、判斷對象死亡的兩種常用算法:
當對象不被引用的時候,這個對象就是死亡的,等待GC進行回收。
1、引用計數(shù)算法:
概念:
給對象中添加一個引用計數(shù)器,每當有一個地方引用它時,計數(shù)器值就加1;當引用失效時,計數(shù)器值就減1;任何時刻計數(shù)器為0的對象就是不可能再被使用的。
但是:
主流的java虛擬機并沒有選用引用計數(shù)算法來管理內(nèi)存,其中最主要的原因是:它很難解決對象之間相互循環(huán)引用的問題。
優(yōu)點:
算法的實現(xiàn)簡單,判定效率也高,大部分情況下是一個不錯的算法。很多地方應用到它
缺點:
引用和去引用伴隨加法和減法,影響性能
致命的缺陷:對于循環(huán)引用的對象無法進行回收
2、根搜索算法:(jvm采用的算法)
概念:
設立若干種根對象,當任何一個根對象(GC Root)到某一個對象均不可達時,則認為這個對象是可以被回收的。
注:這里提到,設立若干種根對象,當任何一個根對象到某一個對象均不可達時,則認為這個對象是可以被回收的。我們在后面介紹標記-清理算法/標記整理算法時,也會一直強調(diào)從根節(jié)點開始,對所有可達對象做一次標記,那什么叫做可達呢?
可達性分析:
從根(GC Roots)的對象作為起始點,開始向下搜索,搜索所走過的路徑稱為“引用鏈”,當一個對象到GC Roots沒有任何引用鏈相連(用圖論的概念來講,就是從GC Roots到這個對象不可達)時,則證明此對象是不可用的。
如上圖所示,ObjectD和ObjectE是互相關聯(lián)的,但是由于GC roots到這兩個對象不可達,所以最終D和E還是會被當做GC的對象,上圖若是采用引用計數(shù)法,則A-E五個對象都不會被回收。
根(GC Roots):
說到GC roots(GC根),在JAVA語言中,可以當做GC roots的對象有以下幾種:
1、棧(棧幀中的本地變量表)中引用的對象。
2、方法區(qū)中的靜態(tài)成員。
3、方法區(qū)中的常量引用的對象(全局變量)
4、本地方法棧中JNI(一般說的Native方法)引用的對象。
注:第一和第四種都是指的方法的本地變量表,第二種表達的意思比較清晰,第三種主要指的是聲明為final的常量值。
在根搜索算法的基礎上,現(xiàn)代虛擬機的實現(xiàn)當中,垃圾搜集的算法主要有三種,分別是標記-清除算法、復制算法、標記-整理算法。這三種算法都擴充了根搜索算法,不過它們理解起來還是非常好理解的。
五、垃圾回收算法:
1、標記-清除算法:
概念:
標記階段:先通過根節(jié)點,標記所有從根節(jié)點開始的可達對象。因此,未被標記的對象就是未被引用的垃圾對象;
清除階段:清除所有未被標記的對象。
缺點:
標記和清除的過程效率不高(標記和清除都需要從頭遍歷到尾)
標記清除后會產(chǎn)生大量不連續(xù)的碎片。
2、復制算法:(新生代的GC)
概念:
將原有的內(nèi)存空間分為兩塊,每次只使用其中一塊,在垃圾回收時,將正在使用的內(nèi)存中的存活對象復制到未使用的內(nèi)存塊中,然后清除正在使用的內(nèi)存塊中的所有對象。
優(yōu)點:
這樣使得每次都是對整個半?yún)^(qū)進行回收,內(nèi)存分配時也就不用考慮內(nèi)存碎片等情況
只要移動堆頂指針,按順序分配內(nèi)存即可,實現(xiàn)簡單,運行效率高
缺點:空間的浪費
從以上描述不難看出,復制算法要想使用,最起碼對象的存活率要非常低才行。
現(xiàn)在的商業(yè)虛擬機都采用這種收集算法來回收新生代,新生代中的對象98%都是“朝生夕死”的,所以并不需要按照1:1的比例來劃分內(nèi)存空間,而是將內(nèi)存分為一塊比較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor。當回收時,將Eden和Survivor中還存活著的對象一次性地復制到另外一塊Survivor空間上,最后清理掉Eden和剛才用過的Survivor空間。HotSpot虛擬機默認Eden和Survivor的大小比例是8:1,也就是說,每次新生代中可用內(nèi)存空間為整個新生代容量的90%(80%+10%),只有10%的空間會被浪費。
當然,98%的對象可回收只是一般場景下的數(shù)據(jù),我們沒有辦法保證每次回收都只有不多于10%的對象存活,當Survivor空間不夠用時,需要依賴于老年代進行分配擔保,所以大對象直接進入老年代。整個過程如下圖所示:
3、標記-整理算法:(老年代的GC)
復制算法在對象存活率高的時候要進行較多的復制操作,效率將會降低,所以在老年代中一般不能直接選用這種算法。
概念:
標記階段:先通過根節(jié)點,標記所有從根節(jié)點開始的可達對象。因此,未被標記的對象就是未被引用的垃圾對象
整理階段:將*將所有的存活對象壓縮到內(nèi)存的一端*;之后,清理邊界外所有的空間
優(yōu)點:
不會產(chǎn)生內(nèi)存碎片。
缺點:
在標記的基礎之上還需要進行對象的移動,成本相對較高,效率也不高。
它們的區(qū)別如下:(>表示前者要優(yōu)于后者,=表示兩者效果一樣)
(1)效率:復制算法 > 標記/整理算法 > 標記/清除算法(此處的效率只是簡單的對比時間復雜度,實際情況不一定如此)。
(2)內(nèi)存整齊度:復制算法=標記/整理算法>標記/清除算法。
(3)內(nèi)存利用率:標記/整理算法=標記/清除算法>復制算法。
注1:標記-整理算法不僅可以彌補標記-清除算法當中,內(nèi)存區(qū)域分散的缺點,也消除了復制算法當中,內(nèi)存減半的高額代價。
注2:可以看到標記/清除算法是比較落后的算法了,但是后兩種算法卻是在此基礎上建立的。
注3:時間與空間不可兼得。
4、分代收集算法:
當前商業(yè)虛擬機的GC都是采用的“分代收集算法”,這并不是什么新的思想,只是根據(jù)對象的存活周期的不同將內(nèi)存劃分為幾塊兒。一般是把Java堆分為新生代和老年代:短命對象歸為新生代,長命對象歸為老年代。
存活率低:少量對象存活,適合復制算法:在新生代中,每次GC時都發(fā)現(xiàn)有大批對象死去,只有少量存活(新生代中98%的對象都是“朝生夕死”),那就選用復制算法,只需要付出少量存活對象的復制成本就可以完成GC。
存活率高:大量對象存活,適合用標記-清理/標記-整理:在老年代中,因為對象存活率高、沒有額外空間對他進行分配擔保,就必須使用“標記-清理”/“標記-整理”算法進行GC。
注:老年代的對象中,有一小部分是因為在新生代回收時,老年代做擔保,進來的對象;絕大部分對象是因為很多次GC都沒有被回收掉而進入老年代。
六、垃圾收集器:
如果說收集算法時內(nèi)存回收的方法論,那么垃圾收集器就是內(nèi)存回收的具體實現(xiàn)。
雖然我們在對各種收集器進行比較,但并非為了挑出一個最好的收集器。因為直到現(xiàn)在位置還沒有最好的收集器出現(xiàn),更加沒有萬能的收集器,所以我們選擇的只是對具體應用最合適的收集器。
1、Serial收集器:(串行收集器)
這個收集器是一個單線程的收集器,但它的單線程的意義并不僅僅說明它只會使用一個CPU或一條收集線程去完成垃圾收集工作,更重要的是在它進行垃圾收集時,必須暫停其他所有的工作線程(Stop-The-World:將用戶正常工作的線程全部暫停掉),直到它收集結束。收集器的運行過程如下圖所示:
上圖中:
- 新生代采用復制算法,Stop-The-World
- 老年代采用標記-整理算法,Stop-The-World
當它進行GC工作的時候,雖然會造成Stop-The-World,但它存在有存在的原因:正是因為它的簡單而高效(與其他收集器的單線程比),對于限定單個CPU的環(huán)境來說,沒有線程交互的開銷,專心做GC,自然可以獲得高的單線程手機效率。所以Serial收集器對于運行在client模式下是一個很好的選擇(它依然是虛擬機運行在client模式下的默認新生代收集器)。
**2、ParNew收集器:**Serial收集器的多線程版本(使用多條線程進行GC)
ParNew收集器是Serial收集器的多線程版本。
* 它是運行在server模式下的選新生代收集器*,除了Serial收集器外,目前只有它能與CMS收集器配合工作。CMS收集器是一個被認為具有劃時代意義的并發(fā)收集器,因此如果有一個垃圾收集器能和它一起搭配使用讓其更加好,那這個收集器必然也是一個不可或缺的部分了。收集器的運行過程如下圖所示:
上圖中:
- 新生代采用復制算法,Stop-The-World
- 老年代采用標記-整理算法,Stop-The-World
3、ParNew *Scanvenge*收集器
類似ParNew,但更加關注吞吐量。目標是:達到一個可控制吞吐量的收集器。
停頓時間和吞吐量不可能同時調(diào)優(yōu)。我們一方買希望停頓時間少,另外一方面希望吞吐量高,其實這是矛盾的。因為:在GC的時候,垃圾回收的工作總量是不變的,如果將停頓時間減少,那頻率就會提高;既然頻率提高了,說明就會頻繁的進行GC,那吞吐量就會減少,性能就會降低。
吞吐量:CPU用于用戶代碼的時間/CPU總消耗時間的比值,即=運行用戶代碼的時間/(運行用戶代碼時間+垃圾收集時間)。比如,虛擬機總共運行了100分鐘,其中垃圾收集花掉1分鐘,那吞吐量就是99%。
4、G1收集器:
是當今收集器發(fā)展的最前言成果之一,知道jdk1.7,sun公司才認為它達到了足夠成熟的商用程度。
優(yōu)點:
它大的優(yōu)點是結合了空間整合,不會產(chǎn)生大量的碎片,也降低了進行gc的頻率。
二是可以讓使用者明確指定指定停頓時間。(可以指定一個最小時間,超過這個時間,就不會進行回收了)
它有了這么高效率的原因之一就是:對垃圾回收進行了劃分優(yōu)先級的操作,這種有優(yōu)先級的區(qū)域回收方式保證了它的高效率。
如果你的應用追求停頓,那G1現(xiàn)在已經(jīng)可以作為一個可嘗試的選擇;如果你的應用追求吞吐量,那G1并不會為你帶來什么特別的好處。
注:以上所有的收集器當中,當執(zhí)行GC時,都會stop the world,但是下面的CMS收集器卻不會這樣。
5、CMS收集器:(老年代收集器)
CMS收集器(Concurrent Mark Sweep:并發(fā)標記清除)是一種以獲取最短回收停頓時間為目標的收集器。適合應用在互聯(lián)網(wǎng)站或者B/S系統(tǒng)的服務器上,這類應用尤其重視服務器的響應速度,希望系統(tǒng)停頓時間最短。
CMS收集器運行過程:(著重實現(xiàn)了標記的過程)
(1)初始標記
根可以直接關聯(lián)到的對象
速度快
(2)并發(fā)標記(和用戶線程一起)
主要標記過程,標記全部對象
(3)重新標記
由于并發(fā)標記時,用戶線程依然運行,因此在正式清理前,再做修正
(4)并發(fā)清除(和用戶線程一起)
基于標記結果,直接清理對象
整個過程如下圖所示:
上圖中,初始標記和重新標記時,需要stop the world。整個過程中耗時最長的是并發(fā)標記和并發(fā)清除,這兩個過程都可以和用戶線程一起工作。
七、Java堆內(nèi)存劃分:
根據(jù)對象的存活率(年齡),Java對內(nèi)存劃分為3種:新生代、老年代、永久代:
1、新生代:
比如我們在方法中去new一個對象,那這方法調(diào)用完畢后,對象就會被回收,這就是一個典型的新生代對象。
現(xiàn)在的商業(yè)虛擬機都采用這種收集算法來回收新生代,新生代中的對象98%都是“朝生夕死”的,所以并不需要按照1:1的比例來劃分內(nèi)存空間,而是將內(nèi)存分為一塊比較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor。當回收時,將Eden和Survivor中還存活著的對象一次性地復制到另外一塊Survivor空間上,最后清理掉Eden和剛才用過的Survivor空間。HotSpot虛擬機默認Eden和Survivor的大小比例是8:1,也就是說,每次新生代中可用內(nèi)存空間為整個新生代容量的90%(80%+10%),只有10%的空間會被浪費。
當然,98%的對象可回收只是一般場景下的數(shù)據(jù),我們沒有辦法保證每次回收都只有不多于10%的對象存活,當Survivor空間不夠用時,需要依賴于老年代進行分配擔保,所以大對象直接進入老年代。同時,長期存活的對象將進入老年代(虛擬機給每個對象定義一個年齡計數(shù)器)。
來看下面這張圖:
Minor GC和Full GC:
GC分為兩種:Minor GC和Full GC
Minor GC:
Minor GC是發(fā)生在新生代中的垃圾收集動作,采用的是復制算法。
對象在Eden和From區(qū)出生后,在經(jīng)過一次Minor GC后,如果對象還存活,并且能夠被to區(qū)所容納,那么在使用復制算法時這些存活對象就會被復制到to區(qū)域,然后清理掉Eden區(qū)和from區(qū),并將這些對象的年齡設置為1,以后對象在Survivor區(qū)每熬過一次Minor GC,就將對象的年齡+1,當對象的年齡達到某個值時(默認是15歲,可以通過參數(shù) –XX:MaxTenuringThreshold設置),這些對象就會成為老年代。
但這也是不一定的,對于一些較大的對象(即需要分配一塊較大的連續(xù)內(nèi)存空間)則是直接進入老年代
Full GC:
Full GC是發(fā)生在老年代的垃圾收集動作,采用的是標記-清除/整理算法。
老年代里的對象幾乎都是在Survivor區(qū)熬過來的,不會那么容易死掉。因此Full GC發(fā)生的次數(shù)不會有Minor GC那么頻繁,并且做一次Full GC要比做一次Minor GC的時間要長。
另外,如果采用的是標記-清除算法的話會產(chǎn)生許多碎片,此后如果需要為較大的對象分配內(nèi)存空間時,若無法找到足夠的連續(xù)的內(nèi)存空間,就會提前觸發(fā)一次GC。
2、老年代:
在新生代中經(jīng)歷了N次垃圾回收后仍然存活的對象就會被放到老年代中。而且大對象直接進入老年代。
3、永久代:
即方法區(qū)。
八、類加載機制:
虛擬機把描述類的數(shù)據(jù)從Class文件加載到內(nèi)存,并對數(shù)據(jù)進行校驗、轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機直接使用的Java類型,這就是虛擬機的類加載機制。
類加載的過程:
包括加載、鏈接(含驗證、準備、解析)、初始化
如下圖所示:
1、加載:
類加載指的是將類的class文件讀入內(nèi)存,并為之創(chuàng)建一個java.lang.Class對象,作為方法區(qū)這個類的數(shù)據(jù)訪問的入口。
也就是說,當程序中使用任何類時,系統(tǒng)都會為之建立一個java.lang.Class對象。具體包括以下三個部分:
(1)通過類的全名產(chǎn)生對應類的二進制數(shù)據(jù)流。(根據(jù)early load原理,如果沒找到對應的類文件,只有在類實際使用時才會拋出錯誤)
(2)分析并將這些二進制數(shù)據(jù)流轉(zhuǎn)換為方法區(qū)方法區(qū)特定的數(shù)據(jù)結構
(3)創(chuàng)建對應類的java.lang.Class對象,作為方法區(qū)的入口(有了對應的Class對象,并不意味著這個類已經(jīng)完成了加載鏈接)
通過使用不同的類加載器,可以從不同來源加載類的二進制數(shù)據(jù),通常有如下幾種來源:
(1)從本地文件系統(tǒng)加載class文件,這是絕大部分程序的加載方式
(2)從jar包中加載class文件,這種方式也很常見,例如jdbc編程時用到的數(shù)據(jù)庫驅(qū)動類就是放在jar包中,jvm可以從jar文件中直接加載該class文件
(3)通過網(wǎng)絡加載class文件
(4)把一個Java源文件動態(tài)編譯、并執(zhí)行加載
2、鏈接:
鏈接指的是將Java類的二進制文件合并到jvm的運行狀態(tài)之中的過程。在鏈接之前,這個類必須被成功加載。
類的鏈接包括驗證、準備、解析這三步。具體描述如下:
2.1 驗證:
驗證是用來確保Java類的二進制表示在結構上是否完全正確(如文件格式、語法語義等)。如果驗證過程出錯的話,會拋出java.lang.VertifyError錯誤。
主要驗證以下內(nèi)容:
文件格式驗證
元數(shù)據(jù)驗證:語義驗證
字節(jié)碼驗證
2.2 準備:
準備過程則是創(chuàng)建Java類中的靜態(tài)域(static修飾的內(nèi)容),并將這些域的值設置為默認值,同時在方法區(qū)中分配內(nèi)存空間。準備過程并不會執(zhí)行代碼。
注意這里是做默認初始化,不是做顯式初始化。例如:
public static int value = 12;
上面的代碼中,在準備階段,會給value的值設置為0(默認初始化)。在后面的初始化階段才會給value的值設置為12(顯式初始化)。
2.3 解析:
解析的過程就是確保這些被引用的類能被正確的找到(將符號引用替換為直接引用)。解析的過程可能會導致其它的Java類被加載。
3、初始化:
初始化階段是類加載過程的最
名稱欄目:Java虛擬機詳解——JVM常見問題總結
本文地址:http://muchs.cn/news38/105238.html
成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站導航、虛擬主機、網(wǎng)站營銷、企業(yè)建站、網(wǎng)站設計、網(wǎng)站改版
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容