資料整理來源以及參考:java
深刻JAVA虛擬機git
https://www.zhihu.com/question/21539353 (關於Java爲啥關於引用計數以及可達性問題,查看gityuan的回答)算法
https://www.cubrid.org/blog/understanding-java-garbage-collection (這篇講的也不錯).net
Java的GC機制主要針對於 堆以及方法區 而言,對於程序計數器,虛擬機棧,本地方法棧三個區域是隨着線程而生,隨線程而滅的,棧中的棧幀隨着方法的進入和退出有條不紊的執行出棧和入棧的操做,每一個棧幀分配的內存在編譯期就是可知的。線程
Java中經過可達性算法來管理對象的引用,算法的基本思路是經過一系列的"GC Roots"的對象做爲起始點,從節點向下搜索,搜索走過的路徑稱爲引用鏈,當一個對象到GC Roots沒有任何引用鏈相連,則說明對象不可用。指針
能夠做爲GC Roots的對象包括:code
圖示以下:對象
上圖能夠看出,對象實例3和對象實例5沒有在GC Roots的路徑下,因此會標記爲不可達的。blog
然而,一個對象是否真正的死亡,至少須要兩次的標記過程:若是對象再進行可達性分析時候沒有對應引用鏈關聯到,則被標記一次,而後進行一次篩選,篩選的條件是此對象是否有必要執行finalize()方法,當對象沒有覆蓋finalize()或者finalize()已經被虛擬機調用過,則不必執行。若是這個對象被斷定有必要執行finalize()方法,則會放置在一個隊列中,稍後GC會對這個隊列進行第二次的小規模標記,若是仍是沒有對應引用,則該對象會被回收。接口
回收方法區主要回收兩部份內容:廢棄常量和無用的類。
回收廢棄常量與回收Java堆中的對象很是相似。以常量池中字面量的回收爲例,假如一個字符串「abc」已經進入了常量池中,可是當前系統沒有任何一個String對象是叫作「abc」的,換句話說是沒有任何String對象引用常量池中的「abc」常量,也沒有其餘地方引用了這個字面量,若是在這時候發生內存回收,並且必要的話,這個「abc」常量就會被系統「請」出常量池。常量池中的其餘類(接口)、方法、字段的符號引用也與此相似。
斷定一個常量是不是「廢棄常量」比較簡單,而要斷定一個類是不是「無用的類」的條件則相對苛刻許多。類須要同時知足下面3個條件才能算是「無用的類」:
該類全部的實例都已經被回收,也就是Java堆中不存在該類的任何實例。
加載該類的ClassLoader已經被回收。
該類對應的java.lang.Class 對象沒有在任何地方被引用,沒法在任何地方經過反射訪問該類的方法。
垃圾收集算法主要有三種:標記-清除算法(mark-sweep)
,複製算法(copying)
和標記-整理算法(mark-compact)
。
標記-清除算法(mark-sweep)
該算法分兩階段進行,一是標記,二是清除。首先標記出須要回收的對象,在標記完成後統一回收全部被標記的對象。
使用該算法有兩點不足:
複製算法
複製算法的出現是爲了解決上述的效率問題,他將內存按容量劃分爲大小相等的兩塊,每次使用其中的一塊。一塊內存若是用完了,將這塊內存還存活的對象複製到第二塊內存當中,而後把已使用過的內存空間一次清理掉。
優勢:每次都是對整個半區進行內存回收,內存分配時也就不用考慮內存碎片等複雜狀況,只要移動堆頂指針,按順序分配內存便可,實現簡單,運行高效。
缺點:算法的代價是將內存縮小爲了原來的一半。
如今JVM都採用該算法進行新生代內存回收,主流的是並不須要按照1:1的比例來劃份內存空間,而是將內存分爲一塊較大的Eden空間和兩塊較小的Suvivor空間,每次使用Eden和其中一塊Suvivor。當回收時,將Suvivor和Eden中還存活的對象一次性複製到另外一塊Suvivor空間中,最後清理用過的Suvivor和Eden空間。HotSpot虛擬機默認Suvivor和Eden的比例爲1:8,也就是每次新生代中可用的內存空間爲整個新生代容量的90%,(80Eden+10Suvivor),只有10%內存會被浪費掉。若是Suvivor空間不夠用了,須要依賴其它內存(老年代)來進行分配黨報
若是另外一塊Suvivor空間沒有足夠的空間去存放上一次新生代收集下來的存活對象,則這些對象將直接經過分配擔保機制進入老年代。
標記-整理算法
複製算法在存活的對象多的狀況下就要進行較多的複製操做,效率將會下降。所以提出了標記-整理算法,該算法適用於老年的內存的回收。過程跟標記-清除同樣,可是後續步驟不是直接對可回收對象進行清理,而是把全部存活的對象向一端移動,而後清理掉邊界外的內存。
分代收集算法根據對象的存活週期將內存劃分爲幾塊。通常是分爲老年代以及新生代。
新生代(Young generation): 絕大多數最新被建立的對象會被分配到這裏,因爲大部分對象在建立後會很快變得不可到達,因此不少對象被建立在新生代,而後消失。對象從這個區域消失的過程咱們稱之爲」minor GC「。
老年代(Old generation): 對象沒有變得不可達,而且重新生代中存活下來,會被拷貝到這裏。其所佔用的空間要比新生代多。也正因爲其相對較大的空間,發生在老年代上的GC要比新生代少得多。對象從老年代中消失的過程,咱們稱之爲」major GC「(或者」full GC「)。
上圖中的持久代( permanent generation )就是方法區(method area)。他用來保存類常量以及字符串常量。所以,這個區域不是用來永久的存儲那些從老年代存活下來的對象。這個區域也可能發生GC。而且發生在這個區域上的GC事件也會被算爲major GC。
收集的過程以下:
執行過程以下:
對象的內存分配主要分配在Eden區上,若是啓動了本地線程分配緩衝,按如今優先在TLAB上分配。內存分配優先集以下:
JVM在內存新生代Eden Space中開闢了一小塊線程私有的區域,稱做TLAB(Thread-local allocation buffer)。默認設定爲佔用Eden Space的1%。在Java程序中不少對象都是小對象且用過即丟,它們不存在線程共享也適合被快速GC,因此對於小對象一般JVM會優先分配在TLAB上,而且TLAB上的分配因爲是線程私有因此沒有鎖開銷。所以在實踐中分配多個小對象的效率一般比分配一個大對象的效率要高。 也就是說,Java中每一個線程都會有本身的緩衝區稱做TLAB(Thread-local allocation buffer),每一個TLAB都只有一個線程能夠操做,TLAB結合bump-the-pointer技術能夠實現快速的對象分配,而不須要任何的鎖進行同步,也就是說,在對象分配的時候不用鎖住整個堆,而只須要在本身的緩衝區分配便可
對象優先在Eden分配:大多狀況下,對象在新生代Eden區分配,當Eden沒有足夠空間時候會進行一次minor GC。
大對象直接進入老年代:大對象指的是大量連續內存空間的Java對象。直接進入老年代的目的是避免在Eden以及兩個Survivor之間發生大量的內存複製。
長期存活的對象進入老年代:虛擬機給每一個對象定義了一個對象年齡計數器。若是對象在Eden出生並通過一次minor GC仍然存活,而且被Survivor容納,將被移動到Survivor控件,年齡置爲1,。對象每熬過一場minor GC,年齡加1,當年齡到達必定程度時候(默認15),遷移至老年代。