1. 程式人生 > >Java虛擬機器詳解----JVM常見問題總結

Java虛擬機器詳解----JVM常見問題總結

【宣告】 

歡迎轉載,但請保留文章原始出處→_→ 

【正文】

宣告:本文只是做一個總結,有關jvm的詳細知識可以參考本人之前的系列文章,尤其是那篇:Java虛擬機器詳解04----GC演算法和種類。那篇文章和本文是面試時的重點。

面試必問關鍵詞:JVM垃圾回收、類載入機制

先把本文的目錄畫一個思維導圖:(圖的原始檔在本文末尾)


一、Java引用的四種狀態:

強引用:

  用的最廣。我們平時寫程式碼時,new一個Object存放在堆記憶體,然後用一個引用指向它,這就是強引用。

  如果一個物件具有強引用,那垃圾回收器絕不會回收它。當記憶體空間不足,Java虛擬機器寧願丟擲OutOfMemoryError錯誤,使程式異常終止,也不會靠隨意回收具有強引用的物件來解決記憶體不足的問題。

軟引用:

  如果一個物件只具有軟引用,則記憶體空間足夠時,垃圾回收器就不會回收它;如果記憶體空間不足了,就會回收這些物件的記憶體。(備註:如果記憶體不足,隨時有可能被回收。)

  只要垃圾回收器沒有回收它,該物件就可以被程式使用。軟引用可用來實現記憶體敏感的快取記憶體。

弱引用:

  弱引用與軟引用的區別在於:只具有弱引用的物件擁有更短暫的生命週期

  每次執行GC的時候,一旦發現了只具有弱引用的物件,不管當前記憶體空間足夠與否,都會回收它的記憶體。不過,由於垃圾回收器是一個優先順序很低的執行緒,因此不一定會很快發現那些只具有弱引用的物件

虛引用:

  “虛引用”顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用並不會決定物件的生命週期

。如果一個物件僅持有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收

  虛引用主要用來跟蹤物件被垃圾回收器回收的活動。

注:關於各種引用的詳解,可以參考這篇部落格:

二、Java中的記憶體劃分:

Java程式在執行時,需要在記憶體中的分配空間。為了提高運算效率,就對資料進行了不同空間的劃分,因為每一片區域都有特定的處理資料方式和記憶體管理方式。


上面這張圖就是jvm執行時的狀態。具體劃分為如下5個記憶體空間:(非常重要)

  • 程式計數器:保證執行緒切換後能恢復到原來的執行位置
  • 虛擬機器棧:(棧記憶體)為虛擬機器執行java方法服務:方法被呼叫時建立棧幀-->區域性變量表->區域性變數、物件引用
  • 本地方法棧:為虛擬機器執使用到的Native方法服務
  • 堆記憶體存放所有new出來的東西
  • 方法區儲存被虛擬機器載入的類資訊、常量、靜態常量、靜態方法等。
  • 執行時常量池(方法區的一部分)

GC對它們的回收:

記憶體區域中的程式計數器虛擬機器棧、本地方法棧這3個區域隨著執行緒而生,執行緒而滅棧中的棧幀隨著方法的進入和退出而有條不紊地執行著出棧和入棧的操作,每個棧幀中分配多少記憶體基本是在類結構確定下來時就已知的。在這幾個區域不需要過多考慮回收的問題,因為方法結束或者執行緒結束時,記憶體自然就跟著回收了。

GC回收的主要物件:而Java堆和方法區則不同,一個介面中的多個實現類需要的記憶體可能不同,一個方法中的多個分支需要的記憶體也可能不一樣,我們只有在程式處於執行期間時才能知道會建立哪些物件,這部分記憶體的分配和回收都是動態的,GC關注的也是這部分記憶體,後面的文章中如果涉及到“記憶體”分配與回收也僅指著一部分記憶體。

1、程式計數器:(執行緒私有)

每個執行緒擁有一個程式計數器,線上程建立時建立,

指向下一條指令的地址

執行本地方法時,其值為undefined

說的通俗一點,我們知道,Java是支援多執行緒的,程式先去執行A執行緒,執行到一半,然後就去執行B執行緒,然後又跑回來接著執行A執行緒,那程式是怎麼記住A執行緒已經執行到哪裡了呢?這就需要程式計數器了。因此為了執行緒切換後能夠恢復到正確的執行位置,每條執行緒都有一個獨立的程式計數器,這塊兒屬於“執行緒私有”的記憶體。

