理解Java垃圾回收
當(dāng)程序創(chuàng)建對(duì)象、數(shù)組等引用類(lèi)型的實(shí)體時(shí),系統(tǒng)會(huì)在堆內(nèi)存中為這一對(duì)象分配一塊內(nèi)存,對(duì)象就保存在這塊內(nèi)存中,當(dāng)這塊內(nèi)存不再被任何引用變量引用時(shí),這塊內(nèi)存就變成垃圾,等待垃圾回收機(jī)制進(jìn)行回收。垃圾回收機(jī)制具有三個(gè)特征:
垃圾回收機(jī)制只負(fù)責(zé)回收堆內(nèi)存中的對(duì)象,不會(huì)回收任何物理資源(例如數(shù)據(jù)庫(kù)連接,打開(kāi)的文件資源等),也不會(huì)回收以某種創(chuàng)建對(duì)象的方式以外的方式為該對(duì)像分配的內(nèi)存,(例如對(duì)象調(diào)用本地方法中malloc的方式申請(qǐng)的內(nèi)存)
程序無(wú)法精確控制垃圾回收的運(yùn)行,只可以建議垃圾回收進(jìn)行,建議的方式有兩種System.gc() 和Runtime.getRuntime().gc()
在垃圾回收任何對(duì)象之前,總會(huì)先調(diào)用它的finalize()方法,但是同垃圾回收的時(shí)機(jī)一致,調(diào)用finalize()方法的時(shí)機(jī)也不確定。
針對(duì)以上三個(gè)特征,有三個(gè)問(wèn)題:
1、必須手動(dòng)的進(jìn)行清理工作,釋放除創(chuàng)建對(duì)象的方式以外的方式分配的內(nèi)存和其它的物理資源。并且要注意消除過(guò)期的對(duì)象引用,否則可能引起OOM。
手動(dòng)清理通常用到try...finally...這樣的代碼結(jié)構(gòu)。
示例如下:
import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; public class ManualClear { public static void main(String[] args) { FileInputStream fileInputStream = null; try { fileInputStream = new FileInputStream("./src/ManualClear.java"); } catch (FileNotFoundException e) { System.out.println(e.getMessage()); e.printStackTrace(); return; } try { byte[] bbuf = new byte[1024]; int hasRead = 0; try { while ((hasRead = fileInputStream.read(bbuf)) > 0) { System.out.println(new String(bbuf, 0, hasRead)); } } catch (IOException e) { e.printStackTrace(); } } finally { try { fileInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } }
對(duì)于過(guò)期對(duì)象的引用,引起的OOM通常有三種常見(jiàn)的情況,這三種情況通常都不易發(fā)現(xiàn),短時(shí)間內(nèi)運(yùn)行也不會(huì)有什么問(wèn)題,但是時(shí)間久了后,泄漏的對(duì)象增加后終會(huì)引起程序崩潰。
類(lèi)自己管理內(nèi)存時(shí),要警惕內(nèi)存泄漏
示例如下:
import java.util.Arrays; import java.util.EmptyStackException; class Stack{ private Object[] elements; private int size; private static final int DEFAULT_INITAL_CAPACITY = 16; public Stack() { elements = new Object[DEFAULT_INITAL_CAPACITY]; } public void push(Object e){ ensureCapacity(); elements[size++] = e; } public Object pop() { if (size == 0) { throw new EmptyStackException(); } return elements[--size]; } private void ensureCapacity() { if (elements.length == size) { elements = Arrays.copyOf(elements, 2 * size + 1); } } } public class StackDemo { public static void main(String[] args) { Stack stack = new Stack(); for (int i = 0; i < 10000; i++) { stack.push(new Object()); } for(int i = 0; i < 10000; i++) { stack.pop(); } } }
之所以會(huì)內(nèi)存泄漏,是因?yàn)槟切┏鰲5膶?duì)象即使程序其它對(duì)象不再引用,但是Stack類(lèi)中的elements[]數(shù)組依然保存著這些對(duì)象的引用,導(dǎo)致這些對(duì)象不會(huì)被垃圾回收所回收,所以,當(dāng)需要類(lèi)自己管理內(nèi)存事,要警惕內(nèi)部維護(hù)的這些過(guò)期引用是否被及時(shí)解除了引用,本例中只需在出棧后,顯示的將
elements[size] = null;即可。
緩存是要警惕內(nèi)存泄漏
出現(xiàn)這樣情況通常是一旦將對(duì)象放入緩存,很可能長(zhǎng)時(shí)間不使用很容易遺忘,通常可以用WakeHashMap代表緩存,在緩存中的項(xiàng)過(guò)期后,他們可以被自動(dòng)刪除?;蛘呖梢杂梢粋€(gè)后臺(tái)線程定期執(zhí)行來(lái)清除緩沖中的過(guò)期項(xiàng)。
監(jiān)聽(tīng)器或回調(diào)的注冊(cè),最好可以顯示的取消注冊(cè)。
2、不要手動(dòng)調(diào)用finalize(),它是給垃圾回收器調(diào)用的
3、避免使用finalize()方法,除非用來(lái)作為判斷終結(jié)條件以發(fā)現(xiàn)對(duì)象中沒(méi)有被適當(dāng)清理的部分;用來(lái)作為安全網(wǎng)在手動(dòng)清理忘記調(diào)用的情況下清理系統(tǒng)資源,延后清理總別永不清理要強(qiáng),并且如果同時(shí)記錄下忘記清理資源的信息的話,也方便后面發(fā)現(xiàn)錯(cuò)誤,并及時(shí)修改忘記清理的代碼;釋放對(duì)象中本地方法獲得的不是很關(guān)鍵的系統(tǒng)資源。
finalize()方法由于其執(zhí)行時(shí)間以及是否確定被執(zhí)行都不能準(zhǔn)確確保,所以最好不用來(lái)釋放關(guān)鍵資源,但是可用于上面所說(shuō)的三種情況。其中第一種情況,示例如下:
class Book { boolean checkout = false; public Book(boolean checkout) { this.checkout = checkout; } public void checkin(){ checkout = false; } @Override protected void finalize() throws Throwable { if (checkout) { System.out.println("Error: check out"); } } } public class FinalizeCheckObjectUse { public static void main(String[] args) { new Book(true); System.gc(); } }
執(zhí)行結(jié)果:
Error: check out
例子中的Book對(duì)象,在釋放前必須處于checkIn的狀態(tài),否則不能釋放,finalize中的實(shí)現(xiàn)可以幫助及時(shí)發(fā)現(xiàn)不合法的對(duì)象,或者更直接的,在finalize中直接使用某個(gè)引用變量引用,使其重新進(jìn)入reachable的狀態(tài),然后再次對(duì)其進(jìn)行處理。
另一點(diǎn)需要注意的時(shí),子類(lèi)如果覆蓋了父類(lèi)的finalize方法,但是忘了手工調(diào)用super.finalize或者子類(lèi)的finalize過(guò)程出現(xiàn)異常導(dǎo)致沒(méi)有執(zhí)行到super.finalize時(shí),那么父類(lèi)的終結(jié)方法將永遠(yuǎn)不會(huì)調(diào)到。
如下:
class Parent{ @Override protected void finalize() throws Throwable { System.out.println(getClass().getName() + " finalize start"); } } class Son extends Parent{ @Override protected void finalize() throws Throwable { System.out.println(getClass().getName() + " finalize start"); } } public class SuperFinalizeLost { public static void main(String[] args) { new Son(); System.gc(); } }
運(yùn)行結(jié)果:
Son finalize start
或者
class Parent{ @Override protected void finalize() throws Throwable { System.out.println(getClass().getName() + " finalize start"); } } class Son extends Parent{ @Override protected void finalize() throws Throwable { System.out.println(getClass().getName() + " finalize start"); int i = 5 / 0; super.finalize(); } } public class SuperFinalizeLost { public static void main(String[] args) { new Son(); System.gc(); } }
執(zhí)行結(jié)果:
Son finalize start
對(duì)于第二種情況,可以使用try...finally...結(jié)構(gòu)解決,但是對(duì)于第一種情況,最好使用一種叫終結(jié)方法守護(hù)者的方式。示例如下
class Parent2{ private final Object finalizeGuardian = new Object() { protected void finalize() throws Throwable { System.out.println("在此執(zhí)行父類(lèi)終結(jié)方法中的邏輯"); }; }; } class Son2 extends Parent2{ @Override protected void finalize() throws Throwable { System.out.println(getClass().getName() + " finalize start"); int i = 5 / 0; super.finalize(); } } public class FinalizeGuardian { public static void main(String[] args) { new Son2(); System.gc(); } }
執(zhí)行結(jié)果:
在此執(zhí)行父類(lèi)終結(jié)方法中的邏輯
Son2 finalize start
這樣可以保證父類(lèi)的終結(jié)方法中所需做的操作執(zhí)行到。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助。
相關(guān)文章
SpringBoot啟動(dòng)過(guò)程逐步分析講解
這篇文章主要介紹了SpringBoot啟動(dòng)過(guò)程的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2023-01-01Java動(dòng)態(tài)代理(設(shè)計(jì)模式)代碼詳解
這篇文章主要介紹了Java動(dòng)態(tài)代理(設(shè)計(jì)模式)代碼詳解,具有一定借鑒價(jià)值,需要的朋友可以參考下2017-12-12Java實(shí)現(xiàn)對(duì)一行英文進(jìn)行單詞提取功能示例
這篇文章主要介紹了Java實(shí)現(xiàn)對(duì)一行英文進(jìn)行單詞提取功能,結(jié)合實(shí)例形式分析了java基于StringTokenizer類(lèi)進(jìn)行字符串分割的相關(guān)操作技巧,需要的朋友可以參考下2017-10-10手工體驗(yàn)smtp和pop3協(xié)議 郵件實(shí)現(xiàn)詳解(二)
POP3/IMAP協(xié)議定義了郵件客戶端軟件和POP3郵件服務(wù)器的通信規(guī)則,這篇文章我們就來(lái)手工體驗(yàn)SMTP和POP3協(xié)議的奧秘,感興趣的小伙伴們可以參考一下2017-10-10Java中對(duì)AtomicInteger和int值在多線程下遞增操作的測(cè)試
這篇文章主要介紹了Java中對(duì)AtomicInteger和int值在多線程下遞增操作的測(cè)試,本文得出AtomicInteger操作 與 int操作的效率大致相差在50-80倍上下的結(jié)論,需要的朋友可以參考下2014-09-09springboot+zookeeper實(shí)現(xiàn)分布式鎖的示例代碼
本文主要介紹了springboot+zookeeper實(shí)現(xiàn)分布式鎖的示例代碼,文中根據(jù)實(shí)例編碼詳細(xì)介紹的十分詳盡,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03java之a(chǎn)ssert關(guān)鍵字用法案例詳解
這篇文章主要介紹了java之a(chǎn)ssert關(guān)鍵字用法案例詳解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08Java中消息隊(duì)列任務(wù)的平滑關(guān)閉詳解
對(duì)于消息隊(duì)列的監(jiān)聽(tīng),我們一般使用Java寫(xiě)一個(gè)獨(dú)立的程序,在Linux服務(wù)器上運(yùn)行。程序啟動(dòng)后,通過(guò)消息隊(duì)列客戶端接收消息,放入一個(gè)線程池進(jìn)行異步處理,并發(fā)的快速處理。這篇文章主要給大家介紹了關(guān)于Java中消息隊(duì)列任務(wù)的平滑關(guān)閉的相關(guān)資料,需要的朋友可以參考下。2017-11-11JDK源碼分析之String、StringBuilder和StringBuffer
這篇文章主要給大家介紹了關(guān)于JDK源碼分析之String、StringBuilder和StringBuffer的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用jdk具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-05-05