深入理解java虛擬機----->垃圾收集器與記憶體分配策略(下)

来源:http://www.cnblogs.com/Mairr/archive/2017/12/14/8037366.html
-Advertisement-
Play Games

1. 前言 記憶體分配與回收策略 JVM堆的結構分析(新生代、老年代、永久代) 對象優先在Eden分配 大對象直接進入老年代 長期存活的對象將進入老年代 動態對象年齡判定 空間分配擔保 JVM堆的結構分析(新生代、老年代、永久代) 對象優先在Eden分配 大對象直接進入老年代 長期存活的對象將進入老年 ...


 

1.  前言

 

  • 記憶體分配與回收策略
    • JVM堆的結構分析(新生代、老年代、永久代)
    • 對象優先在Eden分配
    • 大對象直接進入老年代
    • 長期存活的對象將進入老年代
    • 動態對象年齡判定
    • 空間分配擔保

 

 2.  垃圾收集器與記憶體分配策略

 

  Java技術體系中所提倡的自動記憶體管理最終可以歸結為自動化地解決兩個問題:

  • 給對象分配記憶體;
  • 回收分配給對象的記憶體。

  對象的記憶體分配,往大方向上講就是在堆上的分配,對象主要分配在新生代的Eden區上。少數也可能分配在老年代,取決於哪一種垃圾收集器組合,還有虛擬機中的相關記憶體的參數設置。下麵先介紹一下JVM中的年代劃分:新生代、老年代、永久代(JDK1.8後稱為元空間)。

 

2.1 JVM堆的結構分析(新生代、老年代、永久代)

 

  HotSpot JVM把年輕代分為了三部分:1個Eden區和2個Survivor區(分別叫from(S1)和to(S2)),具體可參下麵的JVM記憶體體系圖。Eden和Survival的預設分配比例為8:1。一般情況下,新創建的對象都會被分配到Eden區(一些大對象特殊處理,後面會說到),這些對象經過第一次Minor GC後,如果仍然存活,將會被移到Survivor區。對象在Survivor區中每熬過一次Minor GC,年齡就會增加1歲,當它的年齡增加到一定程度時,就會被移動到年老代中。

   因為年輕代中的對象基本都是朝生夕死的(80%以上),所以在年輕代的垃圾回收演算法使用的是複製演算法,複製演算法的基本思想就是將記憶體分為兩塊,每次只用其中一塊,當這一塊記憶體用完,就將還活著的對象複製到另外一塊上面。複製演算法不會產生記憶體碎片。

  在GC開始的時候,對象只會存在於Eden區和名為“From”的Survivor區,Survivor區“To”是空的。緊接著進行GC,Eden區中所有存活的對象都會被覆制到“To”,而在“From”區中,仍存活的對象會根據他們的年齡值來決定去向。年齡達到一定值(年齡閾值,可以通過-XX:MaxTenuringThreshold來設置)的對象會被移動到年老代中,沒有達到閾值的對象會被覆制到“To”區域。經過這次GC後,Eden區和From區已經被清空。這個時候,“From”和“To”會交換他們的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎樣,都會保證名為To的Survivor區域是空的。Minor GC會一直重覆這樣的過程,直到“To”區被填滿,“To”區被填滿之後,會將所有對象移動到年老代中。

    在年輕代中經歷了N次垃圾回收後仍然存活的對象,就會被放到年老代中。因此,可以認為年老代中存放的都是一些生命周期較長的對象。
  永久代主要用於存放靜態文件,Java類、方法等。永久代對垃圾回收沒有顯著影響,但是有些應 用可能動態生成或者調用一些class,例如Hibernate 等,在這種時候需要設置一個比較大的持永久代空間來存放這些運行過程中新增的類。永久代大小通過-XX: MaxPermSize = <N> 進行設置。

 

