1. 程式人生 > 其它 >JVM學習及垃圾回收相關

JVM學習及垃圾回收相關

一.腦圖

 

 

二..JVM記憶體模型

JAVA虛擬機器在執行java程式的過程中會把它所管理的記憶體劃分為若干個不同的資料區域.這些區域都有各自的用途以及建立和銷燬的時間,有的區域會隨著虛擬機器程序的啟動而存在,有的則依賴使用者執行緒的啟動和結束而隨著建立和銷燬 . 總結下來 : JVM虛擬機器所管理的記憶體主要包括以下幾個執行時資料區域

 

 


1. 程式計數器(Program Counter Register)

一塊較小的記憶體空間,可以看作是當前執行緒所執行的位元組碼的行號指示器. 位元組碼在工作時就是通過改變這個計數器的值來選取下一條需要執行的位元組碼指令, 分支、迴圈、跳轉、異常處理、執行緒恢復等基礎功能都需要依賴這個計數器來完成 .

java虛擬機器的多執行緒是通過執行緒輪流切換並分配處理器執行時間的方式實現的 . 在任何一個時刻,一個處理器都會執行一條執行緒中的指令 . 因此 , 為了執行緒切換後能恢復到正確的執行位置 , 每條執行緒都需要有一個獨立的程式計數器.且各執行緒的程式計數器獨立儲存,互不影響 . 這塊每個執行緒獨有的記憶體我們成為執行緒私有記憶體 .

若當前在執行java方法, 則當前程式計數器記錄的是正在執行的位元組碼指令的地址 , 如果執行的是native方法 , 計數器中為空(undefined) .

注 : 程式計數器是在虛擬機器規範中沒有規定任何OOM的區域

2.虛擬機器棧(VM Stack)

與程式計數器類似 , 虛擬機器棧也是執行緒私有的 , 且它的生命週期與執行緒相同.

虛擬機器棧描述的是java方法執行的記憶體模型 , 即: 每個方法在執行的同時都會建立一個棧幀(Stack Frame),用於儲存區域性變數、運算元棧、動態連結、方法出口等資訊 . 每一個方法的開始執行到結束,都對應一個棧幀在虛擬機器中入棧到出棧的過程 .

2.1區域性變量表

此處儲存:

    • 編譯期可知的各種基本資料型別
    • 物件引用(此處儲存的不是物件本身,可能指向物件起始地址的指標,也可能是指向一個代表物件的控制代碼或其他於此物件相關的位置)
    • returnAddress型別(指向一條位元組碼指令的地址)

其中 , long和double型別的資料會佔用兩個區域性變數空間(2 Slot),其餘資料型別只佔用一個(1 Slot),當進入一個方法時 , 這個方法所需要在幀中分配多大的區域性變數空間是完全確定的 , 且在方法執行期間不再改變 .

2.2 定義的異常類

  1. StatckOverFlowError : 如果執行緒請求的棧深度大於虛擬機器所允許的深度,將丟擲該異常 .
  2. OOM(Out Of Memory Error) : 當前大部分虛擬機器棧都允許動態擴充套件(也允許固定大長度的虛擬機器棧) , 若擴充套件時無法申請到足夠的記憶體,就會丟擲OOM異常

3. 本地方法棧(Native Method Stack)

本地方法棧與虛擬機器棧的使用方式大致相同 , 只不過虛擬機器棧為虛擬機器執行java方法(位元組碼)服務 . 而本地方法棧則為虛擬機器使用到的native方法服務,且java規範並未對native方法使用的語言、使用方式與資料結構強制規定 .

其異常內容同虛擬機器棧一樣 .

4. 堆(Heap)

堆記憶體是虛擬機器所管理的記憶體中最大的一塊 .java堆是被所有執行緒共享的一塊記憶體區域 , 在虛擬機器啟動時建立(專案部署時可定義其最小最大值) , 堆記憶體唯一的目的就是存放物件例項 . 幾乎所有的物件例項都在堆(Heap)中分配記憶體 (隨著JIT的發展,棧上分配、標量替換優化技術使得這一點也不再絕對 . )

