Java虛擬機(jī)內(nèi)存分配與回收策略問(wèn)題精細(xì)解讀
本文參考于《深入理解Java虛擬機(jī)》
內(nèi)存分配與回收策略
Java技術(shù)體系的自動(dòng)內(nèi)存管理,最根本的目標(biāo)是自動(dòng)化地解決兩個(gè)問(wèn)題:自動(dòng)給對(duì)象分配內(nèi)存以及自動(dòng)回收分配給對(duì)象的內(nèi)存。
1. 綜述
對(duì)象的內(nèi)存分配,從概念上講,應(yīng)該都是在堆上分配(而實(shí)際上也有可能經(jīng)過(guò)即時(shí)編譯后被拆散為標(biāo)量類型并間接地在棧上分配)。在經(jīng)典分代的設(shè)計(jì)下,新生對(duì)象通常會(huì)分配在新生代中,少數(shù)情況下(例如對(duì)象大小超過(guò)一定閾值)也可能會(huì)直接分配在老年代。對(duì)象分配的規(guī)則并不是固定的,《Java虛擬機(jī)規(guī)范》并未規(guī)定新對(duì)象的創(chuàng)建和存儲(chǔ)細(xì)節(jié),這取決于虛擬機(jī)當(dāng)前使用的是哪一種垃圾收集器,以及虛擬機(jī)中與內(nèi)存相關(guān)的參數(shù)的設(shè)定。
(1)、對(duì)象優(yōu)先在Eden分配
大多數(shù)情況下,對(duì)象在新生代Eden區(qū)中分配。當(dāng)Eden區(qū)沒(méi)有足夠空間進(jìn)行分配時(shí),虛擬機(jī)將發(fā)起一次Minor GC。
1. Eden區(qū)有足夠空間的情形
虛擬機(jī)參數(shù)
-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
參數(shù)說(shuō)明
嘗試分配三個(gè)2MB大小和一個(gè)4MB大小的對(duì)象,在運(yùn)行時(shí)通過(guò)-Xms20M、-Xmx20M、-Xmn10M這三個(gè)參數(shù)限制了Java堆大小為20MB,不可擴(kuò)展,其中10MB分配給新生代,剩下的10MB分配給老年代。-XX:Survivor-Ratio=8決定了新生代中Eden區(qū)與一個(gè)Survivor區(qū)的空間比例是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區(qū)沒(méi)有足夠空間的情形
虛擬機(jī)參數(shù)相同
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];
}
}

