Spring bean不被GC的真正原因及分析
概述
自從開始接觸 Spring
之后,一直以來都在思考一個問題,在 Spring
應(yīng)用的運(yùn)行過程中,為什么這些 bean
不會被回收?
今天深入探究了這個問題之后,才有了答案。
思考點(diǎn)
大家都知道,一個 bean
會不會被回收,取決于對象存活判定算法。
在 JVM
底層中使用的是可達(dá)性分析算法,拋開 HotSpot
的實現(xiàn)細(xì)節(jié)不談,那么一個對象被判定為死亡,應(yīng)該與 GC Root
不存在可達(dá)的引用路徑。
所以,Spring 的 bean 肯定是與 GC Root 存在可達(dá)的引用路徑,才不會被回收掉
在 Java
語言對于 GC Root
的定義中,以下幾種對象可以作為 GC Root
:
- 虛擬機(jī)棧的棧幀中的本地變量表中,引用類型對象所指向的堆中的對象
- 處于運(yùn)行中狀態(tài)(RUNNABLE,BLOCKED,WAITING,TIMED_WAITING)的線程對象
- JDK 自帶的類加載器對象
- 本地方法所引用的對象
- JVM 持有的對象,例如基本類型的 Class 對象,NullPointerException 等常用異常對象
- 被 synchronized 關(guān)鍵字修飾的對象
一般來說,只要是符合上面這幾種規(guī)則的對象,或者能由上面的規(guī)則推導(dǎo)出存在引用的對象,都可以作為 GC Root
。
那么 Spring
的 bean
的 GC Root
是哪一種呢?或者說,找到了 Spring
的 bean
的 GC Root
,就找到了問題的答案。
動手尋找答案
首先新建一個 SpringBoot
應(yīng)用,里面定義了兩個 bean
以及一個啟動類,包結(jié)構(gòu)如下:
然后點(diǎn)擊運(yùn)行啟動類,啟動完成之后,打開 jvisualVM
,找到對應(yīng)的應(yīng)用,然后點(diǎn)擊生成當(dāng)前堆 dump
:
然后打開后選擇類,輸入 Hello
過濾類名,找到 HelloWorldService
,點(diǎn)擊在實例視圖中顯示,發(fā)現(xiàn)只有一個實例存在,這符合我們的預(yù)期。
最后右鍵點(diǎn)擊這個實例,選擇顯示最近的垃圾回收根節(jié)點(diǎn),可以觀察到如下的引用路徑:
可以看到,DefaultListableBeanFactory
和 AnnotationConfigServletWebServerApplicationContext
都是我們比較熟悉的 bean
容器,對應(yīng)的往下找發(fā)現(xiàn)有 ConcurrentHashMap$Node
引用。
我們都知道在 Spring
中,正是這兩個容器(準(zhǔn)確地說是 DefaultListableBeanFactory
)中使用 ConcurrentHashMap
存放了實例化好的 bean
。 這都是非常符合我們預(yù)期的。
但是在 AbstractApplicationContext
再往上找后,發(fā)現(xiàn)有個叫 ApplicationShutdownHooks
的東西。意思就是說,我們的容器,最終與這個 ApplicationShutdownHooks
的東西扯上了引用關(guān)系。接
著我們翻閱 Spring
源碼進(jìn)行求證:
發(fā)現(xiàn)在 AbstractApplicationContext
的 registerShutdownHook
方法中調(diào)用了這一行代碼,而 registerShutdownHook
方法正是在 Spring
容器初始化時要調(diào)用的方法:
這說明在 Spring
容器初始化時,調(diào)用的這個方法,然后在繼續(xù)往里跟蹤這個方法:
最后我們可以發(fā)現(xiàn),AbstractApplicationContext
中的 Thread shutdownHook
變量,最終被放在了 ApplicationShutdownHooks
的這個 map
里面,而這個 map
恰好就是一個靜態(tài)變量。
結(jié)論
所以,Spring
的 bean
沒有被回收,正是因為在 AbstractApplicatuonContext
的 registerShutdownHook
方法中,與 ApplicationShutdownHooks
中的一個靜態(tài)變量建立了可達(dá)的引用路徑。
題外話
那么為什么類的靜態(tài)變量可以作為 GC Root
呢?抱著嚴(yán)謹(jǐn)?shù)男膽B(tài),我們繼續(xù)往下求證:
類的靜態(tài)變量屬于類對象,類對象由類加載器進(jìn)行加載,而類加載器是 GC Root,那么類加載器是不是與被加載的類對象存在引用關(guān)系呢?
翻閱 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<>(); }
注釋一目了然,好家伙!原來類加載器把所有的已加載的類對象都保存在這個容器里面,怪不得類對象和類靜態(tài)變量也屬于 GC Root
最后
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
java語言描述Redis分布式鎖的正確實現(xiàn)方式
這篇文章主要介紹了java語言描述Redis分布式鎖的正確實現(xiàn)方式,具有一定借鑒價值,需要的朋友可以參考下。2017-12-12這一次搞懂Spring代理創(chuàng)建及AOP鏈?zhǔn)秸{(diào)用過程操作
這篇文章主要介紹了這一次搞懂Spring代理創(chuàng)建及AOP鏈?zhǔn)秸{(diào)用過程操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-08-08IDEA?+?Maven環(huán)境下的SSM框架整合及搭建過程
這篇文章主要介紹了IDEA?+?Maven環(huán)境下的SSM框架整合及搭建過程,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-01-01Java中關(guān)于優(yōu)先隊列PriorityQueue的使用及相關(guān)方法
這篇文章主要介紹了Java中關(guān)于優(yōu)先隊列PriorityQueue的使用及相關(guān)方法,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-08-08基于springBoot配置文件properties和yml中數(shù)組的寫法
這篇文章主要介紹了springBoot配置文件properties和yml中數(shù)組的寫法,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11