2、Java虛擬機器棧:(執行緒私有)

每個方法被呼叫的時候都會建立一個棧幀,用於儲存區域性變量表、操作棧、動態連結、方法出口等資訊。區域性變量表存放的是:編譯期可知的基本資料型別、物件引用型別。

    每個方法被呼叫直到執行完成的過程,就對應著一個棧幀在虛擬機器中從入棧到出棧的過程。

在Java虛擬機器規範中,對這個區域規定了兩種異常情況:

  (1)如果執行緒請求的棧深度太深,超出了虛擬機器所允許的深度,就會出現StackOverFlowError(比如無限遞迴。因為每一層棧幀都佔用一定空間,而 Xss 規定了棧的最大空間,超出這個值就會報錯)

  (2)虛擬機器棧可以動態擴充套件,如果擴充套件到無法申請足夠的記憶體空間,會出現OOM

3、本地方法棧:

(1)本地方法棧與java虛擬機器棧作用非常類似,其區別是:java虛擬機器棧是為虛擬機器執行java方法服務的,而本地方法棧則為虛擬機器執使用到的Native方法服務

(2)Java虛擬機器沒有對本地方法棧的使用和資料結構做強制規定,Sun HotSpot虛擬機器就把java虛擬機器棧和本地方法棧合二為一。

(3)本地方法棧也會丟擲StackOverFlowError和OutOfMemoryError。

4、Java堆:即堆記憶體(執行緒共享)

(1)堆是java虛擬機器所管理的記憶體區域中最大的一塊,java堆是被所有執行緒共享的記憶體區域,在java虛擬機器啟動時建立,堆記憶體的唯一目的就是存放物件例項幾乎所有的物件例項都在堆記憶體分配。

(2)堆是GC管理的主要區域,從垃圾回收的角度看,由於現在的垃圾收集器都是採用的分代收集演算法,因此java堆還可以初步細分為新生代和老年代

(3)Java虛擬機器規定,堆可以處於物理上不連續的記憶體空間中,只要邏輯上連續的即可。在實現上既可以是固定的,也可以是可動態擴充套件的。如果在堆記憶體沒有完成例項分配,並且堆大小也無法擴充套件,就會丟擲OutOfMemoryError異常。

5、方法區:(執行緒共享)

(1)用於儲存已被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼等資料。

(2)Sun HotSpot虛擬機器把方法區叫做永久代(Permanent Generation),方法區中最終要的部分是執行時常量池。

6、執行時常量池:

(1)執行時常量池是方法區的一部分,自然受到方法區記憶體的限制,當常量池無法再申請到記憶體時就會丟擲OutOfMemoryError異常。 

三、Java物件在記憶體中的狀態:

可達的/可觸及的:

  Java物件被建立後,如果被一個或多個變數引用,那就是可達的。即從根節點可以觸及到這個物件。

  其實就是從根節點掃描,只要這個物件在引用鏈中,那就是可觸及的。

可恢復的:

  Java物件不再被任何變數引用就進入了可恢復狀態。

  在回收該物件之前,該物件的finalize()方法進行資源清理。如果在finalize()方法中重新讓變數引用該物件,則該物件再次變為可達狀態,否則該物件進入不可達狀態

不可達的:

  Java物件不被任何變數引用,且系統在呼叫物件的finalize()方法後依然沒有使該物件變成可達狀態(該物件依然沒有被變數引用),那麼該物件將變成不可達狀態。

  當Java物件處於不可達狀態時,系統才會真正回收該物件所佔有的資源。

四、判斷物件死亡的兩種常用演算法:

    當物件不被引用的時候,這個物件就是死亡的,等待GC進行回收。

1、引用計數演算法

概念:

  給物件中新增一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時,計數器值就減1;任何時刻計數器為0的物件就是不可能再被使用的。

但是:

  主流的java虛擬機器並沒有選用引用計數演算法來管理記憶體,其中最主要的原因是:它很難解決物件之間相互迴圈引用的問題

優點:

  演算法的實現簡單,判定效率也高,大部分情況下是一個不錯的演算法。很多地方應用到它

缺點:

引用和去引用伴隨加法和減法,影響效能

致命的缺陷:對於迴圈引用的物件無法進行回收

2、根搜尋演算法(jvm採用的演算法)

