JVM內(nèi)存模型&GC垃圾回收機制&類加載機制

整理有誤煩請評論區(qū)提醒,及時改進~

一、JVM內(nèi)存模型:

  • JVM 作用:實現(xiàn)跨平臺的基礎(chǔ),一次編譯,到處運行。
  • JVM生命周期:隨程序開始而創(chuàng)建,結(jié)束而銷毀。



  • 方法區(qū):共享內(nèi)存區(qū)域
    1.存儲已經(jīng)被虛擬機加載的:類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼。
    2.常量池:存放編譯期生成的各種字面量(常量,如:int a = 1; String str = "abc")和符號引用量(類和接口的全限定名、字段名和描述符、方法名描述符),基本數(shù)據(jù)類型(Double、Float浮點數(shù)沒有且沒必要實現(xiàn)常量池技術(shù))的常量池是-128到127之間,在這個范圍中的基本數(shù)據(jù)類的包裝類可以自動拆箱,比較時直接比較數(shù)值大小。注:裝箱:自動將基本數(shù)據(jù)類型轉(zhuǎn)換為包裝器類型;拆箱:自動將包裝器類型轉(zhuǎn)換成基本類型,裝箱過程是通過調(diào)用包裝器的valueOf方法實現(xiàn)的,而拆箱過程是通過調(diào)用包裝器的 xxxValue方法實現(xiàn)的(xxx基本數(shù)據(jù)類型)。編譯期和運行期都可以將常量放入常量池中,內(nèi)存有限,無法申請時拋出OOM異常。
    1.好處:避免頻繁的創(chuàng)建和銷毀對象而影響系統(tǒng)性能、實現(xiàn)對象共享。

  • 堆:是JVM啟動時創(chuàng)建的最大的一塊內(nèi)存區(qū)域(一個進程有且僅有一個Java堆),被線程共享,用于存放「對象實例」和「數(shù)組」,堆內(nèi)存的空間在邏輯上是連續(xù)的,在物理地址上是不連續(xù)的,GC主要活動區(qū)域,同樣也會拋出OutOfMemoryError。
    1.JVM初始分配的堆內(nèi)存由-Xms指定,默認是物理內(nèi)存的1/64;
    2.JVM最大分配的堆內(nèi)存由-Xmx指定,默認是物理內(nèi)存的1/4。
    3.默認空余堆內(nèi)存小于40%時,JVM就會增大堆直到-Xmx的最大限制;
    4.空余堆內(nèi)存大于70%時,JVM會減少堆直到-Xms的最小限制。因此服務(wù)器一般設(shè)置-Xms、-Xmx 相等以避免在每次GC 后調(diào)整堆的大小。

  • Java虛擬機棧:為Java方法(字節(jié)碼指令集)服務(wù)。


    1.線程私有,生命周期同線程;
    2.描述的是Java方法的內(nèi)存模型,每個方法執(zhí)行時都會創(chuàng)建一個棧幀,棧幀存儲:「局部變量表」、「操作數(shù)?!?、「動態(tài)鏈接」、「方法出口」等信息。
    3.方法從調(diào)用到執(zhí)行結(jié)束,對應(yīng)此棧幀在虛擬機棧中入棧到出棧的過程。
    4.「局部變量表」存放方法參數(shù)和方法內(nèi)部定義的局部變量:基本數(shù)據(jù)類型(boolean、byte、char、short、int、float、long、double)、reference類型(對象引用,操作堆上的具體對象,指向堆中句柄池里「到對象實例數(shù)據(jù)的指針」或「到對象類型數(shù)據(jù)的指針」來找到具體對象。注:對象實例數(shù)據(jù):對象中各個實例字段的數(shù)據(jù),對象類型數(shù)據(jù):對象的類型、父類、實現(xiàn)的接口、方法等)、returnAddress類型(指向一條字節(jié)碼指令的地址)。注意:對于成員變量和定義在方法外的對象的引用則存儲在堆中
    5.容易出現(xiàn)的Error:超過棧最大深度:StackOverflowError(堆棧溢出:通過-Xss設(shè)置棧大小、遞歸、死循環(huán)容易造成此類問題)、無法申請足夠內(nèi)存:OutOfMemoryError(內(nèi)存溢出)。
    6.虛擬機內(nèi)存有限,分配某一棧內(nèi)存過大會擠占其他線程空間、GC不涉及虛擬機棧

  • 本地方法棧:為本地Native方法服務(wù)。
    同樣會出現(xiàn)StackOverflowError(堆棧溢出)、OutOfMemoryError(內(nèi)存溢出)異常。

  • 程序計數(shù)器:記錄當(dāng)前程序執(zhí)行到哪一步了
    1.特點:唯一無OutOfMemoryError情況的區(qū)域;每個線程都有自己的計數(shù)器(線程私有);內(nèi)存空間很小可忽略不計,也是運行速度最快的存儲區(qū)域;
    2.前提:一個CPU每次只能處理一個程序,CPU會為每個程序分配時間片,多線程在特定的時間段內(nèi)只會執(zhí)行其中某一個線程中的一條指令。CPU不停的做任務(wù)切換,導(dǎo)致大量的中斷和恢復(fù),為了保證準確的記錄各個線程正在執(zhí)行的當(dāng)前字節(jié)碼指令地址,為每個線程分配獨立的計數(shù)器以避免相互干擾。
    2.作用:任何時間一個線程都只有一個方法在執(zhí)行,也就是所謂的當(dāng)前方法。程序計數(shù)器會存儲當(dāng)前線程正在執(zhí)行的Java方法的JVM指令地址;如果是在執(zhí)行native方法,則是未指定值(undefned)。
    它是程序控制流的指示器,分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)等基礎(chǔ)功能都需要依賴這個計數(shù)器來完成。字節(jié)碼解釋器工作時就是通過改變這個計數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令。

