java技術體系中所提到的內存自動化管理歸根結底就是內存的分配與回收兩個問題,以前已經和你們談過java回收的相關知識,今天來和你們聊聊java對象的在內存中的分配。通俗的講,對象的內存分配就是在堆上的分配,對象主要分配在新生代的Eden上(關於對象在內存上的分代在垃圾回收中會補上,想了解的也能夠參考《深刻理解java虛擬機》),若是啓動了本地線程分配緩衝,講按線程優先在TLAB上分配。少數狀況下也是直接在老年代中分配。java
通常狀況下對象都是優先分配在Eden上,當Eden沒有足夠的空間進行分配時,jvm會發起一次Minor GC。若是仍是沒有足夠的空間分配,後面還有另外的措施,下面會提到。算法
設置虛擬機的偶記日誌參數-XX:+PrintGCDetails,在垃圾回收的時候會打印內存的回收日誌,而且在進程退出的時候會輸出當前內存各區域的分配狀況。下面來看下具體的例子,首先須要設置jvm的參數-Xms20m -Xmx20m -Xmn10m,這三個參數說明java堆大小爲20M,且不可擴展,其中10M分配給新生代,剩下的10M分配給老年代。-XX:SurvivorRatio=8是jvm默認的新生代中Eden和Survivor比例,默認爲8:1。緣由是新生代中的對象98%都會在下一次GC的時候回收掉,因此很適合採用複製算法進行垃圾回收,因此新生代10M的內存中,8M是Eden,1M是Survivor,另外的1M是未使用配合複製算法的內存塊,也是Survivor。數組
1 public class ReflectTest { 2 3 private static final int _1MB = 1024*1024; 4 5 public static void testAllocation(){ 6 byte[] allocation1 , allocation2 , allocation3 , allocation4; 7 allocation1 = new byte[2 * _1MB]; 8 allocation2 = new byte[2 * _1MB]; 9 allocation3 = new byte[2 * _1MB]; 10 allocation4 = new byte[6 * _1MB]; 11 } 12 13 public static void main(String[] args) { 14 ReflectTest.testAllocation(); 15 } 16 17 }
輸出以下jvm
Heap PSYoungGen total 9216K, used 6651K [0x000000000b520000, 0x000000000bf20000, 0x000000000bf20000) eden space 8192K, 81% used [0x000000000b520000,0x000000000bb9ef28,0x000000000bd20000) from space 1024K, 0% used [0x000000000be20000,0x000000000be20000,0x000000000bf20000) to space 1024K, 0% used [0x000000000bd20000,0x000000000bd20000,0x000000000be20000) PSOldGen total 10240K, used 6144K [0x000000000ab20000, 0x000000000b520000, 0x000000000b520000) object space 10240K, 60% used [0x000000000ab20000,0x000000000b120018,0x000000000b520000) PSPermGen total 21248K, used 2973K [0x0000000005720000, 0x0000000006be0000, 0x000000000ab20000) object space 21248K, 13% used [0x0000000005720000,0x0000000005a07498,0x0000000006be0000)
能夠看到eden佔用了81%,說明allocation1 , allocation2 , allocation3 都是分配在新生代Eden上。性能
大對象是指須要大量連續內存空間去存放的對象,相似於那種很長的字符串和數組。大對象對於虛擬機的內存分佈來說並非好事,當遇到不少存活僅一輪的大對象jvm更加難處理,寫代碼的時候應該避免這樣的問題。虛擬機中提供了-XX:PretenureSizeThreshold參數,另大於這個值的對象直接分配到老年代,這樣作的目的是爲了不在Eden區和Survivor區之間發生大量的內存copy,在以前講過的垃圾回收算法複製算法有提到過,就很少說了。spa
public class ReflectTestBig { private static final int _1MB = 1024*1024; public static void testAllocation(){ byte[] allocation2 , allocation3 , allocation4; allocation2 = new byte[2 * _1MB]; allocation3 = new byte[2 * _1MB]; allocation4 = new byte[6 * _1MB]; } public static void main(String[] args) { ReflectTestBig.testAllocation(); } }
輸出以下操作系統
Heap PSYoungGen total 8960K, used 4597K [0x000000000b510000, 0x000000000bf10000, 0x000000000bf10000) eden space 7680K, 59% used [0x000000000b510000,0x000000000b98d458,0x000000000bc90000) from space 1280K, 0% used [0x000000000bdd0000,0x000000000bdd0000,0x000000000bf10000) to space 1280K, 0% used [0x000000000bc90000,0x000000000bc90000,0x000000000bdd0000) PSOldGen total 10240K, used 6144K [0x000000000ab10000, 0x000000000b510000, 0x000000000b510000) object space 10240K, 60% used [0x000000000ab10000,0x000000000b110018,0x000000000b510000) PSPermGen total 21248K, used 2973K [0x0000000005710000, 0x0000000006bd0000, 0x000000000ab10000) object space 21248K, 13% used [0x0000000005710000,0x00000000059f7460,0x0000000006bd0000)
能夠看到allocation4已經超過了設置的-XX:PretenureSizeThreshold=3145728,隨意allocation4直接被分配到了老年代,老年代佔用率爲60%。注意這裏設置-XX:PretenureSizeThreshold=3145728不能寫成-XX:PretenureSizeThreshold=3m,不然jvm將沒法識別。線程
虛擬機既然採用了分帶收集的思想來管理內存,那內存回收就必須識別哪些對象應該放在新生代,哪些對象應該放在老年代。爲了打到目的,jvm給每一個對象定義了一個年齡計數器(Age)。若是對象在Eden出生而且能過第一次Minor GC後仍然存活,而且能夠在Survivor存放的話,將被移動到Survivor中,並將對象的年齡設爲1。對象每躲過一次Minor GC,年齡就會加1,當他的年齡超過一年的閾值的時候,該對象就會晉升到老年代。這個閾值jvm默認是15,能夠經過-XX:MaxTenuringThreshold來設置。日誌
public class JavaTest { static int m = 1024 * 1024; public static void main(String[] args) { byte[] a1 = new byte[1 * m / 4];
byte[] a2 = new byte[7 * m];
byte[] a3 = new byte[3 * m]; //GC } }
輸出以下code
[GC [DefNew: 7767K->403K(9216K), 0.0062209 secs] 7767K->7571K(19456K), 0.0062482 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] a3 ok Heap def new generation total 9216K, used 3639K [0x331d0000, 0x33bd0000, 0x33bd0000) eden space 8192K, 39% used [0x331d0000, 0x334f9040, 0x339d0000) from space 1024K, 39% used [0x33ad0000, 0x33b34de8, 0x33bd0000) to space 1024K, 0% used [0x339d0000, 0x339d0000, 0x33ad0000) tenured generation total 10240K, used 7168K [0x33bd0000, 0x345d0000, 0x345d0000) the space 10240K, 70% used [0x33bd0000, 0x342d0010, 0x342d0200, 0x345d0000) compacting perm gen total 12288K, used 381K [0x345d0000, 0x351d0000, 0x385d0000) the space 12288K, 3% used [0x345d0000, 0x3462f548, 0x3462f600, 0x351d0000) ro space 10240K, 55% used [0x385d0000, 0x38b51140, 0x38b51200, 0x38fd0000) rw space 12288K, 55% used [0x38fd0000, 0x396744c8, 0x39674600, 0x39bd0000)
能夠看到a2已經存活了一次,年齡爲1,知足所設置的-XX:MaxTenuringThreshold=1,因此a2進入了老年代,而a3則進入了新生代。
爲了能更好的適應不一樣程序的內存狀態,虛擬機並不老是要求對象的年齡必須達到-XX:MaxTenuringThreshold所設置的值才能晉升到老年代,若是在Survivor空間中相同年齡全部對象大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的對象就能夠直接進入老年區,無須達到-XX:MaxTenuringThreshold中的設置值。
在發生Minor GC的時候,虛擬機會檢測每次晉升到老年代的平均大小是否大於老年代的剩餘空間,若是大於,則直接進行一次FUll GC。若是小於,則查看HandlerPromotionFailyre設置是否容許擔保失敗,若是容許那就只進行Minor GC,若是不容許則也要改進一次FUll GC。也就是說新生代Eden存不下改對象的時候就會將該對象存放在老年代。
一、-Xms: 初始堆大小, 默認(MinHeapFreeRatio參數能夠調整)空餘堆內存小於40%時,JVM就會增大堆直到-Xmx的最大限制。
二、Xmx: 最大堆大小,默認(MaxHeapFreeRatio參數能夠調整)空餘堆內存大於70%時,JVM會減小堆直到 -Xms的最小限制。
三、-Xmn: 年輕代大小(1.4or lator), 此處的大小是(eden+ 2 survivor space).與jmap -heap中顯示的New gen是不一樣的。
整個堆大小=年輕代大小 + 年老代大小 + 持久代大小。
增大年輕代後,將會減少年老代大小.此值對系統性能影響較大,Sun官方推薦配置爲整個堆的3/8。
四、-XX:NewSize: 設置年輕代大小(for 1.3/1.4)。
五、-XX:MaxNewSize: 年輕代最大值(for 1.3/1.4)。
六、-XX:PermSize: 設置持久代(perm gen)初始值。
七、-XX:MaxPermSize: 設置持久代最大值。
八、-Xss: 每一個線程的堆棧大小,JDK5.0之後每一個線程堆棧大小爲1M,之前每一個線程堆棧大小爲256K.更具應用的線程所需內存大小進行 調整.在相同物理內存下,減少這個值能生成更多的線程.可是操做系統對一個進程內的線程數仍是有限制的,不能無限生成,經驗值在3000~5000左右。
九、-XX:NewRatio: 年輕代(包括Eden和兩個Survivor區)與年老代的比值(除去持久代),-XX:NewRatio=4表示年輕代與年老代所佔比值爲1:4,年輕代佔整個堆棧的1/5。Xms=Xmx而且設置了Xmn的狀況下,該參數不須要進行設置。
十、-XX:SurvivorRatio: Eden區與Survivor區的大小比值,設置爲8,則兩個Survivor區與一個Eden區的比值爲2:8,一個Survivor區佔整個年輕代的1/10。
十一、-XX:LargePageSizeInBytes: 內存頁的大小不可設置過大, 會影響Perm的大小。
十二、-XX:+DisableExplicitGC: 關閉System.gc()
1三、-XX:MaxTenuringThreshold: 垃圾最大年齡,若是設置爲0的話,則年輕代對象不通過Survivor區,直接進入年老代. 對於年老代比較多的應用,能夠提升效率.若是將此值設置爲一個較大值,則年輕代對象會在Survivor區進行屢次複製,這樣能夠增長對象再年輕代的存活 時間,增長在年輕代即被回收的機率該參數只有在串行GC時纔有效。
1四、-XX:PretenureSizeThreshold: 對象超過多大是直接在舊生代分配,單位字節 新生代採用Parallel Scavenge GC時無效另外一種直接在舊生代分配的狀況是大的數組對象,且數組中無外部引用對象。
1五、-XX:TLABWasteTargetPercent: TLAB佔eden區的百分比。
Minor GC和FUll GC的區別:
新生代GC(Minor GC):指發生在新生代的垃圾收集動做,由於java對象大對數都是逃不過第一輪的GC,因此Minor GC使用很頻繁,通常回收速度也比較快。
老年代GC(FULL GC/Major GC) :指發生在老年代的GC,出現了Major GC,常常會伴隨至少一次的Minor GC(但非絕對,在ParallelScavenge收集器的收集策略中就有直接進行Major GC的選擇過程 )。Major GC的速度通常會比Minor GC慢10倍以上。