JVM記憶體回收演算法 總結及匯總

來源:互聯網
上載者:User

標籤:gc   記憶體回收   

先看一眼JVM虛擬機器運行時的記憶體模型:

1.方法區 Perm(永久代、非堆)

2.虛擬機器棧

3.本地方法棧 (Native方法)

4.堆

5.程式計數器

1 首先的問題是:jvm如何知道那些對象需要回收 ?

目前兩種標識演算法、三種回收演算法、兩種清除演算法、三種收集器

  • 引用計數法

每個對象上都有一個引用計數,對象每被引用一次,引用計數器就+1,對象引用被釋放,引用計數器-1,直到對象的引用計數為0,對象就標識可以回收

這個可以用資料演算法中的圖形表示,對象A-對象B-對象C 都有引用,所以不會被回收,對象B由於沒有被引用,沒有路徑可以達到對象B,對象B的引用計數就就是0,對象B就會被回收。

 

 

但是這個演算法有明顯的缺陷,對於循環參考的情況下,循環參考的對象就不會被回收。例如:對象A,對象B 循環參考,沒有其他的對象引用A和B,則A和B 都不會被回收。

 

  • root搜尋演算法

這種演算法目前定義了幾個root,也就是這幾個對象是jvm虛擬機器不會被回收的對象,所以這些對象引用的對象都是在使用中的對象,這些對象未使用的對象就是即將要被回收的對象。簡單就是說:如果對象能夠達到root,就不會被回收,如果對象不能夠達到root,就會被回收。

如:對象D訪問不到根對象,所以就會被回收

以下對象會被認為是root對象:

  • 被啟動類(bootstrap載入器)載入的類和建立的對象
  • jvm運行時方法區類靜態變數(static)引用的對象
  • jvm運行時方法去常量池引用的對象
  • jvm當前運行線程中的虛擬機器棧變數表引用的對象
  • 本地方法棧中(jni)引用的對象

由於這種演算法即使存在互相引用的對象,但如果這兩個對象無法訪問到根對象,還是會被回收。如:對象C和對象D互相引用,但是由於無法訪問根,所以會被回收。

jvm在確定是否回收的對象的時候採用的是root搜尋演算法來實現。

在root搜尋演算法的裡面,我們說的引用這裡都指定的是強參考關聯性。所謂強參考關聯性,就是通過用new 方式建立的對象,並且顯示關聯的對象

<span style="font-family:Microsoft YaHei;font-size:14px;">Object obj = new Object();</span>

以上就是代表的是強參考關聯性,變數obj 強引用了 Object的一個對象。

java裡面有四種應用關係,從強到弱分別為:

Strong Reference(強引用) –>Weak Reference (弱引用) -> Soft Reference(軟引用) – > Phantom Reference(引用)

 

Strong Reference : 只有在引用對象root不可達的情況下才會標識為可回收,記憶體回收才可能進行回收

Weak Reference :即使在root演算法中 其引用的對象root可達到,但是如果jvm堆記憶體 不夠的時候,還是會被回收。

Soft Reference : 無論其引用的對象是否root可達,在響應記憶體需要時,由記憶體回收判斷是否需要回收。

Phantom Reference :在回收器確定其指示對象可另外回收之後,被加入記憶體回收隊列.



  • 標記-清除
標記清除的演算法最簡單,主要是標記出來需要回收的對象,然後然後把這些對象在記憶體的資訊清除。如何標記需要回收的對象,在上一篇文章裡面已經有說明。

 

  • 標記-清除-壓縮

這個演算法是在標記-清除的演算法之上進行一下壓縮空間,重新移動對象的過程。因為標記清除演算法會導致很多的留下來的記憶體空間片段,隨著片段的增多,嚴重影響記憶體讀寫的效能,所以在標記-清除之後,會對記憶體的片段進行整理。最簡單的整理就是把對象壓縮到一邊,留出另一邊的空間。由於壓縮空間需要一定的時間,會影響垃圾收集的時間。

 

  • 標記-清除-複製