java堆記憶體(Heap)是垃圾回收器的主要管理區域 . 因此也稱之為GC堆 .

4.1新生代和老年代

從記憶體回收的角度看 , 由於垃圾回收基本都在採用分代收集演算法 . 所以堆記憶體還可細分為 : 新生代和老年代

再細緻則 : Eden空間、From Surviver空間、ToSurviver空間

預設情況下堆記憶體的空間分配

  • 老年代 :
    • 三分之二的堆空間
  • 新生代 :
    • 三分之一的堆空間
    • Eden區 : 8/10的年輕代堆空間
    • survivor0 : 1/10的新生代記憶體空間
    • survivor1 : 1/10的新生代記憶體空間

4.2分配緩衝區

執行緒共享的java堆記憶體中可能劃分出多個執行緒私有的分配緩衝區 .

5. 方法區(Method Area)

同堆(Heap)一樣 , 是各個執行緒共享的記憶體區域 , 用於儲存已被虛擬機器載入的類資訊 , 常量 , 靜態變數 , 即時編譯後的程式碼等資料 .

5.1 回收方法區

GC的主要操作目標是堆記憶體 , 而方法區中也有存在垃圾回收的需要 , 只不過其價效比(效率)較低

方法區在HotSpot虛擬機器中也稱為永久代 , 針對這塊記憶體主要進行回收的目標有 : 廢棄常量和無用的類 . 即若有一常量已經進入了常量池 , 但是在系統中沒有目標指向該常量即無物件引用該常量 , 則會發生GC .

6. 執行時常量池

是方法區的一部分 ,.Class檔案中的除了有類的版本、欄位、方法、介面等描述資訊外, 還有一項就是常量池 , 用於存放編譯器生成的各種字面變數和符號引用 . 這部分在類載入後存入方法區的常量池中

7. 直接記憶體

並不是虛擬機器執行時資料區的一部分 . 也不是java規範中定義的記憶體 .

在JDK1.4中 , 引入了NIO的概念 , 即一種基於通道與緩衝區的I/O方式 , 它可有使用native函式庫直接分配堆外記憶體 , 然後通過儲存在堆中的DirectByteBuffer物件作為這塊記憶體的引用進行操作 . 避免在java堆和Native堆中來回複製資料 . 但受限於java堆記憶體設定大小及機器的記憶體大小 , 當記憶體使用總和大於機器本身限制時 , 會丟擲OOM異常 .

  • 記憶體相關命令 :
    • java -XX:+PrintFlagsFinal -version : 可檢視所有的預設的JVM引數
    • -XX:InitialSurvivorRatio: 新生代中Eden/Survivor空間的初始比例
    • -XX: NewRatio: Old區和Yong區的記憶體比例
  • JDK1.7開始JVM就沒有永久代了, 1.7開始JVM把字串常量池從永久代剝離出來 , 存放在堆空間中

三. 物件

3.1物件在虛擬機器中建立的完整過程

  1. 虛擬機器去檢查這個指令的引數是否能在常量池中定位到一個類的符號的引用,並且檢查該類是否已被載入、解析、初始化.如果沒有,先去執行相應的類載入部分 .
  2. 類載入通過後 , 虛擬機器為物件分配記憶體 . 記憶體大小在物件載入完成後就會確定,為物件分配記憶體的過程等同於把一塊確定大小的記憶體從java堆中劃分出來 .
  3. 記憶體分配完成後 , 虛擬機器需要將分配出的記憶體初始化為0(物件頭除外).這一步保證了物件的例項欄位在java程式碼中不賦予初始值的時候就可以使用 . 程式這時能訪問到這些欄位資料型別的零值 .
  4. 虛擬機器堆物件進行必要設定 , 將以下資訊儲存在物件的物件頭中 . (Object Header)
    1. 包括該物件是哪個類的例項
    2. 如何才能找打類的元資料資訊
    3. 物件的hash碼
    4. 物件的GC分代年齡
  1. 執行init方法 , 把物件按照預期編碼的方向進行初始化 .

