JVM的記憶體分配垃圾回收策略

来源:https://www.cnblogs.com/smilepup-hhr/archive/2019/09/09/11494540.html
-Advertisement-
Play Games

之前看過《深入瞭解Java虛擬機》感覺容易忘,今天寫一篇博客加深一下印象。 JVM的記憶體分配和垃圾回收(GC)主要發生在Java堆中。而Java堆根據對象的存活時間可以分為新生代和老年代,而新生代又細分為Eden區、From Survivor區、To Survivor區,這是由於新生代中的垃圾回收算 ...


之前看過《深入瞭解Java虛擬機》感覺容易忘,今天寫一篇博客加深一下印象。

JVM的記憶體分配和垃圾回收(GC)主要發生在Java堆中。而Java堆根據對象的存活時間可以分為新生代和老年代,而新生代又細分為Eden區、From Survivor區、To Survivor區,這是由於新生代中的垃圾回收演算法基本都是複製演算法

1.對象優先在Eden區中分配

  當Eden區沒有足夠空間進行分配時,虛擬機會發起一次新生代GC(Minor GC)。因為Java對象大多數都具有朝生夕滅的特性,所以Minor GC非常頻繁,回收速度也比較快。

我們可以通過參數-XX:+PrintGCDetails來查看GC日誌。下麵舉個實際例子來看看是不是優先在Eden區中分配記憶體。

-verbose:gc
-Xms20M
-Xmx20M
-Xmn10M
-XX:+PrintGCDetails
-XX:SurvivorRatio=8
設置的參數
package MinorGC;

public class MinorGC {
    private static final int _1MB = 1024*1024;
    public static void main(String[] args) {
        byte[] allocation1,allocation2,allocation3;
        allocation1 = new byte[1*_1MB];
        allocation2 = new byte[2*_1MB];
        allocation3 = new byte[2*_1MB];
    }
}

可以看到控制台輸出

新生代的記憶體使用情況:total 9216K, used 7482K

老年代的記憶體使用情況:total 10240K, used 0K

 這樣可以看出對象優先在Eden區中分配。

 

2.大對象直接進入老年代

  大對象是指需要大量連續記憶體空間的Java對象,例如很長的字元串和數組,上面代碼中的byte[]數組就是大數組。經常出現大對象容易導致記憶體中還有不少記憶體就要提前GC來獲取連續的空間來安放它們。

虛擬機中可以設置參數-XX:PretenureSizeThreshold參數來讓大於這個設置值的對象直接在老年代分配,這樣的目的是避免在Eden區和兩個Survivor區之間進行大量的記憶體複製。

下麵測試一下,這時候要註意一下我用的是Java8,預設的垃圾回收器是Parallel Scavenge(新生代)+Parallel Old(老年代),而Parallel Scavenge不認識參數

-XX:PretenureSizeThreshold(Parallel Scavenge不需要設置),所以要先加上-XX:+UseSerialGC來將虛擬機的垃圾回收器設置成Serial / Serial Old回收器。

-verbose:gc
-Xms20M
-Xmx20M
-Xmn10M
-XX:+PrintGCDetails
-XX:SurvivorRatio=8
-XX:PretenureSizeThreshold=3145728
-XX:+UseSerialGC
設置的參數
package MinorGC;

public class MinorGC {
    private static final int _1MB = 1024*1024;
    public static void main(String[] args) {
        byte[] allocation4;
//        allocation1 = new byte[_1MB];
//        allocation2 = new byte[2*_1MB];
//        allocation3 = new byte[2*_1MB];
        allocation4 = new byte[4*_1MB];
    }
}

 

可以發現4MB的對象直接放到老年代中了

 

 

3.長期存活的對象將進入老年區

  虛擬機給每個對象定義一個對象年齡計數器。如果對象在Eden區出生並經過第一次Minor GC後仍然存活,並能被Survivor容納,將被移動到Survivor的空間中,並且對象年齡設為1對象在Survivor區每“熬過”一次Minor GC,年齡就增加1歲,當它的年齡增加到一定程度(預設15歲),就將被晉升到老年代中。JVM中可以通過-XX:MaxTenuringThreshold設置。

