網站首頁 編程語言 正文
目錄
- 類加載機制
- 類加載機制的步驟
- 加載
- 驗證
- 準備
- 解析
- 初始化
- 雙親委派模型
- 工作原理
- 雙親委派模型的優點
- 垃圾回收機制
- 死亡對象的判斷
- 可達性分析算法
- 可達性分析算法的缺點
- 引用計數算法
- 循環引用問題
- 垃圾回收算法
- 標記-清除算法
- 復制算法
- 標記-整理算法
- 分代算法
類加載機制
對于任意一個類,都需要經歷這樣一個過程:
這個過程就是類的生命周期,從加載到初始化,屬于類加載過程,其中驗證、準備和解析都屬于連接操作。
類加載機制的步驟
加載
加載是整個類加載中的一個階段,該階段通過類加載器將類的字節碼文件加載到內存中,并創建對應的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
- 上一篇:沒有了
- 下一篇:沒有了
相關推薦
- 2022-10-27 scrollview?tableView嵌套解決方案示例_IOS
- 2022-09-08 深入了解Go語言的基本語法與常用函數_Golang
- 2022-05-02 三行Python代碼提高數據處理腳本速度_python
- 2022-11-28 基于Python實現DIT-FFT算法_python
- 2023-10-14 c/c++--編譯指令(預處理之后) #pragma
- 2022-03-16 linux下FastDFS搭建圖片服務器_Linux
- 2022-12-26 Python?Metaclass原理與實現過程詳細講解_python
- 2023-01-27 Python?Flask利用SocketIO庫實現圖表的繪制_python
- 欄目分類
-
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支