JVM-GC基礎

基本的垃圾回收算法

引用計數(Reference Counting)

增加一個引用,引用計數加1,去掉一個引用,引用計數減1,然后回收那些引用計數為0的對象
問題:無法處理循環引用問題(例如A、B兩個對象互相引用,但沒有其他對象引用它們,這時它們也無法被回收)

標記-清除(Mark-Sweep)

從引用根節點開始標記所有被引用的對象,然后遍歷整個堆,清除未標記的對象
問題:產生碎片

標記-清除

復制(Copying)

首先將內存空間分為對等的兩半,每次只使用其中一半
每次回收時,遍歷當前使用區域,將正在使用的對象復制到另外一個區域
好處:一次遍歷即可,且不會產生碎片
問題:需要兩倍空間

復制

標記-整理(Mark-Compact)

從引用根節點開始標記所有被引用的對象,然后遍歷整個堆,清除未標記的對象,并把存活對象壓縮到一塊
好處:避免了空間的浪費,且不會產生碎片

標記-整理

比較

空間:復制>標記-清除=標記-整理(復制需要兩倍空間)
時間:復制<標記-清除<標記-整理(復制最快,一次遍歷即可;標記-整理比標記-清除要慢,因為除了清除之外,還要移動數據)

JVM分代結構

JVM內存采用分代結構,分別為Young、Tenured、Permanent,其中Young又細分為Eden和兩個大小相同的Survivor區:From和To。

分代結構

分代依據

  1. 絕大部分的對象都是臨時對象
  2. 不同對象的生命周期不同,采用不同的算法,可以提高不同的效率

JVM GC過程

GC過程
  1. 新建的對象都在Eden中創建
    大的對象直接在Old中創建:1)超過-XX:PretenureSizeThreshold設置,2)大于整個Eden
    如果Eden滿了,則觸發MinorGC

  2. MinorGC
    暫停程序
    將Eden和From中存活的對象復制到To,同時各個對象的年齡值加1(MinorGC后,Eden和From都是空的)
    如果To滿了,則將對象移到Old,如果此時Old滿了,則發送Promotion Failed錯誤,觸發FullGC
    如果對象的年齡超過-XX:MaxTenuringThreshold,也移到Old(這里有一個動態對象年齡的概念:不是每次都要求對象的年齡一定要超過-XX:MaxTenuringThreshold才晉升到Old,如果Survivor空間中相同年齡所有對象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對象就可以直接進入Old)

  3. FullGC
    如果Old滿了,觸發FullGC
    如果Perm滿了,觸發FullGC
    暫停程序(CMS算法的整個過程可以并行執行,只需短暫暫停程序2次)
    回收Old,如果回收后還是滿了,則拋出OutOfMemoryError: Java heap space
    默認情況下,JVM是不回收Perm區的,要回收需要使用CMS算法,并設置-XX:+CMSClassUnloadingEnabled, -XX:+CMSPermGenSweepingEnabled,如果回收后還是滿了,則拋出OutOfMemoryError: PermGen space

JVM GC算法

串行

效率高,但無法利用多核,一般在小程序使用,使用-XX:+UseSerialGC打開

串行

并行

對Young并行收集,使用-XX:+UseParallelGC打開
JDK6.0后可對Old進行并行收集,使用-XX:+UseParallelOldGC打開

并行

并發

保證大部分回收工作并發執行(應用不暫停),適合響應要求高的應用,使用-XX:+UseConcMarkSweepGC打開

并發

G1

待補

比較

Serial Throughput CMS G1
參數 -XX:+UseSerialGC -XX:+UseParallelGC -XX:+UseConcMarkSweepGC, -XX:+UseParNewGC -XX:+UseG1GC
Young(都是暫停整個應用) 單線程 多線程 多線程 多線程
Old 單線程,暫停應用,壓縮 多線程,暫停應用,壓縮 單或多線程,部分暫停,不壓縮 多線程,部分暫停,壓縮
增加CPU使用率,產生碎片,如果沒有足夠的CPU或者碎片太多,則退化成serial gc 增加CPU使用率,適合Heap大于4G的情況,Old區也是從一個region拷貝到另外一個region

G1和CMS的機制是差不多的,只是G1把old分區了,這樣更有利于多線程的掃描
CMS每次清除后,都不會壓縮整理的,會產生碎片,而G1每次都像young那樣,進行數據移動,也就解決了碎片的問題

選擇

  1. 如果heap少于100MB,選擇Serial
  2. 對于TPS,如果CPU夠用,則選擇并發GC,如果CPU使用率較高,則選擇Throughput
  3. 對于平均響應時間,通常Throughput比并發GC要好
  4. 對于90%或99%的響應時間,并發GC比Throughput要好
  5. 如果選用并發GC,heap少于4G選擇CMS,大于4G選擇G1(這個保留,對G1算法不了解,了解后再修正)

GC Root

垃圾回收從Root開始,棧是程序真正執行的地方,所以從棧開始找,而棧又屬于線程獨有,所以從所有的線程的棧開始找

GC Root
  1. 線程的棧幀中引用的對象
  2. 方法區中的類靜態屬性引用的對象
  3. 方法區中的常量引用的對象
  4. 本地方法棧中JNI引用的對象
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 原文閱讀 前言 這段時間懈怠了,罪過! 最近看到有同事也開始用上了微信公眾號寫博客了,挺好的~給他們點贊,這博客我...
    碼農戲碼閱讀 6,025評論 2 31
  • 作者:一字馬胡 轉載標志 【2017-11-12】 更新日志 日期更新內容備注 2017-11-12新建文章初版 ...
    beneke閱讀 2,235評論 0 7
  • JVM架構 當一個程序啟動之前,它的class會被類裝載器裝入方法區(Permanent區),執行引擎讀取方法區的...
    cocohaifang閱讀 1,700評論 0 7
  • 這篇文章是我之前翻閱了不少的書籍以及從網絡上收集的一些資料的整理,因此不免有一些不準確的地方,同時不同JDK版本的...
    高廣超閱讀 15,731評論 3 83
  • 轉載blog.csdn.net/ning109314/article/details/10411495/ JVM工...
    forever_smile閱讀 5,402評論 1 56