下麵我們測試一下

-verbose:gc
 -Xms20M
 -Xmx20M
 -Xmn10M
 -XX:+PrintGCDetails
 -XX:SurvivorRatio=8
 -XX:MaxTenuringThreshold=1
 -XX:+PrintTenuringDistribution
 -XX:+UseSerialGC
設置的參數
package MinorGC;

public class MinorGC {
    private static final int _1MB = 1024*1024;
    @SuppressWarnings("unused")
    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];
    }
}

該方法中allocation1對象需要256KB記憶體,Survivor區可以容納,所以講它放到Survivor區中並讓它年齡加1,。在第二次GC發生後它就進入老年代了,而Survivor區剛好被清除乾凈。

 

 

 

4.動態對象年齡判定

  為了更好地適應不同程式的記憶體狀況,虛擬機並不是永遠要求對象的年齡必須達到了MaxTenuringThreshold才晉升老年代,如果在Survivor空間中相同年齡所有對象大小的總和大於Survivor空間的一半, 年齡大於或等於該年齡的對象就可以直接進入老年代,無須等到MaxTenuringThreshold中要求的年齡。

測試

配置和前面的一樣,只是MaxTenuringThreshold=15

package MinorGC;

public class MinorGC {
    private static final int _1MB = 1024*1024;
    @SuppressWarnings("unused")
    public static void main(String[] args) {
        byte[] allocation1, allocation2, allocation3, allocation4;
        allocation1 = new byte[_1MB / 4];
        // allocation1+allocation2大於survivo空間一半
        allocation2 = new byte[_1MB / 4];
        allocation3 = new byte[4 * _1MB];
        allocation4 = new byte[4 * _1MB];
        allocation4 = null;
        allocation4 = new byte[4 * _1MB];
    }
}

 

發現運行結果中Survivor的空間占用仍然為0%,而老年代比預期增加了6%,也就是說,allocation1、allocation2對象都直接進入了老年代,而沒有等到15歲的臨界年齡。因為這兩個對象加起來已經到達了

512KB,並且它們是同年的,滿足同年對象達到Survivor空間的一半規則。

 

 在註釋掉allocation2後,發現只有一個256KB進入了老年代。

 

 

5.空間分配擔保

在發生Minor GC之前,虛擬機會先檢查老年代最大可用的連續空間是否大於新生代所有對象總空間,如果這個條件成立,那麼Minor GC可以確保是安全的。如果不成立,則虛擬機會查看HandlePromotionFailure設置值是否允許擔保失敗。如果允許,那麼會繼續檢查老年代最大可用的連續空間是否大於歷次晉升到老年代對象的平均大小如果大於,將嘗試著進行一次Minor GC,儘管這次Minor GC是有風險的;如果小於,或者HandlePromotionFailure設置不允許冒險,那這時也要改為進行一次Full GC。

新生代使用複製收集演算法,但為了記憶體利用率,只使用其中一個Survivor空間來作為輪換備份,因此當出現大量對象在Minor GC後仍然存活的情況(最極端的情況就是記憶體回收後新生代中所有對象都存活),就需要老年代進行分配擔保,把Survivor無法容納的對象直接進入老年代。與生活中的貸款擔保類似,老年代要進行這樣的擔保,前提是老年代本身還有容納這些對象的剩餘空間,一共有多少對象會活下來在實際完成記憶體回收之前是無法明確知道的,所以只好取之前每一次回收晉升到老年代對象容量的平均大小值作為經驗值,與老年代的剩餘空間進行比較,決定是否進行Full GC來讓老年代騰出更多空間