3.1.1指標碰撞

若當前java記憶體為規整的 , 那麼分配記憶體就是把對應的分界指標指向空閒區域那邊挪動與物件大小相同的距離

3.1.2空閒列表

若java記憶體不是規整的 , 虛擬機器就會維護一個列表 , 記錄上可用記憶體塊 , 在分配的時候尋找一個足夠大的記憶體空間劃分給物件例項 . 並更新表中的記錄 .

注 : java記憶體是否規整取決於所選擇的垃圾回收器是否帶有壓縮整理功能 .

因此 , 在使用Serial 、 ParNew等帶Compact過程的收集器時 , 採用的記憶體分配方法是指標碰撞 . 使用CMS這種基於Mark-Sweep(標記清除)演算法的收集器時,採用的是空閒列表方式 .

為確保在記憶體分配中併發出現執行緒不安全的情況 , 可針對每個執行緒在java堆中預先分配一小塊記憶體,即本地執行緒分配緩衝(Thread Local Alocation Buffer),當哪個執行緒需要分配記憶體時 , 就在哪個TLAB中分配.當TLAB用完時 , 才去進行同步鎖定.

虛擬機器中是否使用TLAB: 可配置 : -XX:+/-UseTLAB引數來設定 .

3.2物件的記憶體佈局

在HotShot虛擬機器中 , 物件在記憶體中的佈局可以分為3塊區域 : 物件頭(Object Header)、例項資料(Instance Data)和對其填充(Padding)

3.2.1物件頭

包括 :

1 .Mark Word: 用於儲存物件自身執行時的資料,如雜湊碼,GC分代年齡、鎖狀態標誌、執行緒持有的鎖、偏向執行緒、偏向時間戳等 .這部分為非固定的資料結構以便在極小的空間記憶體儲儘量多的資訊 .

若為陣列 , 物件頭中還需有一塊記錄陣列長度的資料 .

  1. 型別指標: 物件指向它的類的元資料的指標 . 通過該指標確定物件是哪個類的例項 .

注 : 並不是所有的虛擬機器實現都必須在物件資料上保留型別指標 .

四. 垃圾回收

4.1. 判斷物件是否可回收?

4.1.1 引用計數器

JVM會給物件新增一個引用計數器,當有一個地方引用時 , 計數器+1;當引用失效時 , 引用值-1;當任何時刻計數器為0時,這個物件則判斷為不可能在被使用的或當前沒有被使用 . 此時判斷為可回收 .

缺點 : 當迴圈依賴時 ,即便此時無執行緒在使用該物件 , 但是因為兩個物件之間都存在相互引用 , 即此時的計數器都不為0 . 系統認為該物件還處於引用狀態 , 不會進行GC回收

注 : 目前虛擬機器通常不會以引用計數法進行GC判斷是否可回收 .

4.1.2 可達性分析法

基本思想 : 通過一系列的"GC Roots"的物件作為起始點 , 從這些節點開始向下搜尋, 搜尋的節點路徑稱之為引用鏈("Reference Chain"), 當一個物件沒有任何引用鏈可以到達到GC Roots處時, 此時成為物件不可達 , 則證明該物件是不可用的 . 這就是可達性分析 .

而物件是否進行GC回收 .要經過兩次的判斷 .

  1. 第一次進行分析之後 , 若為不可達 , 則JVM會給該物件打上標記標明其為不可達的 .
  2. 虛擬機器會對不可達的物件進行篩選 , 即篩選該物件是否有必要進行finalize()方法 ,. 當物件沒有覆蓋該方法或該方法已被虛擬機器呼叫過 . 虛擬機器視這兩種情況均為沒有必要執行GC . 即不進行GC回收 .

而JVM中 , 可作為GC Roots判斷的物件包括 :

  • 虛擬機器棧(棧幀中的本地變量表)中的引用的物件
  • 方法區中類靜態屬性引用的物件
  • 方法區中常量引用的物件
  • 本地方法棧中JNI(一般指native方法)引用的物件

4.1.3 關於引用

