Spring 只用一招,就擺脫被垃圾回收的命運,拯救了自己

SpringBoot ApplicationContext 會被 JVM 當成垃圾對象,然后回收掉嗎?

最近五陽哥在復習JVM 垃圾回收的知識,被別人問到這個問題,我心里感到一驚,如果Spring 被回收掉,Spring管理的bean全部會被回收,那我們的Java應(yīng)用不就被一鍋端了嗎? 這太可怕了……

雖然我相信 Spring一定會處理好這個問題,確保自身不被垃圾回收,但是巨大的好奇心,驅(qū)使我一探究竟。

我們在歲月靜好時,Spring幫我們做了哪些事情?

回顧一下,垃圾回收的基礎(chǔ)知識

精通Java GC的可以跳過這一段。

五陽哥,前兩天寫了一篇文章,分析為什么gc一定需要Stop the world? # 點擊查看,這篇文章? ,里面已經(jīng)總結(jié)道,“當一個對象無法被 GC Root引用到,那么這個對象將在接下來的垃圾回收過程中被回收”。

在Java堆中,存放著所有Java的對象實例。在進行垃圾收集之前,JVM需要確定哪些對象已經(jīng)不被使用(即垃圾),哪些對象仍然被使用。為了判斷對象是否是“垃圾”,JVM采用了可達性分析算法。

可達性分析算法 是指通過指定 GC Root 根對象,從根對象開始搜索引用的對象,通過引用鏈條,層層遍歷鏈條上的對象,可以到達的對象不可被垃圾回收。而最終沒有被搜索遍歷到的對象,則為 不可達對象,應(yīng)該被垃圾回收。

JVM中的 GC Root根對象包括如下:

  1. 虛擬機棧引用的對象
  2. 本地方法棧內(nèi)JNI(本地方法)引用的對象
  3. 方法區(qū)中類靜態(tài)屬性引用的對象
  4. 方法區(qū)中常量引用的對象
  5. Java虛擬機內(nèi)部的引用

如果 Spring 想不被垃圾回收,那么Spring一定要確保自己被以上 GC Root引用,以上五個,任意一個即可。

接下來,我們將分析Spring 源碼!找到Spring不被垃圾回收的奧秘!

啟動Spring Boot應(yīng)用

以下代碼啟動了一個Spring Boot應(yīng)用,這是官方推薦的啟動方式,通過注解的方式,把啟動類傳遞給 SpringApplication ,調(diào)用run 方法,啟動Spring Boot。

需要說明的是,run方法在Spring boot啟動成功后,會立即返回,不會被阻塞。所以 main 線程在啟動Spring boot后,將退出……

由于Spring Boot會啟動Jetty/Tomcat等其他線程池,所以Java應(yīng)用并不會退出。因為Java進程退出條件之一是:所有非守護線程全部退出,則JVM退出 點擊查看 JVM 如何退出的詳細信息 ,所以只有 main 線程退出,其他業(yè)務(wù)線程還存在情況下,Java不會退出。

由此可見,main 線程在啟動 Spring Boot后,并不會一直持有 Spring boot 對象引用,官方文檔里也沒有 強調(diào),一定要保持 main 線程 不退出。Spring Boot需要把自己交到其他對象手中,確保自己不被回收!

那么如何保證 Spring Boot不被垃圾回收呢?我需要從 SpringApplcaition 內(nèi)部找原因!

Spring Boot 和 Tomcat

從上面的代碼可以看到 Spring Boot控制了Java應(yīng)用的入口,而Web容器 Tomcat等被Spring 管理,如果Spring不會被垃圾回收,那么Tomcat就不用擔心被垃圾回收。

而在Spring Boot之前的Web應(yīng)用,都是將Java項目打包到Tomcat容器中執(zhí)行。那時候 Spring MVC和Spring 是要被Tomcat容器管理的,所以那時的Spring項目不用擔心 被垃圾回收的問題。

而現(xiàn)在 Tomcat和 Spring boot的角色互換,決定了 Spring Boot應(yīng)用必須要處理好垃圾回收問題!

探究Spring Boot代碼

創(chuàng)建和啟動上下文

下圖是 SpringApplication.run方法的源代碼,run 方法主要執(zhí)行兩步,創(chuàng)建 Spring 上下文和啟動上下文。

刷新上下文的奧秘

在Spring Boot刷新上下文的代碼中,首先調(diào)用 Spring Application.refresh方法啟動Spring 上下文。然后 Spring boot就把 Close 方法注冊到 Java shutdownHook 關(guān)閉鉤子程序中!

基本上可以破案了!

因為Spring Boot控制了Java程序的入口,所以要負責整個項目的關(guān)閉流程,于是它 注冊了Java關(guān)閉鉤子。

接下來,我們看一下注冊關(guān)閉鉤子,會被 GC Root引用到嗎?

關(guān)閉鉤子

add 方法,將鉤子程序注冊到 一個容器中!

可以看到 Thread 類型的 鉤子程序,被保存在 hooks Map 中。

而hooks列表的類型定義是 static 類型的。 static 變量都是GC Root。

總結(jié)

Spring Boot 在啟動時會將關(guān)閉流程注冊到 Java 關(guān)閉鉤子中,并通過關(guān)閉鉤子線程引用到 Spring 上下文。

關(guān)閉鉤子會被保存在一個 static 靜態(tài)類型的 Map 中,這個 Map 在 GC Root 上。

因此,Spring Boot 不被垃圾回收的關(guān)鍵是在啟動時注冊了關(guān)閉鉤子。

破案了,spring永遠不會被垃圾回收。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容