日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

JVM的類加載機制和垃圾回收機制

作者:patient-0525 更新時間: 2023-07-22 編程語言

目錄

  • 類加載機制
    • 類加載機制的步驟
      • 加載
      • 驗證
      • 準備
      • 解析
      • 初始化
    • 雙親委派模型
      • 工作原理
      • 雙親委派模型的優點
  • 垃圾回收機制
    • 死亡對象的判斷
      • 可達性分析算法
      • 可達性分析算法的缺點
      • 引用計數算法
      • 循環引用問題
    • 垃圾回收算法
      • 標記-清除算法
      • 復制算法
      • 標記-整理算法
      • 分代算法

類加載機制

對于任意一個類,都需要經歷這樣一個過程:
類的生命周期
這個過程就是類的生命周期,從加載到初始化,屬于類加載過程,其中驗證、準備和解析都屬于連接操作。

類加載機制的步驟

加載

加載是整個類加載中的一個階段,該階段通過類加載器將類的字節碼文件加載到內存中,并創建對應的Class對象。說白了就是找到.class文件。

驗證

驗證加載階段得到的.class文件是否合法(.class文件的格式是固定的)

準備

為.class文件中的類變量(static修飾)分配內存空間并初始化(不會賦值)。
對于public static int num = 10;
準備階段num初始化為0而不是10

解析

將字符串常量池中的符號引用變為直接引用,即初始化常量。

符號引用就相當于字符串在文件內部的位置,不能直接通過符號引用訪問到字符串;直接引用就是絕對位置,即可以通過這個引用直接訪問到字符串。

初始化

執行類的初始化(包括靜態變量的賦值和靜態代碼塊的執行,如果有父類先加載父類)
準備階段和解析階段是可以順序互換的,但是都要在初始化階段之前。

雙親委派模型

雙親委派模型是一種類加載機制,在加載階段執行。
JVM內置了三種類加載器,分別用于加載不同的類:

  • BootStrapClassLoader(引導類加載器):JVM內置的類加載器,負責加載核心類庫
  • ExtensionClassLoader(擴展類加載器):加載Java擴展類庫
  • ApplicationClassLoader(應用程序類加載器):加載應用程序的類
    其中ExtClassLoader是ApplicationClassLoader的父類,BootStrapClassLoader是ExtensionClassLoader的父類。
    程序員也可以自定義類加載器(需要繼承自ApplicationClassLoader)

工作原理

當一個類收到類加載請求,他不會直接去加載,而是把這個請求委托給父類加載器,如果父類加載器還有父類,就繼續向上委托,直到頂層的啟動類加載器。頂層的啟動類加載器沒有父類,就去查找對應范圍內是否存在該類,存在就加載并返回,否則交給子類加載器。如果最底層的類加載器也沒有找到,則拋出ClassNotFoundException(受查異常)
類加載過程

雙親委派模型的優點

  • 避免了重復加載類的問題,保證類的唯一性

    假如有A和B兩個類需要加載,它們的父類是C,在加載A類之前就會先加載C類,此后再去加載B類就不用加載C了;如果直接從頂層類加載器開始加載,就可能出現同一個類被不同的類加載器重復加載的情況,從而得到不同的對象。

  • 保護核心庫的安全性,防止惡意篡改代碼或替換核心庫

    沒有雙親委派模型(先訪問頂層類加載器),就意味著可以自己實現一些核心類,這顯然是不允許的,并且也不能保證安全。

  • 促進了Java程序的模塊化和可擴展,可以自定義類加載器處理需求

雙親委派模型并不是強制的,有些情況下需要打破,例如熱部署等

垃圾回收機制

在傳統的編程語言(如C/C++)中,需要手動分配和釋放內存。這往往會出現內存泄漏和內存溢出的問題,為了盡量避免這樣的問題,JVM引入了垃圾回收機制,自動的釋放無用內存,從而減少開發人員的負擔。

死亡對象的判斷

可達性分析算法

JVM確認是否為垃圾的算法是可達性分析,它的實現思路是:
遍歷所有的GC Roots,找到它們的直接引用,并把這些引用標記為可達;然后遍歷這些可達對象,繼續找到它們的直接引用,并標記為可達;重復以上步驟直到遍歷到沒有引用。最后,沒有標記為可達的對象就是垃圾。(整個走過的路徑就稱為引用鏈)
垃圾確認
在Java語言中,可作為GC Roots的對象包含下面幾種:

  • 虛擬機棧(棧幀中的本地變量表)中引用的對象(虛擬機棧用于存儲方法調用和局部變量);
  • 方法區類靜態屬性引用的對象(方法區用于存儲類信息、常量、靜態變量等);
  • 方法區常量引用的對象;
  • 本地方法棧中 JNI(Native方法)引用的對象(本地方法棧用于支持Java程序與本地代碼(如C、C++等)交互的數據結構)。

