1.年輕代存活的物件太多,老年代了放不下
01.示例代碼
public class DemoTest1 {
public static void main(String[] args) {
byte[] array1 = new byte[4 * 1024 * 1024];
array1 = null;
byte[] array2 = new byte[2 * 1024 * 1024];
byte[] array3 = new byte[2 * 1024 * 1024];
byte[] array4 = new byte[2 * 1024 * 1024];
byte[] array5 = new byte[128 * 1024];
byte[] array6 = new byte[2 * 1024 * 1024];
}
02.啟動JVM引數
-XX:NewSize=10485760 -XX:MaxNewSize=10485760 -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -XX:PretenureSizeThreshold=3145728 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log
其中,引數-XX:PretenureSizeThreshold,引數要設定大物件閾值為3MB,也就是超過3MB,就直接進入老年代,
大物件大小是3MB,一旦物件大小超過3MB,不會進入新生代,直接進入老年代,
啟動命令:
java -jar -XX:NewSize=10485760 -XX:MaxNewSize=10485760 -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:SurvivorRatio=8 -XX:MaxTenuringThre
shold=15 -XX:PretenureSizeThreshold=3145728 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log jvm-demo.jar
03.GC日志
啟動之后就得到如下GC日志:
Java HotSpot(TM) 64-Bit Server VM (25.151-b12) for windows-amd64 JRE (1.8.0_151-b12), built on Sep 5 2017 19:33:46 by "java_re" with MS VC++ 10.0 (VS2010)
Memory: 4k page, physical 16703268k(7458748k free), swap 23781156k(9784196k free)
CommandLine flags: -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:MaxNewSize=10485760 -XX:MaxTenuringThreshold=15 -XX:NewSize=10485760 -XX:OldPLABSize=16 -XX:PretenureSizeThreshold=3145728 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:SurvivorRatio=8 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:-UseLargePagesIndividualAllocation -XX:+UseParNewGC
0.174: [GC (Allocation Failure) 0.174: [ParNew (promotion failed): 7457K->8328K(9216K), 0.0046949 secs]
0.179: [CMS: 8194K->6962K(10240K), 0.0033396 secs] 11553K->6962K(19456K), [Metaspace: 2970K->2970K(1056768K)], 0.0089224 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Heap
par new generation total 9216K, used 2130K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 26% used [0x00000000fec00000, 0x00000000fee14930, 0x00000000ff400000)
from space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
concurrent mark-sweep generation total 10240K, used 6962K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
Metaspace used 2976K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 330K, capacity 386K, committed 512K, reserved 1048576K
04.分析GC日志
先看如下代碼:
byte[] array1 = new byte[4 * 1024 * 1024];
array1 = null;
這行代碼直接分配了一個4MB的大物件,此時這個物件會直接進入老年代,接著array1不再參考這個物件,
此時記憶體分配如下:
緊接著就是如下代碼
byte[] array2 = new byte[2 * 1024 * 1024];
byte[] array3 = new byte[2 * 1024 * 1024];
byte[] array4 = new byte[2 * 1024 * 1024];
byte[] array5 = new byte[128 * 1024];
連續分配了4個陣列,其中3個是2MB的陣列,1個是128KB的陣列,如下圖所示,全部會進入Eden區域中,
接著會執行如下代碼:byte[] array6 = new byte[2 * 1024 * 1024];,此時還能放得下2MB的物件嗎?
不可能了,因為Eden區已經放不下了,因此此時會直接觸發一次Young GC,
我們看下面的GC日志:
0.174: [GC (Allocation Failure) 0.174: [ParNew (promotion failed): 7457K->8328K(9216K), 0.0046949 secs]
這行日志顯示了,Eden區原來是有7000多KB的物件,但是回收之后發現一個都回收不掉,因為上述幾個陣列都被變數參考了,
所以此時,一定會直接把這些物件放入到老年代里去,但是此時老年代里已經有一個4MB的陣列了,還能放的下3個2MB的陣列和1個128KB的陣列嗎?
明顯是不行的,此時一定會超過老年代的10MB大小,
所以此時看gc日志:
0.179: [CMS: 8194K->6962K(10240K), 0.0033396 secs] 11553K->6962K(19456K), [Metaspace: 2970K->2970K(1056768K)], 0.0089224 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
此時執行了CMS垃圾回收器的Full GC,Full GC其實就是會對老年代進行Old GC,同時一般會跟一次Young GC關聯,還會觸發一次元資料區(永久代)的GC,
在CMS Full GC之前,就已經觸發過Young GC了,此時可以看到此時Young GC就已經有了,接著就是執行針對老年代的Old GC,也就是如下日志:
CMS: 8194K->6962K(10240K), 0.0033396 secs
這里看到老年代從8MB左右的物件占用,變成了6MB左右的物件占用,這是怎么個程序呢?
很簡單,一定是在Young GC之后,先把2個2MB的陣列放入了老年代,如下圖,
此時要繼續放1個2MB的陣列和1個128KB的陣列到老年代,一定會放不下,所以此時就會觸發CMS的Full GC,
然后此時就會回收掉其中的一個4MB的陣列,因為他已經沒人參考了,如下圖所示,
所以再看CMS的垃圾回收日志:CMS: 8194K->6962K(10240K), 0.0033396 secs,他是從回收前的8MB變成了6MB,就是上圖所示,
最后在CMS Full GC執行完畢之后,其實年輕代的物件都進入了老年代,此時最后一行代碼要在年輕代分配2MB的陣列就可以成功了,如下圖,
05.總結
這是一個觸發老年代GC的案例,就是年輕代存活的物件太多放不下老年代了,此時就會觸發CMS的Full GC,
2.老年代可用空間小于了歷次Young GC后升入老年代的物件的平均大小
01.示例代碼
public class DemoTest1 {
public static void main(String[] args) {
byte[] array1 = new byte[1 * 1024 * 1024];
array1 = null;
byte[] array2 = new byte[1 * 1024 * 1024];
array2 = null;
byte[] array3 = new byte[1 * 1024 * 1024];
array3 = null;
byte[] array4 = new byte[1 * 1024 * 1024];//觸發YGC 1MB 1
array1 = new byte[1 * 1024 * 1024];
array1 = null;
array2 = new byte[1 * 1024 * 1024];
array2 = null;
array3 = new byte[1 * 1024 * 1024];//觸發YGC Y 1MB O 1MB 2
array3 = null;
byte[] array5 = new byte[1 * 1024 * 1024];// Y 2MB O 1MB
array1 = new byte[1 * 1024 * 1024];// Y 3MB
array1 = null;
array2 = new byte[1 * 1024 * 1024];// Y 1MB O 2MB YGC 3
array2 = null;
array3 = new byte[1 * 1024 * 1024];//Y 2MB O 2MB
array3 = null;
byte[] array6 = new byte[1 * 1024 * 1024];//Y 3MB O 2MB
array1 = new byte[1 * 1024 * 1024];//Y 1MB O 3MB YGC 4
array1 = null;
array2 = new byte[1 * 1024 * 1024];//Y 2MB
array2 = null;
array3 = new byte[1 * 1024 * 1024];//Y 3MB
array3 = null;
byte[] array7 = new byte[1 * 1024 * 1024];//YGC 5
}
}
02.啟動JVM引數
-XX:NewSize=5M -XX:MaxNewSize=5M -XX:InitialHeapSize=10M -XX:MaxHeapSize=10M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -XX:PretenureSizeThreshold=2M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log
其中,引數-XX:PretenureSizeThreshold,引數要設定大物件閾值為2MB,也就是超過2MB,就直接進入老年代,
大物件大小是3MB,一旦物件大小超過3MB,不會進入新生代,直接進入老年代,
啟動命令:
java -jar -XX:NewSize=5M -XX:MaxNewSize=5M -XX:InitialHeapSize=10M -XX:MaxHeapSize=10M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -XX:PretenureSizeThreshold=2M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log jvm-demo.jar
03.GC日志
啟動之后就得到如下GC日志:
老年代
年輕代
Java HotSpot(TM) 64-Bit Server VM (25.151-b12) for windows-amd64 JRE (1.8.0_151-b12), built on Sep 5 2017 19:33:46 by "java_re" with MS VC++ 10.0 (VS2010)
Memory: 4k page, physical 16703268k(7221016k free), swap 23781156k(8613656k free)
CommandLine flags: -XX:InitialHeapSize=10485760 -XX:MaxHeapSize=10485760 -XX:MaxNewSize=5242880 -XX:MaxTenuringThreshold=15 -XX:NewSize=5242880 -XX:OldPLABSize=16 -XX:PretenureSizeThreshold=2097152 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:SurvivorRatio=8 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseConcMarkSweepGC -XX:-UseLargePagesIndividualAllocation -XX:+UseParNewGC
0.121: [GC (Allocation Failure) 0.121: [ParNew: 3155K->512K(4608K), 0.0041165 secs] 3155K->766K(9728K), 0.0042644 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.125: [GC (Allocation Failure) 0.125: [ParNew: 3663K->0K(4608K), 0.0016667 secs] 3917K->1732K(9728K), 0.0017448 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.127: [GC (Allocation Failure) 0.127: [ParNew: 3142K->0K(4608K), 0.0013221 secs] 4875K->2756K(9728K), 0.0013592 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.129: [GC (CMS Initial Mark) [1 CMS-initial-mark: 2756K(5120K)] 4878K(9728K), 0.0004498 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.129: [CMS-concurrent-mark-start]
0.130: [GC (Allocation Failure) 0.130: [ParNew: 3146K->0K(4608K), 0.0005869 secs] 5902K->2756K(9728K), 0.0006362 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.131: [GC (Allocation Failure) 0.131: [ParNew: 3148K->0K(4608K), 0.0007974 secs] 5904K->3780K(9728K), 0.0008262 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
par new generation total 4608K, used 2207K [0x00000000ff600000, 0x00000000ffb00000, 0x00000000ffb00000)
eden space 4096K, 53% used [0x00000000ff600000, 0x00000000ff827f38, 0x00000000ffa00000)
from space 512K, 0% used [0x00000000ffa80000, 0x00000000ffa80000, 0x00000000ffb00000)
to space 512K, 0% used [0x00000000ffa00000, 0x00000000ffa00000, 0x00000000ffa80000)
concurrent mark-sweep generation total 5120K, used 3780K [0x00000000ffb00000, 0x0000000100000000, 0x0000000100000000)
Metaspace used 2976K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 330K, capacity 386K, committed 512K, reserved 1048576K
04.分析GC日志
(1).代碼塊1
先看如下代碼:
byte[] array1 = new byte[1 * 1024 * 1024];
array1 = null;
byte[] array2 = new byte[1 * 1024 * 1024];
array2 = null;
byte[] array3 = new byte[1 * 1024 * 1024];
array3 = null;
byte[] array4 = new byte[1 * 1024 * 1024];
這段代碼直接分配了4個1MB的陣列,并且在第4個陣列的時候,會因為新生代記憶體不足觸發YGC,
此時記憶體分配如下:
對應如下GC日志:
0.121: [GC (Allocation Failure) 0.121: [ParNew: 3155K->512K(4608K), 0.0041165 secs] 3155K->766K(9728K), 0.0042644 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
此時,可以看到新生代就只剩512K的物件,這個奇怪的512KB的物件進入Survivor From區,
那么大小為1MB的陣列物件去哪里呢?肯定不是這個奇怪的512KB的物件,
這1MB的陣列首先肯定是準備進入Survivor From區,可是,在我們設定的JVM引數下,只有0.5MB,明顯是不夠分配的,根據JVM YoungGC的規則,Survivor區放不下GC之后存活的物件,直接進入老年代,
所以,1MB的陣列物件是直接進入到老年代了,
此時,記憶體分配如下:
(2).代碼塊2
緊接這就是這塊代碼:
array1 = new byte[1 * 1024 * 1024];
array1 = null;
array2 = new byte[1 * 1024 * 1024];
array2 = null;
array3 = new byte[1 * 1024 * 1024];
這里再次創建了3個1MB的陣列物件,并且會觸發一次YoungGC;
對應 GC日志如下:
0.125: [GC (Allocation Failure) 0.125: [ParNew: 3663K->0K(4608K), 0.0016667 secs] 3917K->1732K(9728K), 0.0017448 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
此時,Young GC之后,新生代變成0KB,那么存活的大小為1MB的陣列物件去哪里呢?
這1MB的陣列首先肯定是準備進入Survivor From區,可是,在我們設定的JVM引數下,只有0.5MB,明顯是不夠分配的,根據JVM YoungGC的規則,Survivor區放不下GC之后存活的物件,直接進入老年代,
所以,1MB的陣列物件是直接進入到老年代了,
之前看到的未知的物件512KB也進入到老年代,此時記憶體分配如下:
(3).代碼塊3
array3 = null;
byte[] array5 = new byte[1 * 1024 * 1024];
array1 = new byte[1 * 1024 * 1024];
array1 = null;
array2 = new byte[1 * 1024 * 1024];
這里再次創建了3個1MB的陣列物件,并且會觸發一次YoungGC;
對應的GC日志如下:
0.127: [GC (Allocation Failure) 0.127: [ParNew: 3142K->0K(4608K), 0.0013221 secs] 4875K->2756K(9728K), 0.0013592 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
此時記憶體分配如下:
(4).代碼塊4
array2 = null;
array3 = new byte[1 * 1024 * 1024];//Y 2MB O 2MB
array3 = null;
byte[] array6 = new byte[1 * 1024 * 1024];
array1 = new byte[1 * 1024 * 1024];
這里再次創建了3個1MB的陣列物件,并且會觸發一次YoungGC;并且在這兒,觸發Young GC之前觸發了一次CMS的Old GC,觸發的條件就是老年代可用空間小于了歷次Young GC后升入老年代的物件的平均大小,此時新生代大小變成0KB
對應的GC日志如下:
0.129: [GC (CMS Initial Mark) [1 CMS-initial-mark: 2756K(5120K)] 4878K(9728K), 0.0004498 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
0.129: [CMS-concurrent-mark-start]
0.130: [GC (Allocation Failure) 0.130: [ParNew: 3146K->0K(4608K), 0.0005869 secs] 5902K->2756K(9728K), 0.0006362 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
此時記憶體分配如下:
(5).代碼塊5
array1 = null;
array2 = new byte[1 * 1024 * 1024];//Y 2MB
array2 = null;
array3 = new byte[1 * 1024 * 1024];//Y 3MB
array3 = null;
byte[] array7 = new byte[1 * 1024 * 1024];
此時,再創建3個1MB的陣列物件,再次觸發一次Young GC,執行完YoungGC,此時新生代大小變成0KB;
對應的GC日志如下:
0.131: [GC (Allocation Failure) 0.131: [ParNew: 3148K->0K(4608K), 0.0007974 secs] 5904K->3780K(9728K), 0.0008262 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
此時記憶體分配如下:
(6).總結
如下GC堆記憶體日志我們也可以去驗證下上面的推測:
此時新生代使用了53%的大小,我們還有一個1MB的陣列,可能還存在一些未知物件,
在老年代中使用了大約3MB的空間,應該就是上圖中的物件,
Heap
par new generation total 4608K, used 2207K [0x00000000ff600000, 0x00000000ffb00000, 0x00000000ffb00000)
eden space 4096K, 53% used [0x00000000ff600000, 0x00000000ff827f38, 0x00000000ffa00000)
from space 512K, 0% used [0x00000000ffa80000, 0x00000000ffa80000, 0x00000000ffb00000)
to space 512K, 0% used [0x00000000ffa00000, 0x00000000ffa00000, 0x00000000ffa80000)
concurrent mark-sweep generation total 5120K, used 3780K [0x00000000ffb00000, 0x0000000100000000, 0x0000000100000000)
Metaspace used 2976K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 330K, capacity 386K, committed 512K, reserved 1048576K
3.幾個觸發Full GC的條件
第一:是老年代可用記憶體小于新生代全部物件的大小,如果沒開啟空間擔保引數,會直接觸發Full GC,所以一般空間擔保引數都會打開;注:jDK1.8之后已經取消了
-XX:-HandlePromotionFailure機制第二:是老年代可用記憶體小于歷次新生代GC后進入老年代的平均物件大小,此時會提前Full GC;
第三:是新生代Minor GC后的存活物件大于Survivor,那么就會進入老年代,此時老年代記憶體不足,
上述情況都會導致老年代Full GC,
第四:就是“-XX:CMSInitiatingOccupancyFaction”引數,
如果老年代可用記憶體大于歷次新生代GC后進入老年代的物件平均大小,但是老年代已經使用的記憶體空間超過了這個引數指定的比例,也會自動觸發Full GC,默認92%
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/545181.html
標籤:其他
上一篇:Django uwsgi問題決議
