Java的内存分配策略有哪些_Java的内存分配策略
簡單來說,對象內存分配主要是在堆中分配。但是分配的規則并不是固定的,取決于使用的收集器組合以及JVM內存相關參數的設定
以下介紹幾條基本規則(使用的ParNew+Serial Old收集器組合):
一,對象優先在新生代Eden區分配
//-XX:+UseParNewGC?-Xms20m?-Xmx20m?-Xmn10m?-XX:+PrintHeapAtGC?-XX:+PrintGCDetails
publicclasstest?{
staticintmb?=1024*1024;
publicstaticvoidmain(String[]?args)?{
byte[]?b1?=newbyte[2*mb];
System.out.println("b1?over");
byte[]?b2?=newbyte[2*mb];
System.out.println("b2?over");
byte[]?b3?=newbyte[2*mb];
System.out.println("b3?over");//GC
byte[]?b4?=newbyte[4*mb];
System.out.println("b4?over");
}
}
堆內存大小為20M,不可自動擴展,新生代內存大小為10M,根據默認值,Eden區:Survivor區為8:1,Eden區大小應為:10M*8/10=8129KB,Survivor區大小應為1024KB,新生代總可用內存應為9216KB
當b3分配完成后,新生代將使用6M內存(6144KB,b1+b2+b3),同時申請b4的4M=4096KB內存,此時新生代的可用內存為9216-6144=3072KB,不足以分配b4的空間,則觸發一次Minor GC回收新生代內存空間,由于b1、b2以及b3都為存活狀態,并且剩余的一個Survivor區無法裝下b1、b2和b3,則新生代會租借老年代的區域,并將b1、b2和b3移動至租借區域,然后新生代完成Minor GC。由于此時新生代已經沒有對象存放其中,剩余大量內存,則b4將在新生代中分配
b1?over
b2?over
b3?over
{Heap?before?GC?invocations=0?(full?0):
par?new?generation???total?9216K,?used?6487K?[0x03b30000,?0x04530000,?0x04530000)//b1+b2+b3,占6M
eden?space?8192K,??79%?used?[0x03b30000,?0x04185f60,?0x04330000)
from?space?1024K,???0%?used?[0x04330000,?0x04330000,?0x04430000)
to???space?1024K,???0%?used?[0x04430000,?0x04430000,?0x04530000)
tenured?generation???total?10240K,?used?0K?[0x04530000,?0x04f30000,?0x04f30000)//老年代為空
the?space?10240K,???0%?used?[0x04530000,?0x04530000,?0x04530200,?0x04f30000)
compacting?perm?gen??total?12288K,?used?2105K?[0x04f30000,?0x05b30000,?0x08f30000)
the?space?12288K,??17%?used?[0x04f30000,?0x0513e478,?0x0513e600,?0x05b30000)
No?shared?spaces?configured.
[GC?[ParNew:?6487K->150K(9216K),?0.0092952?secs]?6487K->6294K(19456K),?0.0093314?secs]?[Times:?user=0.00?sys=0.00,?real=0.00?secs]//對象仍處于存活狀態,新生代無足夠的空間完成Minor?GC,只能租借老年代的空間,將b1、b2和b3移動至老年代
Heap?after?GC?invocations=1?(full?0):
par?new?generation???total?9216K,?used?150K?[0x03b30000,?0x04530000,?0x04530000)//新生代幾乎被清空
eden?space?8192K,???0%?used?[0x03b30000,?0x03b30000,?0x04330000)
from?space?1024K,??14%?used?[0x04430000,?0x04455a10,?0x04530000)
to???space?1024K,???0%?used?[0x04330000,?0x04330000,?0x04430000)
tenured?generation???total?10240K,?used?6144K?[0x04530000,?0x04f30000,?0x04f30000)//b1+b2+b3
the?space?10240K,??60%?used?[0x04530000,?0x04b30030,?0x04b30200,?0x04f30000)
compacting?perm?gen??total?12288K,?used?2105K?[0x04f30000,?0x05b30000,?0x08f30000)
the?space?12288K,??17%?used?[0x04f30000,?0x0513e478,?0x0513e600,?0x05b30000)
No?shared?spaces?configured.
}
b4?over
Heap
par?new?generation???total?9216K,?used?4410K?[0x03b30000,?0x04530000,?0x04530000)//b4
eden?space?8192K,??54%?used?[0x03b30000,?0x03f82008,?0x04330000)
from?space?1024K,??14%?used?[0x04430000,?0x04455a10,?0x04530000)
to???space?1024K,???0%?used?[0x04330000,?0x04330000,?0x04430000)
tenured?generation???total?10240K,?used?6144K?[0x04530000,?0x04f30000,?0x04f30000)//b1+b2+b3
the?space?10240K,??60%?used?[0x04530000,?0x04b30030,?0x04b30200,?0x04f30000)
compacting?perm?gen??total?12288K,?used?2116K?[0x04f30000,?0x05b30000,?0x08f30000)
the?space?12288K,??17%?used?[0x04f30000,?0x051413c8,?0x05141400,?0x05b30000)
No?shared?spaces?configured.
二,大對象直接進入老年代
為了避免內存回收時大對象在Eden區和2個Survivor區之間的拷貝(ParNew收集器使用復制算法),同時為了避免為了提供足夠的內存空間而提前觸發的GC,虛擬機提供了-XX:PretenureSizeThreshold(該設置只對Serial和ParNew收集器生效)參數,大于該參數設置值的對象將直接在老年代分配
//-XX:+UseParNewGC?-Xms20m?-Xmx20m?-Xmn10m?-XX:+PrintHeapAtGC?-XX:+PrintGCDetails
//-XX:PretenureSizeThreshold=2097152
publicclasstest?{
staticintmb?=1024*1024;
publicstaticvoidmain(String[]?args)?{
byte[]?b1?=newbyte[3*mb];
System.out.println("b1?over");
}
}
由于設置超過2M(2*1024*1024=2097152B)的對象直接在老年代分配,故b1將分配在老年代上
b1?over
Heap
par?new?generation???total?9216K,?used?507K?[0x03b50000,?0x04550000,?0x04550000)//新生代幾乎為空
eden?space?8192K,???6%?used?[0x03b50000,?0x03bcef00,?0x04350000)
from?space?1024K,???0%?used?[0x04350000,?0x04350000,?0x04450000)
to???space?1024K,???0%?used?[0x04450000,?0x04450000,?0x04550000)
tenured?generation???total?10240K,?used?3072K?[0x04550000,?0x04f50000,?0x04f50000)//老年代使用了3*1024K內存
the?space?10240K,??30%?used?[0x04550000,?0x04850010,?0x04850200,?0x04f50000)
compacting?perm?gen??total?12288K,?used?2110K?[0x04f50000,?0x05b50000,?0x08f50000)
the?space?12288K,??17%?used?[0x04f50000,?0x0515f8c8,?0x0515fa00,?0x05b50000)
No?shared?spaces?configured.
三,長期存活對象將進入老年代
由于虛擬機垃圾收集是基于“分代算法”的,故虛擬機必須能夠識別哪些對象存放在新生代,哪些對象應該存放在老年代
虛擬機設計了一個對象年齡計數器,如果對象在Eden區出生并且經過第一次Minor GC后依然存活,并且可以被Survivor區容納,就會被復制至Survivor區并將對象年齡設置為1。以后對象每熬過一次Minor GC,對象年齡便+1。當對象年齡超過對象晉升老年代的年齡閥值(該閥值默認為15)時,便會晉升至老年代,何時晉升,我們接下來研究
虛擬機提供了-XX:MaxTenuringThreshold參數設置晉升閥值
//-XX:+UseParNewGC?-Xms20m?-Xmx20m?-Xmn10m?-XX:+PrintHeapAtGC?-XX:+PrintGCDetails
//-XX:MaxTenuringThreshold=1
publicclasstest?{
staticintmb?=1024*1024;
publicstaticvoidmain(String[]?args)?{
System.out.println("step?1");
byte[]?b1?=newbyte[1*mb/4];
System.out.println("step?2");
byte[]?b2?=newbyte[4*mb];
System.out.println("step?3");
byte[]?b3?=newbyte[4*mb];//GC
System.out.println("step?4");
b3?=?null;
System.out.println("step?5");
b3?=?newbyte[4*mb];//GC
}
}
b1、b2正常分配。在step3,新生代將沒有足夠的內存分配b3所需的4M空間,故引發一次Minor GC。b1只有256KB,可以放置在Survivor區中,故復制b1到Survivor區中,b2為4M,無法放置到Survivor區中,故租借老年代4M內存放置b2,回收新生代內存空間,b1經歷了一次Minor GC后依然存活,故年齡變為1。
在step4,分配給b3對象的內存空間依然被占用,只是將b3對象的引用置為空,由于不涉及到內存分配,故而不涉及到GC,因此對象的年齡也不會發生變化
在step5,重新給b3對象分配4M空間,由于新生代沒有足夠內存,故引發Minor GC,step3分配給b3的4M內存空間由于不再與存活對象相關聯,將被回收,同時,由于b1的年齡到達對象晉升老年代的年齡設置,b1將被移動至老年代
step?1
step?2
step?3
{Heap?before?GC?invocations=0?(full?0):
par?new?generation???total?9216K,?used?4695K?[0x03b80000,?0x04580000,?0x04580000)//b1+b2
eden?space?8192K,??57%?used?[0x03b80000,?0x04015f50,?0x04380000)
from?space?1024K,???0%?used?[0x04380000,?0x04380000,?0x04480000)
to???space?1024K,???0%?used?[0x04480000,?0x04480000,?0x04580000)
tenured?generation???total?10240K,?used?0K?[0x04580000,?0x04f80000,?0x04f80000)//此時老年代為空
the?space?10240K,???0%?used?[0x04580000,?0x04580000,?0x04580200,?0x04f80000)
compacting?perm?gen??total?12288K,?used?2105K?[0x04f80000,?0x05b80000,?0x08f80000)
the?space?12288K,??17%?used?[0x04f80000,?0x0518e450,?0x0518e600,?0x05b80000)
No?shared?spaces?configured.
[GC?[ParNew:?4695K->409K(9216K),?0.0049519?secs]?4695K->4505K(19456K),?0.0049944?secs]?[Times:?user=0.00?sys=0.00,?real=0.00?secs]
Heap?after?GC?invocations=1?(full?0):
par?new?generation???total?9216K,?used?409K?[0x03b80000,?0x04580000,?0x04580000)//b1
eden?space?8192K,???0%?used?[0x03b80000,?0x03b80000,?0x04380000)
from?space?1024K,??39%?used?[0x04480000,?0x044e6610,?0x04580000)
to???space?1024K,???0%?used?[0x04380000,?0x04380000,?0x04480000)
tenured?generation???total?10240K,?used?4096K?[0x04580000,?0x04f80000,?0x04f80000)//b2
the?space?10240K,??40%?used?[0x04580000,?0x04980010,?0x04980200,?0x04f80000)
compacting?perm?gen??total?12288K,?used?2105K?[0x04f80000,?0x05b80000,?0x08f80000)
the?space?12288K,??17%?used?[0x04f80000,?0x0518e450,?0x0518e600,?0x05b80000)
No?shared?spaces?configured.
}
step?4
step?5
{Heap?before?GC?invocations=1?(full?0):
par?new?generation???total?9216K,?used?4669K?[0x03b80000,?0x04580000,?0x04580000)//b1+b3(step3)
eden?space?8192K,??52%?used?[0x03b80000,?0x03fa9098,?0x04380000)
from?space?1024K,??39%?used?[0x04480000,?0x044e6610,?0x04580000)
to???space?1024K,???0%?used?[0x04380000,?0x04380000,?0x04480000)
tenured?generation???total?10240K,?used?4096K?[0x04580000,?0x04f80000,?0x04f80000)//b2
the?space?10240K,??40%?used?[0x04580000,?0x04980010,?0x04980200,?0x04f80000)
compacting?perm?gen??total?12288K,?used?2111K?[0x04f80000,?0x05b80000,?0x08f80000)
the?space?12288K,??17%?used?[0x04f80000,?0x0518fe08,?0x05190000,?0x05b80000)
No?shared?spaces?configured.
[GC?[ParNew:?4669K->43K(9216K),?0.0008256?secs]?8765K->4548K(19456K),?0.0008701?secs]?[Times:?user=0.00?sys=0.00,?real=0.00?secs]
Heap?after?GC?invocations=2?(full?0):
par?new?generation???total?9216K,?used?43K?[0x03b80000,?0x04580000,?0x04580000)//step3分配的b3對象空間被回收
eden?space?8192K,???0%?used?[0x03b80000,?0x03b80000,?0x04380000)
from?space?1024K,???4%?used?[0x04380000,?0x0438ad90,?0x04480000)
to???space?1024K,???0%?used?[0x04480000,?0x04480000,?0x04580000)
tenured?generation???total?10240K,?used?4505K?[0x04580000,?0x04f80000,?0x04f80000)//b1+b2
the?space?10240K,??43%?used?[0x04580000,?0x049e6590,?0x049e6600,?0x04f80000)
compacting?perm?gen??total?12288K,?used?2111K?[0x04f80000,?0x05b80000,?0x08f80000)
the?space?12288K,??17%?used?[0x04f80000,?0x0518fe08,?0x05190000,?0x05b80000)
No?shared?spaces?configured.
}
Heap
par?new?generation???total?9216K,?used?4303K?[0x03b80000,?0x04580000,?0x04580000)//b3(step5)
eden?space?8192K,??52%?used?[0x03b80000,?0x03fa8fe0,?0x04380000)
from?space?1024K,???4%?used?[0x04380000,?0x0438ad90,?0x04480000)
to???space?1024K,???0%?used?[0x04480000,?0x04480000,?0x04580000)
tenured?generation???total?10240K,?used?4505K?[0x04580000,?0x04f80000,?0x04f80000)//b1+b2
the?space?10240K,??43%?used?[0x04580000,?0x049e6590,?0x049e6600,?0x04f80000)
compacting?perm?gen??total?12288K,?used?2116K?[0x04f80000,?0x05b80000,?0x08f80000)
the?space?12288K,??17%?used?[0x04f80000,?0x051913c8,?0x05191400,?0x05b80000)
No?shared?spaces?configured.
如果修改MaxTenuringThreshold的值為2,從打印日志中可以發現,最終老年代的內存使用量為4096KB=4M,也就是說b1沒有晉升至老年代
上面是Minor GC的運行狀況,如果是Full GC呢:
//-XX:+UseParNewGC?-Xms20m?-Xmx20m?-Xmn10m?-XX:+PrintHeapAtGC?-XX:+PrintGCDetails
//-XX:MaxTenuringThreshold=1
publicclasstest?{
staticintmb?=1024*1024;
publicstaticvoidmain(String[]?args)?{
byte[]?b1?=newbyte[1*mb/4];
System.gc();
}
}
這里我們使用的是Full GC,也就是老年代的GC。
Full GC通常至少伴隨著一次Minor GC(并非絕對),看下面日志,這里的Minor GC應該至少發生了2次,一次Minor GC是不會把b1移動至老年代的
{Heap?before?GC?invocations=0?(full?0):
par?new?generation???total?9216K,?used?599K?[0x03b80000,?0x04580000,?0x04580000)//b1
eden?space?8192K,???7%?used?[0x03b80000,?0x03c15f40,?0x04380000)
from?space?1024K,???0%?used?[0x04380000,?0x04380000,?0x04480000)
to???space?1024K,???0%?used?[0x04480000,?0x04480000,?0x04580000)
tenured?generation???total?10240K,?used?0K?[0x04580000,?0x04f80000,?0x04f80000)//老年代為空
the?space?10240K,???0%?used?[0x04580000,?0x04580000,?0x04580200,?0x04f80000)
compacting?perm?gen??total?12288K,?used?2104K?[0x04f80000,?0x05b80000,?0x08f80000)
the?space?12288K,??17%?used?[0x04f80000,?0x0518e278,?0x0518e400,?0x05b80000)
No?shared?spaces?configured.
[Full?GC?(System)?[Tenured:?0K->404K(10240K),?0.0069434?secs]?599K->404K(19456K),?[Perm?:?2104K->2104K(12288K)],?0.0069992?secs]?[Times:?user=0.00?sys=0.00,?real=0.00?secs]
Heap?after?GC?invocations=1?(full?1):
par?new?generation???total?9216K,?used?0K?[0x03b80000,?0x04580000,?0x04580000)//新生代為空
eden?space?8192K,???0%?used?[0x03b80000,?0x03b80000,?0x04380000)
from?space?1024K,???0%?used?[0x04380000,?0x04380000,?0x04480000)
to???space?1024K,???0%?used?[0x04480000,?0x04480000,?0x04580000)
tenured?generation???total?10240K,?used?404K?[0x04580000,?0x04f80000,?0x04f80000)//b1
the?space?10240K,???3%?used?[0x04580000,?0x045e5130,?0x045e5200,?0x04f80000)
compacting?perm?gen??total?12288K,?used?2104K?[0x04f80000,?0x05b80000,?0x08f80000)
the?space?12288K,??17%?used?[0x04f80000,?0x0518e278,?0x0518e400,?0x05b80000)
No?shared?spaces?configured.
}
Heap
par?new?generation???total?9216K,?used?327K?[0x03b80000,?0x04580000,?0x04580000)
eden?space?8192K,???4%?used?[0x03b80000,?0x03bd1f98,?0x04380000)
from?space?1024K,???0%?used?[0x04380000,?0x04380000,?0x04480000)
to???space?1024K,???0%?used?[0x04480000,?0x04480000,?0x04580000)
tenured?generation???total?10240K,?used?404K?[0x04580000,?0x04f80000,?0x04f80000)
the?space?10240K,???3%?used?[0x04580000,?0x045e5130,?0x045e5200,?0x04f80000)
compacting?perm?gen??total?12288K,?used?2116K?[0x04f80000,?0x05b80000,?0x08f80000)
the?space?12288K,??17%?used?[0x04f80000,?0x05191190,?0x05191200,?0x05b80000)
No?shared?spaces?configured.
四:動態對象年齡判定
為了使內存分配更加靈活,虛擬機并不要求對象年齡達到MaxTenuringThreshold才晉升老年代
如果Survivor區中相同年齡所有對象大小的總和大于Survivor區空間的一半,年齡大于或等于該年齡的對象在Minor GC時將復制至老年代
//-XX:+UseParNewGC?-Xms20m?-Xmx20m?-Xmn10m??-XX:MaxTenuringThreshold=10
//-XX:+PrintTenuringDistribution
publicclassTest?{
staticintmb?=1024*1024;
publicstaticvoidmain(String[]?args)?{
System.out.println("step?1");
byte[]?b1?=newbyte[1*mb/4];
byte[]?b3?=newbyte[4*mb];
byte[]?b4?=newbyte[4*mb];//GC
System.out.println("step?2");
byte[]?b2?=newbyte[1*mb/4];//可以嘗試1*mb/2,然后觀察日志
b4?=?null;
System.out.println("step?3");
b4?=?newbyte[4*mb];//GC
System.out.println("step?4");
b4?=?null;
b4?=?newbyte[4*mb];//GC
}
}
先來介紹一個設置-XX:+PrintTenuringDistribution,這個參數很有意思,會在Minor GC時打印Survivor區內存容量的一半,晉升老年代年齡閥值,Survivor區中的對象大小以及對象年齡
根據啟動參數的設置,Survivor大小的一半是524288B,也就是512KB。第一次GC后,b1依然存活,故年齡變為1。第二次GC后,b1和b2依然存活,故b1的年齡變為2,b2的年齡為1。b1+b2的大小加起來超過了Survivor區容量的一半,此時會修改Survivor區晉升老年代年齡閥值為2(如果移動年齡為2的對象可以使Survivor去的內存使用降至512KB以內,則只移動年齡為2的對象,否則將會同時移動年齡為1的對象)。第三次GC時,將年齡等于晉升閥值的對象移動至老年代,執行GC,GC結束后,b1依然在Survivor區(當然可能從Survivor from區拷貝至了Survivor to區),此時b1的年齡變為2。這時Survivor區的使用內存沒有達到512M,修改Survivor區晉升老年代年齡閥值為參數設置的10。
step?1
Desired?survivor?size?524288?bytes,?new?threshold?10?(max?10)
-?age???1:?????412800?bytes,?????412800?total
step?2
step?3
Desired?survivor?size?524288?bytes,?new?threshold?2?(max?10)
-?age???1:?????262160?bytes,?????262160?total
-?age???2:?????412800?bytes,?????674960?total
step?4
Desired?survivor?size?524288?bytes,?new?threshold?10?(max?10)
-?age???1:????????136?bytes,????????136?total
-?age???2:?????262160?bytes,?????262296?total
最后,為什么在第三次GC后,Survivor區還存在一個大小為136B,年齡為1的被使用內存空間?
我猜測,雖然Minor GC時Survivor區沒有足夠的空間完成GC時會租借老年代的內存,但是在Survivor區依然保存了一個指向老年代租借內存起始地址的引用
五:空間分配擔保
這個前面已經出現過多次了,由于新生代使用復制算法,當Minor GC時如果存活對象過多,無法完全放入Survivor區,就會向老年代借用內存存放對象,以完成Minor GC
在觸發Minor GC時,虛擬機會先檢測之前GC時租借的老年代內存的平均大小是否大于老年代的剩余內存,如果大于,則將Minor GC變為一次Full GC,如果小于,則查看虛擬機是否允許擔保失敗(-XX:+/-HandlePromotionFailure。從jdk6.0開始,允許擔保失敗已變為HotSpot虛擬機所有收集器默認設置,虛擬機將不再識別該參數設置,詳見JDK-6990095 : Deprecate and eliminate -XX:-HandlePromotionFailure),如果允許擔保失敗,則只執行一次Minor GC,否則也要將Minor GC變為一次Full GC(直到GC結束時才能確定到底有多少對象需要被移動至老年代,所以在GC前,只能使用粗略的平均值進行判斷)
總結
以上是生活随笔為你收集整理的Java的内存分配策略有哪些_Java的内存分配策略的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 迈瑞医疗合理估值 带你了解这家公司
- 下一篇: 如何计算华夏银行信用卡免息期 这些小技巧