可達性分析算法的缺點

  • 判斷是否為垃圾的過程需要遍歷所有的引用鏈,這會耗費大量資源和時間;
  • 在進行可達性分析時,必須暫停用戶所有線程,即STW(Stop The World),這會影響程序性能以及用戶體驗等。

    為什么要觸發STW
    假如在檢查某條引用鏈時,某個其他線程刪除了這條引用鏈上的某個對象,那么從刪除位置開始往后的對象都是不可達的,這時可達性分析就可能出現誤判。因此必須在進行可達性分析前暫停所有的用戶線程。

引用計數算法

不僅僅是Java實現了垃圾回收機制,也有很多其他的語言也實現了垃圾回收機制,如python、php等則是使用引用計數算法來判斷是否為垃圾的。
引用計數算法的實現思路為:
給對象增加一個計數器,每當有一個對象引用它時,就讓計數器加一,每一個引用失效時,就讓計數器減一,當計數器為0時,認為對象不再使用。
引用計數算法的思路簡單,效率也比可達性分析算法要高,但是Java卻不使用,原因就在于引用計數無法解決循環引用的問題

循環引用問題

例如:

public class Test {
    public Test n;
    public static void main(String[] args) {
        Test test1 = new Test();// test1引用次數為1
        Test test2 = new Test();// test2引用次數為1
        test1.n = test2;// test2引用次數為2
        test2.n = test1;// test1引用次數為2
        test1 = null;// test1引用次數為1
        test2 = null;// test2引用次數為2
    }
}

當把test1和test2都置為null后,顯然是沒有任何對象會使用到這兩個引用的,也就是說這兩個對象已經是垃圾了,但由于這兩個對象互相引用,計數器不為0,就不進行回收。這就是引用計數法的弊端,無法解決循環引用問題。

垃圾回收算法

確定哪些對象是垃圾后,就要執行垃圾回收算法來清除這些垃圾。主要的垃圾回收算法有:

標記-清除算法

顧名思義,就是先標記再清除。通過前文所述操作對所有用不到的對象標記后,統一將其清除。
標記-清除算法雖然實現思路簡單,但是卻有著致命缺點
標記清除后會產生大量不連續的內存碎片,在后續申請內存時,如果剩余內存空間還有1G,但是最大連續的剩余內存空間還不到500MB,那這時去申請500MB的內存空間都申請不下來。
標記-清除算法

從上圖可以看到,雖然剩余空間還有5格,但是要申請一個大小為三格的對象都申請不下來。

復制算法

為了解決標記-清除算法造成的內存碎片化的問題,引入了復制算法。
復制算法的實現思路為:
首先把內存塊分成均勻的兩塊,每次只使用其中的一塊內存,在進行垃圾回收時,把存活的對象復制到另一塊內存中,然后再清除這一塊內存。這樣就解決了內存碎片化的問題。
復制算法
復制算法的實現也比較簡單,但是缺點也很明顯:
內存空間利用率很低,每次只能用一半丟一半。另外,假如內存中存活的對象很多,而死亡的很少,這時復制算法效率也是很低的。

標記-整理算法

標記-整理算法也是對標記-清除算法的一種改進,在標記完不會立即進行清除,而是把所有的存活對象往一端移動,把所有的存活對象和死亡對象隔開以后,在對死亡對象進行清除。
標記-整理算法
標記-整理算法和復制算法一樣,當存活對象很多時,效率會很低。

分代算法

實際開發中的內存情況是多種多樣的,因此不能簡單的使用上述的某一種算法來實現垃圾回收。分代算法通過區域的劃分,在不同的區域執行不同的垃圾回收算法,從而更好地實現垃圾回收。合適的才是最好的。
分代算法將內存區劃分為新生代和老年代兩大內存區塊,根據經驗而談:如果一種事物已經存在了了很久,那么它很可能會繼續存在很久。就像音樂一樣,如果某一首歌已經火了好多年了,那么它很可能繼續火很多年;當然更多的歌都只是曇花一現。依據這條經驗,內存區劃分為新生代和老年代,在新生代中,每次垃圾回收都有大批對象死去,只有少量存活(就像經典歌曲往往是少數的),因此我們采用復制算法;而老年代中對象存活率高、沒有額外空間對它進行分配擔保,就必須采用標記-清除或者標記-整理算法。

  • 新生代分為伊甸區和幸存區,新創建的對象都放在伊甸區,每輪存活的對象放到幸存區
  • 由于大多數的對象生命周期都很短,因此伊甸區很大,幸存區較小
  • 幸存區分為大小相等的兩部分,每次只使用一部分
  • 每次垃圾回收,伊甸區和幸存區的幸村對象一樣,都復制到另一半幸存區。
  • 在幸存區幸存的次數達到一定程度后,就會從新生代轉移到老年代。
  • 如果創建的對象本身很大,則直接放到老年代。
    分代算法
    垃圾回收算法只是垃圾回收的思想,實際的垃圾回收是JVM基于這些思想并加以擴展實現的垃圾回收器來完成的。

原文鏈接:https://blog.csdn.net/weixin_71020872/article/details/131666370

  • 上一篇:沒有了
  • 下一篇:沒有了
欄目分類
最近更新