概念:

  設立若干種根物件,當任何一個根物件(GC Root)到某一個物件均不可達時,則認為這個物件是可以被回收的

注:這裡提到,設立若干種根物件,當任何一個根物件到某一個物件均不可達時,則認為這個物件是可以被回收的。我們在後面介紹標記-清理演算法/標記整理演算法時,也會一直強調從根節點開始,對所有可達物件做一次標記,那什麼叫做可達呢?

可達性分析:

  從根(GC Roots)的物件作為起始點,開始向下搜尋,搜尋所走過的路徑稱為“引用鏈”,當一個物件到GC Roots沒有任何引用鏈相連(用圖論的概念來講,就是從GC Roots到這個物件不可達)時,則證明此物件是不可用的。


如上圖所示,ObjectD和ObjectE是互相關聯的,但是由於GC roots到這兩個物件不可達,所以最終D和E還是會被當做GC的物件,上圖若是採用引用計數法,則A-E五個物件都不會被回收。

根(GC Roots):

說到GC roots(GC根),在JAVA語言中,可以當做GC roots的物件有以下幾種:

1、(棧幀中的本地變量表)中引用的物件

2、方法區中的靜態成員。

3、方法區中的常量引用的物件(全域性變數)

4、本地方法棧中JNI(一般說的Native方法)引用的物件。

注:第一和第四種都是指的方法的本地變量表,第二種表達的意思比較清晰,第三種主要指的是宣告為final的常量值。

在根搜尋演算法的基礎上,現代虛擬機器的實現當中,垃圾蒐集的演算法主要有三種,分別是標記-清除演算法複製演算法標記-整理演算法。這三種演算法都擴充了根搜尋演算法,不過它們理解起來還是非常好理解的。

五、垃圾回收演算法:

1、標記-清除演算法:

概念:

標記階段:先通過根節點,標記所有從根節點開始的可達物件。因此,未被標記的物件就是未被引用的垃圾物件;

清除階段:清除所有未被標記的物件。

缺點:

標記和清除的過程效率不高(標記和清除都需要從頭遍歷到尾)

標記清除後會產生大量不連續的碎片

2、複製演算法:(新生代的GC)

概念:

  將原有的記憶體空間分為兩塊,每次只使用其中一塊,在垃圾回收時,將正在使用的記憶體中的存活物件複製到未使用的記憶體塊中,然後清除正在使用的記憶體塊中的所有物件。

優點:

這樣使得每次都是對整個半區進行回收,記憶體分配時也就不用考慮記憶體碎片等情況

只要移動堆頂指標,按順序分配記憶體即可,實現簡單,執行效率高

缺點:空間的浪費

  從以上描述不難看出,複製演算法要想使用,最起碼物件的存活率要非常低才行。

  現在的商業虛擬機器都採用這種收集演算法來回收新生代,新生代中的物件98%都是“朝生夕死”的,所以並不需要按照1:1的比例來劃分記憶體空間,而是將記憶體分為一塊比較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor。當回收時,將Eden和Survivor中還存活著的物件一次性地複製到另外一塊Survivor空間上,最後清理掉Eden和剛才用過的Survivor空間。HotSpot虛擬機器預設Eden和Survivor的大小比例是8:1,也就是說,每次新生代中可用記憶體空間為整個新生代容量的90%(80%+10%),只有10%的空間會被浪費。

當然,98%的物件可回收只是一般場景下的資料,我們沒有辦法保證每次回收都只有不多於10%的物件存活,當Survivor空間不夠用時,需要依賴於老年代進行分配擔保,所以大物件直接進入老年代。整個過程如下圖所示:


3、標記-整理演算法:(老年代的GC)

    複製演算法在物件存活率高的時候要進行較多的複製操作,效率將會降低,所以在老年代中一般不能直接選用這種演算法。

概念:

標記階段:先通過根節點,標記所有從根節點開始的可達物件。因此,未被標記的物件就是未被引用的垃圾物件

整理階段:將將所有的存活物件壓縮到記憶體的一端;之後,清理邊界外所有的空間

優點:

  不會產生記憶體碎片。

缺點:

  在標記的基礎之上還需要進行物件的移動,成本相對較高,效率也不高。

它們的區別如下:(>表示前者要優於後者,=表示兩者效果一樣)

(1)效率:複製演算法 > 標記/整理演算法 > 標記/清除演算法(此處的效率只是簡單的對比時間複雜度,實際情況不一定如此)。

