Java中GC的工作原理詳細介紹
Java中GC的工作原理
引子:面試時被問到垃圾回收機制,只是粗略的講'程序員不能直接對內(nèi)存操作,jvm負責(zé)對已經(jīng)超過作用域的對象回收處理',面官表情呆滯,也就沒再繼續(xù)深入。
轉(zhuǎn)文:
一個優(yōu)秀的Java程序員必須了解GC的工作原理、如何優(yōu)化GC的性能、如何與GC進行有限的交互,有一些應(yīng)用程序?qū)π阅芤筝^高,例如嵌入式系統(tǒng)、實時系統(tǒng)等,只有全面提升內(nèi)存的管理效率,才能提高整個應(yīng)用程序的性能。本文將從GC的工作原理、GC的幾個關(guān)鍵問題進行探討,最后提出一些Java程序設(shè)計建議,如何從GC角度提高Java程序的性能。
一、GC的基本原理:
GC是什么? 為什么要有GC呢?
GC是垃圾收集的意思(Garbage Collection),內(nèi)存處理是編程人員容易出現(xiàn)問題的地方,忘記或者錯誤的內(nèi)存回收會導(dǎo)致程序或系統(tǒng)的不穩(wěn)定甚至崩潰,Java提供的GC功能可以自動監(jiān)測對象是否超過作用域從而達到自動回收內(nèi)存的目的,Java語言沒有提供釋放已分配內(nèi)存的顯示操作方法。
所以,Java的內(nèi)存管理實際上就是對象的管理,其中包括對象的分配和釋放。
對于Java程序員來說,分配對象使用new關(guān)鍵字;釋放對象時,只要將對象所有引用賦值為null,讓程序不能夠再訪問到這個對象,我們稱該對象為"不可達的".GC將負責(zé)回收所有"不可達"對象的內(nèi)存空間。
對于GC來說,當(dāng)程序員創(chuàng)建對象時,GC就開始監(jiān)控這個對象的地址、大小以及使用情況。通常,GC采用有向圖的方式記錄和管理堆(heap)中的所有對象。通過這種方式確定哪些對象是"可達的",哪些對象是"不可達的".當(dāng)GC確定一些對象為"不可達"時,GC就有責(zé)任回收這些內(nèi)存空間。但是,為了保證GC能夠在不同平臺實現(xiàn)的問題,Java規(guī)范對GC的很多行為都沒有進行嚴(yán)格的規(guī)定。例如,對于采用什么類型的回收算法、什么時候進行回收等重要問題都沒有明確的規(guī)定。因此,不同的JVM的實現(xiàn)者往往有不同的實現(xiàn)算法。這也給Java程序員的開發(fā)帶來行多不確定性。本文研究了幾個與GC工作相關(guān)的問題,努力減少這種不確定性給Java程序帶來的負面影響。
二、增量式GC(Incremental GC):
GC在JVM中通常是由一個或一組進程來實現(xiàn)的,它本身也和用戶程序一樣占用heap空間,運行時也占用CPU.當(dāng)GC進程運行時,應(yīng)用程序停止運行。因此,當(dāng)GC運行時間較長時,用戶能夠感到Java程序的停頓,另外一方面,如果GC運行時間太短,則可能對象回收率太低,這意味著還有很多應(yīng)該回收的對象沒有被回收,仍然占用大量內(nèi)存。因此,在設(shè)計GC的時候,就必須在停頓時間和回收率之間進行權(quán)衡。一個好的GC實現(xiàn)允許用戶定義自己所需要的設(shè)置,例如有些內(nèi)存有限有設(shè)備,對內(nèi)存的使用量非常敏感,希望GC能夠準(zhǔn)確的回收內(nèi)存,它并不在意程序速度的放慢。另外一些實時網(wǎng)絡(luò)游戲,就不能夠允許程序有長時間的中斷。增量式GC就是通過一定的回收算法,把一個長時間的中斷,劃分為很多個小的中斷,通過這種方式減少GC對用戶程序的影響。雖然,增量式GC在整體性能上可能不如普通GC的效率高,但是它能夠減少程序的最長停頓時間。
Sun JDK提供的HotSpot JVM就能支持增量式GC.HotSpot JVM缺省GC方式為不使用增量GC,為了啟動增量GC,我們必須在運行Java程序時增加-Xincgc的參數(shù)。HotSpot JVM增量式GC的實現(xiàn)是采用Train GC算法。它的基本想法就是,將堆中的所有對象按照創(chuàng)建和使用情況進行分組(分層),將使用頻繁高和具有相關(guān)性的對象放在一隊中,隨著程序的運行,不斷對組進行調(diào)整。當(dāng)GC運行時,它總是先回收最老的(最近很少訪問的)的對象,如果整組都為可回收對象,GC將整組回收。這樣,每次GC運行只回收一定比例的不可達對象,保證程序的順暢運行。
三、詳解finalize函數(shù):
finalize 是位于Object類的一個方法,該方法的訪問修飾符為protected,由于所有類為Object的子類,因此用戶類很容易訪問到這個方法。由于,finalize函數(shù)沒有自動實現(xiàn)鏈?zhǔn)秸{(diào)用,我們必須手動的實現(xiàn),因此finalize函數(shù)的最后一個語句通常是 super.finalize()。通過這種方式,我們可以實現(xiàn)從下到上實現(xiàn)finalize的調(diào)用,即先釋放自己的資源,然后再釋放父類的資源。
根據(jù)Java語言規(guī)范,JVM保證調(diào)用finalize函數(shù)之前,這個對象是不可達的,但是JVM不保證這個函數(shù)一定會被調(diào)用。另外,規(guī)范還保證finalize函數(shù)最多運行一次。
很多Java初學(xué)者會認為這個方法類似與C++中的析構(gòu)函數(shù),將很多對象、資源的釋放都放在這一函數(shù)里面。其實,這不是一種很好的方式。原因有三,其一,GC為了能夠支持finalize函數(shù),要對覆蓋這個函數(shù)的對象作很多附加的工作。其二,在finalize運行完成之后,該對象可能變成可達的,GC還要再檢查一次該對象是否是可達的。因此,使用 finalize會降低GC的運行性能。其三,由于GC調(diào)用finalize的時間是不確定的,因此通過這種方式釋放資源也是不確定的。
通常,finalize用于一些不容易控制、并且非常重要資源的釋放,例如一些I/O的操作,數(shù)據(jù)的連接。這些資源的釋放對整個應(yīng)用程序是非常關(guān)鍵的。在這種情況下,程序員應(yīng)該以通過程序本身管理(包括釋放)這些資源為主,以finalize函數(shù)釋放資源方式為輔,形成一種雙保險的管理機制,而不應(yīng)該僅僅依靠finalize來釋放資源。
下面給出一個例子說明,finalize函數(shù)被調(diào)用以后,仍然可能是可達的,同時也可說明一個對象的finalize只可能運行一次。
class MyObject { Test main; // 記錄Test對象,在finalize中時用于恢復(fù)可達性 public MyObject(Test t) { main = t; // 保存Test 對象 } protected void finalize() { main.ref = this;// 恢復(fù)本對象,讓本對象可達 System.out.println("This is finalize");// 用于測試finalize只運行一次 } } class Test { MyObject ref; public static void main(String[] args) { Test test = new Test(); test.ref = new MyObject(test); test.ref = null; // MyObject對象為不可達對象,finalize將被調(diào)用 System.gc(); if (test.ref != null) System.out.println("My Object還活著"); } } class MyObject { Test main; // 記錄Test對象,在finalize中時用于恢復(fù)可達性 public MyObject(Test t) { main = t; // 保存Test 對象 } protected void finalize() { main.ref = this;// 恢復(fù)本對象,讓本對象可達 System.out.println("This is finalize");// 用于測試finalize只運行一次 } }
運行結(jié)果:
This is finalize
MyObject還活著:此例子中,需要注意的是雖然MyObject對象在finalize中變成可達對象,但是下次回收時候,finalize卻不再被調(diào)用,因為finalize函數(shù)最多只調(diào)用一次。
四、Java程序如何與GC進行交互:
Java2 增強了內(nèi)存管理功能,增加了一個java.lang.ref包,其中定義了三種引用類。這三種引用類分別為SoftReference、WeakReference和 PhantomReference.通過使用這些引用類,程序員可以在一定程度與GC進行交互,以便改善GC的工作效率。這些引用類的引用強度介于可達對象和不可達對象之間。
創(chuàng)建一個引用對象也非常容易,例如如果你需要創(chuàng)建一個Soft Reference對象,那么首先創(chuàng)建一個對象,并采用普通引用方式(可達對象);然后再創(chuàng)建一個SoftReference引用該對象;最后將普通引用設(shè)置為null.通過這種方式,這個對象就只有一個Soft Reference引用。同時,我們稱這個對象為Soft Reference 對象。
Soft Reference的主要特點是據(jù)有較強的引用功能。只有當(dāng)內(nèi)存不夠的時候,才進行回收這類內(nèi)存,因此在內(nèi)存足夠的時候,它們通常不被回收。另外,這些引用對象還能保證在Java拋出OutOfMemory 異常之前,被設(shè)置為null.它可以用于實現(xiàn)一些常用圖片的緩存,實現(xiàn)Cache的功能,保證最大限度的使用內(nèi)存而不引起OutOfMemory.以下給出這種引用類型的使用偽代碼;
// 申請一個圖像對象 Image image=new Image();// 創(chuàng)建Image對象 … // 使用 image // … // 使用完了image,將它設(shè)置為soft 引用類型,并且釋放強引用; SoftReference sr=new SoftReference(image); image=null; … // 下次使用時 if (sr!=null) image=sr.get(); else{ // 由于GC由于低內(nèi)存,已釋放image,因此需要重新裝載; image=new Image(); sr=new SoftReference(image);
// 申請一個圖像對象 Image image=new Image();// 創(chuàng)建Image對象 … // 使用 image // … // 使用完了image,將它設(shè)置為soft 引用類型,并且釋放強引用; SoftReference sr=new SoftReference(image); image=null; … // 下次使用時 if (sr!=null) image=sr.get(); else{ // 由于GC由于低內(nèi)存,已釋放image,因此需要重新裝載; image=new Image(); sr=new SoftReference(image);
Weak 引用對象與Soft引用對象的最大不同就在于:GC在進行回收時,需要通過算法檢查是否回收Soft引用對象,而對于Weak引用對象,GC總是進行回收。Weak引用對象更容易、更快被GC回收。雖然,GC在運行時一定回收Weak對象,但是復(fù)雜關(guān)系的Weak對象群常常需要好幾次 GC的運行才能完成。Weak引用對象常常用于Map結(jié)構(gòu)中,引用數(shù)據(jù)量較大的對象,一旦該對象的強引用為null時,GC能夠快速地回收該對象空間。
Phantom 引用的用途較少,主要用于輔助finalize函數(shù)的使用。Phantom對象指一些對象,它們執(zhí)行完了finalize函數(shù),并為不可達對象,但是它們還沒有被GC回收。這種對象可以輔助finalize進行一些后期的回收工作,我們通過覆蓋Reference的clear()方法,增強資源回收機制的靈活性。
五、一些Java編程的建議:
根據(jù)GC的工作原理,我們可以通過一些技巧和方式,讓GC運行更加有效率,更加符合應(yīng)用程序的要求。一些關(guān)于程序設(shè)計的幾點建議:
1.最基本的建議就是盡早釋放無用對象的引用。大多數(shù)程序員在使用臨時變量的時候,都是讓引用變量在退出活動域(scope)后,自動設(shè)置為 null.我們在使用這種方式時候,必須特別注意一些復(fù)雜的對象圖,例如數(shù)組,隊列,樹,圖等,這些對象之間有相互引用關(guān)系較為復(fù)雜。對于這類對象,GC 回收它們一般效率較低。如果程序允許,盡早將不用的引用對象賦為null.這樣可以加速GC的工作。
2.盡量少用finalize函數(shù)。finalize函數(shù)是Java提供給程序員一個釋放對象或資源的機會。但是,它會加大GC的工作量,因此盡量少采用finalize方式回收資源。
3.如果需要使用經(jīng)常使用的圖片,可以使用soft應(yīng)用類型。它可以盡可能將圖片保存在內(nèi)存中,供程序調(diào)用,而不引起OutOfMemory.
4.注意集合數(shù)據(jù)類型,包括數(shù)組,樹,圖,鏈表等數(shù)據(jù)結(jié)構(gòu),這些數(shù)據(jù)結(jié)構(gòu)對GC來說,回收更為復(fù)雜。另外,注意一些全局的變量,以及一些靜態(tài)變量。這些變量往往容易引起懸掛對象(dangling reference),造成內(nèi)存浪費。
5.當(dāng)程序有一定的等待時間,程序員可以手動執(zhí)行System.gc(),通知GC運行,但是Java語言規(guī)范并不保證GC一定會執(zhí)行。使用增量式GC可以縮短Java程序的暫停時間。
感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!
相關(guān)文章
Java基礎(chǔ)教程之基本類型數(shù)據(jù)類型、包裝類及自動拆裝箱
這篇文章主要給大家介紹了關(guān)于Java基礎(chǔ)教程之基本類型數(shù)據(jù)類型、包裝類及自動拆裝箱的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06關(guān)于struts2中Action名字的大小寫問題淺談
這篇文章主要給大家介紹了關(guān)于struts2中Action名字大小寫問題的相關(guān)資料,文中介紹的非常詳細,對大家具有一定的參考學(xué)習(xí)價值,需要的朋友們下面跟著小編一起來學(xué)習(xí)學(xué)習(xí)吧。2017-06-06Kafka中的producer攔截器與consumer攔截器詳解
這篇文章主要介紹了Kafka中的producer攔截器與consumer攔截器詳解,Producer 的Interceptor使得用戶在消息發(fā)送前以及Producer回調(diào)邏輯前有機會對消息做 一些定制化需求,比如修改消息等,需要的朋友可以參考下2023-12-12