JVM的分區+查看GC對象是否存活+3種GC算法+7種垃圾收集器+如何減小GC次數

1、JVM的分區:java

 

一、程序計數器(私有)程序員

程序計數器是一塊較小的內存分區,你能夠把它看作當前線程所執行的字節碼的指示器算法

在虛擬機的概念模型裏,字節碼解釋器工做時,就是經過改變計數器的值來選擇下一條須要執行的字節碼指令。多線程

程序技術器爲線程私有,每一個線程都有它們各自的程序計數器,這樣再多線程的狀況下,線程之間的來回切換,也能正確找到上次切換時執行的位置。併發

若是線程正在執行的是一個Java方法,那麼程序計數器記錄的是當前線程正在執行的字節碼指令的地址;若是線程正在執行的是一個native方法,則計數器值爲空。函數

此內存區域是惟一一個Java虛擬機規範中沒有規定任何OutOfMemoryError(OOM)狀況的區域。佈局

二、Java虛擬機棧(私有)spa

虛擬機棧也爲線程私有的,它的生命週期與線程相同線程

虛擬機棧能夠看作是Java方法執行的內存模型:每一個方法執行的同時都會建立一個棧幀用於存儲局部變量表、操做數棧、動態連接、方法出等信息。一個Java方法從調用到執行完的過程,就對應着一個棧幀從虛擬機棧入棧到出棧的過程;方法調用時,會建立棧幀在棧中,調用完是程序自動出棧釋放,而不是gc釋放。設計

局部變量表中存放了編譯期可知的基本數據類型、對象引用、returnAddress類型(指向了一條字節碼指令的地址);

在虛擬機棧中可能會出現兩種異常:StackOverflowError和OutOfMemory

StackOverflowError:若是線程請求的棧深度大於當前虛擬機所容許的深度,會拋出該異常;

OutOfMemory:若是虛擬機棧能夠動態擴展,當擴展時沒法申請到足夠的內存,會拋出該異常;

三、本地方法棧(私有)

本地方法棧相似與虛擬機棧,它們不一樣之處在於,虛擬機棧是爲虛擬機執行的Java方法服務,而本地方法棧是爲虛擬機使用到的Native方法服務

在HotSpot虛擬機中直接把本地方法棧和虛擬機棧合二爲一;

在本地方法棧可能會出現兩種異常:StackOverflowError和OutOfMemory

四、Java堆(共享)

Java堆是被全部線程共享的一塊區域,它也是Java虛擬機管理的內存中最大的一塊,它在虛擬機啓動時建立

Java堆惟一的目的就是存放對象實,幾乎全部的對象實例的都在這裏分配內存;

Java堆是垃圾收集器管理的主要區域,所以不少時候也被稱爲GC堆

Java堆能夠處於物理上不連續的內存空間中,只要邏輯上連續便可,在實現時既能夠是固定大小也能夠是可擴展的,若是堆中沒有內存完成實例分配,而且堆也沒法再擴展時,將會拋出OutOfMemory異常;

五、方法區(共享)

方法區也是內存共享的一塊區域,它用於存放已被虛擬機加載的類信息常量靜態變量、編譯器編譯後的代碼等數據;

在HotSpot虛擬機中,一般把方法區稱之爲永久代,本質上二者並不相同,只是HotSpot虛擬機的設計團隊使用永久代來實現方法區;

方法區中,垃圾收集比較少見,但並非不進行GC,這個區域的回收目標主要是針對常量池的回收和對類型的卸載

方法區相似於Java堆,不要連續的內存和能夠選擇固定大小或者可擴展。它還能夠選擇不實現垃圾收集;

當方法區沒法知足內存分配需求時,會拋出OutOfMemory異常;

方法區中還存在一個運行時常量池(字符串常量池也在這個裏面),常量池用於存放編譯期生成的各類字面量和符號引用,它具備動態性,不要求常量必定只有編譯期才能產生,運行期間也可能將新的常量放入池中

 

2、堆對象的分配:

1)大多數狀況下,對象在新生代 eden 區中分配,當 Eden 區中沒有足夠的內存空間進行分配時,虛擬機將發起一次 minor GC {minor gc:發生在新生代的垃圾收集動做,很是頻繁,通常回收速度也比較快 full gc:發生在老年代的 gc}。虛擬機給每個對象定義一個對象年齡計數器,若對象在 eden 出生並通過第一次 minor gc 後仍然存活,而且能被 survivor 容納的話,將被移到 survivor 空間中,而且對象年齡設爲1.對象在 survivor 中每熬過一次 minor   gc,年齡就+1,當他年齡達到必定程度(默認爲 15), 就會晉升到老年代。

