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 篇)

相關文章
相關標籤/搜索