java内存分配和回收策略
一、導(dǎo)論
java技術(shù)體系中所提到的內(nèi)存自動化管理歸根結(jié)底就是內(nèi)存的分配與回收兩個問題,之前已經(jīng)和大家談過java回收的相關(guān)知識,今天來和大家聊聊java對象的在內(nèi)存中的分配。通俗的講,對象的內(nèi)存分配就是在堆上的分配,對象主要分配在新生代的Eden上(關(guān)于對象在內(nèi)存上的分代在垃圾回收中會補上,想了解的也可以參考《深入理解java虛擬機》),如果啟動了本地線程分配緩沖,講按線程優(yōu)先在TLAB上分配。少數(shù)情況下也是直接在老年代中分配。
二、經(jīng)典的分配策略
1、對象優(yōu)先在Eden上分配
一般情況下對象都是優(yōu)先分配在Eden上,當(dāng)Eden沒有足夠的空間進行分配時,jvm會發(fā)起一次Minor GC。如果還是沒有足夠的空間分配,后面還有另外的措施,下面會提到。
設(shè)置虛擬機的偶記日志參數(shù)-XX:+PrintGCDetails,在垃圾回收的時候會打印內(nèi)存的回收日志,并且在進程退出的時候會輸出當(dāng)前內(nèi)存各區(qū)域的分配情況。下面來看下具體的例子,首先需要設(shè)置jvm的參數(shù)-Xms20m -Xmx20m -Xmn10m,這三個參數(shù)說明java堆大小為20M,且不可擴展,其中10M分配給新生代,剩下的10M分配給老年代。-XX:SurvivorRatio=8是jvm默認的新生代中Eden和Survivor比例,默認為8:1。原因是新生代中的對象98%都會在下一次GC的時候回收掉,所以很適合采用復(fù)制算法進行垃圾回收,所以新生代10M的內(nèi)存中,8M是Eden,1M是Survivor,另外的1M是未使用配合復(fù)制算法的內(nèi)存塊,也是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?}?輸出如下
HeapPSYoungGen??????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上。
2、大對象直接分配在老年代上
大對象是指需要大量連續(xù)內(nèi)存空間去存放的對象,類似于那種很長的字符串和數(shù)組。大對象對于虛擬機的內(nèi)存分布來講并不是好事,當(dāng)遇到很多存活僅一輪的大對象jvm更加難處理,寫代碼的時候應(yīng)該避免這樣的問題。虛擬機中提供了-XX:PretenureSizeThreshold參數(shù),另大于這個值的對象直接分配到老年代,這樣做的目的是為了避免在Eden區(qū)和Survivor區(qū)之間發(fā)生大量的內(nèi)存copy,在之前講過的垃圾回收算法復(fù)制算法有提到過,就不多說了。
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();}}?輸出如下
HeapPSYoungGen??????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已經(jīng)超過了設(shè)置的-XX:PretenureSizeThreshold=3145728,隨意allocation4直接被分配到了老年代,老年代占用率為60%。注意這里設(shè)置-XX:PretenureSizeThreshold=3145728不能寫成-XX:PretenureSizeThreshold=3m,否則jvm將無法識別。
3、長期存活的對象將進入老年代
虛擬機既然采用了分帶收集的思想來管理內(nèi)存,那內(nèi)存回收就必須識別哪些對象應(yīng)該放在新生代,哪些對象應(yīng)該放在老年代。為了打到目的,jvm給每個對象定義了一個年齡計數(shù)器(Age)。如果對象在Eden出生并且能過第一次Minor GC后仍然存活,并且可以在Survivor存放的話,將被移動到Survivor中,并將對象的年齡設(shè)為1。對象每躲過一次Minor GC,年齡就會加1,當(dāng)他的年齡超過一年的閾值的時候,該對象就會晉升到老年代。這個閾值jvm默認是15,可以通過-XX:MaxTenuringThreshold來設(shè)置。
???m?=?1024?*?1024??[]?a1?=??[1?*?m?/?4[]?a2?=??[7?*[]?a3?=??[3?*?m];?輸出如下
[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已經(jīng)存活了一次,年齡為1,滿足所設(shè)置的-XX:MaxTenuringThreshold=1,所以a2進入了老年代,而a3則進入了新生代。
4、動態(tài)對象年齡判定
為了能更好的適應(yīng)不同程序的內(nèi)存狀態(tài),虛擬機并不總是要求對象的年齡必須達到-XX:MaxTenuringThreshold所設(shè)置的值才能晉升到老年代,如果在Survivor空間中相同年齡所有對象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對象就可以直接進入老年區(qū),無須達到-XX:MaxTenuringThreshold中的設(shè)置值。
5、空間分配擔(dān)保
在發(fā)生Minor GC的時候,虛擬機會檢測每次晉升到老年代的平均大小是否大于老年代的剩余空間,如果大于,則直接進行一次FUll GC。如果小于,則查看HandlerPromotionFailyre設(shè)置是否允許擔(dān)保失敗,如果允許那就只進行Minor GC,如果不允許則也要改進一次FUll GC。也就是說新生代Eden存不下改對象的時候就會將該對象存放在老年代。
三、常用的jvm參數(shù)設(shè)置
1、-Xms: 初始堆大小,?默認(MinHeapFreeRatio參數(shù)可以調(diào)整)空余堆內(nèi)存小于40%時,JVM就會增大堆直到-Xmx的最大限制。
2、Xmx: 最大堆大小,默認(MaxHeapFreeRatio參數(shù)可以調(diào)整)空余堆內(nèi)存大于70%時,JVM會減少堆直到 -Xms的最小限制。
3、-Xmn: 年輕代大小(1.4or lator),?此處的大小是(eden+ 2 survivor space).與jmap -heap中顯示的New gen是不同的。
整個堆大小=年輕代大小 + 年老代大小 + 持久代大小。
增大年輕代后,將會減小年老代大小.此值對系統(tǒng)性能影響較大,Sun官方推薦配置為整個堆的3/8。
4、-XX:NewSize: 設(shè)置年輕代大小(for 1.3/1.4)。
5、-XX:MaxNewSize: 年輕代最大值(for 1.3/1.4)。
6、-XX:PermSize: 設(shè)置持久代(perm gen)初始值。
7、-XX:MaxPermSize: 設(shè)置持久代最大值。
8、-Xss: 每個線程的堆棧大小,JDK5.0以后每個線程堆棧大小為1M,以前每個線程堆棧大小為256K.更具應(yīng)用的線程所需內(nèi)存大小進行 調(diào)整.在相同物理內(nèi)存下,減小這個值能生成更多的線程.但是操作系統(tǒng)對一個進程內(nèi)的線程數(shù)還是有限制的,不能無限生成,經(jīng)驗值在3000~5000左右。
9、-XX:NewRatio: 年輕代(包括Eden和兩個Survivor區(qū))與年老代的比值(除去持久代),-XX:NewRatio=4表示年輕代與年老代所占比值為1:4,年輕代占整個堆棧的1/5。Xms=Xmx并且設(shè)置了Xmn的情況下,該參數(shù)不需要進行設(shè)置。
10、-XX:SurvivorRatio:?Eden區(qū)與Survivor區(qū)的大小比值,設(shè)置為8,則兩個Survivor區(qū)與一個Eden區(qū)的比值為2:8,一個Survivor區(qū)占整個年輕代的1/10。
11、-XX:LargePageSizeInBytes:?內(nèi)存頁的大小不可設(shè)置過大, 會影響Perm的大小。
12、-XX:+DisableExplicitGC:?關(guān)閉System.gc()
13、-XX:MaxTenuringThreshold:?垃圾最大年齡,如果設(shè)置為0的話,則年輕代對象不經(jīng)過Survivor區(qū),直接進入年老代. 對于年老代比較多的應(yīng)用,可以提高效率.如果將此值設(shè)置為一個較大值,則年輕代對象會在Survivor區(qū)進行多次復(fù)制,這樣可以增加對象再年輕代的存活 時間,增加在年輕代即被回收的概率該參數(shù)只有在串行GC時才有效。
14、-XX:PretenureSizeThreshold:?對象超過多大是直接在舊生代分配,單位字節(jié) 新生代采用Parallel Scavenge GC時無效另一種直接在舊生代分配的情況是大的數(shù)組對象,且數(shù)組中無外部引用對象。
15、-XX:TLABWasteTargetPercent:?TLAB占eden區(qū)的百分比。
四、補充
Minor GC和FUll GC的區(qū)別:
新生代GC(Minor GC):指發(fā)生在新生代的垃圾收集動作,因為java對象大對數(shù)都是逃不過第一輪的GC,所以Minor GC使用很頻繁,一般回收速度也比較快。
老年代GC(FULL GC/Major GC) :指發(fā)生在老年代的GC,出現(xiàn)了Major GC,經(jīng)常會伴隨至少一次的Minor GC(但非絕對,在ParallelScavenge收集器的收集策略中就有直接進行Major GC的選擇過程?)。Major GC的速度一般會比Minor GC慢10倍以上。?
本文轉(zhuǎn)自xmgdc51CTO博客,原文鏈接:http://blog.51cto.com/12953214/1940468 ,如需轉(zhuǎn)載請自行聯(lián)系原作者
總結(jié)
以上是生活随笔為你收集整理的java内存分配和回收策略的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 服务器开发的一些框架或者工具收集
- 下一篇: 远程管理Hyper-V Server 虚