取平均值進行比較其實仍然是一種動態概率的手段,也就是說,如果某次Minor GC存活後的對象突增,遠遠高於平均值的話,依然會導致擔保失敗(Handle Promotion Failure)。如果出現了HandlePromotionFailure失敗,那就只好在失敗後重新發起一次Full GC。雖然擔保失敗時繞的圈子是最大的,但大部分情況下都還是會將HandlePromotionFailure開關打開,避免Full GC過於頻繁。

 

補充一下參數有哪些:

1、-Xmx –Xms:指定最大堆和最小堆

2、-Xmn、-XX:NewRatio、-XX:SurvivorRatio:

  • -Xmn:設置新生代大小
  • -XX:NewRatio:新生代(eden+2*s)和老年代(不包含永久區)的比值

        例如:4,表示新生代:老年代=1:4,即新生代占整個堆的1/5

  • -XX:SurvivorRatio(幸存代)設置兩個Survivor區和eden的比值

      例如:8,表示兩個Survivor:eden=2:8,即一個Survivor占年輕代的1/10

 

參考自:深入理解JVM

    https://www.jianshu.com/p/fa3569127416


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

-Advertisement-
Play Games
更多相關文章
  • 前言 在十萬博文終極架構中,我們使用了Tomcat集群,但這並不能保證系統不會出問題,為了保證系統的穩定運行,我們還需要對 Tomcat 進行有效的運維監控手段,不至於問題出現或者許久一段時間才知道。凌晨一點這個鍋可誰都不想背,為此基於目前的情況搭建了以下這麼一套監控預警系統。 架構圖 相關軟體 N ...
  • 分佈系統中,如何保證數據的一致性、原子性,分散式事務。分散式事務分為兩大類,柔性事務、剛性事務。 一、方法論篇 分散式事務主要分為兩部分,剛性事務和柔性事務。剛性事務主要針對DB層面,嚴格保證事務的原子性要麼都成功,要麼執行失敗,全部回滾。 柔性事務,相對於剛性事務來的,為了保證DB的利用率,以及系 ...
  • 我想要一個Python函數,它接受一個字元串,並返回一個數組,其中數組中的每個項目都是一個字元,或者是另一個這樣的數組。嵌套數組在輸入字元串中以'('和以')'開頭標記。 因此,該函數將如下所示: 註意:我更喜歡純粹功能性的解決方案。 解決方案 和, ...
  • 一、方法在執行過程中是如何分配記憶體的,記憶體是如何變化的? 1.方法只定義,不調用,是不會執行的,並且在JVM中也不會給該方法分配”運行所屬“的記憶體空間,只有在調用這個方法的時候,才會動態的給這個方法分配所屬的記憶體空間。 2.在JVM記憶體劃分上有這樣三個主要的記憶體空間(當然除了這三塊之外還有其他的記憶體 ...
  • 這個月公司的項目有點忙,我又生病了,美術同事和我又有幾個周末都有事所以沒有來給我做資源 而我這邊也又遇到了瓶頸,目前是開始攻關飛行道具的部分 UE4的4.23在經歷了8個預覽版之後終於出正式版了,我也第一時間更新下來並且升級了工程 可破壞建築什麼的聽起來可能是不錯的效果,以後做戰爭游戲可能會大量用到 ...
  • isinstance() 判斷isinstance(obj,cls)中obj是否是cls類的對象 issubclass() 判斷issubclass(sub,super)中sub是否是super類的派生類 反射 反射就是用字元串類型的名字去操作變數,python中的一切事物皆為對象(都可以使用反射) ...
  • 今日所學: /* 2019.08.19開始學習,此為補檔。 */ 1.String類 實例化:①String name1 = "張三" ; ②String name2 = new String("李四") ; 2.==比較的是引用,equals比較的是具體內容。 String name3 = nam ...
  • 力爭清晰完整準確(逐步完善,持續更新) 1、String類為什麼是final的 首先分析String的源碼: 類被final關鍵字限定,說明它不可以被繼承,沒有子類。即持有一個String對象的引用,它必然是String類,而不會是其他的類。 value[]是用來存儲值的,被final關鍵字修飾,說 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...