<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 6601字,需瀏覽 14分鐘

           ·

          2021-03-14 10:39


          上一篇:深夜看了張一鳴的微博,讓我越想越后怕


          來源:www.polarxiong.com/

          前言

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

          鑒于網(wǎng)上有太多關(guān)于此問題的誤導,本文將通過實例,深入JVM剖析“對象不再使用時賦值為null”這一操作存在的意義,供君參考。

          本文盡量不使用專業(yè)術(shù)語,但仍需要你對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中實例化了一個數(shù)組placeHolder,然后在if的作用域外通過System.gc();手動觸發(fā)了GC,其用意是回收placeHolder,因為placeHolder已經(jīng)無法訪問到了。

          來看看輸出:

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

          Full GC 65952K->65881K(125952K)代表的意思是:本次GC后,內(nèi)存占用從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后內(nèi)存占用下降到了345K,即placeHolder被成功回收了!對比兩段代碼,僅僅將placeHolder賦值為null就解決了GC的問題,真應該感謝“不使用的對象應手動賦值為null“。

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

          運行時棧

          典型的運行時棧

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

          比如對于下面這段代碼:

          public static void main(String[] args) {
              int a = 1;
              int b = 2;
              int c = a + b;
          }
          其運行時棧的狀態(tài)可以理解成:
          索引變量
          1a
          2b
          3c

          “索引”表示變量在棧中的序號,根據(jù)方法內(nèi)代碼執(zhí)行的先后順序,變量被按順序放在棧中。

          再比如:

          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

          容易理解吧?

          其實仔細想想上面這個例子的運行時棧是有優(yōu)化空間的。

          另外,關(guān)注公眾號互聯(lián)網(wǎng)架構(gòu)師,在后臺回復:2T,可以獲取我整理的 JVM 系列面試題和答案,非常齊全。

          Java的棧優(yōu)化

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

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

          變量d重用了變量a的棧索引,這樣就節(jié)約了內(nèi)存空間。

          提醒

          上面的“運行時?!焙汀八饕笔菫榉奖阋攵室獍l(fā)明的詞,實際上在JVM中,它們的名字分別叫做“局部變量表”和“Slot”。而且局部變量表在編譯時即已確定,不需要等到“運行時”。

          GC一瞥

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

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

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

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

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

          另外,關(guān)注公眾號互聯(lián)網(wǎng)架構(gòu)師,在后臺回復:JVM46,可以獲取一份 46 頁的高清 JVM 調(diào)優(yōu)教程,非常齊全。

          提醒

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

          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   Signature
              0      21     0  args   [Ljava/lang/String;
              5      12     1 placeHolder   [B

          棧中第一個索引是方法傳入?yún)?shù)args,其類型為String[];第二個索引是placeHolder,其類型為byte[]。

          聯(lián)系前面的內(nèi)容,我們推斷placeHolder沒有被回收的原因:System.gc();觸發(fā)GC時,main()方法的運行時棧中,還存在有對args和placeHolder的引用,GC判斷這兩個對象都是存活的,不進行回收。

          也就是說,代碼在離開if后,雖然已經(jīng)離開了placeHolder的作用域,但在此之后,沒有任何對運行時棧的讀寫,placeHolder所在的索引還沒有被其他變量重用,所以GC判斷其為存活。

          為了驗證這一推斷,我們在System.gc();之前再聲明一個變量,按照之前提到的“Java的棧優(yōu)化”,這個變量會重用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   Signature
              0      23     0  args   [Ljava/lang/String;
              5      12     1 placeHolder   [B
             19       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和棧的聯(lián)系,讓GC判斷placeHolder已經(jīng)死亡。

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

          總結(jié)

          希望看到這里你已經(jīng)明白了“不使用的對象應手動賦值為null“這句話背后的奧義。

          我比較贊同《深入理解Java虛擬機》作者的觀點:在需要“不使用的對象應手動賦值為null“時大膽去用,但不應當對其有過多依賴,更不能當作是一個普遍規(guī)則來推廣。

          最后,關(guān)注公眾號互聯(lián)網(wǎng)架構(gòu)師,在后臺回復:JVM46,可以獲取一份 46 頁的高清 JVM 調(diào)優(yōu)教程,非常齊全。

          參考:周志明. 深入理解Java虛擬機:JVM高級特性與最佳實踐[M]. 機械工業(yè)出版社, 2013.


          最后,關(guān)注公眾號互聯(lián)網(wǎng)架構(gòu)師,在后臺回復:2T,可以獲取我整理的 Java 系列面試題和答案,非常齊全


          正文結(jié)束


          推薦閱讀 ↓↓↓

          1.不認命,從10年流水線工人,到谷歌上班的程序媛,一位湖南妹子的勵志故事

          2.如何才能成為優(yōu)秀的架構(gòu)師?

          3.從零開始搭建創(chuàng)業(yè)公司后臺技術(shù)棧

          4.程序員一般可以從什么平臺接私活?

          5.37歲程序員被裁,120天沒找到工作,無奈去小公司,結(jié)果懵了...

          6.IntelliJ IDEA 2019.3 首個最新訪問版本發(fā)布,新特性搶先看

          7.漫畫:程序員相親圖鑒,笑屎我了~

          8.15張圖看懂瞎忙和高效的區(qū)別!

          一個人學習、工作很迷茫?


          點擊「閱讀原文」加入我們的小圈子!

          瀏覽 39
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  一级无码毛片 | 久在操| 中文一级久久黄色 | 国产人妻人伦精品一区 | 黄色级片视频视频 |