一個GCRoot不可達的對象,會立刻被垃圾回收嗎?
這個問題是我在刷??兔娼浀臅r候遇到的,還特地整理在了我的常規(guī)面試題文檔中,所以這道題主要考察的就是finalize方法的影響。
java提供了一個finalize方法,可以幫助我們進行資源釋放,類似于C++中的析構函數。這篇文章對其進行一個說明。
一、為什么有影響
我們都知道一個對象GCRoot不可達,java虛擬機就認為是垃圾對象,就會進行垃圾回收,但是如果這個對象包含了finalize函數,性質就不一樣了。怎么不一樣了呢?
java虛擬機在進行垃圾回收的時候,一看到這個對象類含有finalize函數,就把這個函數交給FinalizerThread處理,而包含了這個finalize的對象就會被添加到FinalizerThread的執(zhí)行隊列,并使用一個鏈表,把這些包含了finalize的對象串起來。
他的影響在于只要finalize沒有執(zhí)行,那么這些對象就會一直存在堆區(qū),不過這里只是4個包含了finalize的對象,影響不是那么大,如果有一萬個或者是十萬個呢?這就影響大了。
finalize的原理其實很簡單,在這里簡要的梳理一下:
(1)對象在初始化的過程中會判斷是否重寫了finalize,方法是判斷兩個字段標志has_finalizer_flag和RegisterFinalizersAtInit。
(2)如果重寫了finalize,那就把當前對象注冊到FinalizerThread的ReferenceQueue隊列中。注冊之后的對象就叫做Finalizer。方法是調用register_finalizer函數。此時java虛擬機一看當前有這個對象的引用,于是就不進行垃圾回收了。
(3)對象開始被調用,FinalizerThread線程負責從ReferenceQueue隊列中獲取Finalizer對象。開始執(zhí)行finalize方法,在執(zhí)行之前,這個對象一直在堆中。
(4)對象執(zhí)行完畢之后,將這個Finalizer對象從隊列中移除,java虛擬機一看對象沒有引用了,就進行垃圾回收了。
這就是整個過程。不過在這里我們主要看的是finalize方法對垃圾回收的影響,其實就是在第三步,也就是這個對象含有finalize,進入了隊列但一直沒有被調用的這段時間,會一直占用內存。
注意:這里其實就是一道面試題,我在看牛客網上的面經時,看到有人被問到過。也就是GCRoot不可達的對象,會立刻被垃圾回收嗎?
我們使用一個案例來分析一波:
二、案例演示
我們創(chuàng)建一個類
- public class TestFinalizer {
- public static class Fdd {
- //分配1M
- private byte[] content = new byte[1024*1024];
- @Override
- protected void finalize() {
- System.out.println("finalize被執(zhí)行");
- }
- }
- public static void main(String[] args) {
- for (int i = 0; i < 1000; i++) {
- Fdd fdd = new Fdd();
- }
- }
- }
現在創(chuàng)建了類,我們設置一下參數。
- # 最大堆內存
- -Xmx5m
- # 最小堆內存
- -Xms5m
- # 堆內存溢出錯誤打印
- -XX:+HeapDumpOnOutOfMemoryError
- # 把堆相關信息保存在下列路徑
- -XX:HeapDumpPath=F:/a.dump
在main方法中,創(chuàng)建了1000個Fdd對象,如果不執(zhí)行finalize方法,那么因為沒有調用所以會進行垃圾回收,此時不斷我們創(chuàng)建多少個,都不會出現任何問題。但是如果存在finalize方法,就不一樣了。
- java.lang.OutOfMemoryError: Java heap space
- Dumping heap to F:/a.dump ...
- finalize被執(zhí)行
- finalize被執(zhí)行
- finalize被執(zhí)行
- finalize被執(zhí)行
- finalize被執(zhí)行
- finalize被執(zhí)行
- finalize被執(zhí)行
- 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)
我們看到每個對象都會執(zhí)行finalize,在執(zhí)行之前的這段時間一直會在堆區(qū),執(zhí)行完了就會被清理,所以你看到這里執(zhí)行了不少于5次的finalize方法。但是對象一旦超出了我們設置的5M,就會出現內存溢出。一句話總結就是出現了對象堆積。現在使用MAT工具來分析一下。
Mat工具是一個插件,也可以自己下載一個。下載完成之后打開我們剛剛生成的a.dump即可。
下面這張圖就是分析的結果:
a這塊的內容就是Finalizer,也就是我們的Fdd對象,b包含的比較多,亂七八糟的剩余信息。當然你也可以查看一些其他的信息。都在MAT工具上。還有一些正在執(zhí)行的finalizer和準備執(zhí)行的。
OK,一些其他的信息就不再展示了。
結論
一個GCRoot不可達的對象,不會立刻被垃圾回收,首先還會判斷是否包含了finalize方法,如果有那就先執(zhí)行finalize方法,如果這樣的對象比較多,那么這部分對象及時GCRoot不可達,變得沒用了,也會留在內存中,影響程序的效率。
本文轉載自微信公眾號「愚公要移山」,可以通過以下二維碼關注。轉載本文請聯(lián)系愚公要移山公眾號。