(2)記憶體整齊度:複製演算法=標記/整理演算法>標記/清除演算法。

(3)記憶體利用率:標記/整理演算法=標記/清除演算法>複製演算法。

注1:標記-整理演算法不僅可以彌補標記-清除演算法當中,記憶體區域分散的缺點,也消除了複製演算法當中,記憶體減半的高額代價。

注2:可以看到標記/清除演算法是比較落後的演算法了,但是後兩種演算法卻是在此基礎上建立的。

注3:時間與空間不可兼得。

4、分代收集演算法:

  當前商業虛擬機器的GC都是採用的“分代收集演算法”,這並不是什麼新的思想,只是根據物件的存活週期的不同將記憶體劃分為幾塊兒。一般是把Java堆分為新生代和老年代:短命物件歸為新生代,長命物件歸為老年代

  • 存活率低:少量物件存活,適合複製演算法:在新生代中,每次GC時都發現有大批物件死去,只有少量存活(新生代中98%的物件都是“朝生夕死”),那就選用複製演算法,只需要付出少量存活物件的複製成本就可以完成GC。
  • 存活率高:大量物件存活,適合用標記-清理/標記-整理:在老年代中,因為物件存活率高、沒有額外空間對他進行分配擔保,就必須使用“標記-清理”/“標記-整理”演算法進行GC。

注:老年代的物件中,有一小部分是因為在新生代回收時,老年代做擔保,進來的物件;絕大部分物件是因為很多次GC都沒有被回收掉而進入老年代

六、垃圾收集器:

如果說收集演算法時記憶體回收的方法論,那麼垃圾收集器就是記憶體回收的具體實現。

雖然我們在對各種收集器進行比較,但並非為了挑出一個最好的收集器。因為直到現在位置還沒有最好的收集器出現,更加沒有萬能的收集器,所以我們選擇的只是對具體應用最合適的收集器

1、Serial收集器:(序列收集器)

這個收集器是一個單執行緒的收集器,但它的單執行緒的意義並不僅僅說明它只會使用一個CPU或一條收集執行緒去完成垃圾收集工作,更重要的是在它進行垃圾收集時,必須暫停其他所有的工作執行緒(Stop-The-World:將使用者正常工作的執行緒全部暫停掉),直到它收集結束。收集器的執行過程如下圖所示:


上圖中:

  • 新生代採用複製演算法,Stop-The-World
  • 老年代採用標記-整理演算法,Stop-The-World

當它進行GC工作的時候,雖然會造成Stop-The-World,但它存在有存在的原因:正是因為它的簡單而高效(與其他收集器的單執行緒比),對於限定單個CPU的環境來說,沒有執行緒互動的開銷,專心做GC,自然可以獲得最高的單執行緒手機效率。所以Serial收集器對於執行在client模式下是一個很好的選擇(它依然是虛擬機器執行在client模式下的預設新生代收集器)。

2、ParNew收集器:Serial收集器的多執行緒版本(使用多條執行緒進行GC)

  ParNew收集器是Serial收集器的多執行緒版本。

  它是執行在server模式下的首選新生代收集器,除了Serial收集器外,目前只有它能與CMS收集器配合工作。CMS收集器是一個被認為具有劃時代意義的併發收集器,因此如果有一個垃圾收集器能和它一起搭配使用讓其更加完美,那這個收集器必然也是一個不可或缺的部分了。收集器的執行過程如下圖所示:


上圖中:

  • 新生代採用複製演算法,Stop-The-World
  • 老年代採用標記-整理演算法,Stop-The-World

3、ParNew Scanvenge收集器

  類似ParNew,但更加關注吞吐量目標是:達到一可控制吞吐量的收集器。

停頓時間和吞吐量不可能同時調優。我們一方買希望停頓時間少,另外一方面希望吞吐量高,其實這是矛盾的。因為:在GC的時候,垃圾回收的工作總量是不變的,如果將停頓時間減少,那頻率就會提高;既然頻率提高了,說明就會頻繁的進行GC,那吞吐量就會減少,效能就會降低。

吞吐量:CPU用於使用者程式碼的時間/CPU總消耗時間的比值,即=執行使用者程式碼的時間/(執行使用者程式碼時間+垃圾收集時間)。比如,虛擬機器總共運行了100分鐘,其中垃圾收集花掉1分鐘,那吞吐量就是99%。

