亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

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

2019-11-14 15:38:06
字體:
來源:轉載
供稿:網友

 【聲明】 

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

生命壹號:http://www.49028c.com/smyhvae/

文章來源:http://www.49028c.com/smyhvae/p/4810168.html

聯系方式:smyhvae@163.com 

 

【正文】

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

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

 

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

 

一、Java引用的四種狀態:

強引用:

  用的最廣。我們平時寫代碼時,new一個Object存放在堆內存,然后用一個引用指向它,這就是強引用。

  如果一個對象具有強引用,那垃圾回收器絕不會回收它。當內存空間不足,Java虛擬機寧愿拋出OutOfMemoryError錯誤,使程序異常終止,也不會靠隨意回收具有強引用的對象來解決內存不足的問題。

軟引用:

  如果一個對象只具有軟引用,則內存空間足夠時,垃圾回收器就不會回收它;如果內存空間不足了,就會回收這些對象的內存。(備注:如果內存不足,隨時有可能被回收。)

  只要垃圾回收器沒有回收它,該對象就可以被程序使用。軟引用可用來實現內存敏感的高速緩存。

弱引用:

  弱引用與軟引用的區別在于:只具有弱引用的對象擁有更短暫的生命周期。

  每次執行GC的時候,一旦發現了只具有弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存。不過,由于垃圾回收器是一個優先級很低的線程,因此不一定會很快發現那些只具有弱引用的對象

虛引用:

  “虛引用”顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用并不會決定對象的生命周期。如果一個對象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。

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

注:關于各種引用的詳解,可以參考這篇博客:

http://zhangjunhd.blog.51cto.com/113473/53092

 

二、Java中的內存劃分:

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

1dca5ecf-0959-46f9-98c0-f92acc31005f

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

  • 程序計數器:保證線程切換后能恢復到原來的執行位置
  • 虛擬機棧:(棧內存)為虛擬機執行java方法服務:方法被調用時創建棧幀-->局部變量表->局部變量、對象引用
  • 本地方法棧:為虛擬機執使用到的Native方法服務
  • 堆內存存放所有new出來的東西
  • 方法區存儲被虛擬機加載的類信息、常量、靜態常量、靜態方法等。
  • 運行時常量池(方法區的一部分)

GC對它們的回收:

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

GC回收的主要對象:而Java堆和方法區則不同,一個接口中的多個實現類需要的內存可能不同,一個方法中的多個分支需要的內存也可能不一樣,我們只有在程序處于運行期間時才能知道會創建哪些對象,這部分內存的分配和回收都是動態的,GC關注的也是這部分內存,后面的文章中如果涉及到“內存”分配與回收也僅指著一部分內存。

 

1、程序計數器:(線程私有)

每個線程擁有一個程序計數器,在線程創建時創建,

指向下一條指令的地址

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

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

 

2、Java虛擬機棧:(線程私有)

每個方法被調用的時候都會創建一個棧幀,用于存儲局部變量表、操作棧、動態鏈接、方法出口等信息。局部變量表存放的是:編譯期可知的基本數據類型、對象引用類型。

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

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

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

  (2)虛擬機??梢詣討B擴展,如果擴展到無法申請足夠的內存空間,會出現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虛擬機詳解02----JVM內存結構

 

三、Java對象在內存中的狀態:

可達的/可觸及的:

  Java對象被創建后,如果被一個或多個變量引用,那就是可達的。即從根節點可以觸及到這個對象。

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

可恢復的:

  Java對象不再被任何變量引用就進入了可恢復狀態。

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

不可達的:

  Java對象不被任何變量引用,且系統在調用對象的finalize()方法后依然沒有使該對象變成可達狀態(該對象依然沒有被變量引用),那么該對象將變成不可達狀態。

  當Java對象處于不可達狀態時,系統才會真正回收該對象所占有的資源。

 

四、判斷對象死亡的兩種常用算法:

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

1、引用計數算法

概念:

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

但是:

  主流的java虛擬機并沒有選用引用計數算法來管理內存,其中最主要的原因是:它很難解決對象之間相互循環引用的問題。

優點:

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

缺點:

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

致命的缺陷:對于循環引用的對象無法進行回收

2、根搜索算法(jvm采用的算法)

概念:

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

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

可達性分析:

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

83fbdb3a-6da2-4307-9cc5-63663080c2ce

如上圖所示,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空間不夠用時,需要依賴于老年代進行分配擔保,所以大對象直接進入老年代。整個過程如下圖所示:

37e28257-008e-40ad-a07f-1a45a38d4be2

 

 

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

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

概念:

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

整理階段:將所有的存活對象壓縮到內存的一端;之后,清理邊界外所有的空間

優點:

  不會產生內存碎片。

缺點:

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

 

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

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

(2)內存整齊度:復制算法=標記/整理算法>標記/清除算法。

(3)內存利用率:標記/整理算法=標記/清除算法>復制算法。

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

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

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

 

