Overview一般来我们公司面试的时候,都会习惯性的问一些JVM的问题。当然,如果他认为自己对JVM掌握的很好,我会酌情多问。毕竟知己难求,难得在这样的“狭路相逢”。比如我今天要说的这个问题,就是我经常问的一个问题,但是和我之前查过的场景不一样,属于另外一种情况。说到这里可能会成为很多公司JVM必问的问题,所以这篇文章值得仔细阅读,相信会让你收获颇丰。这个问题我简单总结为HotspotGC研发工程师可能是少了一块逻辑。昨天,有美团的朋友在群里提问。如下图,在上一次YGC之后,from空间的利用率是12%,但是当下一次YGC即将发生的时候,发现from空间的利用率变成了99%。OK,当你看到这个的时候,请停下来思考10秒,想想这个现象是否正常。如果你觉得这种现象不正常,说明你对JVM内存分析有一定的了解,但还没有完全理解。如果你觉得这种现象没问题,那多半是你对JVM内存分配还不够熟悉,极少数情况是你已经很熟悉了,对它的实现优缺点了如指掌。那么你属于哪一种呢?其实简化的问题就是:在非GC进程中,新创建的对象是否可以分配到from空间?JVM内存分配JVM内存分配简单说简单,复杂说复杂,这里不打算细说,因为要讲的话基本能讲几个小时。我只挑大家熟悉的。暂时把大家归结为上面第一种情况。大家都知道JavaHeap主要由新生代和老年代组成,新生代分别由eden+s0(fromspace)+s1(tospace)组成。通常,s0或s1有一个空块,主要用于GC拷贝。当我们创建一个对象时,我们会申请分配一块内存。这段内存主要是在新生代分配,在eden中分配。当然,在一些特殊情况下,可以直接分配给老年代。按照这个规则,正常情况下是不可能在from空间中分配内存的,所以在上一次GC之后和下一次GC之前是不可能在from空间中分配内存的。事实是什么?是这样吗?从上面的GC日志来看,显然不是这样的。之前有过经验,这种情况跟GCLocker有关。当时让群里美团的同学把完整日志发上来求证。结果比较出乎意料,没有我之前遇到的情况,于是索要了整个完整的GC日志,下面简单描述一下我在这个问题上的思考过程。拿到GC日志后,我做的第一件事就是找到对应的GC日志上下文。这种奇怪的现象是偶尔发生还是一直存在,所以我从空间409600K,99%开始搜索整个日志,找到了***第一次发生的位置,我发现这种情况一开始是不存在的,但是只是在某个时间,而且都集中在中间的某个时间段,然后我马上看下一个发生时间的上下文,发现有一个FullGC和一个CMSGC之前。然后发现上次发生的时候也有FullGC。基于这两种情况,我基本得出了一个大概的结论,可能和FullGC有关。源码验证带着疑惑开始找相关源码验证,因为知道有从空间分配的情况,所以直接找到了对应的方法。从上面的代码,我们可以做一些分析。首先,我们从日志中排除了GCLocker如果是GCLocker,在JDK8下默认会打印出相关原因,但实际上gc的原因是因为分配失败,所以重点在should_allocate_from_spaceboolshould_allocate_from_space()const{return_should_allocate_from_space;}下一步是找到将此属性设置为true的位置。我找到了以下源代码。这是GC发生后的一些处理逻辑,在full为真时为真,也就是说FullGC发生后一定可以设置这个属性。set_should_allocate_from_space(),并且只有在FullGC之后才能清理clear_should_allocate_from_space()这个属性,基本符合我们的现象。在所有FullGC发生后会发生这种情况吗?从上面的代码来看显然不是,只有当!collection_attempt_is_safe()&&!_eden_space->is_empty()为真时才会出现这种情况,这里我简单说一下可能的场景。当我们因为内存分配问题不得不做FullGC时,发现GC效果不是很好。连eden里都有对象,老年代也基本满了,老年代内存不够用。为了容纳eden中的对象,这时候就会出现上面的情况。不过,随着时间的推移,未来可能会有改进。例如,进行一次CMSGC可能能够释放老年代中的一些内存。其实整个内存都会恢复正常,但这带来了一个问题。后面经常会出现从from空间分配内存的情况,这就是我们这次遇到的问题。直到下一次FullGC发生时才会解封,所以即使我们执行一次jmap-histo:live,也足以解封。GC研发工程师遗漏的逻辑?那么这其实带来了一个新的问题,就是尽快将更多的对象提升到老年代,这样就会让老年代的GC相对更加频繁。我觉得这其实应该算是JVM的一个bug,或许更应该说是GC研发工程师不小心遗漏了一段逻辑。我觉得比较合理的做法是,如果后面有CMSGC,那么在CMSGC之后,应该主动clear_should_allocate_from_space(),也就是在CMSGC的sweep阶段执行完之后再执行上面的逻辑,这样才会有得到一定的保证。其实我们也从sweep的源码中看到了一些蛛丝马迹,***调用了gch->clear_incremental_collection_failed(),所以个人认为是开发HotspotGC的同学忘了做这个,只需要在gch->clear_incremental_collection_failed()解决此类问题,后面调用新生代的clear_should_allocate_from_space()。结语至此,大家应该知道问题的答案了。其实直接在from空间分配对象是可以的,只是目前的实现可能存在一些问题,会导致老年代GC变得频繁。这个问题要不要提给OpenJDK社区,不过我的老东家阿里AJDK,我觉得这个问题应该解决了,哈哈哈。顺便说一句,如果你有什么关于GC的有趣现象,可以发邮件给我,我很乐意和你一起了解。【本文为专栏作家李嘉鹏原创文章,转载请微信公众号(你个假笨蛋,id:lovestblog)联系作者授权转载】点此查看本作者更多好文
