前言安全
對象的內存分配,往大的方向上講,就是在堆上分配,少數狀況下也可能會直接分配在老年代中,分配的規則並非百分之百固定的,其細節決定於當前使用的是哪一種垃圾收集器組合,固然還有虛擬機中與內存相關的參數。垃圾收集器組合通常就是Serial+Serial Old和Parallel+Serial Old,前者是Client模式下的默認垃圾收集器組合,後者是Server模式下的默認垃圾收集器組合,文章使用對比學習法對比Client模式下和Server模式下同一條對象分配原則有什麼區別。併發
TLAB學習
首先講講什麼是TLAB。內存分配的動做,能夠按照線程劃分在不一樣的空間之中進行,即每一個線程在Java堆中預先分配一小塊內存,稱爲本地線程分配緩衝(Thread Local Allocation Buffer,TLAB)。哪一個線程須要分配內存,就在哪一個線程的TLAB上分配。虛擬機是否使用TLAB,能夠經過-XX:+/-UseTLAB參數來設定。這麼作的目的之一,也是爲了併發建立一個對象時,保證建立對象的線程安全性。TLAB比較小,直接在TLAB上分配內存的方式稱爲快速分配方式,而TLAB大小不夠,致使內存被分配在Eden區的內存分配方式稱爲慢速分配方式。測試
對象優先分配在Eden區上spa
上面講了不一樣的垃圾收集器組合對於內存分配規則是有影響的,看下影響在什麼地方並解釋一下緣由,虛擬機參數爲「-verbose:gc -XX:+PrintGCDetails -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8」,即10M新生代,10M老年代,10M新生代中8M的Eden區,兩個Survivor區各1M。代碼都是同一段線程
public class EdenAllocationTest { private static final int _1MB = 1024 * 1024; public static void main(String[] args) { byte[] allocation1 = new byte[2 * _1MB]; byte[] allocation2 = new byte[2 * _1MB]; byte[] allocation3 = new byte[2 * _1MB]; byte[] allocation4 = new byte[4 * _1MB]; } }
Client模式下code
[GC [DefNew: 6487K->194K(9216K), 0.0042856 secs] 6487K->6338K(19456K), 0.0043281 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap def new generation total 9216K, used 4454K [0x0000000005180000, 0x0000000005b80000, 0x0000000005b80000) eden space 8192K, 52% used [0x0000000005180000, 0x00000000055a9018, 0x0000000005980000) from space 1024K, 18% used [0x0000000005a80000, 0x0000000005ab0810, 0x0000000005b80000) to space 1024K, 0% used [0x0000000005980000, 0x0000000005980000, 0x0000000005a80000) tenured generation total 10240K, used 6144K [0x0000000005b80000, 0x0000000006580000, 0x0000000006580000) the space 10240K, 60% used [0x0000000005b80000, 0x0000000006180048, 0x0000000006180200, 0x0000000006580000) compacting perm gen total 21248K, used 2982K [0x0000000006580000, 0x0000000007a40000, 0x000000000b980000) the space 21248K, 14% used [0x0000000006580000, 0x0000000006869890, 0x0000000006869a00, 0x0000000007a40000) No shared spaces configured.
Server模式下對象
Heap PSYoungGen total 9216K, used 6651K [0x000000000af20000, 0x000000000b920000, 0x000000000b920000) eden space 8192K, 81% used [0x000000000af20000,0x000000000b59ef70,0x000000000b720000) from space 1024K, 0% used [0x000000000b820000,0x000000000b820000,0x000000000b920000) to space 1024K, 0% used [0x000000000b720000,0x000000000b720000,0x000000000b820000) PSOldGen total 10240K, used 4096K [0x000000000a520000, 0x000000000af20000, 0x000000000af20000) object space 10240K, 40% used [0x000000000a520000,0x000000000a920018,0x000000000af20000) PSPermGen total 21248K, used 2972K [0x0000000005120000, 0x00000000065e0000, 0x000000000a520000) object space 21248K, 13% used [0x0000000005120000,0x0000000005407388,0x00000000065e0000)
看到在Client模式下,最後分配的4M在新生代中,先分配的6M在老年代中;在Server模式下,最後分配的4M在老年代中,先分配的6M在新生代中。說明不一樣的垃圾收集器組合對於對象的分配是有影響的。講下二者差異的緣由:blog
一、Client模式下,新生代分配了6M,虛擬機在GC前有6487K,比6M也就是6144K多,多主要是由於TLAB和EdenAllocationTest這個對象佔的空間,TLAB能夠經過「-XX:+PrintTLAB」這個虛擬機參數來查看大小。OK,6M多了,而後來了一個4M的,Eden+一個Survivor總共就9M不夠分配了,這時候就會觸發一次Minor GC。可是觸發Minor GC也沒用,由於allocation一、allocation二、allocation3三個引用還存在,另外一塊1M的Survivor也不夠放下這6M,那麼此次Minor GC的效果實際上是經過分配擔保機制將這6M的內容轉入老年代中。而後再來一個4M的,因爲此時Minor GC以後新生代只剩下了194K了,夠分配了,因此4M順利進入新生代。內存
二、Server模式下,前面都同樣,可是在GC的時候有一點區別。在GC前還會進行一次判斷,若是要分配的內存>=Eden區大小的一半,那麼會直接把要分配的內存放入老年代中。要分配4M,Eden區8M,恰好一半,並且老年代10M,夠分配,因此4M就直接進入老年代去了。爲了驗證一下結論,咱們把3個2M以後分配的4M改成3M看一下
public class EdenAllocationTest { private static final int _1MB = 1024 * 1024; public static void main(String[] args) { byte[] allocation1 = new byte[2 * _1MB]; byte[] allocation2 = new byte[2 * _1MB]; byte[] allocation3 = new byte[2 * _1MB]; byte[] allocation4 = new byte[3 * _1MB]; } }
運行結果爲
[GC [PSYoungGen: 6487K->352K(9216K)] 6487K->6496K(19456K), 0.0035661 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC [PSYoungGen: 352K->0K(9216K)] [PSOldGen: 6144K->6338K(10240K)] 6496K->6338K(19456K) [PSPermGen: 2941K->2941K(21248K)], 0.0035258 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] Heap PSYoungGen total 9216K, used 3236K [0x000000000af40000, 0x000000000b940000, 0x000000000b940000) eden space 8192K, 39% used [0x000000000af40000,0x000000000b269018,0x000000000b740000) from space 1024K, 0% used [0x000000000b740000,0x000000000b740000,0x000000000b840000) to space 1024K, 0% used [0x000000000b840000,0x000000000b840000,0x000000000b940000) PSOldGen total 10240K, used 6338K [0x000000000a540000, 0x000000000af40000, 0x000000000af40000) object space 10240K, 61% used [0x000000000a540000,0x000000000ab70858,0x000000000af40000) PSPermGen total 21248K, used 2982K [0x0000000005140000, 0x0000000006600000, 0x000000000a540000) object space 21248K, 14% used [0x0000000005140000,0x0000000005429890,0x0000000006600000)
看到3M在新生代中,6M經過分配擔保機制進入老年代了。
大對象直接進入老年代
虛擬機參數爲「-XX:+PrintGCDetails -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=3145728」,最後那個參數表示大於這個設置值的對象直接在老年代中分配,這樣作的目的是爲了不在Eden區和兩個Survivor區之間發生大量的內存複製。測試代碼爲
public class OldTest { private static final int _1MB = 1024 * 1024; public static void main(String[] args) { byte[] allocation = new byte[4 * _1MB]; } }
Client模式下
Heap def new generation total 9216K, used 507K [0x0000000005140000, 0x0000000005b40000, 0x0000000005b40000) eden space 8192K, 6% used [0x0000000005140000, 0x00000000051bef28, 0x0000000005940000) from space 1024K, 0% used [0x0000000005940000, 0x0000000005940000, 0x0000000005a40000) to space 1024K, 0% used [0x0000000005a40000, 0x0000000005a40000, 0x0000000005b40000) tenured generation total 10240K, used 4096K [0x0000000005b40000, 0x0000000006540000, 0x0000000006540000) the space 10240K, 40% used [0x0000000005b40000, 0x0000000005f40018, 0x0000000005f40200, 0x0000000006540000) compacting perm gen total 21248K, used 2972K [0x0000000006540000, 0x0000000007a00000, 0x000000000b940000) the space 21248K, 13% used [0x0000000006540000, 0x00000000068272a0, 0x0000000006827400, 0x0000000007a00000) No shared spaces configured.
Server模式下
Heap PSYoungGen total 9216K, used 4603K [0x000000000afc0000, 0x000000000b9c0000, 0x000000000b9c0000) eden space 8192K, 56% used [0x000000000afc0000,0x000000000b43ef40,0x000000000b7c0000) from space 1024K, 0% used [0x000000000b8c0000,0x000000000b8c0000,0x000000000b9c0000) to space 1024K, 0% used [0x000000000b7c0000,0x000000000b7c0000,0x000000000b8c0000) PSOldGen total 10240K, used 0K [0x000000000a5c0000, 0x000000000afc0000, 0x000000000afc0000) object space 10240K, 0% used [0x000000000a5c0000,0x000000000a5c0000,0x000000000afc0000) PSPermGen total 21248K, used 2972K [0x00000000051c0000, 0x0000000006680000, 0x000000000a5c0000) object space 21248K, 13% used [0x00000000051c0000,0x00000000054a72a0,0x0000000006680000)
看到Client模式下4M直接進入了老年代,Server模式下4M還在新生代中。產生這個差異的緣由是「-XX:PretenureSizeThreshold」這個參數對Serial+Serial Old垃圾收集器組合有效而對Parallel+Serial Old垃圾收集器組合無效。
其餘幾條原則
上面列舉的原則其實不重要,只是演示罷了,也不須要記住,由於實際過程當中咱們可能使用的並非上面的垃圾收集器的組合,可能使用ParNew垃圾收集器,可能使用G1垃圾收集器。場景不少,重要的是要在實際使用的時候有辦法知道使用的垃圾收集器對於對象分配有哪些原則,由於理解這些原則纔是調優的第一步。下面列舉一下對象分配的另外兩條原則:
一、長期存活的對象將進入老年代。Eden區中的對象在一次Minor GC後沒有被回收,則對象年齡+1,當對象年齡達到「-XX:MaxTenuringThreshold」設置的值的時候,對象就會被晉升到老年代中
二、Survivor空間中相同年齡的全部對象大小總和大於Survivor空間的一半,年齡大於或等於該年齡的對象就能夠直接進入老年代,無須等到「-XX:MaxTenuringThreshold」設置要求的年齡