4、G1收集器:

  是當今收集器發展的最前言成果之一,知道jdk1.7,sun公司才認為它達到了足夠成熟的商用程度。

優點:

  它最大的優點是結合了空間整合,不會產生大量的碎片,也降低了進行gc的頻率。

  二是可以讓使用者明確指定指定停頓時間。(可以指定一個最小時間,超過這個時間,就不會進行回收了)

它有了這麼高效率的原因之一就是:對垃圾回收進行了劃分優先順序的操作,這種有優先順序的區域回收方式保證了它的高效率。

如果你的應用追求停頓,那G1現在已經可以作為一個可嘗試的選擇;如果你的應用追求吞吐量,那G1並不會為你帶來什麼特別的好處。

注:以上所有的收集器當中,當執行GC時,都會stop the world,但是下面的CMS收集器卻不會這樣。

5、CMS收集器:(老年代收集器)

CMS收集器(Concurrent Mark Sweep:併發標記清除)是一種以獲取最短回收停頓時間為目標的收集器。適合應用在網際網路站或者B/S系統的伺服器上,這類應用尤其重視伺服器的響應速度,希望系統停頓時間最短。

CMS收集器執行過程:(著重實現了標記的過程)

(1)初始標記

  根可以直接關聯到的物件

  速度快

(2)併發標記(和使用者執行緒一起)

  主要標記過程,標記全部物件

(3)重新標記

  由於併發標記時,使用者執行緒依然執行,因此在正式清理前,再做修正

(4)併發清除(和使用者執行緒一起)

  基於標記結果,直接清理物件

整個過程如下圖所示:


上圖中,初始標記和重新標記時,需要stop the world。整個過程中耗時最長的是併發標記和併發清除,這兩個過程都可以和使用者執行緒一起工作。

優點:

  併發收集,低停頓

缺點:

(1)導致使用者的執行速度降低。

(2)無法處理浮動垃圾。因為它採用的是標記-清除演算法。有可能有些垃圾在標記之後,需要等到下一次GC才會被回收。如果CMS執行期間無法滿足程式需要,那麼就會臨時啟用Serial Old收集器來重新進行老年代的手機。

(3)由於採用的是標記-清除演算法,那麼就會產生大量的碎片。往往會出現老年代還有很大的空間剩餘,但是無法找到足夠大的連續空間來分配當前物件,不得不提前觸發一次full GC

疑問:既然標記-清除演算法會造成記憶體空間的碎片化,CMS收集器為什麼使用標記清除演算法而不是使用標記整理演算法:

答案:

  CMS收集器更加關注停頓,它在做GC的時候是和使用者執行緒一起工作的(併發執行),如果使用標記整理演算法的話,那麼在清理的時候就會去移動可用物件的記憶體空間,那麼應用程式的執行緒就很有可能找不到應用物件在哪裡

七、Java堆記憶體劃分:

根據物件的存活率(年齡),Java對記憶體劃分為3種:新生代、老年代、永久代:

1、新生代:

比如我們在方法中去new一個物件,那這方法呼叫完畢後,物件就會被回收,這就是一個典型的新生代物件。 

現在的商業虛擬機器都採用這種收集演算法來回收新生代,新生代中的物件98%都是“朝生夕死”的,所以並不需要按照1:1的比例來劃分記憶體空間,而是將記憶體分為一塊比較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor。當回收時,將Eden和Survivor中還存活著的物件一次性地複製到另外一塊Survivor空間上,最後清理掉Eden和剛才用過的Survivor空間。HotSpot虛擬機器預設Eden和Survivor的大小比例是8:1,也就是說,每次新生代中可用記憶體空間為整個新生代容量的90%(80%+10%),只有10%的空間會被浪費。

當然,98%的物件可回收只是一般場景下的資料,我們沒有辦法保證每次回收都只有不多於10%的物件存活,當Survivor空間不夠用時,需要依賴於老年代進行分配擔保,所以大物件直接進入老年代。同時,長期存活的物件將進入老年代虛擬機器給每個物件定義一個年齡計數器)。

來看下面這張圖:


Minor GC和Full GC:

GC分為兩種:Minor GC和Full GC

Minor GC:

  Minor GC是發生在新生代中的垃圾收集動作,採用的是複製演算法。