4、分代收集算法:

  當前商業虛擬機的GC都是采用的“分代收集算法”,這并不是什么新的思想,只是根據對象的存活周期的不同將內存劃分為幾塊兒。一般是把Java堆分為新生代和老年代:短命對象歸為新生代,長命對象歸為老年代。

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

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

 

六、垃圾收集器:

如果說收集算法時內存回收的方法論,那么垃圾收集器就是內存回收的具體實現。

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

1、Serial收集器:(串行收集器)

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

9012f0bf-fe1b-4e19-a7ec-b5c0e6fb7174

上圖中:

  • 新生代采用復制算法,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收集器是一個被認為具有劃時代意義的并發收集器,因此如果有一個垃圾收集器能和它一起搭配使用讓其更加完美,那這個收集器必然也是一個不可或缺的部分了。收集器的運行過程如下圖所示:

852249d2-1be3-4126-8cb7-24909c404e17

上圖中:

  • 新生代采用復制算法,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)并發清除(和用戶線程一起)

  基于標記結果,直接清理對象

整個過程如下圖所示:

89af7bbc-5331-4c62-ab7e-18e93350f826

上圖中,初始標記和重新標記時,需要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空間不夠用時,需要依賴于老年代進行分配擔保,所以大對象直接進入老年代。同時,長期存活的對象將進入老年代虛擬機給每個對象定義一個年齡計數器)。

來看下面這張圖:

ef34e1c5-c6e1-4108-a939-87c9f5de0fff

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.);5     }6     public static int a = 1;7 }

 

(2)Son.java:

 

(3)JavaTest.java:

 

上面的測試類中,雖然用上了Son這個類,但是并沒有調用子類里的成員,所以并不會對子類進行初始化。于是運行效果是:

b6de0ab4-1502-46dd-9193-578d1c83ca20

 

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

 

運行效果:

b4757ac3-7476-49ff-84a4-659dca412324

 

 

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

JavaTest.java:

 

運行效果:

e88cc820-e004-4764-9ab3-403d5d660a8a

 

 

代碼舉例2:

我們對上面的第(2)種情況做一個代碼舉例。即:如果是用static final修飾的常量,則不會進行顯式初始化。代碼舉例如下:

(1)Father.java:

 

(2)Son.java:

 

這里面的變量c是一個靜態常量。

(3)JavaTest.java:

 

d04e2783-18db-41c4-958d-e9bbee011854

上面的運行效果顯示,由于c是final static修飾的靜態常量,所以根本就沒有調用靜態代碼塊里面的內容,也就是說,沒有對這個類進行顯式初始化

現在,保持Father.java的代碼不變。將Son.java代碼做如下修改:

 

JavaTest.java:

 

運行效果如下:

235bc0d9-0b08-436c-9b6d-380702f4a8c7

 

 

代碼舉例3:(很容易出錯)

我們來下面這段代碼的運行結果是什么:

 

運行結果:

7281067e-8c73-4929-b23a-5b0992f2deff

之所以有這樣的運行結果,這里涉及到類加載的順序:

(1)在加載階段,加載類的信息

(2)在鏈接的準備階段給instance、a、b做默認初始化并分配空間,此時a和b的值都為0

(3)在初始化階段,執行構造方法,此時a和b的值都為1

(4)在初始化階段,給靜態變量做顯式初始化,此時b的值為0

 

我們改一下代碼的執行順序,改成下面這個樣子:

 

運行效果是:

e71a9e60-be2c-4072-9602-3fa591e0b940

之所以有這樣的運行結果,這里涉及到類加載的順序:

(1)在加載階段,加載類的信息

(2)在鏈接的準備階段給instance、a、b做默認初始化并分配空間,此時a和b的值都為0

(3)在初始化階段,給靜態變量做顯式初始化,此時b的值仍為0

(4)在初始化階段,執行構造方法,此時a和b的值都為1

 

注意,這里涉及到另外一個類似的知識點不要搞混了。知識點如下。

知識點:類的初始化過程(重要)

Student s = new Student();在內存中做了哪些事情?

 

【思維導圖文件下載地址】

