JVM記憶體回收機制

來源:互聯網
上載者:User

標籤:jvm   記憶體回收   gc   分代回收   回收演算法   

範圍:要回收哪些地區

在JVM五種記憶體模型中,有三個是不需要進行記憶體回收的:程式計數器、JVM棧、本地方法棧。因為它們的生命週期是和線程同步的,隨著線程的銷毀,它們佔用的記憶體會自動釋放,所以只有方法區和堆需要進行GC。


前提:如何判斷對象已死

所有的垃圾收集演算法都面臨同一個問題,那就是找出應用程式不可到達的記憶體塊,將其釋放,這裡面得不可到達主要是指應用程式已經沒有記憶體塊的引用了,     在JAVA中,某個對象對應用程式是可到達的是指:這個對象被根(根主要是指類的靜態變數,或者活躍在所有線程棧的對象的引用)引用或者對象被另一個可到達的對象引用。

 

引用計數演算法

引用計數是最簡單直接的一種方式,這種方式在每一個對象中增加一個引用的計數,這個計數代表當前程式有多少個引用引用了此對象,如果此對象的引用計數變為0,那麼此對象就可以作為垃圾收集器的目標對象來收集。

優點:簡單,直接,不需要暫停整個應用

缺點:1.需要編譯器的配合,編譯器要產生特殊的指令來進行引用計數的操作;2.不能處理循環參考的問題

因此這種方法是垃圾收集的早期策略,現在很少使用。Sun的JVM並沒有採用引用計數演算法來進行記憶體回收,是基於根搜尋演算法的。

根搜尋演算法

通過一系列的名為“GC Root”的對象作為起點,從這些節點向下搜尋,搜尋所走過的路徑稱為引用鏈(Reference Chain),當一個對象到GC Root沒有任何引用鏈相連時,則該對象不可達,該對象是不可使用的,垃圾收集器將回收其所佔的記憶體。

 

在java語言中,可作為GCRoot的對象包括以下幾種對象:

a. java虛擬機器棧(棧幀中的本地變數表)中的引用的對象。

b.方法區中的類靜態屬性引用的對象。

c.方法區中的常量引用的對象。

d.本地方法棧中JNI本地方法的引用對象。

 

判斷無用的類:

(1).該類的所有執行個體都已經被回收,即java堆中不存在該類的執行個體對象。

(2).載入該類的類載入器已經被回收。

(3).該類所對應的java.lang.Class對象沒有任何地方被引用,無法在任何地方通過反射機制訪問該類的方法。

四種引用

GC在收集一個對象的時候會判斷是否有引用指向對象,在JAVA中的引用主要有四種:

⑴    強引用(Strong Reference)

強引用是使用最普遍的引用。如果一個對象具有強引用,那記憶體回收行程絕不會回收它。當記憶體空間不足,Java虛擬機器寧願拋出OutOfMemoryError錯誤,使程式異常終止,也不會靠隨意回收具有強引用的對象來解決記憶體不足的問題。

⑵    軟引用(Soft Reference)

如果一個對象只具有軟引用,則記憶體空間足夠,記憶體回收行程就不會回收它;如果記憶體空間不足了,就會回收這些對象的記憶體。只要記憶體回收行程沒有回收它,該對象就可以被程式使用。軟引用可用來實現記憶體敏感的快取。

下面舉個例子,假如有一個應用需要讀取大量的本地圖片,如果每次讀取圖片都從硬碟讀取,則會嚴重影響效能,但是如果全部載入到記憶體當中,又有可能造成記憶體溢出,此時使用軟引用可以解決這個問題。

設計思路是:用一個HashMap來儲存圖片的路徑和相應圖片對象關聯的軟引用之間的映射關係,在記憶體不足時,JVM會自動回收這些緩衝圖片對象所佔用的空間,從而有效地避免了記憶體溢出的問題。

軟引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果軟引用所引用的對象被記憶體回收行程回收,Java虛擬機器就會把這個軟引用加入到與之關聯的引用隊列中。

(3)弱引用(Weak Reference)

 弱引用與軟引用的區別在於:只具有弱引用的對象擁有更短暫的生命週期。在記憶體回收行程線程掃描它所管轄的記憶體地區的過程中,一旦發現了只具有弱引用的對象,不管當前記憶體空間足夠與否,都會回收它的記憶體。不過,由於記憶體回收行程是一個優先順序很低的線程,因此不一定會很快發現那些只具有弱引用的對象。

弱引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果弱引用所引用的對象被記憶體回收,Java虛擬機器就會把這個弱引用加入到與之關聯的引用隊列中。

⑶    虛引用(Phantom Reference)

“虛引用”顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用並不會決定對象的生命週期。如果一個對象僅持有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被記憶體回收行程回收。

