性能文章>Hotspot GC研发工程师也许漏掉了一块逻辑>

Hotspot GC研发工程师也许漏掉了一块逻辑原创

2年前
7547121

概述

今天要说的这个问题,是我经常面试问的一个问题,只是和我之前排查过的场景有些区别,属于另外一种情况。也许我这里讲了这个之后,会成为不少公司JVM必问之题,所以本文还是值得大家好好看看的,相信也会让你很有收获,我把这个问题简单归纳为Hotspot GC研发工程师也许漏掉了一块逻辑。

如下图所示,在上一次YGC之后,from space的使用率是12%,但是在下一次YGC准备发生的时候,发现from space的使用率变成了99%。

image.png

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

image.png

再找了最后一次发生的时候后面的情况

image.png

发现也有次Full GC,那综合这两种情况,我基本得出了一个大致的结论,可能和Full GC有关。

源码验证

带着疑惑我开始找相关源码来验证,因为我知道有从from space分配的情况,于是直接找到了对应的方法

image.png

从上面的代码我们可以做一些分析,首先从日志上我们排除了GC Locker的问题,如果是GC Locker,那在JDK8下默认会打印出相关的cause,但是实际上gc发生的原因是因为分配失败所致,于是重点落在了should_allocate_from_space

bool should_allocate_from_space() const {
    return _should_allocate_from_space;
}

那接下来就是找什么地方会设置这个属性为true,找到了以下源码

image.png

这是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()即可解决此类问题

image.png

结语

至此应该大家知道问题答案了,实际上是可能在from space里直接分配对象的,但是现在的实现可能存在一些问题会导致老生代GC变得频繁。

点赞收藏
分类:标签:
你假笨

通过创新的技术肯定可以让这个世界变得更美好,让我们为技术而狂吧

请先登录,查看1条精彩评论吧
快去登录吧,你将获得
  • 浏览更多精彩评论
  • 和开发者讨论交流,共同进步

为你推荐

【全网首发】一次想不到的 Bootstrap 类加载器带来的 Native 内存泄露分析

【全网首发】一次想不到的 Bootstrap 类加载器带来的 Native 内存泄露分析

记一次线上RPC超时故障排查及后续GC调优思路

记一次线上RPC超时故障排查及后续GC调优思路

解读JVM级别本地缓存Caffeine青出于蓝的要诀 —— 缘何会更强、如何去上手

解读JVM级别本地缓存Caffeine青出于蓝的要诀 —— 缘何会更强、如何去上手

【全网首发】一次疑似 JVM Native 内存泄露的问题分析

【全网首发】一次疑似 JVM Native 内存泄露的问题分析

解读JVM级别本地缓存Caffeine青出于蓝的要诀2 —— 弄清楚Caffeine的同步、异步回源方式

解读JVM级别本地缓存Caffeine青出于蓝的要诀2 —— 弄清楚Caffeine的同步、异步回源方式

【全网首发】从源码角度分析一次诡异的类被加载问题

【全网首发】从源码角度分析一次诡异的类被加载问题

21
1