Java 詳解垃圾回收與對(duì)象生命周期
Java 垃圾回收與對(duì)象生命周期詳解
Java中的垃圾回收與對(duì)象生命周期
1. 垃圾回收
垃圾回收是Java程序設(shè)計(jì)中內(nèi)存管理的核心概念,JVM的內(nèi)存管理機(jī)制被稱為垃圾回收機(jī)制。
一個(gè)對(duì)象創(chuàng)建后被放置在JVM的堆內(nèi)存中,當(dāng)永遠(yuǎn)不再引用這個(gè)對(duì)象時(shí),它將被JVM在堆內(nèi)存中回收。被創(chuàng)建的對(duì)象不能再生,同時(shí)也沒有辦法通過程序語句釋放它們。即當(dāng)對(duì)象在JVM運(yùn)行空間中無法通過根集合到達(dá)(找到)時(shí),這個(gè)對(duì)象被稱為垃圾對(duì)象。根集合是由類中的靜態(tài)引用域與本地引用域組成的。JVM通過根集合索引對(duì)象。
在做Java應(yīng)用開發(fā)時(shí)經(jīng)常會(huì)用到由JVM管理的兩種類型的內(nèi)存:堆內(nèi)存和棧內(nèi)存。簡(jiǎn)單來講,堆內(nèi)存主要用來存儲(chǔ)程序在運(yùn)行時(shí)創(chuàng)建或?qū)嵗膶?duì)象與變量。例如通過new關(guān)鍵字創(chuàng)建的對(duì)象。而棧內(nèi)存則是用來存儲(chǔ)程序代碼中聲明為靜態(tài)或非靜態(tài)的方法。
(1) 堆內(nèi)存
堆內(nèi)存在JVM啟動(dòng)的時(shí)候被創(chuàng)建,堆內(nèi)存中所存儲(chǔ)的對(duì)象可以被JVM自動(dòng)回收,不能通過其他外部手段回收,也就是說開發(fā)人員無法通過添加相關(guān)代碼的手段來回收堆內(nèi)存中的對(duì)象。堆內(nèi)存通常情況下被分為兩個(gè)區(qū)域:新對(duì)象區(qū)域與老對(duì)象區(qū)域。
新對(duì)象區(qū)域:又可細(xì)分為三個(gè)小區(qū)域:伊甸園區(qū)域、From區(qū)域與To區(qū)域。伊甸園區(qū)域用來保存新創(chuàng)建的對(duì)象,它就像一個(gè)堆棧,新的對(duì)象被創(chuàng)建,就像指向該棧的指針在增長(zhǎng)一樣,當(dāng)伊甸園區(qū)域中的對(duì)象滿了之后,JVM系統(tǒng)將要做到可達(dá)性測(cè)試,主要任務(wù)是檢測(cè)有哪些對(duì)象由根集合出發(fā)是不可達(dá)的,這些對(duì)象就可以被JVM回收,并且將所有的活動(dòng)對(duì)象從伊甸園區(qū)域拷貝到To區(qū)域,此時(shí)一些對(duì)象將發(fā)生狀態(tài)交換,有的對(duì)象就從To區(qū)域被轉(zhuǎn)移到From區(qū)域,此時(shí)From區(qū)域就有了對(duì)象。上面對(duì)象遷移的整個(gè)過程,都是由JVM控制完成的。
老對(duì)象區(qū)域:在老對(duì)象區(qū)域中的對(duì)象仍然會(huì)有一個(gè)較長(zhǎng)的生命周期,大多數(shù)的JVM系統(tǒng)垃圾對(duì)象,都是源于"短命"對(duì)象,經(jīng)過一段時(shí)間后,被轉(zhuǎn)入老對(duì)象區(qū)域的對(duì)象,就變成了垃圾對(duì)象。此時(shí),它們都被打上相應(yīng)的標(biāo)記,JVM系統(tǒng)將會(huì)自動(dòng)回收這些垃圾對(duì)象,建議不要頻繁地強(qiáng)制系統(tǒng)作垃圾回收,這是因?yàn)镴VM會(huì)利用有限的系統(tǒng)資源,優(yōu)先完成垃圾回收工作,導(dǎo)致應(yīng)用無法快速地響應(yīng)來自用戶端的請(qǐng)求,這樣會(huì)影響系統(tǒng)的整體性能。
(2) 棧內(nèi)存
堆內(nèi)存主要用來存儲(chǔ)程序在運(yùn)行時(shí)創(chuàng)建或?qū)嵗膶?duì)象與變量。例如通過new關(guān)鍵字創(chuàng)建的對(duì)象。而棧內(nèi)存則是用來存儲(chǔ)程序代碼中聲明為靜態(tài)或非靜態(tài)的方法。
2. JVM中對(duì)象的生命周期
在JVM運(yùn)行空間中,對(duì)象的整個(gè)生命周期大致可以分為7個(gè)階段:
- 創(chuàng)建階段;
- 應(yīng)用階段;
- 不可視階段;
- 不可到達(dá)階段;
- 可收集階段;
- 終結(jié)階段;
- 釋放階段
上面這7個(gè)階段,構(gòu)成了JVM中對(duì)象的完整的生命周期。
(1) 創(chuàng)建階段
在對(duì)象的創(chuàng)建階段,系統(tǒng)主要通過下面的步驟,完成對(duì)象的創(chuàng)建過程:
<1> 為對(duì)象分配存儲(chǔ)空間;
<2> 開始構(gòu)造對(duì)象;
<3> 從超類到子類對(duì)static成員進(jìn)行初始化;
<4> 超類成員變量按順序初始化,遞歸調(diào)用超類的構(gòu)造方法;
<5> 子類成員變量按順序初始化,子類構(gòu)造方法調(diào)用。
在創(chuàng)建對(duì)象時(shí)應(yīng)注意幾個(gè)關(guān)鍵應(yīng)用規(guī)則:
<1> 避免在循環(huán)體中創(chuàng)建對(duì)象,即使該對(duì)象占用內(nèi)存空間不大。
<2> 盡量及時(shí)使對(duì)象符合垃圾回收標(biāo)準(zhǔn)。比如 myObject = null。
<3> 不要采用過深的繼承層次。
<4> 訪問本地變量?jī)?yōu)于訪問類中的變量。
(2) 應(yīng)用階段
在對(duì)象的引用階段,對(duì)象具備如下特征:
<1> 系統(tǒng)至少維護(hù)著對(duì)象的一個(gè)強(qiáng)引用(Strong Reference);
<2> 所有對(duì)該對(duì)象的引用全部是強(qiáng)引用(除非我們顯示地適用了:軟引用(Soft Reference)、弱引用(Weak Reference)或虛引用(Phantom Reference)).
強(qiáng)引用(Strong Reference):是指JVM內(nèi)存管理器從根引用集合出發(fā)遍歷堆中所有到達(dá)對(duì)象的路徑。當(dāng)?shù)竭_(dá)某對(duì)象的任意路徑都不含有引用對(duì)象時(shí),這個(gè)對(duì)象的引用就被稱為強(qiáng)引用。
軟引用(Soft Reference):軟引用的主要特點(diǎn)是有較強(qiáng)的引用功能。只有當(dāng)內(nèi)存不夠的時(shí)候,才回收這類內(nèi)存,因此內(nèi)存足夠時(shí)它們通常不被回收。另外這些引用對(duì)象還能保證在Java拋出OutOfMemory異常之前,被設(shè)置為null。它可以用于實(shí)現(xiàn)一些常用資源的緩存,實(shí)現(xiàn)Cache功能,保證最大限度地使用內(nèi)存你而不引起OutOfMemory。
下面是軟引用的實(shí)現(xiàn)代碼:
import java.lang.ref.SoftReference; ... A a = new A(); ... // 使用a ... // 使用完了a, 將它設(shè)置為soft引用類型,并且釋放強(qiáng)引用 SoftReference sr = new SoftReference(a); a = null; ... // 下次使用時(shí) if (sr != null) { a = sr.get(); } else { // GC由于低內(nèi)存,已釋放a,因此需要重新裝載 a = new A(); sr = new SoftReference(a); }
軟引用技術(shù)的引進(jìn)使Java應(yīng)用可以更好地管理內(nèi)存,穩(wěn)定系統(tǒng),防止系統(tǒng)內(nèi)存溢出,避免系統(tǒng)崩潰。因此在處理一些占用內(nèi)存較大且生命周期較長(zhǎng),但使用并不繁地對(duì)象時(shí)應(yīng)盡量應(yīng)用該技術(shù)。提高系統(tǒng)穩(wěn)定性。
弱引用(Weak Reference):弱應(yīng)用對(duì)象與軟引用對(duì)象的最大不同就在于:GC在進(jìn)行垃圾回收時(shí),需要通過算法檢查是否回收Soft應(yīng)用對(duì)象,而對(duì)于Weak引用,GC總是進(jìn)行回收。Weak引用對(duì)象更容易、更快地被GC回收。Weak引用對(duì)象常常用于Map結(jié)構(gòu)中。
import java.lang.ref.WeakReference; ... A a = new A(); ... // 使用a ... // 使用完了a, 將它設(shè)置為Weak引用類型,并且釋放強(qiáng)引用 WeakReference wr = new WeakReference(a); a = null; ... // 下次使用時(shí) if (wr != null) { a = wr.get(); } else { a = new A(); wr = new WeakReference(a); }
虛引用(Phantom Reference): 虛引用的用途較少,主要用于輔助finalize函數(shù)的使用。
虛引用(Phantom Reference)對(duì)象指一些執(zhí)行完了finalize函數(shù),并為不可達(dá)對(duì)象,但是還沒有被GC回收的對(duì)象。這種對(duì)象可以輔助finalize進(jìn)行一些后期的回收工作,我們通過覆蓋了Refernce的clear()方法,增強(qiáng)資源回收機(jī)制的靈活性。
在實(shí)際程序設(shè)計(jì)中一般很少使用弱引用和虛引用,是用軟引用的情況較多,因?yàn)檐浺每梢约铀貸VM對(duì)垃圾內(nèi)存的回收速度,可以維護(hù)系統(tǒng)的運(yùn)行安全,防止內(nèi)存溢出(OutOfMemory)等問題的產(chǎn)生。
(3) 不可視階段
當(dāng)一個(gè)對(duì)象處于不可視階段,說明我們?cè)谄渌麉^(qū)域的代碼中已經(jīng)不可以在引用它,其強(qiáng)引用已經(jīng)消失,例如,本地變量超出了其可視
的范圍。
try { Object localObj = new Object(); localObj.doSomething(); } catch (Exception e) { e.printStackTrace(); } if (true) { // 此區(qū)域中l(wèi)ocalObj 對(duì)象已經(jīng)不可視了, 編譯器會(huì)報(bào)錯(cuò)。 localObj.doSomething(); }
(4) 不可到達(dá)階段
處于不可達(dá)階段的對(duì)象在虛擬機(jī)的對(duì)象引用根集合中再也找不到直接或間接地強(qiáng)引用,這些對(duì)象一般是所有線程棧中的臨時(shí)變量。所有已經(jīng)裝載的靜態(tài)變量或者是對(duì)本地代碼接口的引用。
(5) 可收集階段、終結(jié)階段與釋放階段
當(dāng)一個(gè)對(duì)象處于可收集階段、終結(jié)階段與釋放階段時(shí),該對(duì)象有如下三種情況:
<1> 回收器發(fā)現(xiàn)該對(duì)象已經(jīng)不可達(dá)。
<2> finalize方法已經(jīng)被執(zhí)行。
<3> 對(duì)象空間已被重用。
感謝閱讀,希望能幫助到大家,謝謝大家對(duì)本站的支持!
相關(guān)文章
Java 根據(jù)某個(gè) key 加鎖的實(shí)現(xiàn)方式
日常開發(fā)中,有時(shí)候需要根據(jù)某個(gè) key 加鎖,確保多線程情況下,對(duì)該 key 的加鎖和解鎖之間的代碼串行執(zhí)行,這篇文章主要介紹了Java 根據(jù)某個(gè) key 加鎖的實(shí)現(xiàn)方式,需要的朋友可以參考下2023-03-03將Swagger2文檔導(dǎo)出為HTML或markdown等格式離線閱讀解析
這篇文章主要介紹了將Swagger2文檔導(dǎo)出為HTML或markdown等格式離線閱讀,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-11-11Spring使用AspectJ注解和XML配置實(shí)現(xiàn)AOP
這篇文章主要介紹了Spring使用AspectJ注解和XML配置實(shí)現(xiàn)AOP的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10springboot+spring?data?jpa實(shí)現(xiàn)新增及批量新增方式
這篇文章主要介紹了springboot+spring?data?jpa實(shí)現(xiàn)新增及批量新增方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11