最近看了畢玄老師分享的的PPT叶沛,對(duì)其中一個(gè)案例進(jìn)行了測(cè)試,參考代碼如下:
/**
* -Xmx20M -Xms20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails
* -XX:PretenureSizeThreshold=4M -Xloggc:D:\gc.log
* @author heyong
*/
public class SerialGCDemo {
private static final int _1MB = 1024 * 1024;
public static void main(String[] args) throws InterruptedException {
ByteArray bytes = new ByteArray(2 * _1MB);
ByteArray bytes2 = new ByteArray(2 * _1MB);
ByteArray bytes3 = new ByteArray(2 * _1MB);
System.out.println("step 1");
ByteArray bytes4 = new ByteArray(2 * _1MB);
Thread.sleep(3000);
System.out.println("step 2");
ByteArray bytes5 = new ByteArray(2 * _1MB);
ByteArray bytes6 = new ByteArray(2 * _1MB);
System.out.println("step 3");
ByteArray bytes7 = new ByteArray(3 * _1MB);
// Thread.sleep(1000 * 60 * 60);
System.out.println("======");
}
private static class ByteArray {
private byte[] bytes;
public ByteArray(int size) {
bytes = new byte[size];
}
}
}
運(yùn)行的代碼和參數(shù)如上忘朝,在復(fù)雜代碼到機(jī)器里面運(yùn)行之前灰署,請(qǐng)大家思考下面2個(gè)問題:
- bytes7對(duì)象能否分配成功?
- 如果bytes7能夠分配成功局嘁,應(yīng)該分配在哪里呢溉箕?
如果大家對(duì)于對(duì)象的分配流程感興趣,可以參考我的博文:
對(duì)象的創(chuàng)建以及YoungGC的觸發(fā):http://www.reibang.com/p/941fe93d21c2
JVM之逃逸分析以及TLAB:http://www.reibang.com/p/3835450d49d0
好了言歸正傳悦昵,回答上面的問題约巷!
bytes7對(duì)象能否分配成功
上面的對(duì)象能夠分配成功,在 eclipse 執(zhí)行上面的程序旱捧,打印出來(lái)的GC日志如下:
0.341: [GC (Allocation Failure) 0.341: [DefNew: 7127K->526K(9216K), 0.0199660 secs] 7127K->6670K(19456K), 0.0202876 secs] [Times: user=0.00 sys=0.00, real=0.02 secs]
3.367: [GC (Allocation Failure) 3.368: [DefNew: 6830K->6830K(9216K), 0.0000683 secs]3.368: [Tenured: 6144K->8192K(10240K), 0.0581733 secs] 12974K->12812K(19456K), [Metaspace: 2655K->2655K(1056768K)], 0.0590199 secs] [Times: user=0.01 sys=0.00, real=0.06 secs]
上面的日志中有兩條GC日志独郎,從第一條日志可以分析出,創(chuàng)建bytes4的時(shí)候枚赡,年輕代不足氓癌,觸發(fā)了一次YoungGC,年輕代存活對(duì)象大小 = 6M贫橙,To Survivor 區(qū)大小 = 1M贪婉,To區(qū)空間不夠,通過空間擔(dān)保機(jī)制晉升到老年代卢肃,老年代的晉升規(guī)則如下:
bool TenuredGeneration::promotion_attempt_is_safe(size_t max_promotion_in_bytes) const {
//老年代最大連續(xù)空間
size_t available = max_contiguous_available();
//平均晉升對(duì)象的大小
size_t av_promo = (size_t)gc_stats()->avg_promoted()->padded_average();
//最大連續(xù)空間大小 > 平均大小或最大連續(xù)空間 > 晉升對(duì)象的大小
bool res = (available >= av_promo) || (available >= max_promotion_in_bytes);
if (PrintGC && Verbose) {
gclog_or_tty->print_cr(
"Tenured: promo attempt is%s safe: available("SIZE_FORMAT") %s av_promo("SIZE_FORMAT"),"
"max_promo("SIZE_FORMAT")",
res? "":" not", available, res? ">=":"<",
av_promo, max_promotion_in_bytes);
}
return res;
}
從上面的代碼可以看出疲迂,如果最大連續(xù)空間大小 > 平均大小或最大連續(xù)空間 > 晉升對(duì)象的大小才顿,就運(yùn)行年輕代對(duì)象晉升到老年代,該程序中老年代空間 = 10m尤蒿,因此可以將年輕代對(duì)象晉升到老年代郑气。
下面繼續(xù)看第二條日志,發(fā)現(xiàn)年輕代在分配bytes7對(duì)象的時(shí)候腰池,年輕代不足觸發(fā)YoungGC,但是老年代空間不足尾组,無(wú)法滿足晉升規(guī)則,因此觸發(fā)了一次FullGC示弓,部分年輕代對(duì)象被晉升到老年代讳侨。從源碼角度分析一下為什么會(huì)觸發(fā)FullGC,源碼如下:
//表示有jni在操作內(nèi)存奏属,此時(shí)不能進(jìn)行GC避免改變對(duì)象在內(nèi)存的位置
if (GC_locker::is_active_and_needs_gc()) {
if (!gch->is_maximal_no_gc()) {
result = expand_heap_and_allocate(size, is_tlab);//擴(kuò)堆
}
return result; // could be null if we are out of space
//consult_young=true的時(shí)候跨跨,表示調(diào)用該方法時(shí),判斷此時(shí)晉升是否的安全的囱皿。
//若=false歹叮,表示只取上次young gc時(shí)設(shè)置的參數(shù),此次不再進(jìn)行額外的判斷铆帽。
} else if (!gch->incremental_collection_will_fail(false /* don't consult_young */)) {
// 對(duì)年輕代進(jìn)行GC
gch->do_collection(false /* full */,
false /* clear_all_soft_refs */,
size /* size */,
is_tlab /* is_tlab */,
number_of_generations() - 1 /* max_level */);
} else {
if (Verbose && PrintGCDetails) {
gclog_or_tty->print(" :: Trying full because partial may fail :: ");
}
//對(duì)老年代進(jìn)行GC
gch->do_collection(true /* full */,
false /* clear_all_soft_refs */,
size /* size */,
is_tlab /* is_tlab */,
number_of_generations() - 1 /* max_level */);
}
//嘗試重新分配
result = gch->attempt_allocation(size, is_tlab, false /*first_only*/);
if (result != NULL) {
assert(gch->is_in_reserved(result), "result not in heap");
return result;
}
// 如果上面分配失敗咆耿,嘗試先闊堆,然后分配
result = expand_heap_and_allocate(size, is_tlab);
if (result != NULL) {
return result;
}
//如果對(duì)象還是沒有分配成功爹橱,進(jìn)行一次FullGC
{
IntFlagSetting flag_change(MarkSweepAlwaysCompactCount, 1); // Make sure the heap is fully compacted
gch->do_collection(true /* full */,
true /* clear_all_soft_refs */,
size /* size */,
is_tlab /* is_tlab */,
number_of_generations() - 1 /* max_level */);
}
//在FullGC以后嘗試分配
result = gch->attempt_allocation(size, is_tlab, false /* first_only */);
從上面的代碼中可以看出如果進(jìn)行YoungGC分配失敗以后萨螺,會(huì)進(jìn)行一次FullGC,進(jìn)行對(duì)象的回收愧驱,從日志可以看出慰技,在進(jìn)行FullGC時(shí),部分年輕代對(duì)象晉升到了老年代组砚。
如果bytes7能夠分配成功吻商,應(yīng)該分配在哪里呢?
重新第二條日志糟红,老年代區(qū)內(nèi)存使用量增加了2M艾帐,有人可能會(huì)問老年代區(qū)增加的 2M 也可能是 bytes7 在老年代分配了啊盆偿?這里提供 3 種方法證明 bytes7 對(duì)象在年輕代分配
First
在上面的程序中將 :
byte[] bytes7 = new byte[2 * _1MB];
修改為:
byte[] bytes7 = new byte[3 * _1MB];
執(zhí)行上面的程序柒爸,查看日志:
Heap
def new generation total 9216K, used 8016K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
eden space 8192K, 97% used [0x00000000fec00000, 0x00000000ff3d43f8, 0x00000000ff400000)
from space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
tenured generation total 10240K, used 8192K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
the space 10240K, 80% used [0x00000000ff600000, 0x00000000ffe00158, 0x00000000ffe00200, 0x0000000100000000)
Metaspace used 2661K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 288K, capacity 386K, committed 512K, reserved 1048576K
發(fā)現(xiàn)年輕代使用量 = 8016K = 7.8 M,老年代使用量 = 8192K = 8M事扭,bytes7分配了3M的內(nèi)存捎稚,因此可以證明bytes7 是在年輕代分配的。
Second
光從日志的角度還是不能證明,下面我們從源碼角度去證實(shí)一下(最好是先看:《對(duì)象的創(chuàng)建以及YoungGC的觸發(fā)》這篇文章):
在年輕代進(jìn)行對(duì)象分配的時(shí)候今野,會(huì)調(diào)用 GenCollectorPolicy::should_try_older_generation_allocation 方法來(lái)判斷對(duì)象是否在老年代分配葡公,具體實(shí)現(xiàn)如下:
bool GenCollectorPolicy::should_try_older_generation_allocation(
size_t word_size) const {
GenCollectedHeap* gch = GenCollectedHeap::heap();
size_t gen0_capacity = gch->get_gen(0)->capacity_before_gc();
return (word_size > heap_word_size(gen0_capacity))//要分配的大小>年輕代容量(eden+from總大小)
|| GC_locker::is_active_and_needs_gc()//某些JNI方法正在被調(diào)用
|| gch->incremental_collection_failed();//最近發(fā)生過一次擔(dān)保失敗
}
從上面的代碼可以看出条霜,一個(gè)對(duì)象要在老年代分配催什,需要滿足下面3中情況中的一種(前提條件:對(duì)象大小 < PretenureSizeThreshold設(shè)置的閾值):
- 要分配的大小>年輕代容量(eden+from總大小)
- 某些JNI方法正在被調(diào)用
- 最近發(fā)生過一次擔(dān)保失敗
當(dāng)分配bytes7時(shí)蛔外,不滿足上面任何一種情況,因此bytes7不可能在老年代分配
Third
去掉注釋掉的代碼前面的斜杠溯乒,執(zhí)行程序夹厌,使用HSDB查看不同分區(qū)的內(nèi)存地址,查看bytes7對(duì)象的地址裆悄,確定bytes7對(duì)象所在的內(nèi)存區(qū)域矛纹。
關(guān)于HSDB的使用可以參考R大的博客:http://rednaxelafx.iteye.com/blog/1847971
差不多了,寫完趕快跑路光稼。
自我介紹
我是何勇或南,現(xiàn)在重慶豬八戒,多學(xué)學(xué)02晒弧!