2)大對象直接進入老年代

3)長期存活的對象將進入老年代

4)若在 survivor 空間中相同年齡全部對象大小的總和>survivor空間的一半,則年齡>=該年齡的對象直接進入老年代,無需等到MaxTeuringThreshold(默認爲15)中的要求。

 

當程序運行時,至少會有兩個線程開啓啓動,一個是咱們的主線程,一個時垃圾回收線程, 垃圾回收線程的priority(優先級)較低
垃圾回收器會對咱們使用的對象進行監視,當一個對象長時間不使用時,垃圾回收器會在空閒的時候(不定時)對對象進行回收,釋放內存空間,程序員是不能夠顯示的調用垃圾回收器回收內存的,可是可使用System.gc()方法建議垃圾回收器進行回收,可是垃圾回收器不必定會執行。
Java的 垃圾回收機制能夠有效的防止內存溢出問題,可是它並不能徹底保證不會出現內存溢出。例如當程序出現嚴重的問題時,也可能出現內存溢出問題。
真正宣佈一個對象死亡,至少須要經歷2次標記過程。見下面的「可達性分析法」。因此進入「DEAD」狀態的線程還能夠恢復,不必定被回收。
 

3、查看GC對象是否存活的方法:

1)引用計數法

基本思想:給對象中添加要給引用計數器,每當一個地方引用時,計數器值+1,當引用失效時,計數器值-1,任什麼時候刻計數器爲0的對象就不可能再被使用。

缺點:很難解決對象之間循環引用的問題。

2)可達性分析法

基本思想:經過一系列的稱爲「GC roots」的對象做爲起始點,從這些節點,開始向下搜索,搜索所走過的路徑稱爲引用鏈,當一個對象到 GC root 沒有任何引用鏈相連(用圖論的話來講,就是從 GC roots 到這個對象不可達),則證實此對象是不可用的。

若對象在進行可達性分析後發現沒有與 GC roots 相鏈接的引用鏈,那麼他將會被第一次標記並進行一次篩選,篩選的條件是該對象是否有必要執行 finalize()方法,當對象沒有重寫

finalize()方法或者  finalize()方法已經被虛擬機調用過,虛擬機將這兩種狀況都視爲不必執行。

若該對象被斷定爲有必要執行 finalize 方法,則這個對象會被放在一個 F-Queue 隊列,

finalize 方法是對象逃脫死亡命運的最後一次機會,稍後 GC 將對 F-queue 中的對象進行第二次小規模的標記,若對象要在 finalize 中成功拯救本身—只要從新與引用鏈上的任何一個對象創建關聯便可(便可以重寫finalize()方法來實現,好比能夠將本身賦值給某個類變量或者對象的成員變量),那麼在第二次標記時他們將會被移出「即將回收」集合。

任何一個對象的 finalize()方法都只會被系統調用一次。

 

可做爲 GC roots 的對象

1)java 虛擬機棧(棧幀中的本地變量表)中引用的對象

2)方法區中類的靜態屬性引用的對象

3)方法區中常量引用的對象

4)本地方法棧中 JNI 引用的對象

引用強度:強引用>軟引用>弱引用>虛引用

1)強引用:相似Object obj = new Object()的引用,只要強引用還存在,垃圾收集器永遠不會回收掉被引用的對象。

2)軟引用:用來描述一些還有用但並不是必須的對象。對於軟引用關聯着的對象,在系統將要發生內存溢出異常以前,將會把這些對象列進回收範圍之中進行第二次回收。若是此次回收尚未足夠的內存,纔會拋出內存溢出異常。SoftReference類實現軟引用。

3)弱引用:用來描述非必須對象,但其強度比軟引用更弱,被弱引用關聯的對象只能生存到下一次垃圾收集發生以前。當垃圾收集器工做時,不管當前內存是否足夠,都會回收掉只被弱引用關聯的對象。WeakReference類實現弱引用。

4)虛引用:一個對象是否有虛引用的存在,徹底不會對其生存時間構成影響,也沒法經過虛引用來取得一個對象實例。爲一個對象設置虛引用關聯的惟一目的就是能在這個對象被收集器回收時收到一個系統通知。PhantomReference類實現虛引用。

 