JDK1.2之後 , java對引用的概念進行了擴充 , 將引用分為: 強引用 , 軟引用 , 若引用 , 虛引用這4種 . 這四種引用強度依次減弱 .

  • 強引用 : 即在程式中普遍存在的 , 例如通過new 關鍵字宣告獲取例項的這類引用 , 只要強引用還存在 , 那麼垃圾回收器就不會回收 .
  • 軟引用: 用於描述一些還有用但非必須的物件 . 對於軟引用關聯的物件 , 在系統即將OOM之前 , 將會把這些物件列入回收範圍內進行回收 . 若回收之後還存在記憶體溢位的情況 , 則會丟擲OOM Error異常 .

注 : 軟引用可通過SoftReference進行實現 .

  • 弱引用 : 也是用來描述非必須物件的 , 但其強度比軟引用要弱一些 , 被弱引用關聯的物件的生命週期只能到下一次垃圾回收之前 . 當垃圾回收器進行GC時 , 會把當前被弱引用關聯的物件進行回收 .

注 : 弱引用 .可通過WeakReference類來實現弱引用

  • 虛引用 : 也稱為幽靈引用或幻影引用 , 是最弱的一種關係 , 一個物件是否有虛引用的存在 , 完全不影響其生命週期 , 也不會對其生存時間構成影響 , 同樣也無法通過虛引用獲取到對應物件的例項 . 設定虛引用的最大目的就是在進行GC回收的時候能收到一個系統通知 .

注 : 虛引用可通過PhantomReference進行實現 .

4.2 垃圾回收演算法

4.2.1 標記-清除(演算法的基礎)

分兩步 :

1. 標記出所有需要進行回收的物件 .

2 . 對需要回收的物件進行統一回收 .

不足 :

1. 效率較差

2. 記憶體空間問題 :容易生成不連續的記憶體碎片 , 記憶體碎片太多容易造成在後續程式執行過程中需要分配較大物件的記憶體時 , 無法找到足夠的連續記憶體而提前觸發垃圾回收 .

4.2.2 複製演算法

將整個可用記憶體分為兩塊 ,當其中一塊記憶體使用完了 , 將可用物件複製到另外一塊記憶體中 , 然後再把前一塊記憶體進行統一回收清理 , 這樣每次都是對整個半塊記憶體進行回收 , 且可以保證記憶體的連續性 .

缺點 : 將整體原有的可用記憶體減小一半 . 其對伺服器記憶體要求較高

4.2.3 標記-整理

步驟 :

  1. 先對需要做GC回收的物件進行標記
  2. 對記憶體結構進行整理 , 把存活的物件向一端進行移動 , 然後直接清理掉端邊界以外的記憶體 .

4.2.4 分代收集

根據物件存活週期的不同分為幾塊 : 一般分為新生代和老年代 . 在新生代中 , 每次垃圾回收都發現有大量的物件死去 , 只有少量存活 , 那就使用複製演算法 . 而老年代中因為物件存活率高,沒有額外的空就按對它進行分配和擔保 , 就必須使用"標記-整理"或者標記-清理演算法進行回收 .

4.2.5 演算法實現

  1. 列舉根節點

目前主流的java虛擬機器都是使用的準確式GC: 即當系統停頓下來後 , 進行可達性分析時 , 不再去逐個進行分析檢查所有執行上下文和全域性的引用位置 , 這樣會大量的消耗系統性能 , 虛擬機器本身會提前知道哪些地方存放著物件引用.

在HotSpotVM中 , 是使用一組稱之為OopMap的資料結構來達到這個效果 .

即在類載入完成後 , JVM會把物件內什麼偏移量上是什麼型別的資料給計算出來 , 在JIT編譯過程中 , 也會在特定的位置記錄下棧和暫存器中哪些位置是引用 , 這樣 , 在進行GC掃描的時候就可以直接得知這些資訊了 .

  1. 安全點

在有了OopMap的支援下 , HotSpot可以快速且準確的完成GC Roots列舉 ,

