一個GCRoot不可達的對象,會馬上被垃圾回收嗎?

這個問題是我在刷牛客面經的時候遇到的,還特意整理在了個人常規面試題文檔中,因此這道題主要考察的就是finalize方法的影響。java

java提供了一個finalize方法,能夠幫助咱們進行資源釋放,相似於C++中的析構函數。可是目前廣泛的認識是不要使用,爲何呢?就是由於對java虛擬機的垃圾回收有影響。這篇文章對其進行一個說明。面試

1、爲何有影響

咱們都知道一個對象GCRoot不可達,java虛擬機就認爲是垃圾對象,就會進行垃圾回收,可是若是這個對象包含了finalize函數,性質就不同了。怎麼不同了呢?ide

java虛擬機在進行垃圾回收的時候,一看到這個對象類含有finalize函數,就把這個函數交給FinalizerThread處理,而包含了這個finalize的對象就會被添加到FinalizerThread的執行隊列,並使用一個鏈表,把這些包含了finalize的對象串起來。函數

他的影響在於只要finalize沒有執行,那麼這些對象就會一直存在堆區,不過這裏只是4個包含了finalize的對象,影響不是那麼大,若是有一萬個或者是十萬個呢?這就影響大了。工具

finalize的原理其實很簡單,在這裏簡要的梳理一下:spa

(1)對象在初始化的過程當中會判斷是否重寫了finalize,方法是判斷兩個字段標誌has_finalizer_flag和RegisterFinalizersAtInit。插件

(2)若是重寫了finalize,那就把當前對象註冊到FinalizerThread的ReferenceQueue隊列中。註冊以後的對象就叫作Finalizer。方法是調用register_finalizer函數。此時java虛擬機一看當前有這個對象的引用,因而就不進行垃圾回收了。線程

(3)對象開始被調用,FinalizerThread線程負責從ReferenceQueue隊列中獲取Finalizer對象。開始執行finalize方法,在執行以前,這個對象一直在堆中。code

(4)對象執行完畢以後,將這個Finalizer對象從隊列中移除,java虛擬機一看對象沒有引用了,就進行垃圾回收了。對象

這就是整個過程。不過在這裏咱們主要看的是finalize方法對垃圾回收的影響,其實就是在第三步,也就是這個對象含有finalize,進入了隊列但一直沒有被調用的這段時間,會一直佔用內存。

注意:這裏其實就是一道面試題,我在看牛客網上的面經時,看到有人被問到過。也就是GCRoot不可達的對象,會馬上被垃圾回收嗎?

咱們使用一個案例來分析一波:

2、案例演示

咱們建立一個類

`public class TestFinalizer {
    public static class Fdd {
     //分配1M
        private byte[] content = new byte[1024*1024];
        @Override
        protected void finalize() {
            System.out.println("finalize被執行");
        }
    }
    public static void main(String[] args) {   
        for (int i = 0; i < 1000; i++) {
         Fdd fdd = new Fdd(); 
        }
    }
}
`

如今建立了類,咱們設置一下參數。

`# 最大堆內存
-Xmx5m

最小堆內存

-Xms5m

堆內存溢出錯誤打印

-XX:+HeapDumpOnOutOfMemoryError

把堆相關信息保存在下列路徑

-XX:HeapDumpPath=F:/a.dump`

在main方法中,建立了1000個Fdd對象,若是不執行finalize方法,那麼由於沒有調用因此會進行垃圾回收,此時不斷咱們建立多少個,都不會出現任何問題。可是若是存在finalize方法,就不同了。

`java.lang.OutOfMemoryError: Java heap space
Dumping heap to F:/a.dump ...
finalize被執行
finalize被執行
finalize被執行
finalize被執行
finalize被執行
finalize被執行
finalize被執行
Unable to create F:/a.dump: File exists
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

at com.fdd.chapter2.TestFinalizer$Fdd.<init>(TestFinalizer.java:6)
at com.fdd.chapter2.TestFinalizer.main(TestFinalizer.java:14)`

咱們看到每一個對象都會執行finalize,在執行以前的這段時間一直會在堆區,執行完了就會被清理,因此你看到這裏執行了很多於5次的finalize方法。可是對象一旦超出了咱們設置的5M,就會出現內存溢出。一句話總結就是出現了對象堆積。如今使用MAT工具來分析一下。

Mat工具是一個插件,也能夠本身下載一個。下載完成以後打開咱們剛剛生成的a.dump便可。

下面這張圖就是分析的結果:

a這塊的內容就是Finalizer,也就是咱們的Fdd對象,b包含的比較多,亂七八糟的剩餘信息。固然你也能夠查看一些其餘的信息。都在MAT工具上。還有一些正在執行的finalizer和準備執行的。

OK,一些其餘的信息就再也不展現了。

結論

一個GCRoot不可達的對象,不會馬上被垃圾回收,首先還會判斷是否包含了finalize方法,若是有那就先執行finalize方法,若是這樣的對象比較多,那麼這部分對象及時GCRoot不可達,變得沒用了,也會留在內存中,影響程序的效率。

相關文章
相關標籤/搜索