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

本文来自: PerfMa技术社区

PerfMa(笨马网络)官网java

概述

今天要说的这个问题,是我常常面试问的一个问题,只是和我以前排查过的场景有些区别,属于另一种状况。也许我这里讲了这个以后,会成为很多公司JVM必问之题,因此本文仍是值得你们好好看看的,相信也会让你颇有收获,我把这个问题简单概括为Hotspot GC研发工程师也许漏掉了一块逻辑。面试

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

image.png

OK,看到这里,请停下来思考10秒钟,想一想这个现象是否正常。学习

  • 若是你以为这个现象不正常,说明你对JVM内存分析有必定的理解,但仍是没有彻底理解。
  • 若是你以为这个现象没问题,绝大部分说明你对JVM内存分配还不够熟悉,极少部分状况说明你对它已经很是熟悉了,对它实现上的优缺点都了如指掌了。

那请问你是属于哪一种呢?spa

其实简化下来的问题就是:日志

非GC过程当中,新建立的对象可能在from space里分配吗?code

JVM内存分配

JVM内存分配说简单也简单,说复杂也复杂,不过我这里不打算说很细,由于要扯开讲,基本能够讲几个小时,我这里只挑你们熟知的来聊。暂时把你们归结为上面的第一种状况。对象

你们知道Java Heap主要由新生代和老生代组成,而新生代又分别由eden+s0(from space)+s1(to space)构成,一般状况下s0或者s1有一块是空的,主要用来作GC copy。blog

当咱们建立一个对象的时候,会申请分配一块内存,这块内存主要在新生代里分配,而且是在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变得频繁。

一块儿来学习吧

PerfMa KO 系列课之 JVM 参数【Memory篇】

一次 Java 进程 OOM 的排查分析(glibc 篇)

相关文章
相关标签/搜索