java記憶體回收機制學習總結

來源:互聯網
上載者:User

標籤:堆記憶體   哪些   空間   位置   field   操作   整理   電腦   模型   

 

  最近學習了一下java記憶體回收機制,將其主要內容大致總結一下:

1.什麼是記憶體回收機制

  java GC機制(garbage collection,垃圾收集,記憶體回收),是java特有的機制,作為java開發人員,一般不需要專門編寫記憶體回收和垃圾清理代碼。這是因為在java虛擬機器中,存在自動記憶體管理和垃圾清理機制,為java開發人員節省了大量的開發時間。

  概括地說,該機制對 JVM(Java Virtual Machine)中的記憶體進行標記,並確定哪些記憶體需要回收,根據一定的回收策略,自動的回收記憶體,永不停息(Nerver Stop)的保證JVM中的記憶體空間,放置出現記憶體泄露和溢出問題。

  下面就來說一說java記憶體回收機制的回收策略,不過首先得瞭解jvm記憶體模型。

2.jvm記憶體模型

  ①程式計數器:程式計數器是一個比較小的記憶體地區,用於指示當前線程所執行的位元組碼執行到了第幾行,可以理解為是當前線程的行號指標。位元組碼解譯器在工作時,會通過改變這個計數器的值來取下一條語句指令。每個程式計數器只用來記錄一個線程的行號,因此它是線程私人的。通俗點說就是,當cpu執行到一個線程的某個位置時,切換去執行另一個線程,當cpu下次再切回來時會繼續從上次切出去的位置繼續往下執行。怎麼確定上次切出去的位置,就是通過程式計數器實現的。

  ②虛擬機器棧:一個線程的每個方法在執行的同時,都會建立一個棧幀(Statck Frame),棧幀中儲存的有局部變數表、操作站、動態連結、方法出口等,當方法被調用時,棧幀在JVM棧中入棧,當方法執行完成時,棧幀出棧。

  局部變數表中儲存著方法的相關局部變數,包括各種基礎資料型別 (Elementary Data Type),對象的引用,返回地址等。在局部變數表中,只有long和double類型會佔 用2個局部變數空間(Slot,對於32位機器,一個Slot就是32個bit),其它都是1個Slot。需要注意的是,局部變數表是在編譯時間就已經確定 好的,方法運行所需要分配的空間在棧幀中是完全確定的,在方法的生命週期內都不會改變。

  虛擬機器棧中定義了兩種異常,如果線程調用的棧深度大於虛擬機器允許的最大深度,則拋出StatckOverFlowError(棧溢出);不過多 數Java虛擬機器都允許動態擴充虛擬機器棧的大小(有少部分是固定長度的),所以線程可以一直申請棧,知道記憶體不足,此時,會拋出 OutOfMemoryError(記憶體溢出)。

  每個線程對應著一個虛擬機器棧,因此虛擬機器棧也是線程私人的。

  ③本地方法棧:本地方法棧在作用,運行機制,異常類型等方面都與虛擬機器棧相同,唯一的區別是:虛擬機器棧是執行Java方法的,而本地方法棧是用來執行native方法的,在很多虛擬機器中(如Sun的JDK預設的HotSpot虛擬機器),會將本地方法棧與虛擬機器棧放在一起使用。

  本地方法棧也是線程私人的。

  ④堆區:堆區是理解Java GC機制最重要的地區,沒有之一。在JVM所管理的記憶體中,堆區是最大的一塊,堆區也是Java GC機制所管理的主要記憶體地區,堆區由所有線程共用,在虛擬機器啟動時建立。堆區的存在是為了儲存物件執行個體,原則上講,所有的對象都在堆區上分配記憶體(不過現代技術裡,也不是這麼絕對的,也有棧上直接分配的)

  ⑤方法區:方法區是各個線程共用的地區,用於儲存已經被虛擬機器載入的類資訊(即載入類時需要載入的資訊,包括版本、field、方法、介面等資訊)、final常量、靜態變數、編譯器即時編譯的代碼等

   3.記憶體回收演算法

  如何高效地進行記憶體回收。由於Java虛擬機器規範並沒有對如何?垃圾收集器做出明確的規定,因此各個廠商的虛擬機器可以採用不同的方式來實現垃圾收集器,所以在此只討論幾種常見的垃圾收集演算法的核心思想。

  ①.Mark-Sweep(標記-清除)演算法

  這是最基礎的記憶體回收演算法,之所以說它是最基礎的是因為它最容易實現,思想也是最簡單的。標記-清除演算法分為兩個階段:標記階段和清除階段。標記階段的任務是標記出所有需要被回收的對象,清除階段就是回收被標記的對象所佔用的空間。具體過程如所示:

  可以很容易看出標記-清除演算法實現起來比較容易,但是有一個比較嚴重的問題就是容易產生記憶體片段,片段太多可能會導致後續過程中需要為大對象分配空間時無法找到足夠的空間而提前觸發新的一次垃圾收集動作。

  ②.Copying(複製)演算法

  為瞭解決Mark-Sweep演算法的缺陷,Copying演算法就被提了出來。它將可用記憶體按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的記憶體用完了,就將還存活著的對象複製到另外一塊上面,然後再把已使用的記憶體空間一次清理掉,這樣一來就不容易出現記憶體片段的問題。具體過程如所示:

  這種演算法雖然實現簡單,運行高效且不容易產生記憶體片段,但是卻對記憶體空間的使用做出了高昂的代價,因為能夠使用的記憶體縮減到原來的一半。

  很顯然,Copying演算法的效率跟存活對象的數目多少有很大的關係,如果存活對象很多,那麼Copying演算法的效率將會大大降低。

  ③.Mark-Compact(標記-整理)演算法

  為瞭解決Copying演算法的缺陷,充分利用記憶體空間,提出了Mark-Compact演算法。該演算法標記階段和Mark-Sweep一樣,但是在完成標記之後,它不是直接清理可回收對象,而是將存活對象都向一端移動,然後清理掉端邊界以外的記憶體。具體過程如所示:

  

  ④.Generational Collection(分代收集)演算法

  分代收集演算法是目前大部分JVM的垃圾收集器採用的演算法。它的核心思想是根據對象存活的生命週期將記憶體劃分為若干個不同的地區。一般情況下將堆區劃分為老年代(Tenured Generation)和新生代(Young Generation),老年代的特點是每次垃圾收集時只有少量對象需要被回收,而新生代的特點是每次記憶體回收時都有大量的對象需要被回收,那麼就可以根據不同代的特點採取最適合的收集演算法。

  

  新生代(Young generation): 絕大多數最新被建立的對象會被分配到這裡,由於大部分對象在建立後會很快變得不可到達,所以很多個物件被建立在新生代,然後消失。對象從這個地區消失的過程我們稱之為”minor GC“。

 

  老年代(Old generation): 對象沒有變得不可達,並且從新生代中存活下來,會被拷貝到這裡。其所佔用的空間要比新生代多。也正由於其相對較大的空間,發生在老年代上的GC要比新生代少得多。對象從老年代中消失的過程,我們稱之為”major GC“(或者”full GC“)

  目前大部分垃圾收集器對於新生代都採取Copying演算法,因為新生代中每次記憶體回收都要回收大部分對象,也就是說需要複製的操作次數較少,但是實際中並不是按照1:1的比例來劃分新生代的空間的,一般來說是將新生代劃分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden空間和其中的一塊Survivor空間,當進行回收時,將Eden和Survivor中還存活的對象複製到另一塊Survivor空間中,然後清理掉Eden和剛才使用過的Survivor空間。

  而由於老年代的特點是每次回收都只回收少量對象,一般使用的是Mark-Compact演算法。

  注意,在堆區之外還有一個代就是永久代(Permanet Generation),它用來儲存class類、常量、方法描述等。對永久代的回收主要回收兩部分內容:廢棄常量和無用的類。

  4.典型的垃圾收集器

  

