GC在回收內存時 :java
在幾個線程私有的運行時區域:
算法
它們的內存分配和回收大多都具備肯定性,隨着線程的建立而產生,隨着線程的中止而被回收。棧幀中的內存大小基本在類的結構肯定下來時就已知。bash
而在線程共有的 Java堆(Heap)
和 方法區(Class(Method) Area)
這兩個區域則不一樣:優化
好比,一個接口有不一樣的實現類(類的信息在方法區中),這幾個實現類的內存大小確定不一,無法在運行前就已知須要多大的內存,只有在運行期間才知道建立的對象的大小。spa
在知道哪些內存須要回收以前,咱們要知道怎麼判斷一個對象是否還存活,當它再也不存活時,就回收它。
而 引用計數算法
就是用來判斷對象是否存活的一個算法。線程
算法描述:給對象添加一個引用計數器,當有一個地方引用了它,計數器+1,當引用失效,計數器-1,在任什麼時候刻,計數器爲0時此對象將不能再被使用。code
引用計數法在大多數狀況下表現都不錯,也有被不少公司採用的應用案例。可是在JVM中並無採用這種算法,緣由是:沒法解決對象之間存在相互引用的問題。cdn
public class Person {
Object instance = null;
public static void main(String[] args) {
Person a = new Person();
Person b = new Person();
a.instance = b;
b.instance = a;
a = null;
b = null;// 正常狀況下在這裏GC就會把a,b回收掉
}
}
複製代碼
正常狀況下在執行11-12行代碼時,JVM的GC會把a,b兩個對象回收,可是在引用計數算法的狀況下:對象
a=null
時,a的引用計數器值爲1,由於b對象在引用它。b=null
時,b的引用計數器值爲1,由於a對象在引用它。在Java語言中是經過可達性分析來判斷對象是否存活。
算法描述 : 經過一系列的 GC Roots
做爲起始點,從這些起始點開始向下搜索,能搜索的到的對象說明其可用,不會被GC回收掉,搜索所走過的路徑稱爲 引用鏈(Reference Chain)
。相反,若是一個對象沒有到達GC Roots的路徑,則說明它不可用,被斷定爲可被GC回收的對象。blog
如圖 : 1區域的對象雖然互相關聯,可是它們不可到達GC Roots,因此他們會被回收掉,而2區域的對象與GC Roots之間是有可到達路徑的,因此它們不會被回收。
這些均可做爲GC Roots.
咱們在上面的 引用計數算法
和 可達性分析
中,都提到了 對象之間的引用
關係。
在Java1.2以前,關於 引用
的定義 :
若是
reference
類型的數據存儲的數值表明的是另外一塊內存的起始地址,就說這塊內存表明一個引用。
JDK1,2以後,又引入了 強引用
, 軟引用
, 弱引用
, 虛引用
,這四個概念,而且這四種表現的引用關係愈來愈弱。
例:
Object o = new Object();
複製代碼
只要強引用還在,GC永遠不會回收掉被引用的對象。
有用,但非必須,在將要發生內存溢出時,會把 軟引用
的對象回收掉,若是內存依然不夠用,則拋出OOM異常。
非必需對象,只要GC發生了垃圾回收,無論此時內存是否充足, 弱引用
的對象都會被回收掉。
一個對象僅僅經過上面說的可達性分析看它沒有與GC ROOTS關聯來斷定這個對象是否可被回收是不夠的。
一個對象要通過下面一段判斷過程來判斷它是否要被回收(建議收藏(^__^) 嘻嘻……):
上面咱們說的是存在於Java堆中的對象的回收,但其實在方法區還要回收如下東西:
假如常量池中有一個字符串 "abc" ,可是系統中沒有一個String 對象指向它,也就是這個常量沒有被引用,
當GC在回收時會回收此字面量。
GC在回收方法區時會採用一下2種方式:
GC在回收內存時會採用多種垃圾收集算法,這些算法各有優劣。
此算法是最基礎也是最古老的垃圾回收算法,該算法主要通過2個過程
複製算法針對效率問題進行了優化,它將內存區域劃分爲2塊,每次只使用其中一塊。
如圖:
左右兩側的區域狀態在每一次回收後都來回轉換...
IBM公司經研究代表,Java堆新生代種的對象98%是 '朝生夕死' 的對象,好比臨時變量等做用域不多的對象。
因此如今的虛擬機並不會按照 1:1的比例劃分兩個區域。
如今的JVM虛擬機中,將新生代劃分爲一塊 Eden
區域,和2塊較小的 Survivor
區域(from ,to區)。
每次使用Eden區和1塊Survivor區(from區)最爲活動區域,當發生內存回收時,將這2塊內存中的存活對象複製到另外一塊Survivor區(to區)。
在 HotSpot
虛擬機中,Eden區和Survivor的劃分是: 8: 1,這樣,活動區域佔新生代的 (8+1)/10 *100% = 90%,只有10%的內存浪費。
老年代 : 當將存活對象從活動區域(Eden,from) 複製到 to區時,若是to區不夠用,則將剩下的存活對象放到老年代。
標記-整理主要運用於老年代中。
此算法與標記-清除算法相似,也是經歷2個階段:
因爲老年代的特色,對象的存活率較高,沒有額外的空閒區域,因此 老年代適用 標記-清除和標記-整理算法。
Reference:
深刻理解Java虛擬機