java面試一日一題:如何判斷一個物件是否為垃圾物件

迷茫中守候發表於2021-05-12

問題:請講下在java中如何判斷一個物件是否為垃圾

分析:該問題主要考察對java中的垃圾回收,用什麼方式去識別一個物件是垃圾;

回答要點:

主要從以下幾點去考慮,

1、GC回收的是什麼,回收發生在記憶體的那部分?

2、怎麼判斷一個物件是否可以被回收?

3、垃圾回收的演算法有哪些?

 

都說C/C++語言難學,難的點其實不是語言本身,而是在記憶體管理方面,因為在C/C++中需要開發者自己管理記憶體,包括申請記憶體和釋放記憶體,不恰當的釋放記憶體經常導致程式崩潰,而在java中開發者卻不需要關心何時釋放記憶體。很多人認為java沒有記憶體管理的概念,其實不是這樣的,只不過java虛擬機器幫我們做了,那就是垃圾回收,簡稱GC。

所謂GC就是要回收java程式允許過程中產生的垃圾,也可以理解為不再使用的記憶體,一個程式的記憶體是有限的,隨著程式的執行,肯定存在申請記憶體的情況,如果使用完記憶體遲遲得不到釋放,那麼程式最終會因為沒有記憶體而停止,所以記憶體的釋放很重要。在物件導向程式中記憶體的釋放意味著物件的銷燬,只有物件銷燬了記憶體才有可能得以釋放,記憶體才至於枯竭。

上面明白了為什麼要有GC,GC的目的就是為了釋放記憶體,使程式可以持續執行。在java中程式執行時的記憶體區域可分為堆、虛擬機器棧、本地方法棧、程式計數器、方法區。GC回收的區域是堆和方法區,為什麼回收這兩個區域那,因為他們是執行緒共享的,即java程式中所有的執行緒都可以訪問,在這兩部分中回收的重點在堆,方法區一般回收起來很困難,下面的介紹均是指堆方法區的垃圾回收。為什麼虛擬機器棧、本地方法棧、程式計數器沒有GC,因為他們是執行緒私有的,隨著執行緒的消亡而消亡。

從網上找了一張執行時記憶體區域的圖,

該圖很形象的說明了java執行時資料區的每個部分,當然是邏輯劃分而不是物理劃分。

現在,弄清楚了GC要回收的記憶體區域是堆,堆中存放的是物件,在java的世界中萬事萬物都是物件,歸根結底要回收的是堆中的物件。那如何確定什麼物件是可回收的什麼物件是不可回收的。java提供了兩種演算法,引用計數法和可達性分析法。java中使用的是可達性分析法

引用計數法

所謂引用計數法,每個物件額外儲存一個計數屬性,如果有一個物件引用了它,那麼該屬性會加1,例,

A a=new A();
A a2=a;

上面這段程式碼會在堆中生成一個A的物件例項,且a、a2都指向了該物件,那麼該物件的計數屬性便是2,又如,

A a=new A();
A a2=a;
a=null;
a2=null;

這時a、a2均指向了null,那麼A的物件例項的計數屬性則為0,按照引用計數法的定義這時該例項可以被回收。

看上去該演算法很完美,但是java中為什麼沒用,有個問題如果出現迴圈引用怎麼辦,

A a=new A();
B b=new B();

a.b=b;
b.a=a;

a=null;
b=null;

上面的程式碼在堆中會有一個A的例項一個B的例項,且計數屬性均為1,執行了第3、4兩行程式碼後,兩個例項的引用計數均為2,執行了5、6兩行程式碼後兩個例項的計數屬性均為1,這時a、b均指向了null,但是堆中的兩個例項的計數屬性的值卻不為0,那麼這兩個例項無法回收,存在記憶體洩漏的風險;

可達性分析法

所謂可達性分析法,就是從一些稱為引用鏈(GC ROOTS)的物件作為起點,從這些節點向下搜尋,搜尋走過的路徑稱為引用鏈(reference chain),當一個物件到GC ROOTS沒有引用鏈的時則該物件不可達,該物件可以被回收。哪些物件是引用鏈那,虛擬機器棧是java程式中方法執行的區域,每個方法的執行對應著一個棧幀的入棧和出棧,方法執行完了其申請的記憶體便可以釋放,所以棧幀中的物件可作為引用鏈物件,同時本地方法棧的情況也是類似的;在方法區中存在常量和類靜態變數,這兩種變數也可以作為引用鏈物件,總結下來有下面幾種,

1、虛擬機器棧中的區域性變數表中的物件;

2、方法區中常量引用的物件;

3、方法區中類的靜態變數引用的物件;

4、本地方法棧中JNI引用的物件;

使用可達性分析方法判斷為可回收的物件,還有一次逃過回收的機會,那就是在Object類中有finalize()方法,如果在該方法中沒有與上述的引用鏈建立連結,那麼該物件則確定要被回收。

 

知道了要回收的記憶體區域,以及如何判定哪些物件可以被回收,確定了回收物件,接下來就是如何回收,且聽下次分解。

 

 

參考:https://www.cnblogs.com/czwbig/p/11127124.html

相關文章