java的finalize方法解讀
我們通常用構(gòu)造器來(lái)創(chuàng)建對(duì)象,而Finalize正好相反,構(gòu)造方法執(zhí)行對(duì)象的初始化操作,finalize方法執(zhí)行對(duì)象的銷(xiāo)毀操作.那我們什么時(shí)候需要使用finalize方法呢,我們都知道Java里垃圾回收器可以回收對(duì)象使用的內(nèi)存空間,但是對(duì)象可能會(huì)持有很多資源比如Socket、文件句柄等,垃圾收集器無(wú)法回收這些資源,因此你需要使用finalize方法幫助GC回收這些資源,比如關(guān)閉打開(kāi)的文件或者網(wǎng)元資源,刪除臨時(shí)文件等.
一個(gè)例子
Object類(lèi)是所有類(lèi)的父類(lèi),如果你去查看java.lang.Object類(lèi)的源碼,你會(huì)發(fā)現(xiàn)里面有個(gè)finalize方法,這個(gè)方法沒(méi)有默認(rèn)實(shí)現(xiàn),需要子類(lèi)根據(jù)實(shí)際情況重寫(xiě)這個(gè)方法,但是如果不恰當(dāng)使用finalize方法可能會(huì)造成很大的負(fù)面影響,
比如下面的例子:
public class Finalizer { @Override protected void finalize() throws Throwable { while (true) { Thread.yield(); } } public static void main(String str[]) { while (true) { for (int i = 0; i < 100000; i++) { Finalizer force = new Finalizer(); } } } }
當(dāng)我們運(yùn)行上述代碼時(shí),可以看到創(chuàng)建大量的Finalizer對(duì)象,運(yùn)行一段時(shí)間后一般出現(xiàn)以下兩種結(jié)果:
- JVM異常退出并且生成了內(nèi)存鏡像Dump
- JVM拋出了一個(gè)異常:Out of Memory:GC OverHead limit exceeded.
不管上述兩種情況,JVM都崩潰了,那到底執(zhí)行finalize方法時(shí)發(fā)生了什么.Jvm會(huì)給每個(gè)實(shí)現(xiàn)了finalize方法的實(shí)例創(chuàng)建一個(gè)監(jiān)聽(tīng),這個(gè)稱(chēng)為Finalizer,每次調(diào)用對(duì)象的finalize方法時(shí),JVM會(huì)創(chuàng)建一個(gè) java.lang.ref.Finalizer
對(duì)象,這個(gè)Finalizer對(duì)象會(huì)持有這個(gè)對(duì)象的引用,由于這些對(duì)象被Finilizer對(duì)象引用了,當(dāng)對(duì)象數(shù)量較多時(shí),就會(huì)導(dǎo)致Eden區(qū)空間滿(mǎn)了,經(jīng)歷多次youngGC后可能對(duì)象就進(jìn)入到老年代了. java.lang.ref.Finalizer
類(lèi)繼承自 java.lang.ref.FinalReference
,也是Refence的一種,因此Finalizer類(lèi)里也有一個(gè)引用隊(duì)列,這個(gè)引用隊(duì)列是JVM和垃圾回收器打交道的唯一途徑,當(dāng)垃圾回收器需要回收該對(duì)象時(shí),會(huì)把該對(duì)象放到引用隊(duì)列中,這樣java.lang.ref.Finalizer類(lèi)就可以從隊(duì)列中取出該對(duì)象,執(zhí)行對(duì)象的finalize方法,并清除和該對(duì)象的引用關(guān)系.需要注意的是只有finalize方法實(shí)現(xiàn)不為空時(shí)JVM才會(huì)執(zhí)行上述操作,JVM在類(lèi)的加載過(guò)程中會(huì)標(biāo)記該類(lèi)是否為finalize類(lèi).
GC怎么處理這些對(duì)象呢
當(dāng)老年代空間達(dá)到了OldGC條件時(shí),JVM執(zhí)行一次OldGC,當(dāng)OldGC執(zhí)行后JVM檢測(cè)到這些對(duì)象只被Finalizer對(duì)象引用,這些對(duì)象會(huì)被標(biāo)記成要被清除的對(duì)象,GC會(huì)把所有的Finalizer對(duì)象放入到一個(gè)引用隊(duì)列: java.lang.ref.Finalizer.ReferenceQueue
.
Finalizer對(duì)象怎么被清理的呢
JVM默認(rèn)會(huì)創(chuàng)建一個(gè)finalizer線程來(lái)處理Finalizer對(duì)象,如果你去抓取線程堆棧的話可以看到這個(gè)線程的堆棧,
如下所示:
"Finalizer" daemon prio=10 tid=0x0962d000 nid=0x4836 runnable [0xafaa8000] java.lang.Thread.State: RUNNABLE at java.lang.Thread.yield(Native Method) at finalizer.finalize(finalizer.java:5) at java.lang.ref.Finalizer.invokeFinalizeMethod(Native Method) at java.lang.ref.Finalizer.runFinalizer(Finalizer.java:83) at java.lang.ref.Finalizer.access$100(Finalizer.java:14) at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:160)
這個(gè)線程唯一的職責(zé)就是不斷的從 java.lang.ref.Finalizer.ReferenceQueue
隊(duì)列中取對(duì)象,當(dāng)一個(gè)對(duì)象進(jìn)入到隊(duì)列中,finalizer線程就執(zhí)行對(duì)象的finalize方法并且把對(duì)象從隊(duì)列中刪除,因此在下一次GC周期中可以看到這個(gè)對(duì)象和Finalizer對(duì)象都被清除了.
大部分場(chǎng)景finalizer線程清理finalizer隊(duì)列是比較快的,但是一旦你在finalize方法里執(zhí)行一些耗時(shí)的操作,可能導(dǎo)致內(nèi)存無(wú)法及時(shí)釋放進(jìn)而導(dǎo)致內(nèi)存溢出的錯(cuò)誤,在實(shí)際場(chǎng)景還是推薦盡量少用finalize方法.
簡(jiǎn)單粗暴,一個(gè)死循環(huán)去隊(duì)列里面拿出Finalizer對(duì)象,并執(zhí)行finalize方法,再置空為null,可供垃圾回收
實(shí)戰(zhàn)案例
public class Finalizer { @Override protected void finalize() throws Throwable { System.out.println("finalize"); } public static void main(String str[]) throws IOException { for (int i = 0; i < 10000; i++) { Finalizer force = new Finalizer(); } //讓線程阻塞住,方便分析內(nèi)存使用情況 System.in.read(); } }
執(zhí)行main方法后使用jmap命令查看內(nèi)存使用情況,可以看到 java.lang.ref.Finalizer
和Finalizer的實(shí)例都創(chuàng)建了10000個(gè):
$ jmap -histo 8700|head -n 10 num #instances #bytes class name ---------------------------------------------- 1: 646 3398408 [I 2: 1851 1511144 [B 3: 6081 808864 [C 4: 10175 407000 java.lang.ref.Finalizer 5: 10000 160000 Finalizer 6: 4328 103872 java.lang.String 7: 601 64208 java.lang.Class 8: 683 40952 [Ljava.lang.Object; 9: 785 31400 java.util.TreeMap$Entry 10: 248 14144 [Ljava.lang.String;
接下來(lái)使用jmap -histo:live 8700|head -n 10命令強(qiáng)制觸發(fā)一次GC,結(jié)果和前面的分析一致,F(xiàn)inalizer對(duì)象都放到引用隊(duì)列中,并依次調(diào)用了對(duì)象的finalize方法,內(nèi)存中java.lang.ref.Finalizer和Finalizer對(duì)象依然存在,不過(guò)這一java.lang.ref.Finalizer
不再引用Finalizer對(duì)象,下一次GC周期時(shí)兩者都屬于垃圾對(duì)象:
$ jmap -histo:live 8700|head -n 10 num #instances #bytes class name ---------------------------------------------- 1: 10175 407000 java.lang.ref.Finalizer 2: 3043 372608 [C 3: 605 273624 [B 4: 10000 160000 Finalizer 5: 2883 69192 java.lang.String 6: 601 64208 java.lang.Class 7: 631 37008 [Ljava.lang.Object;
再觸發(fā)一次jmap -histo:live 8700|head -n 10,可以看到兩者都被回收了:
$ jmap -histo:live 8700|head -n 10 num #instances #bytes class name ---------------------------------------------- 1: 3059 373224 [C 2: 498 138064 [B 3: 2899 69576 java.lang.String 4: 602 64312 java.lang.Class 5: 631 37008 [Ljava.lang.Object; 6: 785 31400 java.util.TreeMap$Entry 7: 227 11256 [Ljava.lang.String;
我們來(lái)總結(jié)一下
finalize對(duì)象至少經(jīng)歷兩次GC才能被回收,因?yàn)橹挥性贔inalizerThread執(zhí)行完了finalize對(duì)象的finalize方法的情況下才有可能被下次GC回收,而有可能期間已經(jīng)經(jīng)歷過(guò)多次GC了,但是一直還沒(méi)執(zhí)行finalize對(duì)象的finalize方法;
CPU資源不足的場(chǎng)景FinalizerThread線程可能因?yàn)閮?yōu)先級(jí)較低而一直沒(méi)有執(zhí)行對(duì)象的finalize方法,可能導(dǎo)致大部分對(duì)象進(jìn)入到老年代,進(jìn)而觸發(fā)老年代GC,設(shè)置觸發(fā)Full GC.
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Spring Boot REST國(guó)際化的實(shí)現(xiàn)代碼
本文我們將討論如何在現(xiàn)有的Spring Boot項(xiàng)目中添加國(guó)際化。只需幾個(gè)簡(jiǎn)單的步驟即可實(shí)現(xiàn)Spring Boot應(yīng)用的國(guó)際化,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-10-10SpringBoot整合Minio實(shí)現(xiàn)文件上傳和讀取功能
最近有一個(gè)需求是關(guān)于視頻上傳播放的,需要設(shè)計(jì)一個(gè)方案,中間談到了Minio這個(gè)技術(shù),于是來(lái)學(xué)習(xí)一下,所以本文給大家介紹了SpringBoot整合Minio實(shí)現(xiàn)文件上傳和讀取功能,文中有詳細(xì)的代碼示例供大家參考,需要的朋友可以參考下2024-07-07使用SpringBoot簡(jiǎn)單了解Druid的監(jiān)控系統(tǒng)的配置方法
這篇文章主要介紹了使用SpringBoot簡(jiǎn)單了解Druid的監(jiān)控系統(tǒng)的配置,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06Springboot錯(cuò)誤處理機(jī)制實(shí)現(xiàn)原理解析
這篇文章主要介紹了springboot錯(cuò)誤處理機(jī)制實(shí)現(xiàn)原理解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04java簡(jiǎn)單手寫(xiě)版本實(shí)現(xiàn)時(shí)間輪算法
這篇文章主要為大家詳細(xì)介紹了java簡(jiǎn)單手寫(xiě)版本實(shí)現(xiàn)時(shí)間輪算法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-04-04MyBatis-Plus 自定義sql語(yǔ)句的實(shí)現(xiàn)
這篇文章主要介紹了MyBatis-Plus 自定義sql語(yǔ)句的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12用SpringBoot+Vue+uniapp小程序?qū)崿F(xiàn)在線房屋裝修管理系統(tǒng)
這篇文章主要介紹了用SpringBoot+Vue+uniapp實(shí)現(xiàn)在線房屋裝修管理系統(tǒng),針對(duì)裝修樣板信息管理混亂,出錯(cuò)率高,信息安全性差,勞動(dòng)強(qiáng)度大,費(fèi)時(shí)費(fèi)力等問(wèn)題開(kāi)發(fā)了這套系統(tǒng),需要的朋友可以參考下2023-03-03