虛引用主要用於檢測對象是否已經從記憶體中刪除,跟蹤對象被記憶體回收行程回收的活動。虛引用與軟引用和弱引用的一個區別在於:虛引用必須和引用隊列 (ReferenceQueue)聯合使用。當記憶體回收行程準備回收一個對象時,如果發現它還有虛引用,就會在回收對象的記憶體之前,把這個虛引用加入到與之關聯的引用隊列中。

ReferenceQueue queue = new ReferenceQueue ();PhantomReference pr = new PhantomReference (object, queue);


策略:JVM中的垃圾收集策略標記-清除演算法


標記清除收集器停止所有的工作,從根掃描每個活躍的對象,然後標記掃描過的對象,標記完成以後,清除那些沒有被標記的對象。

優點:

1 解決循環參考的問題

2       不需要編譯器的配合,從而就不執行額外的指令

缺點:

1. 每個活躍的對象都要進行掃描,收集暫停時間比較長。

2.標記-清除演算法採用從根集合進行掃描,對存活的對象對象標記,標記完畢後,再掃描整個空間中未被標記的對象,進行回收,如所示。

標記-清除演算法不需要進行對象的移動,並且僅對不存活的對象進行處理,在存活對象比較多的情況下極為高效,但由於標記-清除演算法直接回收不存活的對象,因此會造成記憶體片段。

複製演算法


複製收集器將記憶體分為兩塊一樣大小空間,某一個時刻,只有一個空間處於活躍的狀態,當活躍的空間滿的時候,GC就會將活躍的對象複製到未使用的空間中去,原來不活躍的空間就變為了活躍的空間。

優點:

1 只掃描可以到達的對象,不需要掃描所有的對象,從而減少了應用暫停時間

缺點:

1.需要額外的空間消耗,某一個時刻,總是有一塊記憶體處於未使用狀態

2.複製對象需要一定的開銷

 

複製演算法採用從根集合掃描,並將存活對象複製到一塊新的,沒有使用過的空間中,這種演算法當空間存活的對象比較少時,極為高效,但是帶來的成本是需要一塊記憶體交換空間用於進行對象的移動。

標記-整理演算法


標記整理收集器汲取了標記清除和複製收集器的優點,它分兩個階段執行,在第一個階段,首先掃描所有活躍的對象,並標記所有活躍的對象,第二個階段首先清除未標記的對象,然後將活躍的的對象複製到堆得底部

該演算法極大的減少了記憶體片段,並且不需要像複製演算法一樣需要兩倍的空間。

標記-整理演算法採用標記-清除演算法一樣的方式進行對象的標記,但在清除時不同,在回收不存活的對象佔用的空間後,會將所有的存活對象往左端空閑空間移動,並更新對應的指標。標記-整理演算法是在標記-清除演算法的基礎上,又進行了對象的移動,因此成本更高,但是卻解決了記憶體片段的問題。

分代回收演算法

垃圾分代回收演算法(GenerationalCollecting)基於對對象生命週期分析後得出的記憶體回收演算法。

因為我們前面有介紹,記憶體主要被分為三塊,新生代、舊生代、持久代。三代的特點不同,造就了他們所用的GC演算法不同,新生代適合那些生命週期較短,頻繁建立及銷毀的對象,舊生代適合生命週期相對較長的對象,持久代在Sun HotSpot中就是指方法區(有些JVM中根本就沒有持久代這中說法)。首先介紹下新生代、舊生代、持久代的概念及特點。


Young(年輕代、新生代):JVM specification中的 Heap的一部份年輕代分三個區。一個Eden區,兩個Survivor區。大部分對象在Eden區中產生。當Eden區滿時,還存活的對象將被複製到Survivor區(兩個中的一個),當這個Survivor區滿時,此區的存活對象將被複製到另外一個Survivor區,當這個Survivor去也滿了的時候,從第一個Survivor區複製過來的並且此時還存活的對象,將被複製舊生代。需要注意,Survivor的兩個區是對稱的,沒先後關係,所以同一個區中可能同時存在從Eden複製過來對象,和從前一個Survivor複製過來的對象,而複製到年老區的只有從第一個Survivor去過來的對象。而且,Survivor區總有一個是空的。

新生代使用複製演算法和標記-清除垃圾收集演算法,新生代中98%的對象是朝生夕死的短生命週期對象,所以不需要將新生代劃分為容量大小相等的兩部分記憶體,而是將新生代分為Eden區,Survivor from(Survivor 0)和Survivor to(Survivor1)三部分,其佔新生代記憶體容量預設比例分別為8:1:1,其中Survivor from和Survivor to總有一個地區是空白,只有Eden和其中一個Survivor總共90%的新生代容量用於為新建立的對象分配記憶體,只有10%的Survivor記憶體浪費,當新生代記憶體空間不足需要進行記憶體回收時,仍然存活的對象被複製到空白的Survivor記憶體地區中,Eden和非空白的Survivor進行標記-清理回收,兩個Survivor地區是輪換的。