二、垃圾回收機制

※判斷對象是否可回收的機制:
  • 引用計數(shù)法(reference counting):給對象添加引用計數(shù)器,缺點是對象循環(huán)引用則無法回收。JVM沒有使用此算法,而是使用「根搜索算法」GC Roots。


    引用計數(shù)法
  • 可達性分分析(GC Roots):通過一系列名為 ‘GC Roots’ 的對象作為起始點,從這些節(jié)點出發(fā)所走過的路徑稱之為引用鏈,當(dāng)一個對象到 GC Roots 沒有任何引用鏈相連的時候說明對象沒有被引用了。解決了對象循環(huán)引用的問題,即使a和b相互引用,但只要從GC Roots無法到達a或b則都不屬于存活對象。缺點是在多線方程環(huán)境下其他線程可能更新了對象中的引用,導(dǎo)致誤報(將引用設(shè)為null):會讓此次GC無法回收此垃圾對象;導(dǎo)致漏報(將引用設(shè)為未訪問過的對象):錯誤的回收了還在引用的對象,導(dǎo)致Java虛擬機崩潰。
    好在判斷對象真正死亡需要標記兩次,篩選條件是此對象是否有必要執(zhí)行 finalize() 方法。當(dāng)對象沒有覆蓋 finalize() 方法,或者 finalize() 方法已經(jīng)被虛擬機調(diào)用過,虛擬機將這兩種情況都視為“沒有必要執(zhí)行”。如果這個對象被判定為有必要執(zhí)行 finalize() 方法,那么這個對象就會放置在一個叫做 F-Queue 的隊列中,并在稍后由一個由虛擬機自動建立的、低優(yōu)先級的 Finalizer 線程去執(zhí)行它。這里所謂的“執(zhí)行”是指虛擬機會出發(fā)這個方法,并不承諾或等待他運行結(jié)束。finalize() 方法是對象逃脫死亡命運的最后一次機會,稍后 GC 將對 F-Queue 中的對象進行第二次小規(guī)模的標記,如果對象要在 finalize() 中成功拯救自己 —— 只要重新與引用鏈上的任何一個對象建立關(guān)聯(lián)即可。finalize() 方法只會被系統(tǒng)自動調(diào)用一次。


    GC Roots
※主流的垃圾收集算法:
  • 標記- 清除算法(mark-sweep):當(dāng)堆中有效內(nèi)存空間被耗盡時,會停止整個程序然后進行標記(mark)和清除(sweep)工作。
    1.標記(mark)階段:從引用根節(jié)點開始遍歷,標記所有被引用的對象并在對象頭(Object Header)中記錄為可達對象。注:Object Header:包含對象基本信息,如布局、GC狀態(tài)、同步狀態(tài)、hash code(由JVM計算的hash code)、數(shù)組長度(前提是數(shù)組)
    2.清除(sweep)階段:對堆內(nèi)存從頭到尾進行 線性遍歷,如果發(fā)現(xiàn)object header中沒有標記可達對象,則將其回收。
    3.缺點:效率低、GC進行需要停掉整個程序、清理出來的空間不連續(xù),容易產(chǎn)生碎片。

    標記- 清除算法

  • 復(fù)制-清除算法(copying):把內(nèi)存分為 From 和 To 兩個空間,對象一開始只在 From 空間分配,GC時把 From 空間中存活的對象復(fù)制到 To 空間,復(fù)制完成后 To 空間變?yōu)?From 空間,清除之前的 From 空間并變成 To空間,如此反復(fù)。
    優(yōu)點:1.只記錄存活對象,對比「標記-清除算法」少了搜索整個堆內(nèi)存時間,效率相對高。2.少了碎片化內(nèi)存,空間連續(xù)。
    缺點:需要空閑出一半的內(nèi)存,內(nèi)存浪費。


    復(fù)制-清除算法(copying)
  • 標記-壓縮算法(mark-compact):分為標記和壓縮兩個階段,標記階段同「標記-清除算法」,然后遍歷數(shù)次堆進行壓縮,移動對象,把存活的對象重新填裝,使對象緊挨著,避免內(nèi)存碎片。


    標記-壓縮算法(mark-compact)