JDK7一共有5種GC類型:

 

  1. Serial GC

  2. Parallel GC

  3. Parallel Old GC (Parallel Compacting GC)

  4. Concurrent Mark & Sweep GC  (or “CMS”)

  5. Garbage First (G1) GC

 

其中,Serial GC不應該被用在伺服器上。這種GC類型在單核CPU的案頭電腦時代就存在了。使用Serial GC會顯著的降低應用的效能指標。

現在,讓我們共同學習每一種GC類型

 

①. Serial GC (-XX:+UseSerialGC)

 

新生代空間的GC方式我們在前面已經介紹過了,在老年代空間中的GC採取稱之為”mark-sweep-compact“的演算法。

 

  1. 演算法的第一步是標記老年代中依然存活對象。(標記)

  2. 第二步,從頭開始檢查堆記憶體空間,並且只留下依然倖存的對象。(清理)

 

最後一步,從頭開始,順序地填滿堆記憶體空間,並且將對記憶體空間分成兩部分:一個儲存著對象,另一個空著(壓縮)。

 

②. Parallel GC (-XX:+UseParallelGC)

 

 

 

圖 1: Serial GC 與 Parallel GC的區別

 

從中,你可以輕易地看出serial GC和parallel GC的區別,serial GC只使用一個線程執行GC,而parallel GC使用多個線程,因此parallel GC更高效。這種GC在記憶體充足以及多核的情況下會很有用,因此我們也稱之為”throughput GC“。

 

