面試題:Java 對象不使用時,為什么要賦值 null ?

文章目錄

  • 前言
  • 示例代碼
  • 運行時棧
    • 典型的運行時棧
    • Java的棧優化
    • 提醒
  • GC一瞥
    • 提醒
  • JVM的“BUG”
  • 總結


前言

最近,許多Java開發者都在討論說,“不使用的對象應手動賦值為null“ 這句話,而且好多開發者一直信奉著這句話;問其原因,大都是回答“有利于GC更早回收內存,減少內存占用”,但再往深入問就回答不出來了。

鑒于網上有太多關于此問題的誤導,本文將通過實例,深入JVM剖析“對象不再使用時賦值為null”這一操作存在的意義,供君參考。本文盡量不使用專業術語,但仍需要你對JVM有一些概念。


示例代碼

我們來看看一段非常簡單的代碼:

public static void main(String[] args) {if (true) {byte[] placeHolder = new byte[64 * 1024 * 1024];System.out.println(placeHolder.length / 1024);}System.gc();
}

我們在if中實例化了一個數組placeHolder,然后在if的作用域外通過System.gc();手動觸發了GC,其用意是回收placeHolder,因為placeHolder已經無法訪問到了。來看看輸出:

65536
[GC 68239K->65952K(125952K), 0.0014820 secs]
[Full GC 65952K->65881K(125952K), 0.0093860 secs]

Full GC 65952K->65881K(125952K)代表的意思是:本次GC后,內存占用從65952K降到了65881K。意思其實是說GC沒有將placeHolder回收掉,是不是不可思議?

下面來看看遵循“不使用的對象應手動賦值為null“的情況:

public static void main(String[] args) {if (true) {byte[] placeHolder = new byte[64 * 1024 * 1024];System.out.println(placeHolder.length / 1024);placeHolder = null;}System.gc();
}

其輸出為:

65536
[GC 68239K->65952K(125952K), 0.0014910 secs]
[Full GC 65952K->345K(125952K), 0.0099610 secs]

這次GC后內存占用下降到了345K,即placeHolder被成功回收了!對比兩段代碼,僅僅將placeHolder賦值為null就解決了GC的問題,真應該感謝“不使用的對象應手動賦值為null“。

等等,為什么例子里placeHolder不賦值為null,GC就“發現不了”placeHolder該回收呢?這才是問題的關鍵所在。

運行時棧

典型的運行時棧

如果你了解過編譯原理,或者程序執行的底層機制,你會知道方法在執行的時候,方法里的變量(局部變量)都是分配在棧上的;當然,對于Java來說,new出來的對象是在堆中,但棧中也會有這個對象的指針,和int一樣。

比如對于下面這段代碼:

public static void main(String[] args) {int a = 1;int b = 2;int c = a + b;}

其運行時棧的狀態可以理解成:

索引變量
1a
2b
3c

“索引”表示變量在棧中的序號,根據方法內代碼執行的先后順序,變量被按順序放在棧中。

再比如:

public static void main(String[] args) {if (true) {int a = 1;int b = 2;int c = a + b;}int d = 4;}

這時運行時棧就是:

索引變量
1a
2b
3c
4d

容易理解吧?其實仔細想想上面這個例子的運行時棧是有優化空間的。

Java的棧優化

上面的例子,main()方法運行時占用了4個棧索引空間,但實際上不需要占用這么多。當if執行完后,變量a、b和c都不可能再訪問到了,所以它們占用的1~3的棧索引是可以“回收”掉的,比如像這樣:

索引變量
1a
2b
3c
1d

變量d重用了變量a的棧索引,這樣就節約了內存空間。

提醒

上面的“運行時棧”和“索引”是為方便引入而故意發明的詞,實際上在JVM中,它們的名字分別叫做“局部變量表”和“Slot”。而且局部變量表在編譯時即已確定,不需要等到“運行時”。還請注意

GC一瞥

這里來簡單講講主流GC里非常簡單的一小塊:如何確定對象可以被回收。另一種表達是,如何確定對象是存活的。

仔細想想,Java的世界中,對象與對象之間是存在關聯的,我們可以從一個對象訪問到另一個對象。如圖所示。

在這里插入圖片描述

再仔細想想,這些對象與對象之間構成的引用關系,就像是一張大大的圖;更清楚一點,是眾多的樹。

如果我們找到了所有的樹根,那么從樹根走下去就能找到所有存活的對象,那么那些沒有找到的對象,就是已經死亡的了!這樣GC就可以把那些對象回收掉了。

現在的問題是,怎么找到樹根呢?JVM早有規定,其中一個就是:棧中引用的對象。也就是說,只要堆中的這個對象,在棧中還存在引用,就會被認定是存活的。

提醒

上面介紹的確定對象可以被回收的算法,其名字是“可達性分析算法”。

JVM的“BUG”

我們再來回頭看看最開始的例子:

