在前面的一篇文章深刻理解Java虛擬機-如何利用VisualVM進行性能分析中講到了一些關於JVM調優的知識,可是,其實,仍是有一些問題沒有很是清楚的能夠回答的,這裏先給出幾個問題,而後,咱們再展開這篇文章須要講解的知識。html
接下來,咱們就帶着這兩個問題展開全文。java
其實,經過前面幾篇文章的講解,這個問題其實已經見怪不怪了,在大多數的狀況下,對象都是在新生代Eden區分配的,在前面的文章咱們提到,在Eden區中若是內存不夠分配的話,就會進行一次Minor GC
。同時,咱們還知道年輕代中默認下Eden:Survivor0:Survivor2 = 8:1:1
,同時,還能經過參數-XX:SurvivorRatio
來設置這個比例(關於這些參數的分析均可以查看這篇文章:深刻理解Java虛擬機-經常使用vm參數分析)。git
下面咱們經過一個例子來分析是否是這樣的。github
給定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(); } }
輸入結果:數組
分析性能
-XX:SurvivorRatio=8
。byte
數組能夠分配,可是,分配第四個的時候,空間不夠,因此,須要進行一次Minor GC
,GC以後,新生代從12534K
變爲598K
。Minor GC
以後,進入到了Survivor
,可是,Survivor不夠分配,因此進入到了老年代
,老年代已用內存達到了50%
。因此,通過上面的例子咱們發現,對象通常優先在新生代分配的,若是新生代內存不夠,就進行Minor GC回收內存。ui
點個贊,看一看,好習慣!本文 GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收錄,這是我花了3個月總結的一線大廠Java面試總結,本人已拿騰訊等大廠offer。
先給出答案,分爲幾點。spa
通常來講大對象指的是很長的字符串及數組,或者靜態對象。3d
這個虛擬機提供了一個參數-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
兩款收集器有效。
進入老年代規則:這裏須要知道虛擬機對每一個對象有個對象年齡計數器,若是對象在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都沒有佔用內存,而老年代則佔用了不少內存。
條件③是:若是在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。
這篇文章主要講解了JVM內存分配與回收策略的原理,回答了下面的這兩個問題。