垃圾收集器(GC)的做用相信你們都知道,它將咱們的不用的內存空間給回收,Java的垃圾收集器是"動態分配內存和垃圾收集"的。正由於它是動態的,因此不少人都忽略了它,但當出現一些內存泄漏、內存溢出的問題時,咱們必須掌握JVM才能去解決問題java
上文中,咱們說到程序計數器、虛擬機棧、本地方法棧這3個區域隨線程生,隨線程死。算法
棧中的棧幀隨着方法的進入和退出而出棧、入棧,每一個棧幀分配多少內存基本在類結構肯定時就是已知的(不包括JIT的優化)數組
而Java堆和方法區只在程序運行期間纔會知道開闢的空間(),這部份內存分配和回收是動態的,因此垃圾收集器關注的這部份內存安全
當一段內存再也不使用(不處於存活狀態)時就回收,下文會談到哪些內存將再也不使用bash
這就是咱們下文要講到的各類回收機制服務器
首先,來看堆,堆中存放了幾乎全部的實例對象,在對堆進行回收內存時,要先判斷哪些對象能被回收(存活)。多線程
每當有一個地方引用它,計數器+1,每當一個引用失效,計數器-1;任什麼時候刻計數器爲0的對象是不能被使用的。併發
這種分析雖然簡單,但有一個問題,如循環引用:佈局
public class A {
Object obj;
public void testGC(){
A a1 = new A();
A a2 = new A();
a1.obj = a2;
a2.obj = a1;
a1 = null;
a2 = null;
System.gc();//若是採用引用記數法則不回收
}
}
複製代碼
因此,咱們須要一種更全面的回收機制性能
經過一系列被稱爲「GC Roots」的對象做爲起點,從這些節點開始往下搜索,搜所走過的路徑成爲引用鏈,當一個對象到GC Roots沒有任何引用鏈相連,則證實此對象是不可用的
可做爲GC Roots的對象:
垃圾收集器判斷對象存活都和引用有關,下面來看引用有哪些類型
一個對象要被宣告死亡,要經歷兩部:
一個對象的finalize()方法只會被執行一次
在Java9中,finalize()方法已被棄用,緣由以下:
永久代的方法區分爲兩部分:
知道了要回收哪些東西,咱們還要知道如何回收,下面來看一下典型的垃圾回收算法
分爲標記和清除兩個階段:首先標記出全部須要回收的對象,在標記完成後贊成收回被標記的對象
他將內存分爲兩塊,每次只使用其中一塊。當一塊內存用完後,就將還存活的對象複製到另外一塊上面,再將已使用的內存一次清理掉。
可是,咱們通常不將它對半分,而是分爲一塊較大的Eden和兩塊較小的Survivor區域,HotSpot默認Eden:Survivor比例大小爲8:1,即Eden爲收集前的空間,一塊Survivor爲收集後的大小,只浪費了10%的空間。
注:當每次回收有大於10%的對象存活時,經過分配擔保機制讓Survivor中剩餘存不下的進入老年代
標記過程和標記-清除算法同樣,清理以前,讓全部存活的對象都向一端移動,而後直接清理掉邊界之外的內存。
根據對象存活週期的不一樣劃分爲幾塊,通常爲新生代和老年代
以上爲理論的垃圾收集算法,實際如HotSpot虛擬機會對算法有嚴格的考量。。。
OopMap:虛擬機用它來得知哪些地方存放着對象引用,在類加載完成後,HotSpot就把對象內什麼偏移量上是什麼類型的數據計算出來,在JIT編譯過程當中,也會在特定位置記錄棧和寄存器中哪些位置是引用
程序在安全點才能暫停執行GC,因此安全點通常選定爲「是否具備讓程序長時間執行的特徵」(如方法調用,循環跳轉,異常跳轉),前文「特定位置」就被稱爲安全點。
如何讓全部線程都跑到最近安全點上停下來:
安全點保證了程序執行時在不太長的時間內就會遇到可進入的GC的安全點,例如線程處於SLeep或Blocked狀態,這時線程沒法響應JVM中斷請求,這種狀況,就須要安全區域來解決。
安全區域是指在一段代碼中,引用關係不會發生變化。
當線程執行到了安全區域中的代碼,標識本身進入了Safe Region,擋在這段時間裏JVM要發起GC時,就不用管標識本身爲Safe Region狀態的線程了。在線程要離開Safe Region時,去檢查是否完成根節點枚舉,若是完成,線程繼續執行;不然它就必須等待直到收到能夠安全離開Safe Region的信號爲止。
能夠理解爲收集算法是內存回收的方法論,垃圾收集器就是內存回收的具體實現
特色:
特色:
特色:
Serial Old是Serial收集器的老年代版本,它一樣是一個單線程收集器,使用標記整理算法。這個收集器的主要意義也是在於給Client模式下的虛擬機使用。
若是在Server模式下,主要兩大用途:
Parallel Old 是Parallel Scavenge收集器的老年代版本,使用多線程和「標記-整理」算法。這個收集器在1.6中才開始提供。
這類應用尤爲重視服務器的響應速度,但願系統停頓時間最短,以給用戶帶來較好的體驗。CMS收集器就很是符合這類應用的需求
CMS收集器是基於「標記-清除」算法實現的。它的運做過程相對前面幾種收集器來講更復雜一些,整個過程分爲4個步驟:
其中,初始標記、從新標記這兩個步驟仍然須要「Stop The World」.
CMS收集器主要優勢:
CMS三個明顯的缺點:
使用G1收集器時,Java堆的內存佈局是整個規劃爲多個大小相等的獨立區域(稱爲Region,大小爲2的冪次方,如1M,2M,4M),雖然還保留有新生代和老年代的概念,但新生代和老年代再也不是物理隔離的了,它們都是一部分Region的集合。
G1收集器之因此能創建可預測的停頓時間模型,是由於它能夠有計劃地避免在真個Java堆中進行全區域的垃圾收集。G1跟蹤各個Region裏面的垃圾堆積的價值大小(回收所獲取的空間大小以及回收所須要的時間的經驗值),在後臺維護一個優先列表,每次根據容許的收集時間,優先回收價值最大的Region(這也就是Garbage-First名稱的又來)。這種使用Region劃份內存空間以及有優先級的區域回收方式,保證了G1收集器在有限的時間內能夠獲取儘可能可能高的收集效率
問:若一個對象在Region中,但他若是有其餘Region中、甚至整個堆任意對象有引用關係,作可達性斷定對象存活時,要掃描整個對空間嗎?
答:
G1中提供了三種模式垃圾回收模式,young gc、mixed gc 和 full gc,在不一樣的條件下被觸發。
自動內存管理最終能夠歸結爲自動化地解決了兩個問題:
簡單來講,對象內存分配主要是在堆中分配。可是分配的規則並非固定的,取決於使用的收集器組合以及JVM內存相關參數的設定
大多數狀況下,對象在Eden區分配內存
Minor GC和Full GC的區別:
大對象是指,須要連續內存空間的Java對象,例如很長的字符串或數組
虛擬機給每一個對象定義了一個對象年齡(Age)計數器,若是對象在Eden出生並通過第一次Minor GC後仍然存活,而且能被Survivor容納的話,會被移動到Survivor空間中,而且對象年齡爲1.每在Survivor區中渡過一次Minor GC,年齡增長1,當它的年齡增長到必定程度(默認15),就被晉升到老年代。
若是在Survivor空間中相同年齡全部對象大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的對象就能夠直接進入老年代
僞代碼解釋:
//準備Minor GC:
if (老年代中最大連續可用空間>新生代全部對象總空間){
//開始Minor GC
} else {
if (容許擔保失敗){
if (老年代最大連續可用空間>歷次晉升老年代對象平均大小){
//開始Minor GC
} else {
//開始Full GC
}
} else {
//開始Full GC
}
}
複製代碼
通常來講,新生代只使用一個survivor空間來進行輪換時的備份,因此當出現極端狀況(即新生代空間在一次minor GC後所有存活)時survivor空間有可能爆滿,因此此時須要老年代進行分配擔保,即survivor區沒法容納的對象都進入老年代。
在JDK 6 Updale 24 以後,Handle PromotionFailure 不會再影響到虛擬機的空間分配擔保策略,只要老年代的連續空間大於新生代對象總大小或者歷次晉升的平均大小就會進行Minor GC,不然將進行Full GC。
最後,內存回收和垃圾收集器不少時候都是影響系統性能,併發能力的緣由,虛擬機也提供了多種收集器和大量的調節參數,由於不少時候咱們要選擇本身的業務來設置相應的收集方式