public static void main(String[] args) {if (true) {byte[] placeHolder = new byte[64 * 1024 * 1024];System.out.println(placeHolder.length / 1024);}System.gc();
}

看看其運行時棧:

LocalVariableTable:
Start  Length  Slot  Name   Signature0      21     0  args   [Ljava/lang/String;5      12     1 placeHolder   [B

棧中第一個索引是方法傳入參數args,其類型為String[];第二個索引是placeHolder,其類型為byte[]。

聯系前面的內容,我們推斷placeHolder沒有被回收的原因:System.gc();觸發GC時,main()方法的運行時棧中,還存在有對args和placeHolder的引用,GC判斷這兩個對象都是存活的,不進行回收。 也就是說,代碼在離開if后,雖然已經離開了placeHolder的作用域,但在此之后,沒有任何對運行時棧的讀寫,placeHolder所在的索引還沒有被其他變量重用,所以GC判斷其為存活。

為了驗證這一推斷,我們在System.gc();之前再聲明一個變量,按照之前提到的“Java的棧優化”,這個變量會重用placeHolder的索引。

public static void main(String[] args) {if (true) {byte[] placeHolder = new byte[64 * 1024 * 1024];System.out.println(placeHolder.length / 1024);}int replacer = 1;System.gc();
}

看看其運行時棧:

LocalVariableTable:
Start  Length  Slot  Name   Signature0      23     0  args   [Ljava/lang/String;5      12     1 placeHolder   [B19       4     1 replacer   I

不出所料,replacer重用了placeHolder的索引。來看看GC情況:

65536
[GC 68239K->65984K(125952K), 0.0011620 secs]
[Full GC 65984K->345K(125952K), 0.0095220 secs]

placeHolder被成功回收了!我們的推斷也被驗證了。

再從運行時棧來看,加上int replacer = 1;和將placeHolder賦值為null起到了同樣的作用:斷開堆中placeHolder和棧的聯系,讓GC判斷placeHolder已經死亡。

現在算是理清了“不使用的對象應手動賦值為null“的原理了,一切根源都是來自于JVM的一個“bug”:代碼離開變量作用域時,并不會自動切斷其與堆的聯系。為什么這個“bug”一直存在?你不覺得出現這種情況的概率太小了么?算是一個tradeoff了。


總結

希望看到這里你已經明白了“不使用的對象應手動賦值為null“這句話背后的奧義。我比較贊同《深入理解Java虛擬機》作者的觀點:在需要“不使用的對象應手動賦值為null“時大膽去用,但不應當對其有過多依賴,更不能當作是一個普遍規則來推廣。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/162677.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/162677.shtml
英文地址,請注明出處:http://en.pswp.cn/news/162677.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【Flask使用】全知識md文檔,4大部分60頁第3篇:Flask模板使用和案例

本文的主要內容:flask視圖&路由、虛擬環境安裝、路由各種定義、狀態保持、cookie、session、模板基本使用、過濾器&自定義過濾器、模板代碼復用:宏、繼承/包含、模板中特有變量和函數、Flask-WTF 表單、CSRF、數據庫操作、ORM、Flask-SQLAlchemy…

nvm切換版本之后npm用不了

原因是 nvm只給你安了對應的node沒給你安裝對應的node版本的npm 解決辦法如下 1找到你安裝的node版本號 然后去官網下載對應的版本包 這個網址就是node官網的版本列表 Index of /download/release/ 2下載后解壓 把根目錄這倆復制到自己的nvm安裝目錄下 還有那個node_modul…

Java【XML 配置文件解析】

前言 最近考試周忙得要死,但我卻不緊不慢,還有三天復習時間,考試科目幾乎都還沒學呢。今天更新一個算是工具類-XML文件的解析,感覺還是挺有用的,之后可以融進自己的項目里。 XML 配置文件解析 0、導入依賴 有點像我…

海康攝像頭ip地址設置方法

海康攝像頭是當前市場上非常受歡迎的一種監控設備,其可以在各種場合下發揮出極佳的作用。不過,對于初次使用該設備的人來說,設置其ip地址往往比較困難。虎觀代理小二二將會詳細介紹海康攝像頭ip地址設置的具體步驟,幫助大家輕松解…

PS右邊的圖層窗口沒有顯示出來?

問題描述:PS右邊的圖層窗口沒有顯示出來? 解決步驟: 鍵盤F7快捷鍵即可調出來。

企業軟件定制開發的優勢|app小程序網站搭建

企業軟件定制開發的優勢|app小程序網站搭建 企業軟件定制開發是一種根據企業特定需求開發定制化軟件的服務。相比于購買現成的軟件產品,企業軟件定制開發具有許多優勢。 1.企業軟件定制開發可以滿足企業獨特需求。每個企業都有自己獨特的業務流程和需求,…

在 Redis 中使用 JSON 文檔:命令行界面(CLI)和 Navicat 集成

Redis,因其極高的性能而聞名,是一款多功能的 NoSQL 數據庫,擅長處理鍵值對。雖然 Redis主要用于處理簡單數據結構,但是同樣支持更多復雜的數據類型,如列表、集合甚至是 JSON 文件。在本文,我們將深入到 Red…

SAP LU04記賬更改通知單創建轉儲單報錯:L3094 記帳修改沒有份存在

解決辦法: 使用事務碼LU02,修改過賬更改狀態,將過賬更改狀態改為U,強制關閉 1. LU04 查找記賬更改通知單號 2. 事務碼LU02修改狀態 這個時候再用LU04去查看的時候,就不會再顯示了

技術短視頻賬號矩陣seo系統--源頭開發---saas工具

專注短視頻賬號矩陣系統源頭開發---saas營銷化工具,目前我們作為一家純技術開發團隊目前已經專注打磨開發這套系統企業版/線下版兩個版本的saas營銷拓客工具已經3年了,本套系統邏輯主要是從ai智能批量剪輯、賬號矩陣全托管發布、私信觸單收錄、文案ai智能…

網絡安全等級保護2.0國家標準

等級保護2.0標準體系主要標準如下:1.網絡安全等級保護條例2.計算機信息系統安全保護等級劃分準則3.網絡安全等級保護實施指南4.網絡安全等級保護定級指南5.網絡安全等級保護基本要求6.網絡安全等級保護設計技術要求7.網絡安全等級保護測評要求8.網絡安全等級保護測評…

【AGC】云存儲服務端使用方法

【集成準備】 1、Python環境配置 下載Python和PyCharm并安裝。 ? 使用安裝的python本身作為解釋器。 ? 安裝AGC Python SDK。 ?云存儲包安裝完成。 ? 2、AGC環境配置 在AGC創建項目和應用 ? 開通云存儲服務。 返回項目設置界面,選擇Server SDK 頁簽…

雙系統Ubuntu-22.04.3安裝編譯kaldi

Ubuntu物理內存要求85-100G以上,運行內存5-6G以上(如果第一次安裝的Ubuntu物理內存不夠,請勿進行擴容,擴容易出現黑屏、藍屏、死機的情況,應該卸載Ubuntu重新安裝,在安裝過程中進行內存分配;運行…

4.22每日一題(累次積分的計算:交換次序)

注:因為 是積不出的函數,所以先不用算,最后發現,出現dx與dy可以相互抵消,即可算出答案

為企業解決設備全生命周期需求,凌雄科技凸顯DaaS增長價值

企業成長離不開投資,但毫無疑問的是,投資最有價值的部分在業務。相比之下,諸如辦公設備之類的固定資產投資,很容易變成企業現金流的吞噬者。從購買、運維到保養、折舊、回收,現代企業在越來越大的辦公設備規模面前&…

工具 | docker刪除不使用的容器

工具 | docker刪除不使用的容器 Docker 清理命令

數據庫|TiDB v7.1.0 資源管控功能是如何降低運維難度和成本

目錄 一、前言 二、資源管控流程圖 三、資源管控 (Resource Control)測試 1)測試集群環境 2)Request Unit (RU) 概念 3)資源管控參數 4)評估實際負載所需容量 4.1 根據實際負載估算容量 方法一 or: 方法二 4.2 基于硬件…

鴻蒙原生應用/元服務開發-AGC分發如何配置簽名信息

使用制作的私鑰(.p12)文件、在AGC申請的證書文件和Profile(.p7b)文件,在DevEco Studio配置工程的簽名信息,以構建攜帶發布簽名信息的APP。 1.打開DevEco Studio,菜單選擇“File > Project S…

va-Q-tec實現溫度敏感產品運輸過程質量控制溫控無憂

摘要:溫度敏感產品運輸對供應鏈全流程的溫度質量要求較高,往往需要借助特殊的溫濕度監測技術產品。va-Q-tec與虹科Comet合作,采用虹科Comet的U系列溫度記錄儀,為集裝箱運輸過程提供完整的溫控包裝解決方案。 一、客戶背景 va-Q-…

鈷電解液中凈化除鎳除銅樹脂

#鈷電解液中凈化除鎳除銅樹脂 鈷是生產各種合金及電池不可或缺的原材料,鈷資源供給主要來自于大型銅礦和鎳礦的伴生開采,鈷的主要應用領域為動力電池、3C消費電池、各種耐熱合金、硬質合金、防腐合金、磁性合金及各種鈷鹽等。其中,電池領域是…

【洛谷算法題】P5714-肥胖問題【入門2分支結構】

👨?💻博客主頁:花無缺 歡迎 點贊👍 收藏? 留言📝 加關注?! 本文由 花無缺 原創 收錄于專欄 【洛谷算法題】 文章目錄 【洛谷算法題】P5714-肥胖問題【入門2分支結構】🌏題目描述🌏輸入格式&a…