在上篇文章中簡單介紹了JVM內部結構,線程隔離區域隨着線程而生,隨着線程而忘。線程共享區域由於是共享,因此可能多個線程都用到,不能輕易回收,與C語言不一樣,在Java虛擬機自動內存管理機制的幫助下,再也不須要爲每一個new操做去寫配對的delte/free代碼,可以幫助程序員更好的編寫代碼。那麼JVM是如何進行對象內存分配以及回收分配給對象內存呢?程序員
幾乎全部的對象實例都分配在堆中,爲了進行高效的垃圾回收,虛擬機把堆劃分紅新生代(Young Generation)、老年代(Old Generation)。算法
新生代又分爲1個Eden區和2個survivor區(S0,S1),Eden區與Survivor區的內存大小比例默認爲8:1。多線程
除了長期存活的對象會分配到老年代,還有如下狀況對象會分配到老年代:
①大對象(須要大量連續內存空間的Java對象)直接進入老年代,能夠經過參數
-XX:PretenureSizeThreshold設定對象大小閾值,超過其值進入老年代
②若Survivor區域中全部相同GC年齡的對象大小超過Survivor空間的一半,年齡不小於該年齡的對象就直接進入老年代
併發
對象建立是一個很是頻繁的行爲,進行堆內存分配時還須要考慮多線程併發問題,可能出現正在給對象A分配內存,指針或記錄還未更新,對象B又同時分配到原來的內存,解決這個問題有兩種方案:
一、採用CAS保證數據更新操做的原子性;
二、把內存分配的行爲按照線程進行劃分,在不一樣的空間中進行,每一個線程在Java堆中預先分配一個內存塊,稱爲本地線程分配緩衝(Thread Local Allocation Buffer, TLAB);
ide
如何判斷哪些對象佔用的內存須要回收?虛擬機有以下方法:
post
結果:this
public class ReferenceCountingGC {複製代碼public Object instance = null; private static final int _1MB = 1024 * 1024; /** * 方便GC能看清楚是否被回收 */ private byte[] bigSize = new byte[2 * _1MB]; public static void testGC(){ ReferenceCountingGC objA = new ReferenceCountingGC(); ReferenceCountingGC objB = new ReferenceCountingGC(); objA.instance = objB; objB.instance = objA; objA = null; objB = null; System.gc(); } 複製代碼} 複製代碼public Object instance = null; private static final int _1MB = 1024 * 1024; /** * 方便GC能看清楚是否被回收 */ private byte[] bigSize = new byte[2 * _1MB]; public static void testGC(){ ReferenceCountingGC objA = new ReferenceCountingGC(); ReferenceCountingGC objB = new ReferenceCountingGC(); objA.instance = objB; objB.instance = objA; objA = null; objB = null; System.gc(); } 複製代碼
GC Roots的對象包括:
①本地變量表中引用的對象
②方法區中類靜態屬性引用的對象
③方法區中常量引用的對象
④Native方法引用的對象
spa
斷定一個對象是否可回收,至少要經歷兩次標記過程:
①若對象與GC Roots沒有引用鏈,則進行第一次標記
②若此對象重寫了finalize()方法,且還未執行過,那麼它會被放到F-Queue隊列中,並由一個虛擬機自動建立的、低優先級的Finalizer線程去執行此方法(並不是必定會執行)。finalize方法是對象逃脫死亡的最後機會,GC對隊列中的對象進行第二次標記,若該對象在finalize方法中與引用鏈上的任何一個對象創建聯繫,那麼在第二次標記時,該對象會被移出"即將回收"集合。
自我救贖示例:線程
public class FinalizeGC {
public static FinalizeGC obj;
public void isAlive() {
System.out.println("yes, i am still alive");
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("method finalize executed");
obj = this;
}
public static void main(String[] args) throws Exception {
obj = new FinalizeGC();
// 第一次執行,finalize方法會自救
obj = null;
System.gc();
Thread.sleep(500);
if (obj != null) {
obj.isAlive();
} else {
System.out.println("I'm dead");
}
// 第二次執行,finalize方法已經執行過
obj = null;
System.gc();
Thread.sleep(500);
if (obj != null) {
obj.isAlive();
} else {
System.out.println("I'm dead");
}
}
}
複製代碼
結果:
method finalize executed
yes, i am still alive
I'm dead
從結果來看,第一次GC時,finalize方法執行,在回收以前成功自我救贖
第二次GC時,finalize方法已經被JVM調用過,因此沒法再次逃脫指針
知道了如何判斷對象爲"垃圾",接下來就是如何清理這些對象
垃圾收集器組合:
注:
並行:多條垃圾收集線程並行工做,但此時用戶線程仍然處於等待狀態
併發:用戶線程與垃圾收集線程同時執行(不必定是並行,可能交替執行),用戶程序在繼續運行,而垃圾收集程序運行於另外一個CPU上
《深刻理解JAVA虛擬機》 https://www.jianshu.com/p/eaef248b5a2c