但是有一個很現實的問題 : 就是在這個過程中很可能會導致引用關係的變化 . 且OopMap內容變化的指令有很多 , 如果為每條指令都生成OopMap , 這將會大量消耗系統記憶體.增加GC成本 .

為了解決上述問題 , HotSpot引入了安全點的概念 , 上述有說到JVM在進行GC分析的時候 , 是在特定的位置進行分析而不是所有的引用位置進行分析 , 而這些特定的位置即為安全點 . 即程式執行時並非在所有的位置都能停下來開始進行GC , 只有在達到安全點的時候才暫停進行GC .

安全點生成的原則 :

    • 即不能太少以至於讓GC等待時間太長
    • 不能過於頻繁以至於GC次數過多增加執行負載 .
  • 安全點選定標準 :
    • 程式是否具有長時間執行的特徵 .
    • 因為每條指令執行的時間都非常短暫 , 程式不太可能因為指令流長度太長而過長時間執行.
    • 長時間執行的最明顯特徵就是指令序列複用 . 例如方法呼叫 , 迴圈跳轉 , 異常跳轉等. 而具備這些特徵的指令集將被標記為安全點
  • 搶先試中斷
    • 在進行GC時,不需要執行緒的執行程式碼主動去配合, 在GC發生時,首先會把執行緒全部中斷 , 如果有的執行緒不在安全點上 , 則恢復執行緒直到執行緒跑到安全點上 . 該方式現在幾乎沒有JVM採用
  • 主動式中斷
    • 即當GC需要中斷執行緒時 , 不直接對執行緒進行操作 , 僅僅簡單的線上程上設定一個標誌 ,執行緒在執行的過程中會去主動輪詢這個標誌 , 發現中斷標誌為真的時候 , 自己主動掛起,以便進行GC回收 .
  1. 安全區域(Safe Region)

    安全區域指 : 在一段程式碼片段中,引用關係不會發生變化 , 在這個區域的任何地方開始GC都是安全的 . 可以把安全區域視為安全點的擴充套件 .

    1. 即針對執行緒等待或者執行緒在佇列中期間 , JVM可能會誤觸發主動式中斷 , 為避免這一問題 , 當執行緒執行到安全點的時候 , 會首先標識自己進入了安全區域 , 那樣在JVM發起GC時 , 就不需要再考慮執行緒的安全點問題了 .當執行緒離開安全區域時 , 會檢查安全點是否已經完成了GC回收 , 若已完成 , 則繼續執行 . 否則就一直等待直到收到GC完成通知可以離開安全區域為止 .

4.3 GC收集器

4.3.1 Serial收集器

  • Serial收集器是最基本且發展最悠久的收集器 , 在JDK1.3.1之前 , 它是JVM新生代收集的唯一選擇
  • 它是一個單執行緒收集器 , 這裡的單執行緒不僅僅說明它只會使用一個CPU或者一條執行緒進行GC .
  • 它在進行GC時 , 必須中斷其他所有的工作執行緒 , 直到垃圾收集過程結束 .
  • 由於這些過程都是由虛擬機器在後臺自動發起和自由完成的 , 整個過程不可控 , 所以這個收集器在實際使用中不被接受 .
  • 到現在為止 , 它仍是虛擬機器執行在client模式下的預設的新生代收集器 . 畢竟介於其單執行緒的特性 , 對於單個CPU的環境來說 , 沒有執行緒之間互動的消耗 , 其相對要簡單而高效很多 .

收集演算法 :

  1. 新生代採用複製演算法
  2. 老年代採用標記-整理演算法
  3. 都會暫停所有使用者執行緒 .

4.3.2 ParNew收集器

ParNew收集器其實就是Serial收集器的多執行緒版本 , 除了使用多條執行緒進行垃圾回收之外 , 其餘行為包括Serial收集器可用的所有控制引數、收集演算法、Stop The World、物件分配規則、回收策略等

