GC

     以棧或寄存器中的引用【GC Roots】爲起點,找到堆中的對象,又從這些對象找到對堆中其餘對象的引用,這種引用逐步擴展,最終以null引用或者基本類型結束,這樣就造成了一顆以Java棧中引用所對應的對象爲根節點的一顆對象樹,若是棧中有多個引用,則最終會造成多顆對象樹。
    在這些對象樹上的對象,都是當前系統運行所須要的對象,不能被垃圾回收。而其餘剩餘對象,則能夠視爲沒法被引用到的對象,能夠被當作垃圾進行回收。所以,垃圾回收的起點是一些根對象(java棧, 靜態變量, 寄存器…)。而最簡單的Java棧就是Java程序執行的main函數。這便是「標記-清除」的回收方式。java

GC Roots包括:算法

  •  虛擬機棧中引用的對象。
  • 方法區中類靜態屬性實體引用的對象。
  • 方法區中常量引用的對象。
  • 本地方法棧中JNI引用的對象。

分代收集

Minor (Young ) GC

通常狀況下,當新對象生成且在Eden申請空間失敗時,就會觸發Minor GC
對Eden區域進行GC,清除非存活對象,而且把尚且存活的對象移動到Survivor區;而後整理Survivor的兩個區。由於大部分對象都是從Eden區開始的,同時Eden區不會分配的很大,因此Eden區的GC會頻繁進行。因此通常在這裏須要使用速度快、效率高的算法 - 複製,使Eden去能儘快空閒出來。數組

虛擬機給每一個對象定義了一個對象年齡(Age)計數器,能夠經過參數 -XX:MaxTenuringThreshold 默認爲 15 歲)來設置。
若是對象在 Eden 出生並通過第一次 Scavenge GC 後仍然存活,而且能被 Survivor 容納的話,將被移動到 Survivor 空間中,並將對象年齡設爲 1。對象在 Survivor 區中每熬過一次 Scavenge GC,年齡就增長 1 歲,當它的年齡增長到必定程度時,就會晉升到老年代中。對象晉升老年代的年齡閾值。服務器

Full GC

對整個堆進行整理,包括Young、Tenured和MetaSpace。由於須要對整個對進行回收,因此很慢,所以應該儘量減小Full GC的次數。在對JVM調優的過程當中,很大一部分工做就是對於FullGC的調節。多線程

有以下緣由可能致使Full GC:併發

  1.  年老代(Tenured)無可知足需新分配的內存
      當準備要觸發一次young GC時,若是發現統計數據: 以前young GC的平均晉升大小比目前old gen剩餘的空間大(分配擔保機制),則不會觸發young GC而是轉爲觸發full GC。
    (HotSpot VM的GC裏,除了CMS的concurrent collection以外,其它能收集old gen的GC都會同時收集整個GC堆,包括young gen,因此不須要事先觸發一次單獨的young GC)
  2.  System.gc()被顯示調用, 系統建議執行Full GC,可是沒必要然執行。
  3. 上一次GC以後Heap的各域分配策略動態變化
  4. metaSpace 無可分配知足需新內存

Major GC一般是跟full GC是等價的,收集整個GC堆。但由於HotSpot VM發展了這麼多年,外界對各類名詞的解讀已經徹底混亂了,當說「major GC」的時候必定要問清楚他想要指的是上面的full GC仍是old gen。app

Parallel Scavenge(-XX:+UseParallelGC)框架下:
默認是在要觸發full GC前先執行一次young GC,而且兩次GC之間能讓應用程序稍微運行一小下,以期下降full GC的暫停時間(由於young GC會盡可能清理了young gen的死對象,減小了full GC的工做量)。
以CMS GC (-XX:+UseConcMarkSweepGC)爲例,它主要是定時去檢查old gen的使用量,當使用量超過了觸發比例(-XX:CMSInitiatingOccupancyFraction)就會啓動一次CMS GC,對old gen作併發收集。經過配置-XX:+CMSScavengeBeforeRemark開啓或關閉在CMS從新標記階段以前的清除(YGC)嘗試。框架

內存分配策略

  • 對象優先在Eden區分配
    大多數狀況下,對象在先新生代Eden區中分配。當Eden區沒有足夠空間進行分配時,虛擬機將發起一次Young GC,Minor GC期間虛擬機將Eden區域的對象移動到其中一塊Survivor區域。
  • 大對象直接進入老年代

    所謂"大對象" 就是指一個佔用大量連續內存空間的對象。(如很長的字符串及數組)
    若在Eden區 或 當前使用Survior區中存不下,  就須要把Eden區 或 當前使用Survior區的存活對象都移動到老年代中去,而後再將新對象存入Eden區。
    一個大對象可以存入Eden區及 當前使用Survior區的機率比較小, 發生分配擔保的機率比較大,  而分配擔保須要涉及到大量的複製,就會形成效率低下。
    所以: 大對象直接把放到老年代中去,從而就能避免大量的複製操做
    -XX:PretenureSizeThreshold 參數 該參數用於設置大小超過該參數的對象被認爲是"大對象", 直接分配在老年代。(注意:  該參數只對Serial和ParNew收集器有效。)ide

  • 爲了使內存分配更加靈活,年齡相同對象的內存大小總和超過了任一Survivor空間的一半, 那麼全部年齡相同及超過的對象都會被轉移到老年代中,無須等到MaxTenuringThreshold要求的年齡。函數

