1. 如何確定垃圾引用計數法
介紹
- [ ] 在物件中新增一個引用計數器,每當有一個地方引用它時,計數器值就加一;當引用失效時,計數器值就減一;任何時刻計數器為零的物件就是不可能再被使用的
缺陷
- [ ] 倘若兩個物件不被訪問(可當作垃圾被GC回收),但是這兩個物件互相引用著對方,(例如:objA和objB都有欄位name,賦值令objA.name = objeB)導致它們的引用計數器不為零,也就無法回收它們
可達性分析(GC Roots)1、介紹
- [ ] 如下圖所示:GC Roots到這個物件不可達時,則證明這個物件不可能再被使用,即便object5、object6、object7互有關聯但是它們的GC Rootss是不可達的,因此會被判定可回收物件。
2、回收過程
- [ ] 需要注意的是:當被GC Roots判定為不可達時,並不能立刻回收。一個物件的回收死亡,至少經歷兩次標記過程:
-第一個標記過程是:GC Root 判定不可達後,第二個過程是:判斷物件是否執行了finalize()方法
3、生存還是死亡?
- [ ] 當被GC Roots標記後,還有一次機會復活,透過重寫finalize方法並重新與引用鏈建立關聯,如:把自己(this關鍵字)賦值給某個類變數或者物件的成員變數。那麼它將被移出“即將回收”的集合。(這種自救的方式只有一次,因為一個物件的finalize()方法最多隻會被系統自動呼叫一次,並且它的優先順序很低)
public class FinalizeTest01 { public static FinalizeTest01 SAVE_HOOK = null; public static void main(String[] args) throws InterruptedException { SAVE_HOOK = new FinalizeTest01();// 一個物件SAVE_HOOK只能執行一次finalize SAVE_HOOK = null; System.gc(); Thread.sleep(500);// 暫停0.5秒 讓其有時間執行了finalize方法 if (null != SAVE_HOOK) { System.out.println("Yes , I am still alive"); } else { System.out.println("No , I am dead"); } SAVE_HOOK = null; System.gc(); Thread.sleep(500);// 因為SAVE_HOOK物件finalize方法已經執行過一次 if (null != SAVE_HOOK) { System.out.println("Yes , I am still alive"); } else { System.out.println("No , I am dead"); } } @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("execute method finalize()"); SAVE_HOOK = this; }}
4、那GC Roots物件有哪幾種呢
- [ ] 虛擬機器棧(棧幀中的本地變量表)中引用的物件
- [ ] 本地方法棧(native方法)引用的物件
- [ ] 堆中的靜態屬性引用的變數,字串常量引用的物件(JDK1.7將方法區靜態變數,字串常量移至堆中了)
2. 垃圾回收演算法標記清除演算法(Mark-Sweep)最基礎的垃圾回收演算法,分為兩個階段,**標註**和**清除**。標記階段標記出所有需要回收的物件,清除階段回收被標記的物件所佔用的空間。如圖
從圖中我們就可以發現,該演算法最大的問題是記憶體碎片化嚴重,後續可能發生的物件不能找到可利用空間的問題。
> 優點:演算法相對簡單、對標記可回收物件少的區域GC效率高> 應用場景:可回收物件少,標記的物件也少,清除起來自然效率高> 缺點:會產生記憶體碎片、若可回收物件多則效率低複製演算法(copying)為了解決 Mark-Sweep 演算法記憶體碎片化的缺陷而被提出的演算法。按記憶體容量將記憶體劃分為等大小的兩塊。每次只使用其中一塊,當這一塊記憶體滿後將尚存活的物件複製到另一塊上去,把已使用的記憶體清掉,如圖:
這種演算法雖然實現簡單,記憶體效率高,不易產生碎片,但是最大的問題是可用記憶體被壓縮到了原本的一半。且存活物件增多的話,Copying 演算法的效率會大大降低。
> 優點:沒有產生記憶體碎片 、對存活物件少的區域GC效率高> 應用場景:存活物件少,移動至記憶體容量的物件就少,效率自然就高> 缺點:記憶體空間浪費、若記憶體物件多的區域GC效率低標記整理演算法(Mark-Compact)結合了以上兩個演算法,為了避免缺陷而提出。標記階段和 Mark-Sweep 演算法相同,標記後不是清理物件,而是將存活物件移向記憶體的一端。然後清除另一端的物件。如圖:
> 這種演算法不會產生記憶體碎片,不會有記憶體空間浪費,但是效率不高3. 分代收集演算法(新生代——複製演算法)- [ ] 老生代的特點是每次垃圾回收時只有少量物件需要被回收,新生代的特點是每次垃圾回收時都有大量垃圾需要被回收,因此可以根據不同區域選擇不同的演算法。
**1、介紹**
- [ ] 目前大部分 JVM 的 GC 對於新生代都採取 Copying 演算法,因為新生代中每次垃圾回收都要回收大部分物件,即要複製的操作比較少。
- [ ] 一般將新生代劃分為一塊較大的 Eden 空間和兩個較小的Survivor 空間(From Space, To Space),每次使用Eden 空間和其中的一塊 Survivor空間,當進行回收時,將該兩塊空間中還存活的物件複製到另一塊 Survivor 空間中。
- [ ] 當物件在 Survivor 去躲過一次 GC 後,其年齡就會+1。預設情況下年齡到達 15 的物件會被移到老生代中
疑問標記-清除VS標記-整理標記-清除與標記-整理的本質差異在於前者是一種非移動式的回收演算法,而後者是移動式的。
- [ ] 標記-清除演算法:不需要移動,但是會產生記憶體碎片,空間碎片化問題就只能依賴更為複雜的**記憶體分配器和記憶體訪問器來解決。但是會直接影響應用程式的吞吐量**
- [ ] 標記-整理演算法:需要移動存活物件,尤其是在老年代這種每次回收都有大量物件存活物件,移動存活物件並更新引用物件的地址也是一種極為負重的操作,而且這種物件**移動操作必須全程暫停使用者應用程式才能進行**
移不移動物件都tm有坑呀,那怎麼辦啊,標記清除產生記憶體碎片,不過允許它在現有不影響記憶體分配的情況下是可以堅持一段時間的,所以當訪問數量一上來,整體拉低吞吐量
結論:
從GC的停頓時間來看:標記-清除演算法 停頓時間會更短
從整個程式的吞吐量來看:標記-整理演算法 整體吞吐量會更加划算
為啥老年代選標記整理看了前面一節的(標記-清除VS標記-整理)相信你也有了自己的理解,從整體來看又何嘗不是一種較為正確的選擇。整體的吞吐量優先,GC收集器Parallel Scavenge 顯然就是用了這種模式
而CMS其實是兩種演算法的結合,先使用標記-清除,暫時容忍記憶體碎片的存在,當影響到記憶體分配時,再使用標記整理演算法收集一次
gc分代年齡為什麼是15- [ ] 因為物件的分代年齡佔4位,也就是0000,最大值為1111、二進位制轉化成十進位制也就是最大為15