控制引數如有 :

    • -XX:SurvivorRatio: 新生代Eden/survivor的記憶體比例(預設8:1:1)
    • -XX:PretenureSizeThreshold
    • -XX:HandlePromotionFaiilure
  • ParNew除了本身的多執行緒收集之外 , 其他與Serial並無太多創新之處, .但它卻是許多執行在Server模式下的虛擬機器中的首選的新生代收集器 .
  • 目前除了Serial收集器之外 , 只有ParNew收集器可以與CMS收集器配合工作 .
  • 控制引數 :
    • 使用-XX:UseConcMarkSweepGC後為預設新生代收集器
    • 強指定 : -XX:UseParNewGC

4.3.3 Parallel Scavenge收集器

  • 一個新生代收集器
  • 使用複製演算法的收集器
  • 並行的多執行緒收集器
  • Parallel Scavenge收集器的最大關注點在於 : 達到一個可控制的吞吐量(Throughput) .
    • 吞吐量 : CPU執行使用者程式碼的時間與CPU總消耗時間的比值 . 即 : 吞吐量 = 執行使用者程式碼時間 / (執行使用者程式碼時間 + 垃圾收集時間)
  • 控制引數:
    • -XX:MaxGCPauseMillis : 控制最大垃圾收集停頓時間 , (> 0 milliSeconds) , 停頓時間與吞吐量和新生代空間密切相關 , 縮小最大收集停頓時間 , 某種程度上將會犧牲收集器的吞吐量和JVM新生代的空間 .
    • 例如要縮短停頓時間 : 可以調小新生代空間大小 , 這樣當然會加速收集時間, 但會導致GC更加頻繁 , 降低吞吐量
    • -XX:GCTimeRatio: 直接設定吞吐量 . (0 < 吞吐量 < 100). 預設值為99, 即最大允許 1% 的時間進行垃圾收集
    • -XX:UseAdaptiveSizePolicy: 一個開關引數 , 即自適應調整策略
    • 當該引數開啟時 , 不在需要指定-Xxm(新生代的大小) 、 和 -XX:Survivor(Eden()與Servivor(存貨區)的比例)以及-XX:PrementenureSizeThreshold(晉升老年代物件的大小)等細節引數 . 虛擬機器會根據當前執行情況動態調整這些引數以提供最大吞吐量或者最合適停頓時間 . 這種方式成為GC的自適應調節策略(GC Ergonomics)
    • 當使用自適應調整策略時 , 只需要把-Xmx(最大堆記憶體)設定好 , 後面使用-XX:MaxGCPauseMillis(最大垃圾收集停頓時間)或-XX:GCTimeRatio(吞吐量)給虛擬機器設立一個自動調整的優化目標即可 .
  • 老年代收集 : 該收集器架構中預設使用PS MarkSweep收集器進行老年代收集

4.3.4 Serial Old收集器

  • Serial收集器的老年代版本
  • 同樣也是一個單執行緒收集器
  • 使用標記-整理演算法進行收集
  • 主要意義給client模式下的虛擬機器使用
  • server模式下有兩大用途 :
    • 在JDK1.5及之前的版本中可與Parallel Scavenge收集器搭配使用
    • 作為CMS收集器的後備預案,在併發收集發生Concurrent Mode Failure時使用
  • 注 : 在Parallel Scavenge收集中的老年代收集器PS MarkSweep收集器的實現方式與Serial Old大致相近, 所以在在1.5及之前的JDK版本中都可以使用Serial Old收集器代替 .

4.3.5 Parallel Old收集器

  • Parallel Scavenge收集器的老年代版本
  • 使用多執行緒+標記-整理演算法
  • JDK1.6之後提供
  • 為解決使用Parallel Scavenge收集時只能使用Serial Old(或PS MarkSweep)收集器的問題,由於使用Serial Old收集器在服務端整體效能上的拖累,使用Parallel Scavenge收集器也未必能達到整體應用上最大吞吐量的結果

