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

問題:請講下在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()方法,如果在該方法中沒有與上述的引用鏈建立鏈接,那麼該對象則確定要被回收。

 

知道了要回收的內存區域,以及如何判定哪些對象可以被回收,確定了回收對象,接下來就是如何回收,且聽下次分解。

 

 

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

Tags: