成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Java中當對象不再使用時,不賦值為null會導致什么后果 ?

開發 后端
本文將通過實例,深入JVM剖析“對象不再使用時賦值為null”這一操作存在的意義,供君參考。本文盡量不使用專業術語,但仍需要你對JVM有一些概念。

前言

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

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

示例代碼

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

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

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

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

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

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

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

其輸出為: 

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

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

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

運行時棧

典型的運行時棧

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

比如對于下面這段代碼: 

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

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

索引 變量
1 a
2 b
3 c

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

再比如: 

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

這時運行時棧就是:

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

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

Java的棧優化

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

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

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

提醒

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

GC一瞥

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

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

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

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

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

提醒

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

JVM的“bug”

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

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

看看其運行時棧: 

  1. LocalVariableTable:  
  2. Start  Length  Slot  Name   Signature  
  3.     0      21     0  args   [Ljava/lang/String;  
  4.     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的索引。 

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

看看其運行時棧: 

  1. LocalVariableTable:  
  2. Start  Length  Slot  Name   Signature  
  3.     0      23     0  args   [Ljava/lang/String;  
  4.     5      12     1 placeHolder   [B  
  5.    19       4     1 replacer   I 

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

  1. 65536  
  2. [GC 68239K->65984K(125952K), 0.0011620 secs]  
  3. [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“時大膽去用,但不應當對其有過多依賴,更不能當作是一個普遍規則來推廣。 

 

責任編輯:龐桂玉 來源: Java知音
相關推薦

2024-04-25 08:21:36

Java對象計數法

2020-12-31 08:05:27

MySQL服務器版本號

2019-08-12 09:16:43

Windows微軟科技公司

2010-06-02 10:53:28

MySQL版本

2024-11-20 08:00:00

死鎖多線程編程

2020-05-29 09:34:28

httphttps網絡協議

2024-07-18 20:18:51

2025-05-06 07:24:24

2023-04-04 19:14:40

Linux發行版Alpine

2023-05-10 16:15:58

javaScript算法開發

2017-10-19 12:45:07

PHP

2017-01-05 18:43:58

閏秒Linux服務器

2018-11-12 13:27:12

教育區塊鏈學習

2009-06-17 13:26:06

scala繼承模型

2022-09-15 09:54:34

nullPython字符

2013-07-29 09:29:13

PaaS云IaaS云管理

2022-09-14 19:50:22

事務場景流程

2024-02-29 15:46:48

2022-06-27 07:23:44

MySQL常量優化

2024-05-27 08:04:41

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 欧美黄色网络 | 亚洲成人精品久久久 | 黄色片视频 | 在线观看中文字幕 | 欧美日韩在线免费观看 | 久久综合狠狠综合久久综合88 | 99精品欧美一区二区三区 | 免费视频一区二区 | 亚洲精品一区二三区不卡 | 久久精品二区 | 麻豆a级片 | 奇米av| 99pao成人国产永久免费视频 | 国产东北一级毛片 | 国产精品中文字幕一区二区三区 | 欧美一区二区在线观看 | 每日更新av | 国产三区视频在线观看 | 男人的天堂avav | 国产一区二区a | 欧美精品一区二区三区在线播放 | 国产精品69久久久久水密桃 | 天天做日日做 | 99热都是精品 | 久草久草久草 | 精品无码久久久久久国产 | 久久不射网 | 精品欧美一区二区三区久久久 | 一级黄色av电影 | 在线观看国产网站 | 欧美性猛片aaaaaaa做受 | 91精品国产乱码久久久久久久久 | 亚洲欧美视频一区 | 久久国产精品免费 | 日本中文字幕日韩精品免费 | 亚洲视频中文 | 国产精品一区二区三区在线 | 人人干人人干人人干 | 亚洲高清免费 | 福利视频网站 | 日韩精品免费在线 |