顯然進(jìn)行了Minor GC
(2)、大對(duì)象直接進(jìn)入老年代
1. 什么是大對(duì)象?
大對(duì)象就是指需要大量連續(xù)內(nèi)存空間的Java對(duì)象,最典型的大對(duì)象便是那種很長(zhǎng)的字符串,或者元素?cái)?shù)量很龐大的數(shù)組。
2. Java虛擬機(jī)中要避免大對(duì)象的原因
在分配空間時(shí),它容易導(dǎo)致內(nèi)存明明還有不少空間時(shí)就提前觸發(fā)垃圾收集,以獲取足夠的連續(xù)空間才能安置好它們。而當(dāng)復(fù)制對(duì)象時(shí),大對(duì)象就意味著高額的內(nèi)存復(fù)制開(kāi)銷。
3. 大對(duì)象直接進(jìn)入老年代的好處
避免在Eden區(qū)及兩個(gè)Survivor區(qū)之間來(lái)回復(fù)制,產(chǎn)生大量的內(nèi)存復(fù)制操作(HotSpot虛擬機(jī)提供了-XX:PretenureSizeThreshold參數(shù),指定大于該設(shè)置值的對(duì)象直接在老年代分配)。
(3)、長(zhǎng)期存活的對(duì)象將進(jìn)入老年代
1. 虛擬機(jī)是怎樣判斷對(duì)象是否是長(zhǎng)期存活?
內(nèi)存回收時(shí)就必須能決策哪些存活對(duì)象應(yīng)當(dāng)放在新生代,哪些存活對(duì)象放在老年代中。為做到這點(diǎn),虛擬機(jī)給每個(gè)對(duì)象定義了一個(gè)對(duì)象年齡(Age)計(jì)數(shù)器,存儲(chǔ)在對(duì)象頭中。
2. 對(duì)象年齡增加及晉升至老年代過(guò)程
對(duì)象通常在Eden區(qū)里誕生,如果經(jīng)過(guò)第一次Minor GC后仍然存活,并且能被Survivor容納的話,該對(duì)象會(huì)被移動(dòng)到Survivor空間中,并且將其對(duì)象年齡設(shè)為1歲。對(duì)象在Survivor區(qū)中每熬過(guò)一次Minor GC,年齡就增加1歲,當(dāng)它的年齡增加到一定程度(默認(rèn)為15),就會(huì)被晉升到老年代中。對(duì)象晉升老年代的年齡閾值,可以通過(guò)參數(shù)-XX:MaxTenuringThreshold設(shè)置。
3. 長(zhǎng)期存活的對(duì)象將進(jìn)入老年代的原因
我們都知道新生代的垃圾收集算法算法是標(biāo)記-復(fù)制算法,如果長(zhǎng)期存活的對(duì)象仍然存放在新生代的話,那么就會(huì)帶來(lái)復(fù)制的開(kāi)銷增大的問(wèn)題。所以我們將大于某一年齡閾值的對(duì)象放入老年代,這樣可以減輕新生代垃圾回收時(shí)的壓力。
(4)、動(dòng)態(tài)對(duì)象年齡判定
為了能更好地適應(yīng)不同程序的內(nèi)存狀況,HotSpot虛擬機(jī)并不是永遠(yuǎn)要求對(duì)象的年齡必須達(dá)到-XX:MaxTenuringThreshold才能晉升老年代,如果在Survivor空間中相同年齡所有對(duì)象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對(duì)象就可以直接進(jìn)入老年代,無(wú)須等到-XX:MaxTenuringThreshold中要求的年齡。
(5)、空間分配擔(dān)保
1. 空間分配擔(dān)保的內(nèi)容
在發(fā)生Minor GC之前,虛擬機(jī)必須先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對(duì)象總空間,如果這個(gè)條件成立,那這一次Minor GC可以確保是安全的。如果不成立,則虛擬機(jī)會(huì)先查看-XX:HandlePromotionFailure參數(shù)的設(shè)置值是否允許擔(dān)保失?。蝗绻试S,那會(huì)繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對(duì)象的平均大小,如果大于,將嘗試進(jìn)行一次Minor GC,盡管這次Minor GC是有風(fēng)險(xiǎn)的;如果小于,或者 -XX:HandlePromotionFailure設(shè)置不允許冒險(xiǎn),那這時(shí)就要改為進(jìn)行一次Full GC。
2. “冒險(xiǎn)”是冒了什么風(fēng)險(xiǎn)
前面提到過(guò),新生代使用復(fù)制收集算法,但為了內(nèi)存利用率,只使用其中一個(gè)Survivor空間來(lái)作為輪換備份,因此當(dāng)出現(xiàn)大量對(duì)象在Minor GC后仍然存活的情況——最極端的情況就是內(nèi)存回收后新生代中所有對(duì)象都存活,需要老年代進(jìn)行分配擔(dān)保,把Survivor無(wú)法容納的對(duì)象直接送入老年代,這與生活中貸款擔(dān)保類似。老年代要進(jìn)行這樣的擔(dān)保,前提是老年代本身還有容納這些對(duì)象的剩余空間,但一共有多少對(duì)象會(huì)在這次回收中活下來(lái)在實(shí)際完成內(nèi)存回收之前是無(wú)法明確知道的,所以只能取之前每一次回收晉升到老年代對(duì)象容量的平均大小作為經(jīng)驗(yàn)值,與老年代的剩余空間進(jìn)行比較,決定是否進(jìn)行Full GC來(lái)讓老年代騰出更多空間。
3. 那我們需要把擔(dān)保打開(kāi)嗎?
取歷史平均值來(lái)比較其實(shí)仍然是一種賭概率的解決辦法,也就是說(shuō)假如某次Minor GC存活后的對(duì)象突增,遠(yuǎn)遠(yuǎn)高于歷史平均值的話,依然會(huì)導(dǎo)致?lián)J H绻霈F(xiàn)了擔(dān)保失敗,那就只好老老實(shí)實(shí)地重新發(fā)起一次Full GC,這樣停頓時(shí)間就很長(zhǎng)了。雖然擔(dān)保失敗時(shí)繞的圈子是最大的,但通常情況下都還是會(huì)將-XX:HandlePromotionFailure開(kāi)關(guān)打開(kāi),避免Full GC過(guò)于頻繁。
到此這篇關(guān)于Java虛擬機(jī)內(nèi)存分配與回收策略問(wèn)題精細(xì)解讀的文章就介紹到這了,更多相關(guān)Java 虛擬機(jī)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java實(shí)現(xiàn)常用加密算法——單向加密算法MD5和SHA
本篇文章主要介紹了Java實(shí)現(xiàn)常用加密算法——單向加密算法MD5和SHA,信息加密后數(shù)據(jù)更安全,需要的朋友可以參考下。2016-10-10
Java synchronized關(guān)鍵字使用方式及特性解析
這篇文章主要介紹了Java synchronized關(guān)鍵字使用方式及特性解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12
SpringBoot執(zhí)行異步任務(wù)Async介紹
這篇文章主要為大家介紹了SpringBoot執(zhí)行異步任務(wù)Async示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09
SpringBoot配置SwaggerUI訪問(wèn)404錯(cuò)誤的解決方法
這篇文章主要為大家詳細(xì)介紹了SpringBoot配置SwaggerUI訪問(wèn)404錯(cuò)誤的解決方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12
Java對(duì)象級(jí)別與類級(jí)別的同步鎖synchronized語(yǔ)法示例
這篇文章主要為大家介紹了Java對(duì)象級(jí)別與類級(jí)別的同步鎖synchronized語(yǔ)法示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-03-03

