基本面試都會用到,假如面試官來一句,說說你對Java垃圾回收機制的瞭解,若是你沒概念,基本涼了,一些大廠最後面也基本會問這個問題,通常是爲了幫你定級。java
系列文章分3個部分面試
這裏說的GC回收,指的是 Java 堆的地方,這是一篇你能看懂 Java JVM 文章 中,咱們知道了程序計算器,虛擬機棧和本地方法棧都是隨線程開啓,隨線程關閉的,所以這幾塊區域的內存分配和回收都具有肯定性。而Java 堆和方法區則不同,一個接口中的多個實現類須要的內存可能不同,一個方法中的多個分支須要的內存也可能不同,只有程序在運行時,才知道建立了哪些對象,這部份內存的分配和回收都是動態的,垃圾收集器所關注的就是這部份內存。 而 GC 關注的也就3個點.net
怎麼判斷對象是「存活」 的,仍是已經"死亡"呢?主要有如下方法:線程
給對象添加一個引用計算器,每當有一個地方引用它,則加1,當引用失效,則減1;任什麼時候刻計算器爲0的對象就是不可能再被使用的。但它很難解決對象之間相互循環引用的問題,因此主流的Java虛擬機都沒有采用這種算法。3d
經過一系列的 "GC Roots" 的對象做爲起始點,從這些起始點開始向下搜索,搜索走過的路徑被稱爲引用鏈(Reference Chain),當一個對象到GC Roots 沒有任何引用鏈項鍊,即GC Roots 不可達,則證實此對象是不可用的,以下圖(java 虛擬機第三版) code
關於這幾個區的介紹,這是一篇你能看懂 Java JVM 。cdn
不管是引用計數算法,仍是可達性分析算法,對象是否存貨都跟 "引用" (reference)有關,在JDK1.2 以後,引用可分爲如下4個
在可達性分析算法不可達的對象,也不必定"非死不可',它會經歷兩次標記,一是當不可達 GC Roots 時,標記一次並篩選,篩選的條件是該對象是不是否有必要執行 finalize 方法。當對象沒有覆蓋 finalize 方法,或者已經執行過 finalize 方法時,則認爲此對象會被回收。以下圖
上面都是對 Java 堆進行回收,雖然說 Java 堆 能夠回收70%~95%的空間,但方法區一樣能夠回收一些資源,方法區主要回收兩個部分廢棄常量和無用的類。 廢棄常量: 當前系統沒有任何一個 String 對象引用這個 "abc" 的常量池,也沒有其餘地方引用了這個字面量,這時能夠判斷這個常量是能夠廢棄回收的;其餘常量池中的接口,字段的符號引用也以此相似 無用的類: 無用類的回收,須要知足三個條件
關於對象的回收,就涉及到 垃圾收集算法了。能夠參考 JAVA 垃圾回收機制(二) --- GC回收具體實現 第二節
在說明這些回收機制以前,我想說明之前就據說過的 新生代和老年代的問題。那麼這個分代算法是怎麼回事呢? 首先先要理解,新生代和老年代都是一個內存空間,由參數配置,只是能夠根據算法,決定對象是在新生代仍是在老年代的內存區域!!! 一塊內存能夠分爲3個區域,一個 Eden 和兩個 Survivor 區,當對象在 Eden 建立,並經理了第一次 GC 以後仍然存活,而且能被 survivor 區容納的話,將移到 survivor 區;對象在Survivor 區中「熬過」一次,年齡增長1,當增長到 15 歲(默認,這個閾值能夠經過 -XX:MaxTenuringThreshold 設置),就會晉升成老年代的對象。 從這裏來看,能夠獲得兩個結論
爲何是劃分紅一個 Eden 和 兩個 Survivor 呢,能夠查看這篇文章: blog.csdn.net/qq_35181209…
這裏的標記指的是對象進過前面第三章咱們介紹的那樣,已經能夠斷定就是能夠回收的意思;這個算法首先標記處全部須要回收的對象,在標記完成以後統一回收全部被標記的對象。 看似美好,實則否則,主要有如下兩個缺點:
標記和清除的執行過程以下圖:
複製算法能夠分爲等比例的和8:2兩種
基於 標記-清除算法的效率問題,複製算法出現的。 這種算法是把內存分爲相等的兩塊,一塊用來存儲對象,當GC操做後,把還存留的對象移動到未存對象的那塊內存區域,再把使用過的內存清掉。這樣每次都對整個半區進行內存回收,就不用擔憂內存碎片的問題了。執行過程以下圖:
因爲上面的 1:1 的鋪張浪費,基本主流的 Java 虛擬機都是採用 一個 Eden 和 兩個 Survivor 空間,這也是上面說的新生代和老年代的區分。由於新生代的對象 98% 都是"朝生夕死"的,每次都是用塊個 Eden 和 一塊 Survivor 空間,每次GC以後,還存活在 Eden 和 Survivor 空間的對象會被移動到另一塊Survivor上,並清掉 Eden 和剛纔用過的 Survivor 空間。當Survivor 控件不夠用時,還得依賴其餘內存(這裏指老年代),進行分配擔保。即存活下來的內存不夠放新生代上,只有移動到老年代的內存空間上。 舉個簡單例子:假如虛擬機中設置了新生代的內存大小爲10M,老年代的也爲10M,Eden 和 Survivor 爲 8:1 的關係,那麼Eden就只有8M,Survivor 爲1M;下面建立4個對象
public static void test(){
// 假設 _1MB 字符創爲 1MB
byte[] b1 = new byte[2*_1MB];
byte[] b2 = new byte[2*_1MB];
byte[] b3 = new byte[2*_1MB];
byte[] b4 = new byte[4*_1MB];
}
複製代碼
當執行 test() ,當要分配 b4 對象時,會執行一次 Minor GC,緣由是 Dden 才 6M,被b1,b2,b3填充以後,已經沒有數據去填充 b4了,就會觸發 GC,而b4沒辦法,只有移動到老年代的內存區域了。
那咱們能夠獲得,** 對象存活率較高的狀況下,效率較低;若是不想浪費 50% 的控件,就須要額外的控件進行分配擔保,也就是內存跑到老年代去了,那這個對象沒進行GC就跑到老年代去了,那確定是很佔內存的** 因此就有了 標記-整理算法
標記整理算法,是針對老年代的。它與標記-清除算法同樣,分兩個步驟
在商業的虛擬機中,都採用 分代收集算法,根據對象存活週期將內存劃分爲幾塊,即新生代和老年代; 在新生代中,每次都有大量對象死去,只有少許活着,就選用複製算法;而老年代的對象存活率比較高,沒有額外空間對它進行分配擔保,就必須使用 「標記-整理」或者「標記-清理」 進行回收。
至此,Java 回收機制(一) -- 對象回收與算法初識就講完啦。。。