JVM垃圾回收--年輕代、年老點和持久代


關鍵字約定

 

  • Young generation –>新生代
  • Tenured / Old Generation –>老年代
  • Perm Area –>永久代

 

年輕代:

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

  年老代:

  在年輕代中經歷了N次垃圾回收后仍然存活的對象,就會被放到年老代中。因此,可以認為年老代中存放的都是一些生命周期較長的對象。

  持久代:

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

 

重要的東東

  • 在Java中,對象實例都是在堆上創建。一些類信息,常量,靜態變量等存儲在方法區。堆和方法區都是線程共享的。
  • GC機制是由JVM提供,用來清理需要清除的對象,回收堆內存。
  • GC機制將Java程序員從內存管理中解放了出來,可以更關注於業務邏輯。
  • 在Java中,GC是由一個被稱為垃圾回收器的守護線程執行的。
  • 在從內存回收一個對象之前會調用對象的finalize()方法。
  • 作為一個Java開發者不能強制JVM執行GC;GC的觸發由JVM依據堆內存的大小來決定。
  • System.gc()和Runtime.gc()會向JVM發送執行GC的請求,但是JVM不保證一定會執行GC。
  • 如果堆沒有內存創建新的對象了,會拋出OutOfMemoryError

GC針對什么對象?

了解GC機制的第一步就是理解什么樣的對象會被回收。當一個對象通過一系列根對象(比如:靜態屬性引用的常量)都不可達時就會被回收。簡而言之,當一個對象的所有引用都為null。循環依賴不算做引用,如果對象A有一個指向對象B的引用,對象B也有一個指向對象A的引用,除此之外,它們沒有其他引用,那么對象A和對象B都、需要被回收(如下圖,ObjA和ObjB需要被回收)。
GC回收對象GC回收對象

堆內存是如何划分的?

Java中對象都在堆上創建。為了GC,堆內存分為三個部分,也可以說三代,分別稱為新生代,老年代和永久代。其中新生代又進一步分為Eden區,Survivor 1區和Survivor 2區(如下圖)。新創建的對象會分配在Eden區,在經歷一次Minor GC后會被移到Survivor 1區,再經歷一次Minor GC后會被移到Survivor 2區,直到升至老年代,需要注意的是,一些大對象(長字符串或數組)可能會直接存放到老年代。
堆內存堆內存
永久代有一些特殊,它用來存儲類的元信息。對於GC是否發生在永久代有許多不同的看法,在我看來這取決於采用的JVM。大家可以通過創建大量的字符串來觀察是發生了GC還是拋出了OutOfMemoryError。

GC算法

  1. 標記清除算法
    分為標記和清除兩個階段:首先標記出所有需要回收的對象,在標記完成后統一回收所有被標記的對象。該算法的缺點是效率不高並且會產生不連續的內存碎片。
    imageimage
  2. 復制算法
    把內存空間划為兩個區域,每次只使用其中一個區域。垃圾回收時,遍歷當前使用區域,把正在使用中的對象復制到另外一個區域中。次算法每次只處理正在使用中的對象,因此復制成本比較小,同時復制過去以后還能進行相應的內存整理,不會出現“碎片”問題。優點:實現簡單,運行高效。缺點:會浪費一定的內存。一般新生代采用這種算法。
    imageimage
  3. 標記整理算法
    標記階段與標記清除算法一樣。但后續並不是直接對可回收的對象進行清理,而是讓所有存活對象都想一端移動,然后清理。優點是不會造成內存碎片。
    imageimage

Java中垃圾回收器的類型