如果空白Survivor空間無法存放下仍然存活的對象時,使用記憶體配置擔保機制,直接將新生代依然存活的對象複製到年老代記憶體中,同時對於建立大對象時,如果新生代中無足夠的連續記憶體時,也直接在年老代中分配記憶體空間。

Java虛擬機器對新生代的記憶體回收稱為Minor GC,次數比較頻繁,每次回收時間也比較短。

使用java虛擬機器-Xmn參數可以指定新生代記憶體大小。

 

Tenured(年老代、舊生代):JVMspecification中的 Heap的一部份年老代存放從年輕代存活的對象。一般來說年老代存放的都是生命期較長的對象。

年老代中的對象一般都是長生命週期對象,對象的存活率比較高,因此在年老代中使用標記-整理記憶體回收演算法。

Java虛擬機器對年老代的記憶體回收稱為MajorGC/Full GC,次數相對比較少,每次回收的時間也比較長。

java虛擬機器-Xms參數可以指定最小記憶體大小,-Xmx參數可以指定最大記憶體大小,這兩個參數分別減去Xmn參數指定的新生代記憶體大小,可以計算出年老代最小和最大記憶體容量。

 

Perm(持久代、永久代): JVM specification中的 Method area 用於存放靜態檔案,如今Java類、方法等。持久代對記憶體回收沒有顯著影響,但是有些應用可能動態產生或者調用一些class,例如Hibernate等,在這種時候需要設定一個比較大的持久代空間來存放這些運行過程中新增的類。

java虛擬機器記憶體中的方法區在SunHotSpot虛擬機器中被稱為永久代,是被各個線程共用的記憶體地區,它用於儲存已被虛擬機器載入的類資訊、常量、靜態變數、即時編譯後的代碼等資料。永久代記憶體回收比較少,效率也比較低,但是也必須進行記憶體回收,否則會永久代記憶體不夠用時仍然會拋出OutOfMemoryError異常。

永久代也使用標記-整理演算法進行記憶體回收,java虛擬機器參數-XX:PermSize和-XX:MaxPermSize可以設定永久代的初始大小和最大容量。

記憶體回收過程

上面我們看了JVM的記憶體分區管理,現在我們來看JVM的記憶體回收工作是怎樣運作的。

首先當啟動J2EE應用伺服器時,JVM隨之啟動,並將JDK的類和介面,應用伺服器運行時需要的類和介面以及J2EE應用的類和介面定義檔案也及編譯後的Class檔案或JAR包中的Class檔案裝載到JVM的永久儲存區。在伊甸園中建立JVM,應用伺服器運行時必須的JAVA對象,建立J2EE應用啟動時必須建立的JAVA對象;J2EE應用啟動完畢,可對外提供服務。

JVM在伊甸園區根據使用者的每次請求建立相應的JAVA對象,當伊甸園的空間不足以用來建立新JAVA對象的時候,JVM的記憶體回收行程執行對伊甸園區的記憶體回收工作,銷毀那些不再被其他對象引用的JAVA對象(如果該對象僅僅被一個沒有其他對象引用的對象引用的話,此對象也被歸為沒有存在的必要,依此類推),並將那些被其他對象所引用的JAVA對象移動到倖存者0區。

如果倖存者0區有足夠空間存放則直接放到倖存者0區;如果倖存者0區沒有足夠空間存放,則JVM的記憶體回收行程執行對倖存者0區的記憶體回收工作,銷毀那些不再被其他對象引用的JAVA對象,並將那些被其他對象所引用的JAVA對象移動到倖存者1區。

如果倖存者1區有足夠空間存放則直接放到倖存者1區;如果倖存者1區沒有足夠空間存放,則JVM的記憶體回收行程執行對倖存者1區的記憶體回收工作,銷毀那些不再被其他對象引用的JAVA對象,並將那些被其他對象所引用的JAVA對象移動到養老區。

如果養老區有足夠空間存放則直接放到養老區;如果養老區沒有足夠空間存放,則JVM的記憶體回收行程執行對養老區區的記憶體回收工作,銷毀那些不再被其他對象引用的JAVA對象,並保留那些被其他對象所引用的JAVA對象。

如果到最後養老區,倖存者1區,倖存者0區和伊甸園區都沒有空間的話,則JVM會報告“JVM堆空間溢出(java.lang.OutOfMemoryError: Java heap space)”,也即是在堆空間沒有空間來建立對象。

這就是JVM的記憶體分區管理,相比不分區來說;一般情況下,記憶體回收的速度要快很多;因為在沒有必要的時候不用掃描整片記憶體而節省了大量時間。

對象的空間分配和晉陞

(1)對象優先在Eden上分配

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

虛擬機器提供了-XX:PretenureSizeThreshold參數,大於這個參數值的對象將直接分配到老年代中。因為新生代採用的是標記-複製策略,在Eden中分配大對象將會導致Eden區和兩個Survivor區之間大量的記憶體拷貝。

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

對象在Survivor區中每熬過一次MinorGC,年齡就增加1歲,當它的年齡增加到一定程度(預設為15歲)時,就會晉陞到老年代中。


觸發:何時開始GC

Minor GC(新生代回收)的觸發條件比較簡單,Eden空間不足就開始進行Minor GC回收新生代。

而Full GC(老年代回收,一般伴隨一次MinorGC)則有幾種觸發條件:

(1)老年代空間不足

(2)PermSpace空間不足

(3)統計得到的MinorGC晉陞到老年代的平均大小大於老年代的剩餘空間

 

這裡注意一點:PermSpace並不等同於方法區,只不過是HotspotJVM用PermSpace來實現方法區而已,有些虛擬機器沒有PermSpace而用其他機制來實現方法區。


實現:JVM中的回收器類型串列回收器(Serial Collector)

單線程執行回收操作,回收期間暫停所有應用線程的執行,client模式下的預設回收器

 

年輕代的回收演算法(Minor Collection):把Eden區的存活對象移到To區,To區裝不下直接移到年老代,把From區的移到To區,To區裝不下直接移到年老代,From區裡面年齡很大的升級到年老代。 回收結束之後,Eden和From區都為空白,此時把From和To的功能互換,From變To,To變From,每一輪迴收之前To都是空的。設計的選型為複製。

 

年老代的回收演算法(Full Collection):年老代的回收分為三個步驟,標記(Mark)、清除(Sweep)、合并(Compact)。標記階段把所有存活的對象標記出來,清除階段釋放所有死亡的對象,合并階段把所有活著的對象合并到年老代的前部分,把閒置片段都留到後面。設計的選型為合并,減少記憶體的片段。

並行回收器(Parallel Collector)

使用多個線程同時進行記憶體回收,多核環境裡面可以充分的利用CPU資源,減少回收時間,增加JVM生產率,Server模式下的預設回收器。與串列回收器相同,回收期間暫停所有應用線程的執行。

 

年輕代的回收演算法(Minor Collection):使用多個線程回收垃圾,每一個線程的演算法與串列回收器相同。

 

年老代的回收演算法(Full Collection):年老代依然是單線程的,與串列回收器相同。

並行合并收集器(Parallel Compacting Collection)

年輕代和年老代的回收都是用多執行緒。與並行回收器相比,年老代的回收時間更短,從而減少了暫停時間間隔(Pause time)。

 

年輕代的回收演算法(Minor Collection):與並行回收器(ParallelCollector)相同

 

年老代的回收演算法(Full Collection) :年老代分為三個步驟,標記、統計、合并。這裡用到分的思想,把年老代劃分為很多個固定大小的區(region)。標記階段,把所有存活的對象劃分為N組(應該與回收線程數相同),每一個線程獨立的負責自己那一組,標記存活對象的位置以及所在區(Region)的存活率資訊,標記為並行的。統計階段,統計每一個區(Region)的存活率,原則上靠前面的存活率較高,從前到後,找到值得合并的開始位置(絕大多數對象都存活的區不值得合并),統計階段是串列的(單線程)。合并階段,依據統計階段的資訊,多線程並行的把存活的對象從一個區(Region)複製到另外一個區(Region)。

並發標記清除回收器(Concurrent Mark-Sweep Collector)

又名低延時收集器(Low-latencyCollector),通過各種手段使得應用程式被掛起的時間最短。基本與應用程式並發地執行回收操作,沒有合并和複製操作。

 

年輕代的回收演算法(Minor Collection):與並行回收器(ParallelCollector)相同

 

年老代的回收演算法(Full Collection) :分為四個步驟,初始標記(Initial Mark)、並發標記(ConcurrentMark)、再次標記(Remark)、以及並發清理(Concurrent Sweep)。特別注意,沒有合併作業,所以會有片段。

初始化階段: 暫停應用線程,找出所有存活的對象,耗時比較短,回收器使用單線程。

並發標記階段: 回收器標記操作與應用並發運行,回收器使用單線程標記存活對象。

再次標記:並發標記階段由於應用程式也在運行,這個過程中可能新增或者修改對象。所以再次暫停應用線程,找出所有修改的對象,使用多線程標記。

並發清理:回收器清理操作與應用並發運行,回收器使用單線程清理死亡對象。

 

著作權聲明:本文為博主原創文章,未經博主允許不得轉載。

JVM記憶體回收機制

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.