JVM記憶體模型及GC回收機制的相關理解
在面試中我們經常會被問道關於JVM的面試問題。我們來整理下
這篇不錯
這個可以讓你恍然大悟
1 JAVA記憶體模型初體驗
JVM記憶體模型:
1 堆 :物件
2 棧(本地方法棧,虛擬機器棧):引數列表、基本資料型別
3 方法區(包括常量池):類變數、常量、程式碼段(code segement)
4 程式計數區
有時候我們又會講JVM記憶體分為主記憶體和工作記憶體。
主記憶體: 堆記憶體、方法區 (共享)
工作記憶體:程式計數區、棧記憶體
請記住上面這些。我們再來介紹下這些區域是做什麼用的:
1 堆記憶體:所有的物件例項以及陣列都要在堆上分配。Java 堆是垃圾收集器管理的主要區域,因此很多時候也被稱做“GC 堆”(Garbage Collected Heap,幸好國內沒翻譯成“垃圾堆”)。如果在堆中沒有記憶體完成例項分配,並且堆也無法再擴充套件時,將會丟擲OutOfMemoryError 異常。
簡單講:存放物件,GC回收。
2 方法區
3 程式計數器: 位元組碼直譯器工作時就是通過改變這個計數器的值來選取下一條需要執行的位元組碼指令,分支、迴圈、跳轉、異常處理、執行緒恢復等基礎功能都需要依賴這個計數器來完成。是執行緒私有的。
4 本地方法棧:為虛擬機器使用到的Native 方法服務。
5 虛擬機器棧:與程式計數器一樣,Java 虛擬機器棧(Java Virtual Machine Stacks)也是執行緒私有的,它的生命週期與執行緒相同。儲存區域性變數的值,包括:1.用來儲存基本資料型別的值;2.儲存類的例項,即堆區物件的引用(指標)。也可以用來儲存載入方法時的幀。
看了上面的內容可能有了一個大概的理解,我們再來針對具體的程式碼做一個解析。
看這裡
2 JAVA中變數的記憶體分配
這個很詳細
首先我們來分類一下JAVA中的變數:
全域性變數(類變數、例項變數)
區域性變數
public class Variable{
static int allClicks=0; // 類變數
String str="hello world"; // 例項變數
public void method(){
int i =0; // 區域性變數
}
}
我們來看這些變數型別的儲存位置
區域性變數是方法私有的變數,是儲存在棧記憶體中,隨著方法的結束而消失
類變數
例項變數儲存在堆記憶體中
對下面進行記憶體分析:
class BirthDate {
private int day;
private int month;
private int year;
public BirthDate(int d, int m, int y) {
day = d;
month = m;
year = y;
}
省略get,set方法………
}
public class Test{
public static void main(String args[]){
int date = 9;
Test test = new Test();
test.change(date);
BirthDate d1= new BirthDate(7,7,1970);
}
public void change(int i){
i = 1234;
}
1 int date = 9;
date在main方法中定義屬於區域性變數,且是int屬於基本資料型別,所以其值、引用都在棧記憶體中
2 Test test = new Test();
test引用存在棧記憶體中,new Test() 物件存在堆記憶體中。
3 test.change(date);
i為區域性變數,引用和值存在棧中。當方法change執行完成後,i就會從棧中消失。
4 BirthDate d1= new BirthDate(7,7,1970);
d1引用存在棧中,new BirthDate 物件存在堆記憶體中,且int d, int m, int y為建構函式中的變數存在棧記憶體中,而day,month,year是例項變數存在堆記憶體中
3 GC簡單瞭解
哪些區域需要回收
在JVM五種記憶體模型中,有三個是不需要進行垃圾回收的:程式計數器、JVM棧、本地方法棧。因為它們的生命週期是和執行緒同步的,隨著執行緒的銷燬,它們佔用的記憶體會自動釋放,所以只有方法區和堆需要進行GC。
如何判斷物件已死
判斷物件在程式中沒有被引用即物件已死。
判斷物件回收
引用計數演算法
引用計數演算法在每個物件中加入一個引用計數器,如果此物件的引用計數變為0,那麼此物件就可以作為垃圾收集器的目標物件來收集。
優點:簡單,直接,不需要暫停整個應用。
缺點:1.需要編譯器的配合,編譯器要生成特殊的指令來進行引用計數的操作;2.不能處理迴圈引用的問題。
可達性分析演算法(根搜尋演算法)
通過一系列的名為“GC Root”的物件作為起點,從這些節點向下搜尋,搜尋所走過的路徑稱為引用鏈(Reference Chain),當一個物件到GC Root沒有任何引用鏈相連時,則該物件不可達,該物件是不可使用的,垃圾收集器將回收其所佔的記憶體。
finalize方法
如果要回收一個不可達的物件,要經歷兩次標記過程。首先是第一次標記,並判斷物件是否覆寫了 finalize 方法,如果沒有覆寫,則直接進行第二次標記並被回收。
不建議使用finalize 方法,它的執行代價高,不確定性大,GC 也不會等待它執行完成,它的功能完全可以被 try-finally 代替。
各種垃圾收集演算法
-
標記-清除演算法
步驟:
1、標記:從根集合開始掃描,標記存活物件;
2、清除:再次掃描真個記憶體空間,回收未被標記的物件。
缺點:
1:每個活躍的物件都要進行掃描,而且要掃描兩次,效率較低,收集暫停的時間比較長。
2:產生不連續的記憶體碎片
-
標記-整理演算法
步驟:
1、標記:從根集合開始掃描,標記存活物件;
2、整理:再次掃描真個記憶體空間,並往記憶體一段移動存活物件,再清理掉邊界的物件。
不會產生記憶體碎片,但是依舊移動物件的成本。
-
複製演算法
將記憶體分成兩塊容量大小相等的區域,每次只使用其中一塊,當這一塊記憶體用完了,就將所有存活物件複製到另一塊記憶體空間,然後清除前一塊記憶體空間。
缺點:
1、複製的代價較高,所有適合新生代,因為新生代的物件存活率較低,需要複製的物件較少;
2、需要雙倍的記憶體空間,而且總是有一塊記憶體空閒,浪費空間。
GC 型別
1.Minor GC 針對新生代的 GC Minor GC
2.Major GC 針對老年代的 GC
3.Full GC 針對新生代、老年代、永久帶的 GC
為什麼要分不同的 GC 型別,主要是 1、物件有不同的生命週期,經研究,98%的物件都是臨時物件;2、根據各代的特點應用不同的 GC演算法,提高 GC 效率。
現代商用虛擬機器基本都採用分代收集演算法來進行垃圾回收。這種演算法沒什麼特別的,無非是上面內容的結合罷了,根據物件的生命週期的不同將記憶體劃分為幾塊,然後根據各塊的特點採用最適當的收集演算法。大批物件死去、少量物件存活的(新生代),使用複製演算法,複製成本低;物件存活率高、沒有額外空間進行分配擔保的(老年代),採用標記-清理演算法或者標記-整理演算法。
不同區域採用不同的垃圾收集器。
-
物件的一生 轉載
我是一個普通的java物件,我出生在Eden區,在Eden區我還看到和我長的很像的小兄弟,我們在Eden區中玩了挺長時間。有一天Eden區中的人實在是太多了,我就被迫去了Survivor區的“From”區,自從去了Survivor區,我就開始漂了,有時候在Survivor的“From”區,有時候在Survivor的“To”區,居無定所。直到我18歲的時候,爸爸說我成人了,該去社會上闖闖了。於是我就去了年老代那邊,年老代裡,人很多,並且年齡都挺大的,我在這裡也認識了很多人。在年老代裡,我生活了20年(每次GC加一歲),然後被回收。 -
請問GC什麼時候觸發
“什麼時候”即就是GC觸發的條件。
Minor GC觸發條件:
當Eden區滿時,觸發Minor GC。
Full GC觸發條件:
(1)呼叫System.gc時,系統建議執行Full GC,但是不必然執行
(2)老年代空間不足
(3)方法區空間不足
(4)通過Minor GC後進入老年代的平均大小大於老年代的可用記憶體
(5)由Eden區、From Space區向To Space區複製時,物件大小大於To Space可用記憶體,則把該物件轉存到老年代,且老年代的可用記憶體小於該物件大小