2.2 對象在Eden上分配

 

  大多數新生代對象都在Eden區中分配。當Eden區沒有足夠的空間進行分配時,虛擬機將發起一次Minor GC。

  下麵做一個測試程式demo,詳細說明,新生代對象在Eden區的記憶體分配情況。嘗試分配3個2MB大小和一個4MB大小的對象,在運行時候通過VM參數設置(看代碼註釋),限制java堆大小為20MB,不可擴展,其中10M分配給新生代,10M分給老年代,需要註意的是Eden區與一個Survivor區的空間比例是8:1,從輸出結果也可以看出"eden space 8192K,from space 1024K,to space 1024K"的信息,新生代的總空間為9216KB(endn區+1個survivor區的總容量)。測試代碼如下:

public class Minor_GC {
    private static final int _1MB = 1024 * 1024;

    /*
     * VM 參數配置: -Xms20M
     *             -Xmx20M
     *             -Xmn10M
     *             -XX:+PrintGCDetails
     */

    public static void main(String args[]){
        byte[] allocation1,allocation2,allocation3,allocation4;

        allocation1 = new byte[2 * _1MB];
        allocation2 = new byte[2 * _1MB];
        allocation3 = new byte[2 * _1MB];
        allocation4 = new byte[4 * _1MB];   // 出現一次GC回收

    }
}

  輸出GC日誌如下:

   上述參數可以看出: 執行main函數中,分配給allocation4對象時候發生了一次Minor GC(新生代回收),這次GC的結果是新生代記憶體7684k---->365k,然而堆上總記憶體的占用幾乎沒有改變,因為allocation1、allocation2、allocation3都存活,本次回收基本上沒有找到可回收的對象。分析如下:

  1. 新生代一共被分配10M,其中Enden:8M,survivor:2M(From:1M,To:1M);
  2. 給allocation4分配記憶體時,Eden已經被占用6M(allocation1、2、3共6M,所以剩下2M),所以記憶體已經不夠用了---->發生GC;
  3. 然而,6M放不進Survivor的From(只有1M),所以只能通過分配擔保機制提前轉移到老年代

  這次GC結束後,Eden中有4M的allocation4對象(一共8M,被占用50%左右),survivor為空閑,老年代為6M(被allocation1、2、3占用),日誌中顯示為6146k,其中老年代採用Mark-sweep(標誌清除)回收的方法。

   [註意]區別新生代(Minor GC)和老年代(Full GC):

  1. 新生代GC(Minor GC):指發生在新生代的垃圾收集動作,因為Java對象大多數都具備朝生夕滅的特性,所以Minor GC非常的頻繁,一般回收速度也比較快;
  2. 老年代GC(Major GC/Full GC):指發生在老年代的垃圾回收動作,出現Major GC,經常會有至少一次的MinorGC(因為對象大多數都是先在Eden分配空間的,但是並非絕對)。Major GC回收的速度會比Minor GC慢十倍以上(因為Minor GC回收一般都是大面積的回收採用複製演算法;而Major GC沒有額外空間為他擔保,只能採用標記-清理方法),這兩者的回收思路是相反的,是一個空間換時間和時間換空間的關係。

 

2.2 大對象直接進入老年代

 

  大對象是指需要大量記憶體空間的Java對象,最典型的大對象就是那種很長的字元串和數組(byte[ ]就是典型的大對象)。出現達對象很容易導致記憶體還有不少空間就提前觸發垃圾收集以獲取足夠的連續空間來“安置”它們。

  虛擬機提供了一個-XX:pretenureSize Threshold()參數,令大於這個設置直的對象直接在老年代分配。這樣做的目的是避免Eden和Survivor區之間發生大量的記憶體複製(新生帶採用複製的方法完成GC)。下麵做個測試demo說明問題:

public class Major_GC {
        private static final int _1MB = 1024 * 1024;
    /*
     * VM 參數配置: -Xms20M
     *             -Xmx20M
     *             -Xmn10M
     *             -XX:+PrintGCDetails
     *             -XX:PretenureSizeThreshold=3145728(等於3M)
     */
        public static void main(String args[]){
            byte[] allocation;
            allocation = new byte[4 * _1MB];   // 直接會分配到老年代
        }
}

  運行後可以看到,記憶體會直接在老年代分配。[說明]:這裡不給出運行結果,以免產生誤導,因為在Parallel Scavenge收集器是不支持PretenureSizeThreshold這個參數的,得不到這樣的結論。

 

 2.3 長期存活對象將進入老年代

 

  Java虛擬機採用分代收集的思想來管理虛擬機記憶體。虛擬機給每個對象定義了一個對象年齡(Age)計數器。如果對象在Eden出生並且經過第一次Minor GC後仍然存活,並且能被Survivor的話,將被移動到Survivor空間中,並且對象年齡增加到一定程度(預設15歲),就會被晉升到老年代。對晉升到老年代的對象的閾值可以通過-XX:MaxTenuringThreshold設置。

   下麵給出測試demo:

public class LongTimeExistObj {
        private static final int _1MB = 1024 * 1024;

    /*
     * VM 參數配置: -Xms20M
     *              -Xmx20M
     *              -Xmn10M
     *              -XX:+PrintGCDetails
     *              -XX:MaxTenuringThreshold=1
     *              -XX:+PrintTenuringDistribution
     */

        public static void main(String args[]){
            byte[] allocation1,allocation2,allocation3;
            allocation1 = new byte[_1MB/4];

            // 什麼時候進入老年代取決於-XX:MaxTenuringThreshold的設置
            allocation2 = new byte[4 * _1MB];
            allocation3 = new byte[4 * _1MB];
            allocation3 = null;
            allocation3 = new byte[4 * _1MB];
    }
}

  

  測試結果如下所示:

 

2.4 動態對象年齡判定

 

  虛擬並不是永遠都要求對象年齡必須達到MaxTenuringThreshold才能晉升為老年代的,如果在Survivor的空間相同年齡的所有對象大小總和大於Survivor空間的一半時,年齡大於或者等於該年齡的對象直接靜如老年代,無需要等到MaxTenuringThreshold中要求的年齡。

  下麵做一個動態年齡測試demo:

public class LongTimeExistObj {
        private static final int _1MB = 1024 * 1024;

    /*
     * VM 參數配置: -Xms20M
     *              -Xmx20M
     *              -Xmn10M
     *              -XX:+PrintGCDetails
     *              -XX:MaxTenuringThreshold=15
     *              -XX:+PrintTenuringDistribution
     */
        @SuppressWarnings("unused")
        public static void main(String args[]){
            byte[] allocation1,allocation2,allocation3,allocation4;
            allocation1 = new byte[_1MB/4];

            // 使得allocation1 + allocation2 > survivor空間的一半(0.5M)
            allocation2 = new byte[_1MB/4];

            allocation3 = new byte[4 * _1MB];
            allocation4 = new byte[4 * _1MB];
            allocation4 = null;
            allocation4 = new byte[4 * _1MB];
    }
}

  測試結果如下:

   執行代碼結果中,可以看出:Survivor區占用空間仍然為0(from = 0,to = 0);而老年代的記憶體使用為5M,而其他對象都為4M,可以知道,alloccation1和allocation2都在沒有達到15歲的時候就提前進入了老年代。驗證了我們的結論---->在Survivor的空間相同年齡的所有對象大小總和大於Survivor空間的一半時,年齡大於或者等於該年齡的對象直接靜如老年代

 

