您好,登錄后才能下訂單哦!
小編給大家分享一下Java虛擬機中內存分配與回收策略的示例分析,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
Java技術體系的自動內存管理,最根本的目標是自動化地解決兩個問題:自動給對象分配內存以及自動回收分配給對象的內存。
1. 綜述
對象的內存分配,從概念上講,應該都是在堆上分配(而實際上也有可能經過即時編譯后被拆散為標量類型并間接地在棧上分配)。在經典分代的設計下,新生對象通常會分配在新生代中,少數情況下(例如對象大小超過一定閾值)也可能會直接分配在老年代。對象分配的規則并不是固定的,《Java虛擬機規范》并未規定新對象的創建和存儲細節,這取決于虛擬機當前使用的是哪一種垃圾收集器,以及虛擬機中與內存相關的參數的設定。
大多數情況下,對象在新生代Eden區中分配。當Eden區沒有足夠空間進行分配時,虛擬機將發起一次Minor GC。
1. Eden區有足夠空間的情形
虛擬機參數
-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
參數說明
嘗試分配三個2MB大小和一個4MB大小的對象,在運行時通過-Xms20M、-Xmx20M、-Xmn10M這三個參數限制了Java堆大小為20MB,不可擴展,其中10MB分配給新生代,剩下的10MB分配給老年代。-XX:Survivor-Ratio=8決定了新生代中Eden區與一個Survivor區的空間比例是8∶1。
package com.xiao.test.Test; public class test { private static final int _1MB = 1024 * 1024; public static void main(String[] args) { byte[] byte1,byte2,byte3; byte1 = new byte[2 * _1MB]; byte2 = new byte[2 * _1MB]; byte3 = new byte[2 * _1MB]; } }
2. Eden區沒有足夠空間的情形
虛擬機參數相同
package com.xiao.test.Test; public class test { private static final int _1MB = 1024 * 1024; public static void main(String[] args) { byte[] byte1,byte2,byte3,byte4; byte1 = new byte[2 * _1MB]; byte2 = new byte[2 * _1MB]; byte3 = new byte[2 * _1MB]; byte4 = new byte[3 * _1MB]; } }
顯然進行了Minor GC
1. 什么是大對象?
大對象就是指需要大量連續內存空間的Java對象,最典型的大對象便是那種很長的字符串,或者元素數量很龐大的數組。
2. Java虛擬機中要避免大對象的原因
在分配空間時,它容易導致內存明明還有不少空間時就提前觸發垃圾收集,以獲取足夠的連續空間才能安置好它們。而當復制對象時,大對象就意味著高額的內存復制開銷。
3. 大對象直接進入老年代的好處
避免在Eden區及兩個Survivor區之間來回復制,產生大量的內存復制操作(HotSpot虛擬機提供了-XX:PretenureSizeThreshold參數,指定大于該設置值的對象直接在老年代分配)。
1. 虛擬機是怎樣判斷對象是否是長期存活?
內存回收時就必須能決策哪些存活對象應當放在新生代,哪些存活對象放在老年代中。為做到這點,虛擬機給每個對象定義了一個對象年齡(Age)計數器,存儲在對象頭中。
2. 對象年齡增加及晉升至老年代過程
對象通常在Eden區里誕生,如果經過第一次Minor GC后仍然存活,并且能被Survivor容納的話,該對象會被移動到Survivor空間中,并且將其對象年齡設為1歲。對象在Survivor區中每熬過一次Minor GC,年齡就增加1歲,當它的年齡增加到一定程度(默認為15),就會被晉升到老年代中。對象晉升老年代的年齡閾值,可以通過參數-XX:MaxTenuringThreshold設置。
3. 長期存活的對象將進入老年代的原因
我們都知道新生代的垃圾收集算法算法是標記-復制算法,如果長期存活的對象仍然存放在新生代的話,那么就會帶來復制的開銷增大的問題。所以我們將大于某一年齡閾值的對象放入老年代,這樣可以減輕新生代垃圾回收時的壓力。
為了能更好地適應不同程序的內存狀況,HotSpot虛擬機并不是永遠要求對象的年齡必須達到-XX:MaxTenuringThreshold才能晉升老年代,如果在Survivor空間中相同年齡所有對象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對象就可以直接進入老年代,無須等到-XX:MaxTenuringThreshold中要求的年齡。
1. 空間分配擔保的內容
在發生Minor GC之前,虛擬機必須先檢查老年代最大可用的連續空間是否大于新生代所有對象總空間,如果這個條件成立,那這一次Minor GC可以確保是安全的。如果不成立,則虛擬機會先查看-XX:HandlePromotionFailure參數的設置值是否允許擔保失敗;如果允許,那會繼續檢查老年代最大可用的連續空間是否大于歷次晉升到老年代對象的平均大小,如果大于,將嘗試進行一次Minor GC,盡管這次Minor GC是有風險的;如果小于,或者 -XX:HandlePromotionFailure設置不允許冒險,那這時就要改為進行一次Full GC。
2. “冒險”是冒了什么風險
前面提到過,新生代使用復制收集算法,但為了內存利用率,只使用其中一個Survivor空間來作為輪換備份,因此當出現大量對象在Minor GC后仍然存活的情況——最極端的情況就是內存回收后新生代中所有對象都存活,需要老年代進行分配擔保,把Survivor無法容納的對象直接送入老年代,這與生活中貸款擔保類似。老年代要進行這樣的擔保,前提是老年代本身還有容納這些對象的剩余空間,但一共有多少對象會在這次回收中活下來在實際完成內存回收之前是無法明確知道的,所以只能取之前每一次回收晉升到老年代對象容量的平均大小作為經驗值,與老年代的剩余空間進行比較,決定是否進行Full GC來讓老年代騰出更多空間。
3. 那我們需要把擔保打開嗎?
取歷史平均值來比較其實仍然是一種賭概率的解決辦法,也就是說假如某次Minor GC存活后的對象突增,遠遠高于歷史平均值的話,依然會導致擔保失敗。如果出現了擔保失敗,那就只好老老實實地重新發起一次Full GC,這樣停頓時間就很長了。雖然擔保失敗時繞的圈子是最大的,但通常情況下都還是會將-XX:HandlePromotionFailure開關打開,避免Full GC過于頻繁。
以上是“Java虛擬機中內存分配與回收策略的示例分析”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。