最近看了毕玄老师分享的的PPT,对其中一个案例进行了测试,参考代码如下:
/**
* -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];
}
}
}
运行的代码和参数如上,在复杂代码到机器里面运行之前,请大家思考下面2个问题:
- bytes7对象能否分配成功?
- 如果bytes7能够分配成功,应该分配在哪里呢?
如果大家对于对象的分配流程感兴趣,可以参考我的博文:
对象的创建以及YoungGC的触发:http://www.jianshu.com/p/941fe93d21c2
JVM之逃逸分析以及TLAB:http://www.jianshu.com/p/3835450d49d0
好了言归正传,回答上面的问题!
bytes7对象能否分配成功
上面的对象能够分配成功,在 eclipse 执行上面的程序,打印出来的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日志,从第一条日志可以分析出,创建bytes4的时候,年轻代不足,触发了一次YoungGC,年轻代存活对象大小 = 6M,To Survivor 区大小 = 1M,To区空间不够,通过空间担保机制晋升到老年代,老年代的晋升规则如下:
bool TenuredGeneration::promotion_attempt_is_safe(size_t max_promotion_in_bytes) const {
//老年代最大连续空间
size_t available = max_contiguous_available();
//平均晋升对象的大小
size_t av_promo = (size_t)gc_stats()->avg_promoted()->padded_average();
//最大连续空间大小 > 平均大小或最大连续空间 > 晋升对象的大小
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;
}
从上面的代码可以看出,如果最大连续空间大小 > 平均大小或最大连续空间 > 晋升对象的大小,就运行年轻代对象晋升到老年代,该程序中老年代空间 = 10m,因此可以将年轻代对象晋升到老年代。
下面继续看第二条日志,发现年轻代在分配bytes7对象的时候,年轻代不足触发YoungGC,但是老年代空间不足,无法满足晋升规则,因此触发了一次FullGC,部分年轻代对象被晋升到老年代。从源码角度分析一下为什么会触发FullGC,源码如下:
//表示有jni在操作内存,此时不能进行GC避免改变对象在内存的位置
if (GC_locker::is_active_and_needs_gc()) {
if (!gch->is_maximal_no_gc()) {
result = expand_heap_and_allocate(size, is_tlab);//扩堆
}
return result; // could be null if we are out of space
//consult_young=true的时候,表示调用该方法时,判断此时晋升是否的安全的。
//若=false,表示只取上次young gc时设置的参数,此次不再进行额外的判断。
} else if (!gch->incremental_collection_will_fail(false /* don't consult_young */)) {
// 对年轻代进行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 :: ");
}
//对老年代进行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;
}
//如果对象还是没有分配成功,进行一次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 */);
从上面的代码中可以看出如果进行YoungGC分配失败以后,会进行一次FullGC,进行对象的回收,从日志可以看出,在进行FullGC时,部分年轻代对象晋升到了老年代。
如果bytes7能够分配成功,应该分配在哪里呢?
重新第二条日志,老年代区内存使用量增加了2M,有人可能会问老年代区增加的 2M 也可能是 bytes7 在老年代分配了啊?这里提供 3 种方法证明 bytes7 对象在年轻代分配
First
在上面的程序中将 :
byte[] bytes7 = new byte[2 * _1MB];
修改为:
byte[] bytes7 = new byte[3 * _1MB];
执行上面的程序,查看日志:
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
发现年轻代使用量 = 8016K = 7.8 M,老年代使用量 = 8192K = 8M,bytes7分配了3M的内存,因此可以证明bytes7 是在年轻代分配的。
Second
光从日志的角度还是不能证明,下面我们从源码角度去证实一下(最好是先看:《对象的创建以及YoungGC的触发》这篇文章):
在年轻代进行对象分配的时候,会调用 GenCollectorPolicy::should_try_older_generation_allocation 方法来判断对象是否在老年代分配,具体实现如下:
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方法正在被调用
|| gch->incremental_collection_failed();//最近发生过一次担保失败
}
从上面的代码可以看出,一个对象要在老年代分配,需要满足下面3中情况中的一种(前提条件:对象大小 < PretenureSizeThreshold设置的阈值):
- 要分配的大小>年轻代容量(eden+from总大小)
- 某些JNI方法正在被调用
- 最近发生过一次担保失败
当分配bytes7时,不满足上面任何一种情况,因此bytes7不可能在老年代分配
Third
去掉注释掉的代码前面的斜杠,执行程序,使用HSDB查看不同分区的内存地址,查看bytes7对象的地址,确定bytes7对象所在的内存区域。
关于HSDB的使用可以参考R大的博客:http://rednaxelafx.iteye.com/blog/1847971
差不多了,写完赶快跑路。
自我介绍
我是何勇,现在重庆猪八戒,多学学!!!