2.5 空間分配擔保

 

  在發生Minor GC之前,虛擬機會先檢查老年代可用的連續空間是否大於所有新生代的總空間,如果大於的話,那麼這個GC就可以保證安全,如果不成立的,那麼可能會造成晉升老年代的時候記憶體不足。在這樣的情況下,虛擬機會先檢查HandlePromotionFailure設置值是否允許擔保失敗,如果是允許的,那麼說明虛擬機允許這樣的風險存在並堅持運行,然後檢查老年代的最大連續可用空間是否大於歷次晉升老年代對象的平均大小,如果大於的話,就執行Minor GC,如果小於,或者HandlePromotionFailure設置不允許冒險,那麼就會先進行一次Full GC將老年代的記憶體清理出來,然後再判斷。

  上面提到的風險,是由於新生代因為存活對象採用複製演算法,但為了記憶體利用率,只使用其中的一個Survivor空間,將存活的對象備份到Survivor空間上,一旦出現大量對象在一次Minor GC以後依然存活(最壞的計劃就是沒有發現有對象死亡需要清理),那麼就需要老年代來分擔一部分記憶體,把在Survivor上分配不下的對象直接進入老年代,因為我們不知道實際上具體需要多大記憶體,我們只能估算一個合理值,這個值採用的方法就是計算出每次晉升老年代的平均記憶體大小作為參考,如果需要的話,那就提前進行一次Full GC.

  取平均值在大多數情況下是可行的,但是因為記憶體分配的不確定性太多,保不定哪次運行突然出現某些大對象或者Minor GC以後多數對象依然存活,導致記憶體遠遠高於平均值的話,依然會導致擔保失敗(Handle Promotion Failure)。如果出現了HandlePromotionFailure失敗,那就只好在失敗後重新發起一次Full GC。這樣的情況下,擔保失敗是要付出代價的,大部分情況下都還是會將HandlePromotionFailure開關打開,畢竟失敗的幾率比較小,這樣的擔保可以避免Full GC過於頻繁,垃圾收集器頻繁的啟動肯定是不好的。

   上面很繁瑣(詳細),實在看不下去就看圖吧:

 

 

 

 

文中關於新生代、老年代的概念部分內容參考了博文:https://www.cnblogs.com/E-star/p/5556188.html

 本文參考書籍:《深入理解java虛擬機》


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.彈出框 ,小括弧中就是彈出的內容 alert("我是一個彈出框"); 2.控制台輸出 小括弧裡面就是 控制台輸出的東西 console.log("我是控制台輸出的內容"); 3.彈出輸入框,可以接受用戶輸入的信息。 3.1 prompt("請輸入你的名字"); 3.2 prompt("請輸入你的 ...
  • vue——props的兩種常用方法 1、實現父—— 子的通信 舉例如下: 父組件 parent.vue <children :channel="object1" </children 子組件 children.vue export default{ name:"children", pr ...
  • 背景圖片自適應瀏覽器大小 之前在網上看到的一個小技巧,記錄一下:背景圖片鋪滿屏幕並且可以隨著瀏覽器進行自適應,代碼如下: <body <div style="position:absolute; width:100%; height:100%; z index: 1" <img s ...
  • 1、塊級作用域 (1)使用let代替var 好處:變數應該只在其聲明的代碼塊內有效;var命令存在變數提升效用,let命令沒有這個問題。 (2)全局常量 在let和const之間,建議優先使用const,尤其是在全局環境,不應該設置變數,只應設置常量。 const優於let有幾個原因。一個是cons ...
  • Apache Log4j 2 Apache Log4j 2是對Log4j的升級,它比它的前輩Log4j 1提供了顯著的改進。在解決Logback的架構中存在的一些固有問題時,提供了許多可用的改進。 特性 API分離 Log4j的API與實現分離,使應用程式開發人員清楚地知道,他們可以使用哪些類和方法 ...
  • CLR(Common Language Runtime)公共語言進行時是一個可由多種編程語言使用的“進行時”。 將源代碼編譯成托管模塊 可用支持CLR的任何語言創建源代碼文件,然後用對應的編譯器檢查語法和分析源代碼。無論選擇哪個編譯器,結果都是托管模塊(managed module)。托管模塊是標準 ...
  • 元類 在 Python中,實例對象是由類生成的,而類本身也是可以被傳遞和自省的對象。那麼類對象是用什麼創建和生成的呢?答案是元類,元類就是一種知道如何創建和管理類的對象。 讓我們回顧一個內置函數type(),type不僅可以返回對象的類型,而且可以使用類名稱、基類元組、類主體定義的字典作為參數來創建 ...
  • 今天學了前後臺的連接。之前腦子一團漿糊,就連後端對資料庫的操作都不熟。就一點點來寫吧,會多少寫多少,錯了以後再改吧,可能進步比較慢,但肯定會慢慢好起來的。 以前一直對資料庫連接就不太懂。先從資料庫連接寫起。 疑問點1:之前就是不明白為什麼要有一個con,而且還是Connection類型的, 理解:你 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...