③. Parallel Old GC(-XX:+UseParallelOldGC)

 

Parallel Old GC在JDK5之後出現。與parallel GC相比,唯一的區別在於針對老年代的GC演算法。Parallel Old GC分為三步:標記-匯總-壓縮(mark – summary – compaction)。匯總(summary)步驟與清理(sweep)的不同之處在於,其將依然倖存的對象分發到GC預先處理好的不同地區,演算法相對清理來說略微複雜一點。

 

④. CMS GC (-XX:+UseConcMarkSweepGC)

 

 

 

圖 2: Serial GC & CMS GC

 

就像你從看到的那樣, CMS GC比我之前解釋的各種演算法都要複雜很多。第一步初始化標記(initial mark) 比較簡單。這一步驟只是尋找那些距離類載入器最近的倖存對象。因此,停頓的時間非常短暫。在之後的並行標記( concurrent mark )步驟,所有被倖存對象引用的對象會被確認是否已經被追蹤和校正。這一步的不同之處在於,在標記的過程中,其他的線程依然在執行。在重新標記(remark)步驟,會再次檢查那些在並行標記步驟中增加或者刪除的與倖存對象引用的對象。最後,在並行交換( concurrent sweep )步驟,轉交記憶體回收過程處理。記憶體回收工作會在其他線程的執行過程中展開。一旦採取了這種GC類型,由GC導致的暫停時間會極其短暫。CMS GC也被稱為低延遲GC。它經常被用在那些對於回應時間要求十分苛刻的應用之上。

 

當然,這種GC類型在擁有stop-the-world時間很短的優點的同時,也有如下缺點:

 

  •  它會比其他GC類型佔用更多的記憶體和CPU

  •  預設情況下不支援壓縮步驟

 

在使用這個GC類型之前你需要謹慎考慮。如果因為記憶體片段過多而導致壓縮任務不得不執行,那麼stop-the-world的時間要比其他任何GC類型都長,你需要考慮壓縮任務的發生頻率以及執行時間。

 

⑤. G1 GC

 

最後,我們來學習記憶體回收優先(G1)GC類型。

 

 

 

圖 3:  G1 GC的結構

 

   如果你想要理解G1,首先你要忘記你所學過的新生代和老年代的概念。正如你在所看到的,每個對象被分配到不同的格子,隨後GC執行。當一個地區裝滿之後,對象被分配到另一個地區,並執行GC。這中間不再有從新生代移動到老年代的三個步驟。這個類型是為了替代CMS GC而被建立的,因為CMS GC在長時間持續運作時會產生很多問題。

 參考文章:http://www.cnblogs.com/hnrainll/archive/2013/11/06/3410042.html

         http://www.cnblogs.com/dolphin0520/p/3783345.html

   

 

java記憶體回收機制學習總結

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.