javascript
Spring bean 不被 GC 的真正原因
概述
自從開始接觸 Spring 之后,一直以來都在思考一個問題,在 Spring 應用的運行過程中,為什么這些 bean 不會被回收?
今天深入探究了這個問題之后,才有了答案。
思考點
大家都知道,一個 bean 會不會被回收,取決于對象存活判定算法。在 JVM 底層中使用的是可達性分析算法,拋開 HotSpot 的實現細節不談,那么一個對象被判定為死亡,應該與 GC Root 不存在可達的引用路徑。
所以,Spring 的 bean 肯定是與 GC Root 存在可達的引用路徑,才不會被回收掉在 Java 語言對于 GC Root 的定義中,以下幾種對象可以作為 GC Root:
- 虛擬機棧的棧幀中的本地變量表中,引用類型對象所指向的堆中的對象
- 處于運行中狀態(RUNNABLE,BLOCKED,WAITING,TIMED_WAITING)的線程對象
- JDK 自帶的類加載器對象
- 本地方法所引用的對象
- JVM 持有的對象,例如基本類型的 Class 對象,NullPointerException 等常用異常對象
- 被 synchronized 關鍵字修飾的對象
一般來說,只要是符合上面這幾種規則的對象,或者能由上面的規則推導出存在引用的對象,都可以作為 GC Root。
那么 Spring 的 bean 的 GC Root 是哪一種呢?或者說,找到了 Spring 的 bean 的 GC Root,就找到了問題的答案。
動手尋找答案
首先新建一個 SpringBoot 應用,里面定義了兩個 bean 以及一個啟動類,包結構如下:
然后點擊運行啟動類,啟動完成之后,打開 jvisualVM ,找到對應的應用,然后點擊生成當前堆 dump:
然后打開后選擇類,輸入 Hello 過濾類名,找到 HelloWorldService,點擊在實例視圖中顯示,發現只有一個實例存在,這符合我們的預期。
最后右鍵點擊這個實例,選擇顯示最近的垃圾回收根節點,可以觀察到如下的引用路徑:
可以看到,DefaultListableBeanFactory 和 AnnotationConfigServletWebServerApplicationContext 都是我們比較熟悉的 bean 容器,對應的往下找發現有 ConcurrentHashMap$Node 引用。我們都知道在 Spring 中,正是這兩個容器(準確地說是 DefaultListableBeanFactory)中使用 ConcurrentHashMap 存放了實例化好的 bean。 這都是非常符合我們預期的。
但是在 AbstractApplicationContext 再往上找后,發現有個叫 ApplicationShutdownHooks 的東西。意思就是說,我們的容器,最終與這個 ApplicationShutdownHooks 的東西扯上了引用關系。接著我們翻閱 Spring 源碼進行求證:
發現在 AbstractApplicationContext 的 registerShutdownHook 方法中調用了這一行代碼,而 registerShutdownHook 方法正是在 Spring 容器初始化時要調用的方法:
這說明在 Spring 容器初始化時,調用的這個方法,然后在繼續往里跟蹤這個方法:
最后我們可以發現,AbstractApplicationContext 中的 Thread shutdownHook 變量,最終被放在了 ApplicationShutdownHooks 的這個 map 里面,而這個 map 恰好就是一個靜態變量。
結論
所以,Spring 的 bean 沒有被回收,正是因為在 AbstractApplicatuonContext 的 registerShutdownHook 方法中,與 ApplicationShutdownHooks 中的一個靜態變量建立了可達的引用路徑。
題外話
那么為什么類的靜態變量可以作為 GC Root 呢?抱著嚴謹的心態,我們繼續往下求證:
類的靜態變量屬于類對象,類對象由類加載器進行加載,而類加載器是 GC Root,那么類加載器是不是與被加載的類對象存在引用關系呢?翻閱 ClassLoader 類,赫然看到這一段代碼:
public abstract class ClassLoader {// The classes loaded by this class loader. The only purpose of this table// is to keep the classes from being GC'ed until the loader is GC'ed.private final Vector<Class<?>> classes = new Vector<>(); }注釋一目了然,好家伙!原來類加載器把所有的已加載的類對象都保存在這個容器里面,怪不得類對象和類靜態變量也屬于 GC Root
總結
以上是生活随笔為你收集整理的Spring bean 不被 GC 的真正原因的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 可靠性不是测试出来的,是设计出来的!
- 下一篇: java 中 的 =,java 中的 |