关于java:Hotspot-GC研发工程师也许漏掉了一块逻辑

8次阅读

共计 2515 个字符,预计需要花费 7 分钟才能阅读完成。

本文来自: 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 篇)

正文完
 0