4.3.6 CMS收集器

  • CMS: (Concurrent Mark Sweep)收集器
  • 以獲取最短回收停頓時間(Stop The World的時間)為目標,多應用於網際網路站或者B/S架構系統的伺服器上
    • Concurrent : 是指垃圾回收的GC執行緒和執行使用者程式的執行緒是可以併發執行的
  • 基於標記-清除演算法實現,整個過程為 :
    • 初始標記(CMS initial mark)
    • 併發標記(CMS Concurrent Mark)
    • 併發預先清除(CMS Preclean)
    • 併發可能失敗的預先清除(Concurrent Abortable Preclean)
    • 重新標記(CMS remark)
    • 併發清除(CMS Concurrent Sweep)
    • 併發重置(Concurrent Reset)
    • 注意 :
    • 標記:會將存活的物件和要回收的物件都標記出來
    • 清除: 清除要回收的物件 . 即進行物件回收
  • 其中 :
    • 初始標記和重新標記時需Stop The World . 即仍需停掉其他所有執行緒
    • 併發標記階段並不會影響執行使用者程式碼的執行緒的執行 . 因此才會有重新標記 .
    • 重新標記是為了修正併發標記期間因使用者程式繼續執行而導致標記產生變動的這一部分的記錄標記
    • 在整個過程中, 除初始標記和重新標記外 , 併發標記和併發清除都不會觸發stop the world . 即都可以與執行使用者程式碼的執行緒併發執行 .
  • 優點 :
    • 支援併發收集 . 低停頓時間(指的是停止使用者執行緒的時間低) .
  • 缺點:
    • CMS收集器對CPU資源比較敏感 .
    • 無法處理浮動垃圾 , 可能會出現Concurrent Mode Failure失敗而觸發另一次Full GC.
    • 浮動垃圾: 即在初始標記時物件被判斷為存活的物件 , 在清除之前 , 因為使用者執行緒是並行執行的,可能有些執行緒已經執行完畢 , 有些物件變成了需要回收 , 這部分本該在本次GC過程中進行回收但是沒有回收的物件稱之為浮動垃圾
    • 如果程式中老年代增長不是很快 , 可適當調高: -XX:CMSInitiatingOccupancyFration的值來提高觸發百分比

以降低記憶體回收次數 . 進而提高整體效能 .

    • 或者通過-XX:UseSerial Old啟動Serial Old回收器進行老年代的回收
    • -XX:CMSInitiatingOccupancyFration設定太高容易導致大量的併發收集失敗.反而影響效能
    • 基於併發-清除的演算法 , 收集結束時會有大量空間碎片產生 , 會給大記憶體物件的記憶體分配帶來麻煩 .

4.3.7 G1收集器

  • 併發收集器,也稱之為垃圾優先收集器
  • 設計初衷 :
    • 為了儘量縮短處理超大堆(大於4G)時產生的停頓 , 相對於CMS而言產生記憶體碎片的概率大大降低 .
  • 設計原則
    • 簡單可行的效能調優
  • 取消了新生代和老年代的物理空間劃分
    • G1演算法將堆劃分為若干個區域(Region),它仍然屬於分代收集器。不過,這些區域的一部分包含新生代,新生代的垃圾收集依然採用暫停所有應用執行緒的方式,將存活物件拷貝到老年代或者Survivor空間。老年代也分成很多區域,G1收集器通過將物件從一個區域複製到另外一個區域,完成了清理工作。這就意味著,在正常的處理過程中,G1完成了堆的壓縮(至少是部分堆的壓縮),這樣也就不會有cms記憶體碎片問題的存在了。
    • 在G1中,還有一種特殊的區域,叫Humongous區域。 如果一個物件佔用的空間超過了分割槽容量50%以上,G1收集器就認為這是一個巨型物件。這些巨型物件,預設直接會被分配在年老代,但是如果它是一個短期存在的巨型物件,就會對垃圾收集器造成負面影響。為了解決這個問題,G1劃分了一個Humongous區,它用來專門存放巨型物件。如果一個H區裝不下一個巨型物件,那麼G1會尋找連續的H分割槽來儲存。為了能找到連續的H區,有時候不得不啟動Full GC。

PS:在java 8中,持久代也移動到了普通的堆記憶體空間中,改為元空間。

  • 常用命令 :
    • -XX:UseG1GC : 使用G1收集器
    • -XX:MaxGCPauseMillis=200:設定GC的最大停頓時間為200ms