2015-09-12-Java虛擬機詳解----JVM常見問題總結

 


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
成人精品一区二区三区电影黑人| 成人a免费视频| 亚洲自拍av在线| 亚洲自拍偷拍色片视频| 欧美老肥婆性猛交视频| 亚洲午夜国产成人av电影男同| 欧美激情成人在线视频| 欧美日韩国产综合视频在线观看中文| 91国产视频在线| 国产一区二区三区直播精品电影| 久久天天躁狠狠躁夜夜av| 日本道色综合久久影院| 亚洲第一页中文字幕| 久久97精品久久久久久久不卡| 欧美在线观看www| 欧美亚洲激情在线| 黑人巨大精品欧美一区二区| 午夜伦理精品一区| 亚洲欧洲自拍偷拍| 国产伦精品一区二区三区精品视频| 97成人在线视频| 日韩大片免费观看视频播放| 在线视频欧美日韩| 亚洲国产高清福利视频| 日韩av在线网址| 亚州欧美日韩中文视频| 4k岛国日韩精品**专区| 91香蕉国产在线观看| 热99精品只有里视频精品| 国产一区二区三区直播精品电影| 欧美福利在线观看| 久久精品成人欧美大片古装| 欧美日韩国产限制| 91国产美女视频| 成人网页在线免费观看| 国产午夜精品全部视频播放| 久久国产精品视频| 国产成人精品av在线| 1769国内精品视频在线播放| 欧美国产日本在线| 久久夜色精品国产欧美乱| 色老头一区二区三区在线观看| 一区二区福利视频| 亚洲综合精品一区二区| 日韩一区二区三区国产| 国产精品久久久久久中文字| 国产精品扒开腿做爽爽爽视频| 欧美激情视频在线免费观看 欧美视频免费一| 国产综合色香蕉精品| 亚洲成人激情在线观看| 亚洲深夜福利网站| 亚洲天堂成人在线视频| 日韩精品视频在线播放| 欧美精品久久久久久久免费观看| 久久久久久久久久久免费| 久久精品国产欧美亚洲人人爽| 亚洲成色777777在线观看影院| 亚洲精品久久久久中文字幕欢迎你| 91国产在线精品| 亚洲国产精品福利| xxxxx成人.com| 久久亚洲精品毛片| 欧美一级淫片aaaaaaa视频| 日韩69视频在线观看| 日韩电影免费在线观看| 亚洲自拍偷拍色片视频| 亚洲免费av电影| 日韩免费中文字幕| 日韩高清有码在线| 中文字幕日韩综合av| 精品国内自产拍在线观看| 理论片在线不卡免费观看| 91久久精品美女高潮| 亚洲电影免费观看高清完整版在线观看| 日韩中文字幕不卡视频| 亚洲一区999| 成人在线小视频| 欧美精品制服第一页| 一区二区三区四区在线观看视频| 亚洲国产一区二区三区在线观看| 久久久久一本一区二区青青蜜月| 欧美日本国产在线| 一道本无吗dⅴd在线播放一区| 96国产粉嫩美女| 国产美女主播一区| 久久久久久尹人网香蕉| 亚洲国产成人精品一区二区| 91久久久久久久久久久久久| 欧洲午夜精品久久久| 日韩在线免费高清视频| 国产91网红主播在线观看| 精品性高朝久久久久久久| 久久精品人人爽| 欧美午夜丰满在线18影院| 久久伊人91精品综合网站| 亚洲国产成人精品久久久国产成人一区| 色狠狠av一区二区三区香蕉蜜桃| 国产成人综合一区二区三区| 色综合久久精品亚洲国产| 欧美精品videossex88| 国产精品露脸自拍| 日韩成人久久久| 欧美一级视频在线观看| 成人性生交xxxxx网站| 1769国内精品视频在线播放| 欧美成人中文字幕在线| 亚洲影院色在线观看免费| 精品久久久久久中文字幕大豆网| 欧美极品欧美精品欧美视频| 亚洲欧美在线一区| 精品国产一区二区三区四区在线观看| 亚洲男人av电影| 欧美大荫蒂xxx| 色av中文字幕一区| 欧美体内谢she精2性欧美| 欧美巨大黑人极品精男| 精品国产区一区二区三区在线观看| 亚洲自拍偷拍一区| 91精品视频在线| 国产成人一区二区在线| 亚洲激情在线观看视频免费| 国产亚洲精品久久久久久| 亚洲欧美在线播放| 91理论片午午论夜理片久久| 欧美性69xxxx肥| 91亚洲人电影| 亚洲乱码一区av黑人高潮| 在线观看视频99| 亚洲成人999| 中文字幕国产精品| 国产精品成人久久久久| 欧美精品一二区| 国产一区视频在线| 欧美性资源免费| 亚洲91av视频| 午夜免费在线观看精品视频| 欧美在线视频网站| 久久亚洲春色中文字幕| 蜜臀久久99精品久久久久久宅男| 欧美激情亚洲一区| 91美女片黄在线观看游戏| 国外成人在线播放| 黄色精品在线看| 亚洲精品网址在线观看| 黑人巨大精品欧美一区免费视频| 日韩精品极品视频免费观看| 91色琪琪电影亚洲精品久久| 日韩av在线网站| 欧美日韩国产中文字幕| 日产精品99久久久久久| 亚洲a级在线观看| 国产精品久久电影观看| 国产精品一久久香蕉国产线看观看| 欧美大片免费观看| 欧美日韩在线视频首页| 欧美一级bbbbb性bbbb喷潮片| 国产成人亚洲综合| 亚洲色图欧美制服丝袜另类第一页| 国产狼人综合免费视频| 国产免费一区视频观看免费| 欧美理论电影在线播放| 日韩有码在线播放| 亚洲精品福利视频| 精品呦交小u女在线|