分配擔保

分配擔保是老年代爲新生代做擔保,擔保有足夠的空間存入新生代可存活的對象, 若是OldGeneration空間還不夠就OOM。

發生MinorGC前, JVM首先會檢查老年代中最大可用的連續空間是否大於新生代中全部對象的大小。若此條件:

  • 成立:  直接執行minorGC.
  • 不成立: 斷定HandlePromotionFailure設置值是否容許擔保失敗。若容許, 繼續檢查老年代最大可用的空間是否大於 歷次 晉升到老年代對象的平均大小
    • 若大於: 將嘗試一次MinorGC,雖然這次MinorGC是有風險的.
    • 若小於或HandlePromotionFailure設置不容許冒險:則進行一次FullGC,經過清除老年代中廢棄數據來擴大老年代空閒空間, 以便給新生代做擔保。 

在jdk5後,晉升不須要連續空間了。

In 5.0 we added the ability in the low pause collector to start a young generation collection and then to back out of it if there was not enough space in the tenured generation. Being able to backout of a young generation collection allowed us to make a couple of changes. We now keep an average of the amount of space that is used for promotions and use that (with some appropriate padding to be on the safe side) as the requirement for the space needed in the tenured generation. Additionally we no longer need a single contiguous chunk of space for the promotions so we look at the total amount of free space in the tenured generation in deciding if we can do a young generation collection. Not having to have a single contiguous chunk of space to support promotions is where fragmentation comes in (or rather where it doesn't come in as often).
此外,咱們再也不須要一個連續的空間大塊晉升,因此咱們經過老年代的自由空間量總數決定是否能夠作一個新生代的回收。沒必要擁有一個連續的空間來支持晉升活動是兼容碎片化的狀況

https://blog.csdn.net/fei33423/article/details/70941113?utm_source=blogxgwz4

GC策略

回收算法

  1. 引用計數(Reference Counting): 
    原理是此對象有一個引用,即增長一個計數,刪除一個引用則減小一個計數。垃圾回收時,只用收集計數爲0的對象。此算法最致命的是沒法處理循環引用的問題。
  2. 複製(Copying):  
    把內存空間劃爲兩個相等的區域,每次只使用其中一個區域。垃圾回收時,遍歷當前使用區域,把正在使用中的對象複製到另一個區域中。此算法每次只處理正在使用中的對象,所以只須要付出少許存活對象的複製成本就能夠完成收集,同時複製過去之後還能進行相應的內存整理,不會出現「碎片」問題。固然,此算法的缺點也是很明顯的,就是須要兩倍內存空間
  3. 標記-清除(Mark-Sweep): 
    第一階段從引用根節點開始標記全部被引用的對象,第二階段遍歷整個堆,把未標記的對象清除。此算法須要暫停整個應用,同時會產生內存碎片
  4. 標記-整理(Mark-Compact):
    結合了「標記-清除」和「複製」兩個算法的優勢。也是分兩階段,從根節點開始標記全部被引用對象完成後,不是清理掉須要回收的對象,而是將全部存活的對象向一端移動,而後將邊界之外的內存所有清理掉。避免了「標記-清除」的碎片問題,同時也避免了「複製」算法的空間問題

根據JVM的內存結構對堆進行分代收集:

在新生代中,每次垃圾收集都是有大批對象死去,少許存活; 且分Eden區+2個Survivor區。 比較適合複製算法。
而老年代中,對象存活率高,沒有額外空間供其分配,就必須用「標記-清理-整理」算法。 常見如: CMS(Concurrent Mark-Sweep)以犧牲吞吐量爲代價來得到最短回收停頓時間。對於要求服務器響應速度的應用上,這種垃圾回收器很是適合。在啓動JVM參數加上-XX:+UseConcMarkSweepGC 表示對於老年代的回收採用CMS。其基礎算法是:標記—清除。因此須要增長-XX:+UseCMSCompactAtFullCollection、-XX:CMSFullGCsBeforeCompaction來開啓「整理」操做。

收集器

串行(Serial)收集: 使用單線程處理全部垃圾回收工做; 

並行(ParNew)收集: 並行收集使用多線程處理垃圾回收工做,於是速度快,效率高。並且理論上CPU數目越多,越能體現出並行收集器的優點。

併發(Parallel)收集: 能夠保證大部分工做都併發進行(應用不中止),垃圾回收只暫停不多的時間 。相對於串行收集和並行收集而言,前面兩個在進行垃圾回收工做時,須要暫停整個運行環境,而只有垃圾回收程序在運行。所以,系統在垃圾回收時會有明顯的暫停,並且暫停時間會由於堆越大而越長(ps: 因此堆內存大小分配須要合理分片,並非越大越好)

相關文章
相關標籤/搜索