這個演算法是吧記憶體配置為兩個空間,一個空間(A)用來負責裝載正常的對象資訊,,另外一個記憶體空間(B)是記憶體回收用的。每次把空間A中存活的對象全部複製到空間B裡面,在一次性的把空間A刪除。這個演算法在效率上比標記-清除-壓縮高,但是需要兩塊空間,對記憶體要求比較大,記憶體的利用率比較低。適用於短生存期的對象,持續複製長生存期的對象則導致效率降低

 

由於現在的處理器都是多核的,處理器的效能得到了極大的提升,所以在此基礎上有產生了幾種垃圾收集演算法。主要包括兩種演算法

  • 並行標記清除

所謂並行,就是原來記憶體回收只是一個線程進行。現在建立多個記憶體回收線程。並行的進行標記和清除。比如把需要標記的對象平均分配到多個線程之後,當標記完成之後,多個線程進行清除。

 

  • 並發標記清除

所謂並發,就是應用程式和記憶體回收可以同時執行。在標記清除演算法中,在標記對象和清除對象,以及壓縮對象的情況下是需要暫停應用的。那麼並行標記清除壓縮演算法則是在標記清除壓縮演算法的基礎上,把標記清除壓縮演算法分為以下幾個過程

初始標記->並發標記->重新標記->並發清除->重設

 

以上幾種演算法是記憶體回收的基本演算法,jvm記憶體回收就是在以上幾種演算法為基礎的,在以上幾種演算法的基礎上,java記憶體回收行程可以分為以下幾種:

  • 串列收集器

用單線程處理所有記憶體回收工作,因為無需多線程互動,所以效率比較高。但是,也無法使用多處理器的優勢,所以此收集器適合單一處理器機器

單線程收集器。在目前多核伺服器端啟動並執行情況下,效率比較低。比較適合堆記憶體小的情況下使用。

  • 並行收集器

用多執行緒所有記憶體回收工作,利用多核處理器的優勢。但是如果線程數量過多,導致線程之間頻繁調度,也會影響效能。一半並行收集的線程是處理器的個數。

“對輸送量有高要求”,多CPU、對應用回應時間無要求的中、大型應用。舉例:幕後處理、科學計算。

  • 並發收集器

並發收集器主要減少年老代的暫停時間,他在應用不停止的情況下使用獨立的記憶體回收線程,跟蹤可達對象。在每個年老代記憶體回收周期中,在收集初期並發收集器 會對整個應用進行簡短的暫停(初始標記的過程),在收集中還會再暫停一次。第二次暫停會比第一次稍長(重新標記的過程),在此過程中多個線程同時進行記憶體回收工作。

並發收集器使用處理器換來短暫的停頓時間。在一個N個處理器的系統上,並發收集部分使用K/N個可用處理器進行回收,一般情況下1<=K<=N/4。

在只有一個處理器的主機上使用並發收集器,設定為incremental mode模式也可獲得較短的停頓時間。

浮動垃圾:由於在應用啟動並執行同時進行記憶體回收,所以有些垃圾可能在記憶體回收進行完成時產生,這樣就造成了“Floating Garbage”,這些垃圾需要在下次記憶體回收周期時才能回收掉。所以,並發收集器一般需要20%的預留空間用於這些浮動垃圾。

Concurrent Mode Failure:並發收集器在應用運行時進行收集,所以需要保證堆在記憶體回收的這段時間有足夠的空間供程式使用,否則,記憶體回收還未完成,堆空間先滿了。這種情況下將會發生“併發模式失敗”,此時整個應用將會暫停,進行記憶體回收。

並發收集器,在記憶體回收的時候採用並發標記清除演算法的收集器

對回應時間要求高的,多CPU,大型應用。比如頁面請求/web伺服器。前端業務系統用的比較多。

 

串列處理器:

--適用情況:資料量比較小(100M左右);單一處理器下並且對回應時間無要求的應用。

--缺點:只能用於小型應用

平行處理器:

--適用情況:“對輸送量有高要求”,多CPU、對應用回應時間無要求的中、大型應用。舉例:幕後處理、科學計算。

--缺點:垃圾收集過程中應用回應時間可能加長

並發處理器:

--適用情況:“對回應時間有高要求”,多CPU、對應用回應時間有較高要求的中、大型應用。舉例:Web伺服器/應用伺服器、電信交換、整合式開發環境。

JDK5.0適用的分代記憶體回收演算法

       分代的記憶體回收策略,是基於這樣一個事實:不同的對象的生命週期是不一樣的。因此,不同生命週期的對象可以採取不同的收集方式,以便提高回收效率。

       在Java程式啟動並執行過程中,會產生大量的對象,其中有些對象是與商務資訊相關,比如Http請求中的Session對象、線程、Socket串連,這類對象跟業務直接掛鈎,因此生命週期比較長。但是還有一些對象,主要是程式運行過程中產生的臨時變數,這些對象生命週期會比較短,比如:String對象,由於其不變類的特性,系統會產生大量的這些對象,有些對象甚至只用一次即可回收。

       試想,在不進行對象存活時間區分的情況下,每次記憶體回收都是對整個堆空間進行回收,花費時間相對會長,同時,因為每次回收都需要遍曆所有存活對象,但實際上,對於生命週期長的對象而言,這種遍曆是沒有效果的,因為可能進行了很多次遍曆,但是他們依舊存在。因此,分代記憶體回收採用分治的思想,進行代的劃分,把不同生命週期的對象放在不同代上,不同代上採用最適合它的記憶體回收方式進行回收。

如何分代

:

       虛擬機器中的共劃分為三個代:年輕代(Young Generation)、年老點(Old Generation)和持久代(Permanent Generation)。其中持久代主要存放的是Java類的類資訊,與垃圾收集要收集的Java對象關係不大。年輕代和年老代的劃分是對垃圾收集影響比較大的。

年輕代:

      所有新產生的對象首先都是放在年輕代的。年輕代的目標就是儘可能快速的收集掉那些生命週期短的對象。年輕代分三個區。一個Eden區,兩個Survivor區(一般而言)。大部分對象在Eden區中產生。當Eden區滿時,還存活的對象將被複製到Survivor區(兩個中的一個),當這個Survivor區滿時,此區的存活對象將被複製到另外一個Survivor區,當這個Survivor去也滿了的時候,從第一個Survivor區複製過來的並且此時還存活的對象,將被複製“年老區(Tenured)”。需要注意,Survivor的兩個區是對稱的,沒先後關係,所以同一個區中可能同時存在從Eden複製過來 對象,和從前一個Survivor複製過來的對象,而複製到年老區的只有從第一個Survivor去過來的對象。而且,Survivor區總有一個是空的。同時,根據程式需要,Survivor區是可以配置為多個的(多於兩個),這樣可以增加對象在年輕代中的存在時間,減少被放到年老代的可能。

年老代:

      在年輕代中經曆了N次記憶體回收後仍然存活的對象,就會被放到年老代中。因此,可以認為年老代中存放的都是一些生命週期較長的對象。

持久代:

      用於存放靜態檔案,如今Java類、方法等。持久代對記憶體回收沒有顯著影響,但是有些應用可能動態產生或者調用一些class,例如Hibernate等,在這種時候需要設定一個比較大的持久代空間來存放這些運行過程中新增的類。持久代大小通過-XX:MaxPermSize=&lt;N>進行設定。

什麼情況下觸發記憶體回收

由於對象進行了分代處理,因此記憶體回收地區、時間也不一樣。GC有兩種類型:Scavenge GC和Full GC。

GC類型 
GC有兩種類型:Scavenge GC和Full GC。 

1. Scavenge GC 

一般情況下,當新對象產生,並且在Eden申請空間失敗時,就好觸發Scavenge GC,堆Eden地區進行GC,清除非存活對象,並且把尚且存活的對象移動到Survivor區。然後整理Survivor的兩個區。 
2. Full GC 
對整個堆進行整理,包括Young、Tenured和Perm。Full GC比Scavenge GC要慢,因此應該儘可能減少Full GC。有如下原因可能導致Full GC: 
* Tenured被寫滿 
* Perm域被寫滿 
* System.gc()被顯示調用 
* 上一次GC之後Heap的各域分配策略動態變化



 

 

 

 





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.