※JVM的垃圾回收策略:分代回收!合理劃分內(nèi)存區(qū)域并根據(jù)各個區(qū)域定制不同的回收算法。劃分區(qū)域如下圖:

分代回收機制流程:新創(chuàng)建的對象放入Eden區(qū),當(dāng)此區(qū)域的內(nèi)存使用到達閾值時觸發(fā)Young GC,然后將Eden區(qū)存活的對象復(fù)制到From區(qū),下次再觸發(fā)Young GC再將Eden區(qū)和From區(qū)存活的對象放入To區(qū),F(xiàn)rom區(qū)和To區(qū)頻繁的來回復(fù)制,存活次數(shù)過多的對象就會進入Old老年區(qū)。

※GC類型:Scavenge GC和Full GC兩種類型。
  • Scavenge GC:一般當(dāng)新對象生成,并且在Eden申請空間失敗時,就好觸發(fā)Scavenge GC,對Eden區(qū)域進行GC,清除非存活對象,并且把尚且存活的對象移動到Survivor幸存區(qū)。然后整理Survivor的兩個區(qū)(From、To)。
  • Full GC:對整個堆進行整理,包括Young、Tenured(老年代)和Perm(永久代)。Full GC比Scavenge GC要慢,因此應(yīng)該盡可能減少Full GC。有如下原因可能導(dǎo)致Full GC:1.Tenured被寫滿、2.Perm被寫滿、3.System.gc()被顯示調(diào)用、4.上一次GC之后Heap的各域分配策略動態(tài)變化。
※垃圾回收器:串行收集器、并行收集器、并發(fā)收集器,選用哪種JVM會根據(jù)當(dāng)前系統(tǒng)配置進行判斷:
  • 串行處理器:使用單線程處理所有垃圾回收工作,因為無需多線程交互,所以效率比較高,使用-XX:+UseSerialGC打開。
    適用情況:數(shù)據(jù)量比較?。?00M左右);單處理器下并且對響應(yīng)時間無要求的應(yīng)用。
    缺點:只能用于小型應(yīng)用
  • 并行處理器:對年輕代進行并行垃圾回收,因此可以減少垃圾回收時間。一般在多線程多處理器機器上使用。使用-XX:+UseParallelGC.打開。
    適用情況:“對吞吐量有高要求”,多CPU、對應(yīng)用響應(yīng)時間無要求的中、大型應(yīng)用。舉例:后臺處理、科學(xué)計算。
    缺點:應(yīng)用響應(yīng)時間可能較長
  • 并發(fā)處理器:可以保證大部分工作都并發(fā)進行(應(yīng)用不停止),垃圾回收只暫停很少的時間,此收集器適合對響應(yīng)時間要求比較高的中、大規(guī)模應(yīng)用。使用-XX:+UseConcMarkSweepGC打開。
    適用情況:“對響應(yīng)時間有高要求”,多CPU、對應(yīng)用響應(yīng)時間有較高要求的中、大型應(yīng)用。舉例:Web服務(wù)器/應(yīng)用服務(wù)器、電信交換、集成開發(fā)環(huán)境。
※引用的回收:強>軟>弱>虛

強引用:Object obj = new Object(); 創(chuàng)建的,只要強引用在就不回收。
軟引用:SoftReference 類實現(xiàn)軟引用。在系統(tǒng)要發(fā)生內(nèi)存溢出異常之前,將會把這些對象列進回收范圍之中進行二次回收。
弱引用:WeakReference 類實現(xiàn)弱引用。對象只能生存到下一次垃圾收集之前。在垃圾收集器工作時,無論內(nèi)存是否足夠都會回收掉只被弱引用關(guān)聯(lián)的對象。
虛引用:PhantomReference 類實現(xiàn)虛引用。無法通過虛引用獲取一個對象的實例,為對象設(shè)置虛引用唯一目的就是能在這個對象被收集器回收時收到一個系統(tǒng)通知。

