GC常見問題

(一)強引用、軟引用、弱引用、虛引用

  • 強引用(Strong Reference)
  1. 最普遍的引用:Object obj = new Object();
  2. 拋出OutOfMemoryError 終止程序也不會回收具有強引用的對象;
  3. 通過將對象設置爲null來弱化引用,讓它被回收
  • 軟引用(soft reference)
  1. 對象處在有用但非必須的狀態
  2. 只有當內存空間不足時,GC會回收該引用的對象的內存
  3. 可實現高速緩存
String str = new String(“abc”);
SoftReference softRef = new SoftReference(Str); //軟引用
  • 弱引用WeakReference
  1. 非必須對象,比軟引用更弱一些
  2. GC時會被回收
  3. 被回收的概率也不大,因爲GC線程優先級較低
  4. 適用於引用偶爾被使用且不影響垃圾收集的對象
String str = new String(“abc”);
WeakReference weakRefer = new WeakReference(str);
  • 虛引用PhantomReference
  1. 不會決定對象的生命週期
  2. 任何時候都可能被垃圾收集器回收
  3. 跟蹤對象被垃圾收集器回收的活動,起哨兵作用
  4. 必須和引用隊列ReferenceQueue聯合使用
ReferenceQueue queue = new ReferenceQueue();
PhantomReference ref = new PhantoReference(“abc”,queue);

(二)GC的算法有哪些?怎麼使用

  • 常見的垃圾回收算法有:複製算法,標記清除算法,標記壓縮算法。
  1. 因爲新生代中對象存活率低,複製算法主要在新生代,在from區和to區之間的拷貝。因爲是將內存劃分爲兩部分,內存回收的時候,將存活下來的對象複製到另一半內存,這樣只用一半內存,就會導致內存的浪費。
  2. 標記清除算法:是直接將標記的垃圾清除掉,並沒有發生內存位置的移動,也就是沒有整理內存,就會導致內存位置不連續,產生很多的內存碎片,利用不起來,就會導致內存利用率低下。
  3. 標記壓縮算法:它是把後面存活的區域拷貝到垃圾區和空閒區,沒有碎片,但是標記清除的過程中做了一次整理和壓縮,導致效率偏低。對於拷貝來說,內存的拷貝是非常快的(就是一個線性地址的拷貝),但是壓縮就不是那麼容易了,因爲任何一塊內存在移動的時候,如果是多線程,就要進行線程同步,如果是單線程,那麼效率就更低了。
  • 所以,可以得出結論,沒有好的算法,只有合適的算法,於是就有了內存分代模型,針對各個分區的特性,採用分代回收垃圾,現在的jvm採用的算法就是分代收集算法。
  1. 新生代:朝生夕滅的對象(例如:方法的局部變量等)。
  2. 老年代:存活得比較久,但還是要死的對象(例如:緩存對象、單例對象等)。
  3. 永久代:對象生成後幾乎不滅的對象(例如:加載過的類信息)。
  • 分代垃圾回收
    堆大小=新生代+老年代,新生代與老年代的比例爲1:2,新生代細分爲一塊較大的Eden空間和兩塊較小的Survivor空間,分別被命名爲from和to。(Edan:from:to=8:1:1

(三)輕GC和重GC什麼時候會發生?

  • 輕GC(Minor GC
  1. Eden 空間佔滿, 觸發 輕GC(minor GC),仍然存活的對象會被複制到 S0 (survivor 0)中去,這樣 Eden 就被清空可以分配給新的對象。
  2. 又觸發了一次 Minor GC ,S0 和 Eden 中存活的對象被複制到 S1 中,清空S0和 Eden區。默認情況下如果複製發生超過 16.次,JVM 會停止複製並把他們移到老年代中去。
  • 重GC(Full GC
  1. 老年代的空間被佔滿會觸發老年代的Full GC。
  2. Full GC 是一個壓縮處理過程, 所以它比 Minor GC 要慢很多。
  3. 有如下原因可能導致Full GC:
    年老代(Tenured)被寫滿;
    持久代(Perm)被寫滿;
    System.gc()被顯示調用;
    上一次GC之後Heap的各域分配策略動態變化

(四)常見的垃圾收集器有哪些?

  • 新生代收集器
    1. Serial
    2. ParNew
    3. Parallel Scavenge

  • 老年代收集器
    1. Serial Old
    2. CMS
    3. Parallel Old

  • 堆內存垃圾收集器:G1

CMS(Concurrent Mark Sweep) 收集器

  • CMS 收集器是一種以最短回收停頓時間爲目標的收集器,以 “ 最短用戶線程停頓時間 ” 著稱。整個垃圾收集過程分爲 4 個步驟:

① 初始標記:標記一下 GC Roots 能直接關聯到的對象,速度較快。
② 併發標記:進行 GC Roots Tracing,標記出全部的垃圾對象,耗時較長。
③ 重新標記:修正併發標記階段引用戶程序繼續運行而導致變化的對象的標記記錄,耗時較短。
④ 併發清除:用標記-清除算法清除垃圾對象,耗時較長。

整個過程耗時最長的併發標記和併發清除都是和用戶線程一起工作,所以從總體上來說,CMS 收集器垃圾收集可以看做是和用戶線程併發執行的。

  • CMS 收集器也存在一些缺點:
  1. 對 CPU 資源敏感:默認分配的垃圾收集線程數爲(CPU 數+3)/4,隨着 CPU 數量下降,佔用 CPU 資源越多,吞吐量越小
  2. 無法處理浮動垃圾:在併發清理階段,由於用戶線程還在運行,還會不斷產生新的垃圾,CMS 收集器無法在當次收集中清除這部分垃圾。同時由於在垃圾收集階段用戶線程也在併發執行,CMS 收集器不能像其他收集器那樣等老年代被填滿時再進行收集,需要預留一部分空間提供用戶線程運行使用。當 CMS 運行時,預留的內存空間無法滿足用戶線程的需要,就會出現 “ Concurrent Mode Failure ”的錯誤,這時將會啓動後備預案,臨時用 Serial Old 來重新進行老年代的垃圾收集。
  3. 因爲 CMS 是基於標記-清除算法,所以垃圾回收後會產生空間碎片,可以通過 -XX:UserCMSCompactAtFullCollection 開啓碎片整理(默認開啓),在 CMS 進行 Full GC 之前,會進行內存碎片的整理。還可以用 -XX:CMSFullGCsBeforeCompaction 設置執行多少次不壓縮(不進行碎片整理)的 Full GC 之後,跟着來一次帶壓縮(碎片整理)的 Full GC。
  • 適用場景:重視服務器響應速度,要求系統停頓時間最短。可以使用 -XX:+UserConMarkSweepGC 來選擇 CMS 作爲老年代收集器。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章