015、大廠面試題:什么情況下JVM內(nèi)存中的一個對象會被垃圾回收?

大廠面試題
什么情況下JVM內(nèi)存中的一個對象會被垃圾回收?
目錄:
什么時候會觸發(fā)垃圾回收?
被哪些變量引用的對象是不能回收的?
Java中對象不同的引用類型
finalize()方法的作用
昨日思考題
今日思考題
1、什么時候會觸發(fā)垃圾回收?
通過之前的學(xué)習(xí),相信大家都知道一點,平時我們系統(tǒng)運行創(chuàng)建的對象都是優(yōu)先分配在新生代里的,如下圖所示。

然后如果新生代里的對象越來越多,都快滿了,此時就會觸發(fā)垃圾回收,把新生代沒有人引用的對象給回收掉,釋放內(nèi)存空間
大家務(wù)必注意,這就是新生代一個核心的垃圾回收觸發(fā)時機(jī),如下圖。

那么本文就來針對這個過程,再次梳理其中的一些細(xì)節(jié),看看觸發(fā)垃圾回收的時候,到底是按照一個什么樣的規(guī)則來回收垃圾對象的。
2、被哪些變量引用的對象是不能回收的?
首先第一個問題,一旦新生代快滿了,那么垃圾回收的時候,到底哪些對象是能回收的,哪些對象是不能回收的呢?
這個問題非常好解釋,JVM中使用了一種可達(dá)性分析算法來判定哪些對象是可以被回收的,哪些對象是不可以被回收的。
這個算法的意思,就是說對每個對象,都分析一下有誰在引用他,然后一層一層往上去判斷,看是否有一個GC Roots。
這句話相當(dāng)?shù)某橄螅遣皇牵?/p>
沒關(guān)系,我們的特點就是一步一圖,保證你看明白。
比如最常見的,就是下面的一種情況。

上面的代碼其實就是在一個方法中創(chuàng)建了一個對象,然后有一個局部變量引用了這個對象,這種情況是最常見的
此時如下圖所示。“main()”方法的棧幀入棧,然后調(diào)用“l(fā)oadReplicasFromDisk()”方法,棧幀入棧,接著讓局部變量“replicaManager”引用堆內(nèi)存里的“ReplicaManager”實例對象。

假設(shè)現(xiàn)在上圖中“ReplicaManager”對象被局部變量給引用了,那么此時一旦新生代快滿了,發(fā)生垃圾回收,會去分析這個“ReplicaManager”對象的可達(dá)性
這時,發(fā)現(xiàn)他是不能被回收的,因為他被人引用了,而且是被局部變量“replicaManager”引用的。
在JVM規(guī)范中,局部變量就是可以作為GC Roots的
只要一個對象被局部變量引用了,那么就說明他有一個GC Roots,此時就不能被回收了。
另外比較常見的一個情況,其實就是類似下面的代碼。

大家可以分析一下上面的代碼,如下圖所示。

大家按照上圖思考一下,此時垃圾回收的時候一分析,發(fā)現(xiàn)這個“ReplicaManager”對象被Kafka類的一個靜態(tài)變量“replicaManager”給引用了
此時在JVM的規(guī)范里,靜態(tài)變量也可以看做是一種GC Roots,此時只要一個對象被GC Roots引用了,就不會去回收他。
所以說,一句話總結(jié):只要你的對象被方法的局部變量、類的靜態(tài)變量給引用了,就不會回收他們。
3、Java中對象不同的引用類型
關(guān)于引用和垃圾回收的關(guān)系,大家在這里務(wù)必有腦子里要引入一個新的概念,那就是Java里有不同的引用類型。
分別是強(qiáng)引用、軟引用、弱引用和虛引用。下面分別用代碼來示范一下。
強(qiáng)引用,就是類似下面的代碼:

這個就是最普通的代碼,一個變量引用一個對象,只要是強(qiáng)引用的類型,那么垃圾回收的時候絕對不會去回收這個對象的。
接著是軟引用,類似下面的代碼。

就是把“ReplicaManager”實例對象用一個“SoftReference”軟引用類型的對象給包裹起來了,此時這個“replicaManager”變量對“ReplicaManager”對象的引用就是軟引用了。
正常情況下垃圾回收是不會回收軟引用對象的,但是如果你進(jìn)行垃圾回收之后,發(fā)現(xiàn)內(nèi)存空間還是不夠存放新的對象,內(nèi)存都快溢出了
此時就會把這些軟引用對象給回收掉,哪怕他被變量引用了,但是因為他是軟引用,所以還是要回收。
接著是弱引用,類似下面的代碼。

這個其實非常好解釋,你這個弱引用就跟沒引用是類似的,如果發(fā)生垃圾回收,就會把這個對象回收掉。
虛引用,這個大家其實暫時忽略他也行,因為很少用。
其實這里比較常用的,就是強(qiáng)引用和軟引用,強(qiáng)引用就是代表絕對不能回收的對象,軟引用就是說有的對象可有可無,如果內(nèi)存實在不夠了,可以回收他。
4、finalize()方法的作用
現(xiàn)在大家理解完了GC Roots和引用類型的概念,基本都知道了,哪些對象可以回收,哪些對象不能回收。
有GC Roots引用的對象不能回收,沒有GC Roots引用的對象可以回收,如果有GC Roots引用,但是如果是軟引用或者弱引用的,也有可能被回收掉。
接著就是到回收的環(huán)節(jié)了,假設(shè)沒有GC Roots引用的對象,是一定立馬被回收嗎?
其實不是的,這里有一個finalize()方法可以拯救他自己,看下面的代碼。

假設(shè)有一個ReplicaManager對象要被垃圾回收了,那么假如這個對象重寫了Object類中的finialize()方法
此時會先嘗試調(diào)用一下他的finalize()方法,看是否把自己這個實例對象給了某個GC Roots變量,比如說代碼中就給了ReplicaManager類的靜態(tài)變量。
如果重新讓某個GC Roots變量引用了自己,那么就不用被垃圾回收了。
不過說實話,這個東西沒必要過多解讀,因為其實平時很少用,就是給大家梳理出來這些細(xì)節(jié),讓大家清楚而已。
5、昨日思考題
上周的思考題和作業(yè)是一個意思,就是讓大家去思考,自己負(fù)責(zé)的系統(tǒng)的內(nèi)存壓力,然后就是JVM內(nèi)存大小是否合理,如果業(yè)務(wù)暴增100倍,是否會有內(nèi)存問題。
作業(yè)更加詳細(xì)的提示大家,自己畫出核心業(yè)務(wù)流程圖,然后一點點去分析,這是一個非常重要的技能。
其實JVM實戰(zhàn)技能里的第一步,就是合理預(yù)估系統(tǒng)內(nèi)存壓力,合理設(shè)置JVM內(nèi)存大小。
6、今日思考題
思考下面的代碼。

上述代碼下,如果垃圾回收,會回收ReplicaFetcher對象嗎?為什么?
End
版權(quán):公眾號儒猿技術(shù)窩
未經(jīng)許可不得傳播,如有侵權(quán)將追究法律責(zé)任