物件在Eden和From區出生後,在經過一次Minor GC後,如果物件還存活,並且能夠被to區所容納,那麼在使用複製演算法時這些存活物件就會被複制到to區域,然後清理掉Eden區和from區,並將這些物件的年齡設定為1,以後物件在Survivor區每熬過一次Minor GC,就將物件的年齡+1,當物件的年齡達到某個值時(預設是15歲,可以通過引數 --XX:MaxTenuringThreshold設定),這些物件就會成為老年代。

但這也是不一定的,對於一些較大的物件(即需要分配一塊較大的連續記憶體空間)則是直接進入老年代

Full GC:

  Full GC是發生在老年代的垃圾收集動作,採用的是標記-清除/整理演算法。

老年代裡的物件幾乎都是在Survivor區熬過來的,不會那麼容易死掉。因此Full GC發生的次數不會有Minor GC那麼頻繁,並且做一次Full GC要比做一次Minor GC的時間要長。

另外,如果採用的是標記-清除演算法的話會產生許多碎片,此後如果需要為較大的物件分配記憶體空間時,若無法找到足夠的連續的記憶體空間,就會提前觸發一次GC。

2、老年代:

    在新生代中經歷了N次垃圾回收後仍然存活的物件就會被放到老年代中。而且大物件直接進入老年代。

3、永久代:

    即方法區。

八、類載入機制:

    虛擬機器把描述類的資料從Class檔案載入到記憶體,並對資料進行校驗、轉換解析和初始化,最終形成可以被虛擬機器直接使用的Java型別,這就是虛擬機器的類載入機制。

類載入的過程:

    包括載入、連結(含驗證、準備、解析)、初始化

如下圖所示:


1、載入:

  類載入指的是將類的class檔案讀入記憶體,併為之建立一個java.lang.Class物件,作為方法區這個類的資料訪問的入口

也就是說,當程式中使用任何類時,系統都會為之建立一個java.lang.Class物件。具體包括以下三個部分:

(1)通過類的全名產生對應類的二進位制資料流。(根據early load原理,如果沒找到對應的類檔案,只有在類實際使用時才會丟擲錯誤)

(2)分析並將這些二進位制資料流轉換為方法區方法區特定的資料結構

(3)建立對應類的java.lang.Class物件,作為方法區的入口(有了對應的Class物件,並不意味著這個類已經完成了載入連結)

通過使用不同的類載入器,可以從不同來源載入類的二進位制資料,通常有如下幾種來源:

(1)從本地檔案系統載入class檔案,這是絕大部分程式的載入方式

(2)從jar包中載入class檔案,這種方式也很常見,例如jdbc程式設計時用到的資料庫驅動類就是放在jar包中,jvm可以從jar檔案中直接載入該class檔案

(3)通過網路載入class檔案

(4)把一個Java原始檔動態編譯、並執行載入

2、連結:

    連結指的是將Java類的二進位制檔案合併到jvm的執行狀態之中的過程。在連結之前,這個類必須被成功載入。

類的連結包括驗證、準備、解析這三步。具體描述如下:

2.1  驗證:

    驗證是用來確保Java類的二進位制表示在結構上是否完全正確(如檔案格式、語法語義等)。如果驗證過程出錯的話,會丟擲java.lang.VertifyError錯誤。

主要驗證以下內容:

  • 檔案格式驗證
  • 元資料驗證:語義驗證
  • 位元組碼驗證

2.2  準備:

  準備過程則是建立Java類中的靜態域(static修飾的內容),並將這些域的值設定為預設值,同時在方法區中分配記憶體空間。準備過程並不會執行程式碼。

注意這裡是做預設初始化,不是做顯式初始化。例如:

public static int value = 12;

上面的程式碼中,在準備階段,會給value的值設定為0(預設初始化)。在後面的初始化階段才會給value的值設定為12(顯式初始化)。

2.3  解析:

  解析的過程就是確保這些被引用的類能被正確的找到(將符號引用替換為直接引用)。解析的過程可能會導致其它的Java類被載入。

3、初始化:

  初始化階段是類載入過程的最後一步。到了初始化階段,才真正執行類中定義的Java程式程式碼(或者說是位元組碼)。

在以下幾種情況中,會執行初始化過程:

(1)建立類的例項