※GC優(yōu)化:

GC算法觸發(fā)時,因為線程不安全,除GC所需的線程外,所有線程都進入等待狀態(tài)(native 方法可以繼續(xù)執(zhí)行但也不能跟JVM交互),直到GC任務(wù)完成,這個階段叫做「stop-the-world」!所以GC優(yōu)化盡可能減少GC的觸發(fā)時機。
GC回收的區(qū)域位于「堆」和「方法區(qū)」內(nèi)的對象?!笚!估锏臄?shù)據(jù)在超出作用域后會被JVM自動釋放掉。

三、類加載機制

概述:JVM把.class字節(jié)碼文件加載到虛擬機內(nèi)存中的過程。
  • 我們編寫業(yè)務(wù)的.java文件,Java編譯器將其轉(zhuǎn)化為.class文件,JVM將class文件字節(jié)碼文件加載到內(nèi)存中, 并將這些靜態(tài)數(shù)據(jù)轉(zhuǎn)換成方法區(qū)中的運行時數(shù)據(jù)結(jié)構(gòu),在堆(并不一定在堆中,HotSpot在方法區(qū)中)中生成一個代表這個類的java.lang.Class 對象,作為方法區(qū)類數(shù)據(jù)的訪問入口。
  • 運行時非全部一次性加載,而是按需加載。
  • 一個class類通常只加載一次,之后從jvm的class實例的緩存中獲取而免去文件系統(tǒng)中加載.class文件了(??:JVM在執(zhí)行某段代碼時遇到class A,然后去緩存中找,找不到會去相應(yīng)的class文件找class A的類信息并加載到內(nèi)存中)。


    Java從編碼到執(zhí)行
類加載過程:

類加載主要有三個過程:加載(loading)、連接(linking)、初始化(initializing)。其中連接又分三個步驟:校驗、準備和解析。

  • 加載(Loading):把.class文件轉(zhuǎn)化成靜態(tài)數(shù)據(jù)結(jié)構(gòu),存儲在方法區(qū)內(nèi),并在堆中生成一個便用用戶調(diào)用的Java.lang.Class類型的對象
  • 連接(Linking):
    • 校驗(verification):校驗加載進來的.class文件是否符合標準,不符合標準拒絕加載到內(nèi)存。(這塊主要驗證的是元數(shù)據(jù)和字節(jié)碼,為了確保此class文件是安全的,不會危害虛擬機的自身安全。)
    • 準備(preparation):將.class文件的靜態(tài)變量賦 初始值,初始值都為0,但被final修飾的初始值 = 默認值,即聲明的值。(JDK8之前永久代實現(xiàn)方法區(qū),存放類的元信息、常量池、靜態(tài)變量等。JDK8改為元空間實現(xiàn)方法區(qū),存放類的元信息,而常量池和靜態(tài)變量則存在了堆中)
    • 解析(resolution):把.class文件常量池中用到的符號引用轉(zhuǎn)化成直接內(nèi)存地址,可以訪問到的內(nèi)容。(此階段會驗證符號引用)
  • 初始化(initializing):成功初始化,此階段給靜態(tài)變量賦予初始值,


    類加載過程
雙親委派機制:

解釋:當(dāng)有一個類需要被加載時,首先要判斷這個類是否已經(jīng)被加載到內(nèi)存,判斷加載與否的過程是有順序的,如果有自己定義的類加載器,會先到custom class loader 的cache(緩存)中去找是否已經(jīng)加載,若已加載直接返回結(jié)果,否則到App的cache中查找,如果已經(jīng)存在直接返回,如果不存在,到Extension中查找,存在直接返回,不存在繼續(xù)向父加載器中尋找直到Bootstrap頂層,如果依然沒找到,那就是沒有加載器加載過這個類,需要委派對應(yīng)的加載器來加載,先看看這個類是否在自己的加載范圍內(nèi),如果是直接加載返回結(jié)果,若不是繼續(xù)向下委派,以此類推直到最下級,如果最終也沒能加載,就會直接拋異常 ClassNotFoundException,這就是雙親委派模式。如下圖:


查找順序圖示

雙親委派圖示
類加載順序

父類的靜態(tài)字段 -->父類的靜態(tài)代碼塊 -->子類靜態(tài)字段 -->子類靜態(tài)代碼塊 -->父類成員變量(非靜態(tài)字段) -->父類非靜態(tài)代碼塊 -->父類構(gòu)造器 -->子類成員變量 -->子類非靜態(tài)代碼塊 -->子類構(gòu)造器。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容