前言

想要搞明白Java对象内存申请过程的原因,是因为第一次接触线上GC日志的时候,发现了一些很奇怪的现象,就是young gc触发了full gc。为了搞清楚这个现象,得先要来个测试去复现。

复现现象

我所使用的实验代码和配置原本是用来测试空间担保机制的,不过我们重点不是这个机制而是fullgc的问题:

-Xmx20m
-Xms20m
-Xmn10m
-XX:+PrintGCTimeStamps
-Xloggc:D:/gc.log
-XX:+PrintGCDetails
-XX:SurvivorRatio=8
public static void main(String[] args) throws Exception{Thread.sleep(15000);byte[] a1,a2,a3,a4;a1 = new byte[2*1024*1024];a2 = new byte[2*1024*1024];a3 = new byte[2*1024*1024];a4 = new byte[3*1024*1024];try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}
}

注意了,观察gc的命令在jdk1.7的时候一定要带上-gccause参数,才能更加清晰,采集的时间间隔要尽可能的短,我这里用的是5ms。我之前在做实验的时候因为不知道这两个技巧,搞了很久。

观察这个图片,看到第一次触发young gc是因为eden无法分配导致的,原因旁边写的很明白了。第二次fullgc的原因也有写,但是我当时并不知道,我当时用的只是-gcutil而已。到这里如果迫不及待想知道为什么,可以直接去看:读懂一行Full GC日志(回复JVM内存分配担保机制一文中 Mr/Mrs Xxx 在留言区提出的问题)。

Java HotSpot(TM) 64-Bit Server VM (24.79-b02) for windows-amd64 JRE (1.7.0_79-b15), built on Apr 10 2015 12:36:16 by "java_re" with unknown MS VC++:1600
Memory: 4k page, physical 8309608k(1597972k free), swap 27075900k(16055712k free)
CommandLine flags: -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:MaxNewSize=10485760 -XX:NewSize=10485760 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:SurvivorRatio=8 -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
15.115: [GC [PSYoungGen: 7996K->664K(9216K)] 7996K->6808K(19456K), 0.0044966 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
15.120: [Full GC [PSYoungGen: 664K->0K(9216K)] [ParOldGen: 6144K->6725K(10240K)] 6808K->6725K(19456K) [PSPermGen: 3233K->3232K(21504K)], 0.0189363 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]
HeapPSYoungGen      total 9216K, used 3237K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)eden space 8192K, 39% used [0x00000000ff600000,0x00000000ff9296e8,0x00000000ffe00000)from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)to   space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)ParOldGen       total 10240K, used 6725K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)object space 10240K, 65% used [0x00000000fec00000,0x00000000ff291790,0x00000000ff600000)PSPermGen       total 21504K, used 3239K [0x00000000f9a00000, 0x00000000faf00000, 0x00000000fec00000)object space 21504K, 15% used [0x00000000f9a00000,0x00000000f9d29f18,0x00000000faf00000)

从gc日志的时间就可以看到是直接触发的FGC。而且我们也发现了这个原因很重要,没有这个原因就没办法针对的去查询资料,所以如果gc日志里面可以打印出来这个原因的话,排查就很方便了。现在现象也说了,资料地址也给了,剩下的就是只是动手看一遍源码,按照自己的方式整理一遍。

ParallelScavenge回收器中GC任务的前后两个Check

我用的是openjdk7,电脑上面调试用的是openjdk10。

首先来看第一次check:

//hotspot\src\share\vm\gc_implementation\parallelScavenge\psScavenge.cpp
void PSScavenge::invoke() {ParallelScavengeHeap* heap = (ParallelScavengeHeap*)Universe::heap();PSAdaptiveSizePolicy* policy = heap->size_policy();IsGCActiveMark mark;bool scavenge_was_done = PSScavenge::invoke_no_policy();//...
}bool PSScavenge::invoke_no_policy() {//...scavenge_entry.update();if (GC_locker::check_active_before_gc()) {return false;}//获取堆ParallelScavengeHeap* heap = (ParallelScavengeHeap*)Universe::heap();//从堆中获取gc的原因GCCause::Cause gc_cause = heap->gc_cause();assert(heap->kind() == CollectedHeap::ParallelScavengeHeap, "Sanity");// Check for potential problems.//在youngc之前进行检查,看看是否要直接触发fullgcif (!should_attempt_scavenge()) {return false;}//...
}