內存分配規則:

1.對象優先分配在Eden區,若是Eden區沒有足夠的空間,執行一次Minor GC.

2.大對象直接進入老年代。

3.長期存活的對象進入老年代。虛擬機爲每一個對象定義了一個年齡計數器,若是對象通過了1次Minor GC那麼對象會進入Survivor區,以後每通過一次Minor GC年齡+1,達到16,進入老年代。

4.動態判斷對象的年齡。若是Survivor區中相同年齡的全部對象大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的對象能夠直接進入老年代。

5.空間分配擔保。

 

4、3種GC算法:

標記-清除算法(老年代):分爲「標記」和「清除」兩個階段:首先標記出全部須要回收的對象,在標記完成後統一回收全部被標記對象。

缺點:1)產生大量不連續的內存碎片 2)標記和清除效率都不高

複製算法(新生代):它將可用內存按照容量劃分爲大小相等的兩塊,每次只使用其中一塊。當這一塊的內存用完了,則就將還存活的對象複製到另外一塊上面,而後再把已經使用過的內存空間一次清理掉。使得每次都是對整個半區進行內存回收。

優勢:1)不會出現內存碎片。2)只需移動堆頂指針,按順序分配內存便可,實現簡單,運行高效。

缺點:1)將內存縮小爲原來的一半。2)在對象存活率較高時會進行較多複製操做,效率較低。

商業虛擬機的分配擔保機制:將內存分爲一塊較大的 eden 空間和兩塊較小的 survivor 空間,默認比例是 8:1:1,即每次新生代中可用內存空間爲整個新生代容量的 90%,每次使用 eden 和其中一個 survivour。當回收時,將 eden 和 survivor 中還存活的對象一次性複製到另一塊 survivor 上,最後清理掉 eden 和剛纔用過的 survivor,若另一塊 survivor 空間沒有足夠內存空間存放上次新生代收集下來的存活對象時,這些對象將直接經過分配擔保機制進入老年代。

標記-清理算法(老年代): 標記過程和「標記-清除」算法同樣,但後續步驟不是直接對可回收對象進行清除,而是讓全部存活對象都向一端移動,而後直接清理掉端邊界之外的內存。

 

5、7種垃圾收集器,前 3 個是新生代,後 3 個是老年代:

並行收集和併發收集的區別:

(1)並行(Parallel)

       指多個垃圾收集線程並行工做,但此時用戶線程仍然處於等待狀態;

       如ParNew、Parallel Scavenge、Parallel Old;

(2)併發(Concurrent)

       指用戶線程與垃圾收集線程同時執行(但不必定是並行的,可能會交替執行);用戶程序在繼續運行,而垃圾收集程序線程運行於另外一個CPU上;    

       如CMS、G1(也有並行);

(1)  serial (複製算法):單線程(單線程的意義不只僅說明它會使用一個 cpu或一條垃圾收集線程去完成垃圾收集工做,更重要的是在它進行垃圾收集的時候,必須暫停其餘全部工做線程,直到他收集結束)。

應用場景:

對於運行在 client 模式下的虛擬機來講是個很好的選擇。

優勢:

1)簡單高效(與其餘收集器的單線程相比);

2)對於限定單個CPU的環境來講,Serial收集器沒有線程交互(切換)開銷,能夠得到最高的單線程收集效率;

3)在用戶的桌面應用場景中,可用內存通常不大(幾十M至一兩百M),能夠在較短期內完成垃圾收集(幾十MS至一百多MS),只要不頻繁發生,這是能夠接受的。

 

(2)  parNew (複製算法):serial 收集器的多線程版本,是許多運行在 server 模式下的虛擬機首選的新生代收集器。

 應用場景:

Server模式下,ParNew收集器是一個很是重要的收集器,由於除Serial外,目前只有它能與CMS收集器配合工做;但在單個CPU環境中,不會比Serail收集器有更好的效果,由於存在線程交互開銷。

 

(3)  parallel scaverge(複製算法):其餘與ParNew相似,特別之處在於:CMS等收集器的關注點是儘量地縮短垃圾收集時用戶線程的停頓時間;而其目標是達到一個可控制的吞吐量,適合在後臺運算,沒有太多的交互。

應用場景:

高吞吐量爲目標,即減小垃圾收集時間,讓用戶代碼得到更長的運行時間;當應用程序運行在具備多個CPU上,對暫停時間沒有特別高的要求時,即程序主要在後臺進行計算,而不須要與用戶進行太多交互;例如,那些執行批量處理、訂單處理、工資支付、科學計算的應用程序。

 

(4)  serial old(標記-清理):serial 的老年代版本,單線程,

應用場景:

主要用於Client模式;而在Server模式有兩大用途:

(A)、在JDK1.5及以前,與Parallel Scavenge收集器搭配使用(JDK1.6有Parallel Old收集器可搭配);

(B)、做爲CMS收集器的後備預案,在併發收集發生Concurrent Mode Failure時使用(後面詳解);

 

(5)  parallel old(標記-清理):parallel scaverge 老年代的版本,多線程 

應用場景:

JDK1.6及以後用來代替老年代的Serial Old收集器;特別是在Server模式,多CPU的狀況下;這樣在注重吞吐量以及CPU資源敏感的場景,就有了Parallel Scavenge加Parallel Old收集器的"給力"應用組合。

 

(6)  cms(標記-清除) :一種以獲取最短回收停頓時間爲目標的收集器 「標記-清除」,有 4 個過程:

1)初始標記(僅標記一下GC Roots能直接關聯到的對象;速度很快;但須要"Stop The World";)

2)併發標記(進行GC Roots Tracing的過程;剛纔產生的集合中標記出存活對象;應用程序也在運行;並不能保證能夠標記出全部的存活對象;)

3)從新標記(爲了修正併發標記期間因用戶程序繼續運做而致使標記變更的那一部分對象的標記記錄;須要"Stop The World",且停頓時間比初始標記稍長,但遠比並發標記短;採用多線程並行執行來提高效率;)

4)併發清除 (回收全部的垃圾對象)。

優勢:併發收集,低停頓;

缺點:

1)不能處理浮動垃圾,因爲 cms 併發清除階段,用戶線程還在繼續執行,伴隨程序進行,還有新的垃圾產生,這一部分垃圾發生在標記以後,cms 沒法在當次收集時處理他們,只能留到下一次gc。可能出現"Concurrent Mode Failure"失敗。這時JVM啓用後備預案:臨時啓用Serail Old收集器,而致使另外一次Full GC的產生。

2)對 cpu 資源敏感。併發收集雖然不會暫停用戶線程,但由於佔用一部分CPU資源,仍是會致使應用程序變慢,總吞吐量下降。cms 默認啓動的回收線程數是(cpu 數量+3)/4。當CPU數量多於4個,收集線程佔用的CPU資源多於25%,對用戶程序影響可能較大;不足4個時,影響更大,可能沒法接受。

3)產生大量內存碎片 ,大對象分配困難,須要提早觸發另外一次Full GC動做。

  

(7)G1:是一款面向服務端應用的商用垃圾收集器。具有四個特色:

1)並行與併發:能充分利用多CPU、多核環境下的硬件優點;可使用多個CPU並行來縮短"Stop The World"停頓時間;也能夠併發讓垃圾收集與用戶程序同時進行。

2)分代收集:能獨立管理整個GC堆(新生代和老年代),而不須要與其餘收集器搭配;可以採用不一樣方式處理不一樣時期的對象;雖然保留分代概念,但Java堆的內存佈局有很大差異;將整個堆劃分爲多個大小相等的獨立區域(Region);新生代和老年代再也不是物理隔離,它們都是一部分Region(不須要連續)的集合。

3)空間整合,不產生碎片:從總體看,是基於標記-整理算法;從局部(兩個Region間)看,是基於複製算法;都不會產生內存碎片,有利於長時間運行,不會提早觸發一次GC。

4)可預測的停頓:低停頓的同時實現高吞吐量;G1除了追求低停頓處,還能創建可預測的停頓時間模型;能夠明確指定M毫秒時間片內,垃圾收集消耗的時間不超過N毫秒。

 

6、如何減小GC出現的次數:

1.對象不用時顯示置null。

2.少用System.gc()。

3.儘可能少用靜態變量。

4.儘可能使用StringBuffer,而不用String累加字符串。

5.分散對象建立或刪除的時間。

6.少用finalize函數。

7.若是須要使用常常用到的圖片,可使用軟引用類型,它能夠儘量將圖片保存在內存中,供程序調用,而不引發OOM。

8.能用基本類型就不用基本類型封裝類。

9.增大-Xmx的值。

相關文章
相關標籤/搜索