?博主主頁: 33的博客?
??文章專欄分類:JavaEE??
🚚我的代碼倉庫: 33的代碼倉庫🚚
🫵🫵🫵關注我帶你了解更多進階知識
目錄
- 1.前言
- 2.JVM內存區域劃分
- 3.類加載
- 3.1雙親委派模型
- 4.垃圾回收(GC)
- 4.1垃圾識別
- 4.1.1引用計數
- 4.1.2可達性分析
- 4.2垃圾釋放
- 4.2.1標記釋放
- 4.2.2復制算法
- 4.2.3標記整理
- 4.2.4分代回收
- 5.總結
1.前言
JVM 是 Java Virtual Machine 的簡稱,意為 Java虛擬機。虛擬機是指通過軟件模擬的具有完整硬件功能的、運行在一個完全隔離的環境中的完整計算機系統,JVM本省是一個比較復雜的東西,我主要從三個方面進行講述:內存區域劃分,類加載機制垃圾回收算法。
2.JVM內存區域劃分
JVM其實就是一個進程,進程在運行過程中,要從操作系統申請資源空間,JVM申請的空間會劃分為幾個不同的區域,每個區域作用各不相同。這些資源支持了后續Java程序的執行。
堆區:整個進程只有一份,代碼中new出來的對象,對象中的非靜態成員變量,放在堆區
棧區:虛擬機棧記錄了JAVA代碼中的調用關系,java局部變量。
程序計數器:專門用來存儲下一條Java指令的地址
元數據:整個進程只有一份,一些輔助性質的,描述性質的屬性,我們所寫的JAVA代碼,各種邏輯運算,會通過javac完成代碼轉換成字節碼,此時這些字節碼在程序運行時就會被JVM加載到元數據中,此時當前程序如何執行,做哪些事就按照上述元數據區記錄的字節執行。
下列元素n,m,t各在什么區?
class Test{
int n;
static int m;
}
main(){
Test t=new Test();
}
t為局部變量在棧區
new Test在堆區
n是成員變量也在堆中
m是static修飾,類屬性在元數據區
3.類加載
類加載就是指JAVA程序運行是,把.class文件從硬盤中讀到內存,再進行一系列解析。
類加載大致可以分為5步:
1)加載
把硬盤上的.class文件找到,打開文件讀取文件內容
2)驗證
確保讀到的文件內容是合法的
3)準備
給類申請內存空間,默認值為全0
4)解析
主要針對類中的字符串常量進行處理
例如有一串代碼為String s=”hello";s變量存入的是hello的地址,但是再.calss文件中不純在地址的概率,那么為了就可以給s填一個偏移量。
5)初始化
把類對象的各個部分的屬性進行賦值填充
3.1雙親委派模型
在類加載的時候有一個重要模型就是雙親委派模型,描述了如何找到.class文件。在進行加載操作的時候有一個專門的模塊叫做類加載器,默認含有三個
BootstrapClassLoader:負責查找標準庫的目錄
ExtensionClassLoader:負責查找擴展庫的目錄
ApplicationClassLoader:負責查找當前項目的代碼目錄,第三方庫目錄
上述三個類加載器存在父子關系,類似于二叉樹,有一個指針指向父類加載器
雙親委派工作流程:
1)從ApplicationClassLoader作為入口,開始工作
2)ApplicationClassLoader不會立即搜索自己負責的目錄,會把搜索的任務交給自己的父親
3)進入ExtensionClassLoader,也不會立即搜索自己負責的目錄,也會把搜索的任務交給自己的父親
4)進入BootstrapClassLoader,不會立即搜索自己負責的目錄,也會把搜索的任務交給自己的父親
5)BootstrapClassLoader發現自己沒有父親節點,此時會真正的搜索負責的目錄,如果找到了就執行后續操作,沒有找到就返回給孩子
6)ExtensionClassLoader收到父親的任務以后,會搜索自己負責的目錄,如果找到了就執行后續操作,沒有找到就返回給孩子
7)ApplicationClassLoader收到父親的任務以后,會搜索自己負責的目錄,如果找到了就執行后續操作,沒有找到就返回給孩子,但如果沒有孩子就說明類加載失敗,拋出ClassNotFoundException
4.垃圾回收(GC)
垃圾回收是回收的內存,其中主要回收的是堆中的內存,棧中的內存在代碼塊結束以后會自動銷毀。那么垃圾回收具體是怎么展開的呢?主要分為垃圾識別和垃圾釋放
4.1垃圾識別
判定new出來的對象在后續是否要使用,如果不再使用舊標記為垃圾。
例:
void func(){
Test t=new Test();
t.find();
}
當程序執行到}時,t就被釋放,此后就不再使用new Test()對象了,就可以標記為垃圾,但如果有些大媽比較復雜,例如
Test t=new Test();
Test m=t;
Test n=m;
Test z=n;
此時就有很多引用指向new Test()對象,就學要確保沒有任何一個引用指向這個對象才能標記為垃圾,那么我們怎么知道什么時候沒有引用指向它呢?
4.1.1引用計數
當我們創建一個對象時,給每個對象分配一個額外的空間記錄當前對象有幾個引用。
每增加一個引用,計數位置+1,每減少一個引用,技術位置-1,如果為0就標記為垃圾
問題一:
這樣會消耗額外的空間,當我們的對象非常多,但對象的體積非常小,那么久可能導致計數所占的空間就占了所有空間的大部分。
問題二:
可能會引起循環引用,那么就永遠釋放不了資源
class Test{
Test t;
}
Test a=new Test();
Test b=new Test();
a.t=b;
b.t=a;
a=null;
b=null;
這倆對象不能再使用也釋放不了
4.1.2可達性分析
在寫代碼的時候會定義很多變量,就可以從這些變量作為起點開始遍歷,所謂的遍歷就是會沿著這些變量的引用類型成員再京一部訪問,所有能被訪問到的自然不是垃圾
4.2垃圾釋放
4.2.1標記釋放
最直接的方法就是把標記為垃圾的直接釋放掉:但是這樣會生成很多內存碎片,后續如果有類對象再申請空間可能就不夠用
4.2.2復制算法
把一個空間分成兩半,假設數據存放于左半邊那么把不是垃圾的數據全部賦值到右半再講左半數據全部釋放掉。
灰色為垃圾標記,數字為數據
這樣總的內存空間減少,且復制的開銷也很大。
4.2.3標記整理
該方案是把所有的數據依次向前搬運,覆蓋掉垃圾區,再把剩下的垃圾進行釋放。
雖然這樣能解決內存碎片的問題,但搬運的內存開銷很大
4.2.4分代回收
JVM中有專門的線程負責周期性掃描,一個對象如果被掃描了一次,年齡就+1,JVM會根據對象年齡的差異,把整個堆分成2部分,新生代,老年代。
1)當代碼中new出一個新的對象,這個對象就是被創建在伊甸區,伊甸區的對象大部分都活不夠第一輪,生命周期非常短
2)第一輪GC掃描完成以后,少數伊甸區幸存的對象會通過復制算法拷貝到生存區,在后續GC掃描的時候不僅會掃描伊甸區還會掃描生存區的對象,生存區的大多數對象也會在掃描中被標記為垃圾,少數存活,就會繼續通過復制算法拷貝到另一個生存區,每次經歷一輪GC年齡就+1.
3)如果這個對象在生存區中經歷了若干輪依然在,那么就會把這個對象拷貝到老年區。
4)老年代的對象也會被GC掃描只是頻次大大減小
5)對象在老年代結束以后就會釋放內存。
5.總結
本篇文章主要JVM內存區域劃分,類加載,雙親委派模型,垃圾識別,引用計數,可達性分析,垃圾釋放,分代回收等等。
下期預告:MySQL