Java提供多種類型的垃圾回收器。JVM中的垃圾收集一般都采用“分代收集”,不同的堆內存區域采用不同的收集算法,主要目的就是為了增加吞吐量或降低停頓時間。

  • Serial收集器:新生代收集器,使用復制算法,使用一個線程進行GC,串行,其它工作線程暫停。
  • ParNew收集器:新生代收集器,使用復制算法,Serial收集器的多線程版,用多個線程進行GC,並行,其它工作線程暫停。使用-XX:+UseParNewGC開關來控制使用ParNew+Serial Old收集器組合收集內存;使用-XX:ParallelGCThreads來設置執行內存回收的線程數。
  • Parallel Scavenge 收集器:吞吐量優先的垃圾回收器,作用在新生代,使用復制算法,關注CPU吞吐量,即運行用戶代碼的時間/總時間。使用-XX:+UseParallelGC開關控制使用Parallel Scavenge+Serial Old收集器組合回收垃圾。
  • Serial Old收集器:老年代收集器,單線程收集器,串行,使用標記整理算法,使用單線程進行GC,其它工作線程暫停。
  • Parallel Old收集器:吞吐量優先的垃圾回收器,作用在老年代,多線程,並行,多線程機制與Parallel Scavenge差不錯,使用標記整理算法,在Parallel Old執行時,仍然需要暫停其它線程。
  • CMS(Concurrent Mark Sweep)收集器:老年代收集器,致力於獲取最短回收停頓時間(即縮短垃圾回收的時間),使用標記清除算法,多線程,優點是並發收集(用戶線程可以和GC線程同時工作),停頓小。使用-XX:+UseConcMarkSweepGC進行ParNew+CMS+Serial Old進行內存回收,優先使用ParNew+CMS(原因見Full GC和並發垃圾回收一節),當用戶線程內存不足時,采用備用方案Serial Old收集。

可以看Java Performance一書來獲取更多關於GC調優的信息。

與GC有關的JVM參數

做GC調優需要大量的實踐,耐心和對項目的分析。我曾經參與過高容量,低延遲的電商系統,在開發中我們需要通過分析造成Full GC的原因來提高系統性能,在這個過程中我發現做GC的調優很大程度上依賴於對系統的分析,系統擁有怎樣的對象以及他們的平均生命周期。
舉個例子,如果一個應用大多是短生命周期的對象,那么應該確保Eden區足夠大,這樣可以減少Minor GC的次數。可以通過-XX:NewRatio來控制新生代和老年代的比例,比如-XX:NewRatio=3代表新生代和老年代的比例為1:3。需要注意的是,擴大新生代的大小會減少老年代的大小,這會導致Major GC執行的更頻繁,而Major GC可能會造成用戶線程的停頓從而降低系統吞吐量。JVM中可以用NewSize和MaxNewSize參數來指定新生代內存最小和最大值,如果兩個參數值一樣,那么就相當於固定了新生代的大小。
個人建議,在做GC調優之前最好深入理解Java中GC機制,推薦閱讀Sun Microsystems提供的有關GC的文檔。這個鏈接可能會對理解GC機制提供一些幫助。下面的圖列出了各個區可用的一些JVM參數。
jvm參數jvm參數

Full GC和並發垃圾回收

並發垃圾回收器的內存回收過程是與用戶線程一起並發執行的。通常情況下,並發垃圾回收器可以在用戶線程運行的情況下完成大部分的回收工作,所以應用停頓時間很短。
但由於並發垃圾回收時用戶線程還在運行,所以會有新的垃圾不斷產生。作為擔保,如果在老年代內存都被占用之前,如果並發垃圾回收器還沒結束工作,那么應用會暫停,在所有用戶線程停止的情況下完成回收。這種情況稱作Full GC,這意味着需要調整有關並發回收的參數了。
由於Full GC很影響應用的性能,要盡量避免或減少。特別是如果對於高容量低延遲的電商系統,要盡量避免在交易時間段發生Full GC。

總結

  • 為了分代垃圾回收,Java堆內存分為3代:新生代,老年代和永久代。
  • 新的對象實例會優先分配在新生代,在經歷幾次Minor GC后(默認15次),還存活的會被移至老年代(某些大對象會直接在老年代分配)。
  • 永久代是否執行GC,取決於采用的JVM。
  • Minor GC發生在新生代,當Eden區沒有足夠空間時,會發起一次Minor GC,將Eden區中的存活對象移至Survivor區。Major GC發生在老年代,當升到老年代的對象大於老年代剩余空間時會發生Major GC。
  • 發生Major GC時用戶線程會暫停,會降低系統性能和吞吐量。
  • JVM的參數-Xmx和-Xms用來設置Java堆內存的初始大小和最大值。依據個人經驗這個值的比例最好是1:1或者1:1.5。比如,你可以將-Xmx和-Xms都設為1GB,或者-Xmx和-Xms設為1.2GB和1.8GB。
  • Java中不能手動觸發GC,但可以用不同的引用類來輔助垃圾回收器工作(比如:弱引用或軟引用)。

以上就是關於Java中GC的一些內容。通過這篇博客,我們可以知道堆內存是如何划分的;一個對象在沒有任何強引用指向他或該對象通過根節點不可達時需要被垃圾回收器回收;當垃圾收集器意識到需要進行GC時會觸發Minor GC或Major GC,是自動的,無法強制執行。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM