關於GC(中):Java垃圾回收相關基礎知識

Java內存模型

(圖源: )

區域名 英文名 訪問權限 作用 備註
程序計數器 Program Counter Register 線程隔離 標記待取的下一條執行的指令 執行Native方法時為空; JVM規範中唯一不會發生OutOfMemoryError的區域
虛擬機棧 VM Stack 線程隔離 每個Java方法執行時創建,用於存儲局部變量表,操作棧,動態鏈接,方法出口等信息 方法執行的內存模型
本地方法棧 Native Method Stack 線程隔離 Native方法執行時使用 JVM規範沒有強制規定,如Hotspot將VM和Native兩個方法棧合二為一
Java堆 Java Heap 線程共享 存放對象實例 更好的回收內存 vs 更快的分配內存
方法區 Method Area 線程共享 存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據 JVM規範不強制要求做垃圾收集
運行時常量池 Runtime Constant Pool 線程共享 方法區的一部分
直接內存 Direct Memory 堆外內存,通過堆的DirectByteBuffer訪問 不是運行時數據區的一部分,但也可能OutOfMemoryError

對象的創建——new的時候發生了什麼

討論僅限於普通Java對象,不包括數組和Class對象。

  1. 常量池查找類的常量引用,如果沒有先做類加載
  2. 分配內存,視堆內存是否是規整(由垃圾回收器是否具有壓縮功能而定)而使用“指針碰撞”或“空閑列表”模式
  3. 內存空間初始化為零值,可能提前在線程創建時分配TLAB時做初始化
  4. 設置必要信息,如對象是哪個類的示例、元信息、GC分代年齡等
  5. 調用<init>方法

垃圾回收器總結

垃圾回收,針對的都是堆。

分代

  • 新生代:適合使用複製算法, 以下三個區一般佔比為8:1:1
    • Eden 新對象誕生區
    • From Survivor 上一次GC的倖存者(見“GC種類-minor GC”)
    • To Survivor 本次待存放倖存者的區域
  • 老年代:存活時間較久的,大小較大的對象,因此使用標記-整理或標記-清除算法比較合適
  • 永久代:存放類信息和元數據等不太可能回收的信息。Java8中被元空間(Metaspace)代替,不再使用堆,而是物理內存。

分代的原因

  • 不同代的對象生命周期不同,可以針對性地使用不同的垃圾回收算法
  • 不同代可以分開進行回收

回收算法

名稱 工作原理 優點 缺點
標記-清除 對可回收對對象做一輪標記,標記完成后統一回收被標記的對象 易於理解,內存利用率高 效率問題;內存碎片;分配大對象但無空間時提前GC
複製 內存均分兩塊,只使用其中一塊。回收時將這一塊存活對象全部複製到另一塊 效率高 可用空間減少; 空間不夠時需老年代分配擔保
標記-整理 對可回收對對象做一輪標記,標記完成后將存活對象統一左移,清理掉邊界外內存 內存利用率高 效率問題

標記-X算法適用於老年代,複製算法適用於新生代。

GC種類

  • Minor GC,只回收新生代,將Eden和From Survivor區的存活對象複製到To Survivor
  • Major GC,清理老年代。但因為伴隨着新生代的對象生命周期升級到老年代,一般也可認為伴隨着FullGC。
  • FullGC,整個堆的回收
  • Mixed GC,G1特有,可能會發生多次回收,可以參考

垃圾回收器小結

垃圾回收器名稱 特性 目前工作分代 回收算法 可否與Serial配合 可否與ParNew配合 可否與ParallelScavenge配合 可否與SerialOld配合 可否與ParallelOld配合 可否與CMS配合 可否與G1配合
Serial 單線程 新生代 複製 Y N Y N/A
ParNew 多線程 新生代 複製 N N Y N/A
ParallelScavenge 多線程, 更關注吞吐量可調節 新生代 複製 N N Y N/A
SerialOld 單線程 老年代 標記-整理 Y Y N N/A
ParallelOld 多線程 老年代 標記-整理 N N Y N/A
CMS 多線程,併發收集,低停頓。但無法處理浮動垃圾,標記-清除會產生內存碎片較多 老年代 標記-清除 Y Y N Y N/A
G1 并行併發收集,追求可預測但回收時間,整體內存模型有所變化 新生代/老年代 整體是標記-整理,局部(兩Region)複製 N N N N N N

在本系列的上一篇文章中,減少FullGC的方式是使用G1代替CMS,計劃在下一篇文章中對比CMS和G1的區別。

理解GC日誌

只舉比較簡單的例子,具體各項的格式視情況分析,不同回收器也會有差異。

2019-11-22T10:28:32.177+0800: 60188.392: [GC (Allocation Failure) 2019-11-22T10:28:32.178+0800: 60188.392: [ParNew: 1750382K->2520K(1922432K), 0.0312604 secs] 1945718K->198045K(4019584K), 0.0315892 secs] [Times: user=0.09 sys=0.01, real=0.03 secs]

開始時間-(方括號[)-發生區域(ParNew,命名和GC回收器有關)-回收前大小-回收后大小-(方括號])-GC前堆已使用容量-GC后堆已使用容量大小-回收時間-使用時間詳情(用戶態時間-內核時間-牆上時鐘時間)

注意這裏沒有包括“2019-11-22T10:28:32.177+0800: 60188.392: [GC (Allocation Failure)”這部分的分析。

可借鑒的編程模式

對象分配的併發控制

對象創建是很頻繁的,在線程共享的堆中會遇到併發的問題。兩種解決辦法:

  1. 同步鎖定:CAS+失敗重試,確保原子性
  2. 堆中預先給每個線程劃分一小塊內存區域——本地線程分配緩衝(TLAB),TLAB使用完並分配新的TLAB時才做同步鎖定。可看作1的優化。

CAS: Conmpare And Swap,用於實現多線程同步的原子指令。 將內存位置的內容與給定值進行比較,只有在相同的情況下,將該內存位置的內容修改為新的給定值。關於CAS可以參考:

對象訪問的定位方式

前提條件:通過上本地變量表的reference訪問中的對象及它在方法區的對象類型數據(類信息)
主流的兩種方式,這兩種方式各有優點,可以看出方式2是方式1的優化,但並不是全面超越方式1,無法完全取代。
這裏可以看到要權衡垃圾回收和訪問速度兩方面。

方式1: 直接指針訪問實例數據

reference直接存放對象實例地址,只需要一次訪問即可,執行效率較高。

方式2: 使用句柄池

reference中地址穩定,對象被移動時只需要改句柄池的地址。相對的,訪問實例需要兩次指針定位。

參考資料

  1. 周志明.著《深入理解JAVA虛擬機》

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

您可能也會喜歡…