(2)訪問類或介面的靜態變數(特例:如果是用static final修飾的常量,那就不會對類進行顯式初始化。static final 修改的變數則會做顯式初始化

(3)呼叫類的靜態方法

(4)反射(Class.forName(packagename.className))

(5)初始化類的子類。注:子類初始化問題:滿足主動呼叫,即父類訪問子類中的靜態變數、方法,子類才會初始化;否則僅父類初始化。

(6)java虛擬機器啟動時被標明為啟動類的類

程式碼舉例1:

我們對上面的第(5)種情況做一個程式碼舉例。

(1)Father.java:

複製程式碼
1 public class Father {
2 
3     static {
4         System.out.println("*******father init");
5     }
6     public static int a = 1;
7 }
複製程式碼

(2)Son.java:

1 public class Son extends Father {
2     static {
3         System.out.println("*******son init");
4     }
5     public static int b = 2;
6 }

(3)JavaTest.java:

1 public class JavaTest {
2     public static void main(String[] args) {
3         System.out.println(Son.a);
4     }
5 }

上面的測試類中,雖然用上了Son這個類,但是並沒有呼叫子類裡的成員,所以並不會對子類進行初始化。於是執行效果是:


如果把JavaTest.java改成下面這個樣子:

            
           

相關推薦

Java虛擬機器----JVM常見問題總結

【宣告】  歡迎轉載,但請保留文章原始出處→_→  【正文】 宣告:本文只是做一個總結,有關jvm的詳細知識可以參考本人之前的系列文章,尤其是那篇:Java虛擬機器詳解04----GC演算法和種類。那篇文章和本文是面試時的重點。 面試必問關鍵詞:JVM垃圾回

Java虛擬——JVM常見問題總結

can 語言 嘗試 意思 是把 fff rom com serial 【正文】 聲明:本文只是做一個總結,有關jvm的詳細知識可以參考之前的系列文章,尤其是那篇:Java虛擬機詳解04—-GC算法和種類。那篇文章和本文是面試時的重點。 面試必問關鍵詞:JVM垃圾回收、類加載

Java虛擬機器----JVM記憶體結構

http://www.cnblogs.com/smyhvae/p/4748392.htm   主要內容如下: JVM啟動流程 JVM基本結構 記憶體模型 編譯和解釋執行的概念   一、JVM啟動流程: JVM啟動時,是由java命令/javaw命令來啟

JVM內幕:Java虛擬機器

這篇文章解釋了Java 虛擬機器(JVM)的內部架構。下圖顯示了遵守 Java SE 7 規範的典型的 JVM 核心內部元件。 上圖顯示的元件分兩個章節解釋。第一章討論針對每個執行緒建立的元件,第二章節討論了執行緒無關元件。 執行緒 JVM 系統執行緒

Java虛擬機器03----常用JVM配置引數

本文主要內容: Trace跟蹤引數 堆的分配引數 棧的分配引數 零、在IDE的後臺列印GC日誌: 既然學習JVM,閱讀GC日誌是處理Java虛擬機器記憶體問題的基礎技能,它只是一些人為確定的規則,沒有太多技術含量。 既然如此,那麼在I

Java虛擬機器----常用JVM配置引數

原文地址:http://www.cnblogs.com/smyhvae/p/4736162.html 本文主要內容: Trace跟蹤引數堆的分配引數棧的分配引數 零、在IDE的後臺列印GC日誌: 既然學習JVM,閱讀GC日誌是處理Java虛擬機器記憶體問題的

Java虛擬機器(五)------JVM引數(持續更新)

  JVM引數有很多,其實我們直接使用預設的JVM引數,不去修改都可以滿足大多數情況。但是如果你想在有限的硬體資源下,部署的系統達到最大的執行效率,那麼進行相關的JVM引數設定是必不可少的。下面我們就來對這些JVM引數進行詳細的介紹。   JVM引數主要分為以下三種(可以根據書寫形式來區分): 1、標準引

Java虛擬機器04----GC演算法和種類【重要】

【宣告】  歡迎轉載,但請保留文章原始出處→_→  本文主要內容: GC的概念 GC演算法     引用計數法(無法解決迴圈引用的問題,不被java採納)       根搜尋演算法       現代虛擬機

Java虛擬機器----GC演算法和種類【重要】

轉載自:http://www.cnblogs.com/smyhvae/p/4744233.html 本文主要內容: GC的概念GC演算法    引用計數法(無法解決迴圈引用的問題,不被java採納)       根搜尋演算法       現代虛擬機器中的垃圾蒐集演算法:

Java虛擬機器(一)------簡介

  本系列部落格我們將以當前預設的主流虛擬機器HotSpot 為例,詳細介紹 Java虛擬機器。以 JDK1.7 為主,同時介紹與 JDK1.8 的不同之處,通過Oracle官網以及各種文獻進行整理,並加以驗證,力求保證這塊知識的正確性,完整性。   以下是本系列部落格參考的相關文件:   ①、JDK1.

Java虛擬機器(二)------執行時記憶體結構

  首先通過一張圖瞭解 Java程式的執行流程:      我們編寫好的Java原始碼程式,通過Java編譯器javac編譯成Java虛擬機器識別的class檔案(位元組碼檔案),然後由 JVM 中的類載入器載入編譯生成的位元組碼檔案,載入完畢之後再由 JVM 執行引擎去執行。在載入完畢到執行過程中,J

Java虛擬機器(三)------垃圾回收

  如果對C++這門語言熟悉的人,再來看Java,就會發現這兩者對垃圾(記憶體)回收的策略有很大的不同。   C++:垃圾回收很重要,我們必須要自己來回收!!!   Java:垃圾回收很重要,我們必須交給系統來幫我們完成!!!   我想這也能看出這兩門語言設計者的心態吧,總之,Java和C++之間有一堵

Java虛擬機器(四)------垃圾收集器

  上一篇部落格我們介紹了Java虛擬機器垃圾回收,介紹了幾種常用的垃圾回收演算法,包括標記-清除,標記整理,複製等,這些演算法我們可以看做是記憶體回收的理論方法,那麼在Java虛擬機器中,由誰來具體實現這些方法呢?   沒錯,就是本篇部落格介紹的內容——垃圾收集器。 1、垃圾收集

Java虛擬機器(六)------記憶體分配

  我們說Java是自動進行記憶體管理的,所謂自動化就是,不需要程式設計師操心,Java會自動進行記憶體分配和記憶體回收這兩方面。   前面我們介紹過如何通過垃圾回收器來回收記憶體,那麼本篇部落格我們來聊聊如何進行分配記憶體。   物件的記憶體分配,往大方向上講,就是堆上進行分配(但也有可能經過JIT編譯

Java虛擬機器(七)------虛擬機器監控和分析工具(1)——命令列

  通過前面的幾篇部落格,我們介紹了Java虛擬機器的記憶體分配以及記憶體回收等理論知識,瞭解這些知識對於我們在實際生產環境中提高系統的執行效率是有很大的幫助的。但是話又說回來,在實際生產環境中,線上專案正在執行,我們怎麼去監控虛擬機器執行效率?又或者線上專案發生了OOM,異常堆疊資訊,我們又怎麼去抓取,然後

Java虛擬機器(八)------虛擬機器監控和分析工具(2)——視覺化

  上篇部落格我們介紹了虛擬機器監控和分析命令列工具,由於其不夠直觀,不是很容易排查問題,那麼本篇部落格我們就來介紹幾個視覺化工具。 1、JConsole   JConsole(Java Monitoring and Management Console)是一款基於 JMX 的視覺化監視和管理的工具。它管

Java虛擬機器(十)------類載入過程

  在上一篇文章中,我們詳細的介紹了Java類檔案結構,那麼這些Class檔案是如何被載入到記憶體,由虛擬機器來直接使用的呢?這就是本篇部落格將要介紹的——類載入過程。 1、類的生命週期   類從被載入到虛擬機器記憶體開始,到卸載出記憶體為止,其宣告週期流程如下:      上

Java虛擬機器(十一)------雙親委派模型

  在上一篇部落格,我們介紹了類載入過程,包括5個階段,分別是“載入”,“驗證”,“準備”,“解析”,“初始化”,如下圖所示:        本篇部落格,我們來介紹Java虛

我理解的JVM-----JavaJVM虛擬機器

很多大佬們在推薦深入理解jvm這本書,奈何時間不夠用來部落格上簡單的取取經記錄一下,再此立個flag,明天去圖書館還書的時候一定一定要泡一整天,牆裂建議學校開個通宵自習室!!!我去買咖啡@[email protected] 1、 什麼是JVM?   JVM是Ja

JavaJVM虛擬機器

1、 什麼是JVM?   JVM是Java Virtual Machine(Java虛擬機器)的縮寫,JVM是一種用於計算裝置的規範,它是一個虛構出來的計算機,是通過在實際的計算機上模擬模擬各種計算機功能來實現的。Java虛擬機器包括一套位元組碼指令集、一組暫存器、一個棧、