【拥抱大厂系列】百度面试官问过的 “JVM内存分配与回收策略原理”,我用这篇文章搞定了
點個贊,看一看,好習慣!本文 GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收錄,這是我花了3個月總結的一線大廠Java面試總結,本人已拿騰訊等大廠offer。
在前面的一篇文章深入理解Java虛擬機-如何利用VisualVM進行性能分析中講到了一些關于JVM調優的知識,但是,其實,還是有一些問題沒有非常清楚的可以回答的,這里先給出幾個問題,然后,我們再展開這篇文章需要講解的知識。
- 我們生成的對象最開始在哪分配?Eden?Survivor?還是老年代呢?
- 進入到老年代需要滿足什么條件呢?
接下來,我們就帶著這兩個問題展開全文。
1 對象優先在哪分配
其實,通過前面幾篇文章的講解,這個問題其實已經見怪不怪了,在大多數的情況下,對象都是在新生代Eden區分配的,在前面的文章我們提到,在Eden區中如果內存不夠分配的話,就會進行一次Minor GC。同時,我們還知道年輕代中默認下Eden:Survivor0:Survivor2 = 8:1:1,同時,還能通過參數-XX:SurvivorRatio來設置這個比例(關于這些參數的分析都可以查看這篇文章:深入理解Java虛擬機-常用vm參數分析)。
下面我們通過一個例子來分析是不是這樣的。
1.1 實例
給定JVM參數:-Xms40M -Xmx40M -Xmn10M -verbose:gc -XX:+PrintGCDetails -XX:SurvivorRatio=4
前面三個參數設置Java堆的大小為40M,新生代為10M,緊跟著后面兩個是用于輸入GC信息。更多參數可以查看這篇文章:深入理解Java虛擬機-常用vm參數分析。
/*** @ClassName Test_01* @Description 參數:-Xms40M -Xmx40M -Xmn20M -XX:+PrintGCDetails -XX:+UseSerialGC -XX:SurvivorRatio=8* @Author 歐陽思海* @Date 2019/12/3 16:00* @Version 1.0**/ public class Test_01 {private static final int M = 1024 * 1024;public static void test() {byte[] alloc1, alloc2, alloc3, alloc4;alloc1 = new byte[5 * M];alloc2 = new byte[5 * M];alloc3 = new byte[5 * M];alloc4 = new byte[10 * M];}public static void main(String[] args) {test();}}輸入結果:
分析
- eden:from:to=8:1:1,這個因為前面設置了參數-XX:SurvivorRatio=8。
- 新生代分配了20M的內存,所以前面三個byte數組可以分配,但是,分配第四個的時候,空間不夠,所以,需要進行一次Minor GC,GC之后,新生代從12534K變為598K。
- 前面在新生代分配的內存Minor GC之后,進入到了Survivor,但是,Survivor不夠分配,所以進入到了老年代,老年代已用內存達到了50%。
1.2 回答問題
所以,經過上面的例子我們發現,對象一般優先在新生代分配的,如果新生代內存不夠,就進行Minor GC回收內存。
2 進入到老年代需要滿足什么條件
先給出答案,分為幾點。
- 條件①:大對象直接進入到老年代
- 條件②:長期存活的對象可以進入到老年代
- 條件③:如果在Survivor空間中相同年齡所有對象的大小的總和大于Survivor空間的一半,年齡大于等于該年齡的對象直接進入到老年代
2.1 分析條件①
- 哪些屬于大對象呢?
一般來說大對象指的是很長的字符串及數組,或者靜態對象。
- 那么需要滿足多大才是大對象呢?
這個虛擬機提供了一個參數-XX:PretenureSizeThreshold=n,只需要大于這個參數所設置的值,就可以直接進入到老年代。
step1: 解決了這兩個問題,首先,我們不設置上面的參數的例子,將對象的內存大于Eden的大小看看情況。
/*** @ClassName Test_01* @Description 參數:-Xms40M -Xmx40M -Xmn20M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+UseSerialGC* @Author 歐陽思海* @Date 2019/12/3 16:00* @Version 1.0**/ public class Test_01 {private static final int M = 1024 * 1024;public static void test() {byte[] alloc1, alloc2, alloc3, alloc4; // alloc1 = new byte[5 * M]; // alloc2 = new byte[5 * M]; // alloc3 = new byte[5 * M];alloc4 = new byte[22 * M];}public static void main(String[] args) {test();}}我們發現分配失敗,Java堆溢出,因為超過了最大值。
step2: 下面我們看一個例子:設置-XX:PretenureSizeThreshold=104,857,600,這個單位是B字節(Byte/bait),所以這里是100M。
/*** @ClassName Test_01* @Description 參數:-Xms2048M -Xmx2048M -Xmn1024M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+UseSerialGC -XX:PretenureSizeThreshold=104,857,600* @Author 歐陽思海* @Date 2019/12/3 16:00* @Version 1.0**/ public class Test_01 {private static final int M = 1024 * 1024;public static void test() {byte[] alloc1, alloc2, alloc3, alloc4; // alloc1 = new byte[5 * M]; // alloc2 = new byte[5 * M]; // alloc3 = new byte[5 * M];alloc4 = new byte[500 * M];}public static void main(String[] args) {test();}}發現新生代沒有分配,直接在老年代分配。
注意: 參數PretenureSizeThreshold只對Serial和ParNew兩款收集器有效。
2.2 分析條件②
進入老年代規則:這里需要知道虛擬機對每個對象有個對象年齡計數器,如果對象在Eden出生經過第一次Minor GC后任然存活,并且能夠被Survivor容納,將被移動到Survivor空間中,并且年齡設置為1。接下來,對象在Survivor中每次經過一次Minor GC,年齡就增加1,默認當年齡達到15,就會進入到老年代。
晉升到老年代的年齡閾值,可以通過參數-XX:MaxTenuringThreshold設置。
在下面的實例中,我們設置-XX:MaxTenuringThreshold=1。
/*** @ClassName Test_01* @Description 參數:-Xms2048M -Xmx2048M -Xmn1024M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+UseSerialGC -XX:MaxTenuringThreshold=1* @Author 歐陽思海* @Date 2019/12/3 16:00* @Version 1.0**/ public class Test_01 {private static final int M = 1024 * 1024;public static void test() {byte[] alloc1, alloc2, alloc3, alloc4;alloc1 = new byte[300 * M];alloc2 = new byte[300 * M];alloc3 = new byte[300 * M];alloc4 = new byte[500 * M];}public static void main(String[] args) {test();}}從結果可以看出,from和to都沒有占用內存,而老年代則占用了很多內存。
2.3 分析條件③
條件③是:如果在Survivor空間中相同年齡所有對象的大小的總和大于Survivor空間的一半,年齡大于等于該年齡的對象直接進入到老年代,而不需要等到參數-XX:MaxTenuringThreshold設置的年齡。
實例分析
/*** @ClassName Test_01* @Description 參數:-Xms2048M -Xmx2048M -Xmn1024M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+UseSerialGC* @Author 歐陽思海* @Date 2019/12/3 16:00* @Version 1.0**/ public class Test_01 {private static final int M = 1024 * 1024;public static void test() {byte[] alloc1, alloc2, alloc3, alloc4;alloc1 = new byte[100 * M];alloc2 = new byte[100 * M];//分配alloc3之前,空間不夠,所以minor GC,接著分配alloc3=900M大于Survivor空間一半,直接到老年代。alloc3 = new byte[900 * M];// alloc4 = new byte[500 * M];}public static void main(String[] args) {test();}}輸入結果:
分配alloc3之前,空間不夠,所以minor GC,接著分配alloc3=900M大于Survivor空間一半,直接到老年代。從而發現,survivor占用0,而老年代占用900M。
3 總結
這篇文章主要講解了JVM內存分配與回收策略的原理,回答了下面的這兩個問題。
- 我們生成的對象最開始在哪分配?Eden?Survivor?還是老年代呢?
- 進入到老年代需要滿足什么條件呢?
最后,再分享我歷時三個月總結的 Java 面試 + Java 后端技術學習指南,這是本人這幾年及春招的總結,已經拿到了大廠offer,整理成了一本電子書,拿去不謝,目錄如下:
現在免費分享大家,在我的公眾號 好好學java 回復 Java面試 即可獲取。
有收獲?希望老鐵們來個三連擊,給更多的人看到這篇文章
1、老鐵們,關注我的原創微信公眾號「好好學java」,專注于Java、數據結構和算法、微服務、中間件等技術分享,保證你看完有所收獲。
2、給俺點個贊唄,可以讓更多的人看到這篇文章,順便激勵下我繼續寫作,嘻嘻。
點贊是對我最大的鼓勵
↓↓↓↓↓↓
總結
以上是生活随笔為你收集整理的【拥抱大厂系列】百度面试官问过的 “JVM内存分配与回收策略原理”,我用这篇文章搞定了的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 聊一聊二维码扫描登录原理
- 下一篇: Java程序员必备:序列化全方位解析