本文来自: PerfMa技术社区PerfMa(笨马网络)官网
概述
明天要说的这个问题,是我常常面试问的一个问题,只是和我之前排查过的场景有些区别,属于另外一种状况。兴许我这里讲了这个之后,会成为不少公司JVM必问之题,所以本文还是值得大家好好看看的,置信也会让你很有播种,我把这个问题简略演绎为Hotspot GC研发工程师兴许漏掉了一块逻辑。
如下图所示,在上一次YGC之后,from space的使用率是12%,然而在下一次YGC筹备产生的时候,发现from space的使用率变成了99%。
OK,看到这里,请停下来思考10秒钟,想想这个景象是否失常。
- 如果你感觉这个景象不失常,阐明你对JVM内存剖析有肯定的了解,但还是没有齐全了解。
- 如果你感觉这个景象没问题,绝大部分阐明你对JVM内存调配还不够相熟,极少局部状况阐明你对它曾经十分相熟了,对它实现上的优缺点都一目了然了。
那请问你是属于哪种呢?
其实简化下来的问题就是:
非GC过程中,新创建的对象可能在from space里调配吗?
JVM内存调配
JVM内存调配说简略也简略,说简单也简单,不过我这里不打算说很细,因为要撕开讲,根本能够讲几个小时,我这里只挑大家熟知的来聊。临时把大家归结为下面的第一种状况。
大家晓得Java Heap次要由新生代和老生代组成,而新生代又别离由eden+s0(from space)+s1(to space)形成,通常状况下s0或者s1有一块是空的,次要用来做GC copy。
当咱们创立一个对象的时候,会申请调配一块内存,这块内存次要在新生代里调配,并且是在eden里调配,当然某些非凡状况能够间接到老生代去调配,依照这种规定,失常状况下怎么也轮不到到from space去分配内存,因而在上次GC完之后到下次GC之前不可能去from space分配内存。
事实是怎么呢
那到底是不是这样呢?从下面的GC日志来看显然不是这样,我之前有过一次教训是这种状况和GC Locker无关,过后在群里要美团的同学把前面的日志发全一点验证下,后果比拟意外,不是我之前碰到的状况,因而我要了整个残缺的GC日志,上面简略形容下我对这个问题的思考过程。
我拿到GC日志后,第一件事就是找到对应的GC日志上下文,这种诡异的景象到底是偶然产生的还是始终存在,于是我整个日志搜寻from space 409600K, 99%
,找到第一次状况产生的地位,发现并不是一开始就有这种状况的,而是到某个时候才开始有,并且全副集中在两头某一段时间里,那我立马看了下第一次产生的时候的上下文,发现之前有过一次Full GC和一次CMS GC
再找了最初一次产生的时候前面的状况
发现也有次Full GC,那综合这两种状况,我根本得出了一个大抵的论断,可能和Full GC无关。
源码验证
带着纳闷我开始找相干源码来验证,因为我晓得有从from space调配的状况,于是间接找到了对应的办法
从下面的代码咱们能够做一些剖析,首先从日志上咱们排除了GC Locker的问题,如果是GC Locker,那在JDK8下默认会打印出相干的cause,然而实际上gc产生的起因是因为调配失败所致,于是重点落在了should_allocate_from_space
上
bool should_allocate_from_space() const { return _should_allocate_from_space;}
那接下来就是找什么中央会设置这个属性为true,找到了以下源码
这是gc产生之后的一些解决逻辑,并且是full为true的状况,那意味着必定是Full GC产生之后才有可能设置这个属性set_should_allocate_from_space()
,并且也只有在Full GC之后才可能清理这个属性clear_should_allocate_from_space()
,那根本就和咱们的景象吻合了。
那是不是所有的Full GC产生之后都会这样呢,从下面的代码来看显然不是,只有当!collection_attempt_is_safe() && !_eden_space->is_empty()
为true的时候才会有这种状况,这里我简略说下可能的场景,当咱们因为分配内存不得已产生了一次Full GC的时候,发现GC成果不怎么样,甚至eden里还有对象,老生代也根本是满的,老生代里的内存也不足以包容eden里的对象,此时就会产生下面的状况。
不过随着工夫的推移,有可能接下来有恶化,比方做一次CMS GC或者就能把老生代的一些内存开释掉,那其实整个内存就又复原了失常,然而这带来的一个问题就是发现前面常常会产生从from space里分配内存的状况,也就是咱们这次碰到的问题,直到下次Full GC产生之后才会解封,所以咱们哪怕执行一次jmap -histo:live也足以解封。
GC研发工程师漏掉的逻辑?
那这样其实带来了一个新的问题,那就是会让更多的对象尽快降职到老生代,这会促使老生代GC变得绝对比拟频繁,我感觉这其实应该算是JVM的一个bug,或者更应该说是GC研发工程师不小心漏掉了一块逻辑。
我感觉一个正当的做法是如果前面有CMS GC,那在CMS GC之后,应该被动clear_should_allocate_from_space()
,也就是在CMS GC的sweep阶段执行完之后执行下面的逻辑,这样就会有肯定保障,事实上,咱们从sweep的源码里也看到了局部端倪,最初调用了gch->clear_incremental_collection_failed()
,所以我集体认为是Hotspot GC开发的同学遗记做这件事件了,只须要在gch->clear_incremental_collection_failed()
前面调用新生代的clear_should_allocate_from_space()
即可解决此类问题
结语
至此应该大家晓得问题答案了,实际上是可能在from space里间接调配对象的,然而当初的实现可能存在一些问题会导致老生代GC变得频繁。
一起来学习吧:
PerfMa KO 系列课之 JVM 参数【Memory篇】
一次 Java 过程 OOM 的排查剖析(glibc 篇)