上面只是说明了should_attempt_scavenge这个在前面判断的,还未到正题:

//hotspot\src\share\vm\gc_implementation\parallelScavenge\psScavenge.cpp
//是否要尝试回收,当返回false-触发fullgc
bool PSScavenge::should_attempt_scavenge() {ParallelScavengeHeap* heap = (ParallelScavengeHeap*)Universe::heap();PSGCAdaptivePolicyCounters* counters = heap->gc_policy_counters();PSYoungGen* young_gen = heap->young_gen();PSOldGen* old_gen = heap->old_gen();if (!ScavengeWithObjectsInToSpace) {// Do not attempt to promote unless to_space is empty//若toSpace不为空则直接返回false,触发fullgcif (!young_gen->to_space()->is_empty()) {//...return false;}}// Test to see if the scavenge will likely fail.PSAdaptiveSizePolicy* policy = heap->size_policy();// A similar test is done in the policy's should_full_GC().  If this is// changed, decide if that test should also be changed.size_t avg_promoted = (size_t) policy->padded_average_promoted_in_bytes();//取最小值(之前YGC晋升到old的平均大小,新生代已使用大小)size_t promotion_estimate = MIN2(avg_promoted, young_gen->used_in_bytes());//若小于old的空闲空间,则表示无需fullgcbool result = promotion_estimate < old_gen->free_in_bytes();if (PrintGCDetails && Verbose) {//...}//...return result;
}

注释写的很清楚了,就是一个预测判断操作,第二个check是在gc任务的最后面:

//看注释,好像是说,所有堆的回收策略都在这里。然后重点说了invoke_no_policy
//方法:它只会做一件事,就是尝试回收
void PSScavenge::invoke() {ParallelScavengeHeap* heap = (ParallelScavengeHeap*)Universe::heap();PSAdaptiveSizePolicy* policy = heap->size_policy();IsGCActiveMark mark;bool scavenge_was_done = PSScavenge::invoke_no_policy();PSGCAdaptivePolicyCounters* counters = heap->gc_policy_counters();//...if (!scavenge_was_done ||//是否要fullgcpolicy->should_full_GC(heap->old_gen()->free_in_bytes())) {//gc的原因是_adaptive_size_policy,也就是说是jvm自动判断引起的fullgcGCCauseSetter gccs(heap, GCCause::_adaptive_size_policy);CollectorPolicy* cp = heap->collector_policy();//清理软引用,默认情况是不清理const bool clear_all_softrefs = cp->should_clear_all_soft_refs();//开始fullgc,默认是标记压缩算法if (UseParallelOldGC) {PSParallelCompact::invoke_no_policy(clear_all_softrefs);} else {PSMarkSweep::invoke_no_policy(clear_all_softrefs);}}
}

实现的方式和第一次check有点类似:

//hotspot\src\share\vm\gc_implementation\parallelScavenge\psAdaptiveSizePolicy.cpp
// If the remaining free space in the old generation is less that
// that expected to be needed by the next collection, do a full
// collection now.
//如果old的闲置空间小于下次预期晋升的空间,就要做一次fullgc
bool PSAdaptiveSizePolicy::should_full_GC(size_t old_free_in_bytes) {// A similar test is done in the scavenge's should_attempt_scavenge().  If// this is changed, decide if that test should also be changed.//和should_attempt_scavenge()类似,都是计算一个历代平均值是否大于当前old的free空间bool result = padded_average_promoted_in_bytes() > (float) old_free_in_bytes;//...return result;
}

看完这个就基本上明白了,为什么会出现连续gc,一次YGC,一次FGC的情况,为了证明这个现象,当然也有对应的测试代码,不过这个代码不是我想出来的,具体地址给忘了,具体配置和代码如下:

-Xmx30m
-Xms30m
-Xmn10m
-XX:+PrintGCTimeStamps
-XX:+PrintGCDetails
-Xloggc:D:/gc.log
-XX:SurvivorRatio=8
import java.util.ArrayList;
import java.util.List;public class YGCBeforeOrAfterCheckToFullGC {public static void main(String[] args) throws Exception{Thread.sleep(15000);List<byte[]> list = new ArrayList<>();for(int i=0; i<7; i++){//每次分配3MB,是的eden在第三次的时候触发ygc//这个时候会将eden数据全部放到老年代中,晋升6MB//到第5,7次的时候,分别再次触发ygc,一共晋升到old18MB,平均每次6MBlist.add(new byte[3*1024*1024]);Thread.sleep(500);}Thread.sleep(1000);//注意第7次ygc,可以看到old已经到了18MB,所以触发fullgc势在必行//但是本次清理,并没有清理掉任何东西list.clear();//这个时候eden里面还有3MB,我们再来分配两次3MB,让其准备触发YGCfor(int i=0; i<2; i++){list.add(new byte[3*1024*1024]);}//但是在YGC之前,先对比是否要直接fullgc,min(6MB,6MB)>old的2MB//所以触发了一次fullgc,这次fullgc将old清空,然后将eden中的3MB移动到old中}
}

代码上面有注释,这个我是根据之前代码分析得来的。我们来看看具体的现象:

根据之前的理论分析,一共2次fullgc,3次ygc,完全吻合。为了更为细致的观察:

gc日志:

6.135: [GC [PSYoungGen: 7998K->664K(9216K)] 7998K->6808K(29696K), 0.0034513 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
17.139: [GC [PSYoungGen: 6987K->616K(9216K)] 13131K->12904K(29696K), 0.0022924 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
18.141: [GC [PSYoungGen: 6822K->632K(9216K)] 19110K->19064K(29696K), 0.0031362 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
18.144: [Full GC [PSYoungGen: 632K->0K(9216K)] [ParOldGen: 18432K->19006K(20480K)] 19064K->19006K(29696K) [PSPermGen: 3232K->3231K(21504K)], 0.0169288 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]
20.663: [Full GC [PSYoungGen: 6185K->0K(9216K)] [ParOldGen: 19006K->3628K(20480K)] 25191K->3628K(29696K) [PSPermGen: 3231K->3231K(21504K)], 0.0431159 secs] [Times: user=0.03 sys=0.00, real=0.04 secs]
HeapPSYoungGen      total 9216K, used 3235K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)eden space 8192K, 39% used [0x00000000ff600000,0x00000000ff928fd0,0x00000000ffe00000)from space 1024K, 0% used [0x00000000ffe00000,0x00000000ffe00000,0x00000000fff00000)to   space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000)ParOldGen       total 20480K, used 3628K [0x00000000fe200000, 0x00000000ff600000, 0x00000000ff600000)object space 20480K, 17% used [0x00000000fe200000,0x00000000fe58b390,0x00000000ff600000)PSPermGen       total 21504K, used 3238K [0x00000000f9000000, 0x00000000fa500000, 0x00000000fe200000)object space 21504K, 15% used [0x00000000f9000000,0x00000000f9329888,0x00000000fa500000)

Java对象的内存申请流程

到这里是不是开始对gc以及内存分配开始产生兴趣了?赶紧去下载一个源码一起看。别看这个标题好像很厉害,其实前人都已经研究过了,这里先贴地址,我看的几篇都是占小狼博客,这里面我感觉讲的很详细了:

JVM源码分析之Java对象的创建过程

JVM源码分析之线程局部缓存TLAB

这里不涉及gc的细节,只是大致说一下分配过程,最好可以自己看代码过一遍,看别人写远不如自己也看一遍来的印象深刻,即便是跟着别人的博客看。

现在我们以new指令为例,这个反编译class文件就可以看到。

第一步,解析new指令。解释器解析到new指令的时候,会去调用:

//hotspot\src\share\vm\interpreter\interpreterRuntime.cpp
IRT_ENTRY(void, InterpreterRuntime::_new(JavaThread* thread, constantPoolOopDesc* pool, int index))klassOop k_oop = pool->klass_at(index, CHECK);instanceKlassHandle klass (THREAD, k_oop);// Make sure we are not instantiating an abstract klass//不能是抽象类klass->check_valid_for_instantiation(true, CHECK);// Make sure klass is initialized//class必须是已经初始化的klass->initialize(CHECK);//内存分配开始oop obj = klass->allocate_instance(CHECK);thread->set_vm_result(obj);
IRT_END

看到上面的那个pool,那是一个常量池,根据代码推测可以知道这是去池子里面去拿class对象去了。这个时候就可以去查阅一下深入JVM那本书了,简单理解就是,每个class都有一个类常量池,一开始这个池子里面只是一个符号而已,也就是符号引用,我们要将其解析为直接引用,这样第二次获取就不用解析了。当JVM发现这只是个符号,就把这个全限定名丢给当前类的加载器去加载,然后返回直接引用。再就是看看是不是抽象类,有没有初始化好?这些准备工作做好后,然后就是申请空间,初始化对象。

第二步,调用class对象的内存分配方法进行内存分配和初始化,这里暂时不理解InstanceKlass和其他一些类的关系,暂时这么说。

//hotspot\src\share\vm\oops\instanceKlass.cpp
instanceOop InstanceKlass::allocate_instance(TRAPS) {//是否自定义了finalizebool has_finalizer_flag = has_finalizer(); // Query before possible GC//计算要分配的空间int size = size_helper();  // Query before forming handle.instanceOop i;//从堆中申请内存空间,并创建对象i = (instanceOop)CollectedHeap::obj_allocate(this, size, CHECK_NULL);//和自定义finalize相关的操作if (has_finalizer_flag && !RegisterFinalizersAtInit) {i = register_finalizer(i, CHECK_NULL);}return i;
}

跟着跑去堆里面调用分配对象方法,到这里为止其实都没有什么看头:

//hotspot\src\share\vm\gc_interface\collectedHeap.inline.hpp
oop CollectedHeap::obj_allocate(KlassHandle klass, int size, TRAPS) {debug_only(check_for_valid_allocation_state());assert(!Universe::heap()->is_gc_active(), "Allocation during gc not allowed");assert(size >= 0, "int won't convert to size_t");//分配HeapWord* obj = common_mem_allocate_init(size, false, CHECK_NULL);//初始化post_allocation_setup_obj(klass, obj, size);NOT_PRODUCT(Universe::heap()->check_for_bad_heap_word_value(obj, size));return (oop)obj;
}

继续跟进,看到下面有个填0处理,根据网上博客来看,是说用于初始化实例数据的,这样代码里面就不用特别赋值了,直接就是0:

//hotspot\src\share\vm\gc_interface\collectedHeap.inline.hpp
HeapWord* CollectedHeap::common_mem_allocate_init(size_t size, bool is_noref, TRAPS) {//分配HeapWord* obj = common_mem_allocate_noinit(size, is_noref, CHECK_NULL);//Copy::fill_to_aligned_words(obj + hs, size - hs);//对非对象头的部分做填0处理init_obj(obj, size);return obj;
}

第三步,尝试在TLAB上分配,TLAB也是从堆上申请的空间,只不过分配权只属于申请TLAB的线程:

//hotspot\src\share\vm\gc_interface\collectedHeap.inline.hpp
if (UseTLAB) {result = allocate_from_tlab(klass, THREAD, size);if (result != NULL) {assert(!HAS_PENDING_EXCEPTION,"Unexpected exception, will result in uninitialized storage");return result;}}

在TLAB上分配有两个路径,第一种是快速分配,因为TLAB的空间有三个指针组成,start,top,end。start和end标识TLAB范围,top表示可分配的起始位置,所以直接对指针进行计算就行了,也就是前面博客里面说的指针碰撞,对于无空间碎片(规整)的分配形式,可以用指针碰撞来形容;而不规则有空间碎片的,可以用内存列表来形容分配方式。

HeapWord* CollectedHeap::allocate_from_tlab(Thread* thread, size_t size) {assert(UseTLAB, "should use UseTLAB");HeapWord* obj = thread->tlab().allocate(size);if (obj != NULL) {return obj;}// Otherwise...return allocate_from_tlab_slow(thread, size);
}
HeapWord* obj = top();
if (pointer_delta(end(), obj) >= size) {//填充数据,让堆误以为是连续的size_t hdr_size = oopDesc::header_size();Copy::fill_to_words(obj + hdr_size, size - hdr_size, badHeapWordVal);set_top(obj + size);//...return obj;
}
return NULL;

快速分配不行,那就只能慢速分配了:

1. 是否超过TLAB本身允许的浪费阈值?超过就会直接返回进行下一步,返回之前会对这个阈值进行增大。

  // Retain tlab and allocate object in shared space if// the amount free in the tlab is too large to discard.// tlab的剩余空间是否超过了最大浪费的阈值,就直接返回,不再tlab中分配,直接到eden中分配// 并且记录下,慢分配到大小if (thread->tlab().free() > thread->tlab().refill_waste_limit()) {//这里set_refill_waste_limit(refill_waste_limit() + refill_waste_limit_increment());//这里阈值时可以增长的,每次都会自增TLABWasteIncrement大小,看debug,应该是默认时4thread->tlab().record_slow_allocation(size);return NULL;}

2. 若在允许的范围之内,那么就会去申请一块新的TLAB进行存放

//计算所需空间
size_t new_tlab_size = thread->tlab().compute_size(size);
if (new_tlab_size == 0) {return NULL;
}
// 从堆中分配新的TLAB
HeapWord* obj = Universe::heap()->allocate_new_tlab(new_tlab_size);
if (obj == NULL) {return NULL;
}
//统计refill的次数;初始化重新申请到的内存块;旧的TLAB末尾浪费的部分填充数据
//让堆误以为是连续的
thread->tlab().fill(obj, obj + size, new_tlab_size);

年轻代的申请方式用的是cas,指针碰撞申请空间。

第四步,TLAB不行那就只能去堆上分配。这里有两种分配策略,基础和失败时的分配策略。基础策略就是cas分配方式,失败分配就是要去触发GC,这个时候申请内存的线程是被阻塞的,等待VM线程的GC操作完成。当然在触发GC之前还有一下判断是否要直接放入old中如果本来放不下,而且又大于eden的一半,那就直接进入old里面:

//先尝试分配
HeapWord* result = young_gen()->allocate(size, is_tlab);
while (result == NULL) {result = young_gen()->allocate(size, is_tlab);if (result != NULL) {return result;}//看这个是size>=eden/2直接进入oldif (!is_tlab &&size >= (young_gen()->eden_space()->capacity_in_words(Thread::current()) / 2)) {//直接在old上分配result = old_gen()->allocate(size, is_tlab);if (result != NULL) {return result;}}//...//准备YGCif (result == NULL) {// Generate a VM operationVM_ParallelGCFailedAllocation op(size, is_tlab, gc_count);VMThread::execute(&op); //阻塞等待// GC操作是否执行成功,因为可能会有多个线程一起执行,导致可能没法拿到锁if (op.prologue_succeeded()) {// If GC was locked out during VM operation then retry allocation// and/or stall as necessary.// 如果gc在vm操作的时候被锁住,那么会循环重试,因为可能被其他线程触发了if (op.gc_locked()) {assert(op.result() == NULL, "must be NULL if gc_locked() is true");continue;  // retry and/or stall as necessary}//...}}//...
}

第四步,触发Allocation Failed(VM_GenCollectForAllocation)的GC,这个交给VM线程去处理,当前线程就会阻塞等待。后面的就是先check是否要fullgc,如果不需要就触发YGC回收,YGC之后又会有一次判断,是否要进行fullgc。然后循环又重头开始,cas分配,以此类推。至于如何退出循环的,可能是堆溢出异常?或者是超过了分配次数,这块不是很清楚,先不理会。

最后如果还是未能分配成功,那就是返回NULL,结果就是抛出堆溢出的异常。

//无法分配内存//是否开启-XX:+UseGCOverheadLimit,开启就说明,若回收耗费98%,但是回收内存不到2%则//自动认为OOMif (!gc_overhead_limit_was_exceeded) {//内存溢出,看看是否要dump堆文件// -XX:+HeapDumpOnOutOfMemoryError and -XX:OnOutOfMemoryError supportreport_java_out_of_memory("Java heap space");if (JvmtiExport::should_post_resource_exhausted()) {JvmtiExport::post_resource_exhausted(JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR | JVMTI_RESOURCE_EXHAUSTED_JAVA_HEAP,"Java heap space");}//抛出堆溢出异常THROW_OOP_0(Universe::out_of_memory_error_java_heap());} else {//java.lang.OutOfMemoryError: GC overhead limit exceeded// -XX:+HeapDumpOnOutOfMemoryError and -XX:OnOutOfMemoryError supportreport_java_out_of_memory("GC overhead limit exceeded");if (JvmtiExport::should_post_resource_exhausted()) {JvmtiExport::post_resource_exhausted(JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR | JVMTI_RESOURCE_EXHAUSTED_JAVA_HEAP,"GC overhead limit exceeded");}THROW_OOP_0(Universe::out_of_memory_error_gc_overhead_limit());}

gc任务投递给工作池执行,看到任务名称带有root,应该是根节点搜索吧:

// We'll use the promotion manager again later.PSPromotionManager* promotion_manager = PSPromotionManager::vm_thread_promotion_manager();{// TraceTime("Roots");ParallelScavengeHeap::ParStrongRootsScope psrs;GCTaskQueue* q = GCTaskQueue::create();for(uint i=0; i<ParallelGCThreads; i++) {q->enqueue(new OldToYoungRootsTask(old_gen, old_top, i));}q->enqueue(new SerialOldToYoungRootsTask(perm_gen, perm_top));q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::universe));q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::jni_handles));// We scan the thread roots in parallelThreads::create_thread_roots_tasks(q);q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::object_synchronizer));q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::flat_profiler));q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::management));q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::system_dictionary));q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::jvmti));q->enqueue(new ScavengeRootsTask(ScavengeRootsTask::code_cache));ParallelTaskTerminator terminator(gc_task_manager()->workers(),(TaskQueueSetSuper*) promotion_manager->stack_array_depth());if (ParallelGCThreads>1) {for (uint j=0; j<ParallelGCThreads; j++) {q->enqueue(new StealTask(&terminator));}}gc_task_manager()->execute_and_wait(q);}

第五步,如果申请成功,那就是初始化对象。。。

小结

总结一下流程:

1. 解析new命令

2. 从类常量池中定位class对象,如果是第一次使用就解析符号引用为直接引用,然后初始化类

3. 进行内存分配,先在TLAB上分配,不行就在堆上分配

4. 先尝试用cas在eden上分配,不行就看看有没有超过eden的一般,有就是在old上分配,这块如果分配不成功也不会有fullgc

5. 都不行,那就触发GC任务,和具体算法有关系。

以默认的ParallelScavenge为例,先check是否要fullgc,min(历代晋升old均值,年轻代总大小)如果大于old剩余空间就触发;

如果不触发那就进行YGC。

(1) YGC会把eden和from中存活对象放到to里面,如果年龄到了(动态年龄计算)就放到old里面,但是如果survivor放不下,那就只能将不能放的部分放到到old里面,如果old也放不下,那就只能fullgc,还是不行就溢出;

(2) 如果YGC可以,那就while循环,如果发现eden还是分配不了,那就再次GC,直到抛出退出循环抛出异常为止。

里面还有空间担保机制等等,具体实现我也没看,只是看到深入JVM里面有说到这个,自个做了实验。

如果有人看到这里,可以发现,上面也有很多模棱两可的表达或者根本没说,这些地方我并没有搞懂:比如说并发触发GC的情况;比如说什么时候退出循环,什么时候被判定为溢出;还有TLAB分配的最大值,如何计算TLAB分配空间;对象如何初始化,对象的布局是什么,通过什么方式访问对象的实例数据等。其实了解这些并不是没有好处,对于底层的理解越深,越可以掌控全局。

所以这篇文章还有另外一个目的,就是一个引子。

JVM源码简析(楔子)-对象内存分配过程和PS回收器中YGC触发FGC的现象相关推荐

  1. django源码简析——后台程序入口

    django源码简析--后台程序入口 这一年一直在用云笔记,平时记录一些tips或者问题很方便,所以也就不再用博客进行记录,还是想把最近学习到的一些东西和大家作以分享,也能够对自己做一个总结.工作中主 ...

  2. java ArrayList 概述 与源码简析

    ArrayList 概述 与源码简析 1 ArrayList 创建 ArrayList<String> list = new ArrayList<>(); //构造一个初始容量 ...

  3. Spring Boot源码简析 @EnableTransactionManagement

    相关阅读 Spring Boot源码简析 事务管理 Spring Boot源码简析 @EnableAspectJAutoProxy Spring Boot源码简析 @EnableAsync Sprin ...

  4. Log-Pilot 源码简析

    Log-Pilot 源码简析 简单介绍 源码简析 Pilot结构体 Piloter接口 main函数 Pilot.Run Pilot.New Pilot.watch Pilot.processEven ...

  5. 【Golang源码分析】Go Web常用程序包gorilla/mux的使用与源码简析

    目录[阅读时间:约10分钟] 一.概述 二.对比: gorilla/mux与net/http DefaultServeMux 三.简单使用 四.源码简析 1.NewRouter函数 2.HandleF ...

  6. (Ajax)axios源码简析(三)——请求与取消请求

    传送门: axios源码简析(一)--axios入口文件 axios源码简析(二)--Axios类与拦截器 axios源码简析(三)--请求与取消请求 请求过程 在Axios.prototype.re ...

  7. ffmpeg实战教程(十三)iJKPlayer源码简析

    要使用封装优化ijk就必须先了解ffmpeg,然后看ijk对ffmpeg的C层封装! 这是我看ijk源码时候的笔记,比较散乱.不喜勿喷~ ijk源码简析: 1.ijkplayer_jni.c 封装的播 ...

  8. 【Android项目】本地FM收音机开发及源码简析

    [Android项目]本地FM收音机开发及源码简析 目录 1.概述 2.收音机的基本原理 3.收音机其他信息 RDS功能 4.Android开发FM收音机源码解析 5.App层如何设计本地FM应用 6 ...

  9. Spring源码阅读之bean对象的创建过程

    Spring源码阅读之bean对象的创建过程 ​ Spring是通过IOC容器来管理对象的,该容器不仅仅只是帮我们创建了对象那么简单,它负责了对象的整个生命周期-创建.装配.销毁.这种方式成为控制反转 ...

最新文章

  1. C/C++:sizeof('a')的值为什么不一样?
  2. 媒体洞察 | 让企业自由发展的云时代
  3. 每天看一片代码系列(二):WebSocket-Node
  4. android:layout_width=0.0dip,【教程】状态栏显示网速
  5. mysql 事务实例_mysql实现事务的提交和回滚实例
  6. 计算机科学考试题目,附录A 计算机科学与技术学科综合考试人工智能真题
  7. 数字全息干涉重建算法研究
  8. mysql取值范围1-10_mysql各种数据类型取值范围
  9. 基于51单片机智能交通灯电路方案设计
  10. 汉印HPRT XT130 打印机驱动
  11. html 的header标签和head标签
  12. C语言程序设计笔记(浙大翁恺版) 第七章:函数
  13. 商家入驻天猫商城需要多少钱 天猫入住有哪些常见问题
  14. CCG7D双C口车用充电芯片
  15. Bacterial Melee CodeForces - 756D (dp去重)
  16. MATLAB调用Kakadu中的可执行文件,实现JPEG2000压缩
  17. 数字信号处理中的声学基础知识
  18. 《Sony Vegas Pro 12标准教程》——2.2 使用Vegas采集视频
  19. Devexpress控件中gridcontrol Drag a column header here to group by that column 更换
  20. 饥荒联机服务器配置文件翻译,饥荒联机世界设置翻译 | 手游网游页游攻略大全...

热门文章

  1. BUUCTF misc 专题(61)黑客帝国
  2. 计算鬼成像学习笔记二:二阶关联函数探究
  3. 《人工智能》之《绪论》习题解析
  4. Python大牛带你实现12306全自动抢票,学完后春运回家抢票无忧
  5. android lr 输出分辨率设置,Resolume Arena设置输出分辨率的方法
  6. 《GOD OF WAR2 / 战神2》demo ISO
  7. 【教程】使用MATLAB进行公式推导、解方程组
  8. 你们的老朋友又来啦!来听小强讲视频超分辨率算法前沿进展
  9. 范儿变迁:从北帝到叶问
  10. 惠普Linux系统启动光驱,HP ProLiant MicroServer Gen8使用Super GRUB2 Disk从TF卡启动光驱位安装的Debian 8.3...