【深入理解JVM】JVM垃圾回收机制
最近又回頭翻了翻《java編程思想》。有關垃圾回收機制的問題還沒有總結過,剛好今天周六,總結一下吧。
1、垃圾回收的目的
垃圾回收的目的是查找和回收(清理)無用的對象,以便讓JVM更有效的使用內存。由于有垃圾回收機制,java中的對象不再有“作用域”的概念,只有對象的引用才有“作用域”。垃圾回收可以有效的防止內存泄漏,有效的使用空閑的內存。
注意:內存泄漏是指該內存空間使用完畢之后未回收,在不涉及復雜數據結構的一般情況下,java的內存泄漏表現為一個內存對象的生命周期超出了程序需要它的時間長度,我們有時也將其稱為“對象游離”。
范圍:要回收哪些區域
在JVM五種內存模型中,有三個是不需要進行垃圾回收的:程序計數器、JVM棧、本地方法棧。因為它們的生命周期是和線程同步的,隨著線程的銷毀,它們占用的內存會自動釋放,所以只有方法區和堆需要進行GC。
2、垃圾回收的類型
由于對象進行了分代處理,因此垃圾回收區域、時間也不一樣。GC有兩種類型:Scavenge GC和Full GC。
2.1、Scavenge GC
一般情況下,當新對象生成,并且在Eden申請空間失敗時,就會觸發Scavenge GC,對Eden區域進行GC,清除非存活對象,并且把尚且存活的對象移動到Survivor區。然后整理Survivor的兩個區。這種方式的GC是對年輕代的Eden區進行,不會影響到年老代。因為大部分對象都是從Eden區開始的,同時Eden區不會分配的很大,所以Eden區的GC會頻繁進行。因而,一般在這里需要使用速度快、效率高的算法,使Eden去能盡快空閑出來。
2.2、Full GC
對整個堆進行整理,包括Young、Tenured和Perm。Full GC因為需要對整個堆進行回收,所以比Scavenge GC要慢,因此應該盡可能減少Full GC的次數。在對JVM調優的過程中,很大一部分工作就是對于FullGC的調節。有如下原因可能導致Full GC:
1.年老代(Tenured)被寫滿
2.持久代(Perm)被寫滿
3.System.gc()被顯示調用
4.上一次GC之后Heap的各域分配策略動態變化
3、如何判斷對象已死
所有的垃圾收集算法都面臨同一個問題,那就是找出應用程序不可到達的內存塊,將其釋放,這里面得不可到達主要是指應用程序已經沒有內存塊的引用了, 在JAVA中,某個對象對應用程序是可到達的是指:這個對象被根(根主要是指類的靜態變量,或者活躍在所有線程棧的對象的引用)引用或者對象被另一個可到達的對象引用。
3.1 引用計數算法
引用計數是最簡單直接的一種方式,這種方式在每一個對象中增加一個引用的計數,這個計數代表當前程序有多少個引用引用了此對象,如果此對象的引用計數變為0,那么此對象就可以作為垃圾收集器的目標對象來收集。
優點:簡單,直接,不需要暫停整個應用;
缺點:
1.需要編譯器的配合,編譯器要生成特殊的指令來進行引用計數的操作;
2.不能處理循環引用的問題
因此這種方法是垃圾收集的早期策略,現在很少使用。Sun的JVM并沒有采用引用計數算法來進行垃圾回收,是基于根搜索算法的。看下面這段代碼:
public class Main {public static void main(String[] args) {MyObject object1 = new MyObject();MyObject object2 = new MyObject();object1.object = object2;object2.object = object1;object1 = null;object2 = null;} }class MyObject{public Object object = null; }最后面兩句將object1和object2賦值為null,也就是說object1和object2指向的對象已經不可能再被訪問,但是由于它們互相引用對方,導致它們的引用計數都不為0,那么垃圾收集器就永遠不會回收它們。
為了解決這個問題,在Java中采取了 可達性分析法。即為下面的根搜索算法。該方法的基本思想是通過一系列的“GC Roots”對象作為起點進行搜索,如果在“GC Roots”和一個對象之間沒有可達路徑,則稱該對象是不可達的,不過要注意的是被判定為不可達的對象不一定就會成為可回收對象。被判定為不可達的對象要成為可回收對象必須至少經歷兩次標記過程,如果在這兩次標記過程中仍然沒有逃脫成為可回收對象的可能性,則基本上就真的成為可回收對象了。
3.2 根搜索算法
通過一系列的名為“GC Root”的對象作為起點,從這些節點向下搜索,搜索所走過的路徑稱為引用鏈(Reference Chain),當一個對象到GC Root沒有任何引用鏈相連時,則該對象不可達,該對象是不可使用的,垃圾收集器將回收其所占的內存。
在java語言中,可作為GCRoot的對象包括以下幾種對象:
a. java虛擬機棧(棧幀中的本地變量表)中的引用的對象。
b.方法區中的類靜態屬性引用的對象。
c.方法區中的常量引用的對象。
d.本地方法棧中JNI本地方法的引用對象。
判斷無用的類:
(1).該類的所有實例都已經被回收,即java堆中不存在該類的實例對象。
(2).加載該類的類加載器已經被回收。
(3).該類所對應的java.lang.Class對象沒有任何地方被引用,無法在任何地方通過反射機制訪問該類的方法。
3.3 四種引用
GC在收集一個對象的時候會判斷是否有引用指向對象,在JAVA中的引用主要有四種:
⑴ 強引用(Strong Reference)
強引用是使用最普遍的引用。如果一個對象具有強引用,那垃圾回收器絕不會回收它。當內存空間不足,Java虛擬機寧愿拋出OutOfMemoryError錯誤,使程序異常終止,也不會靠隨意回收具有強引用的對象來解決內存不足的問題。
⑵ 軟引用(Soft Reference)
如果一個對象只具有軟引用,則內存空間足夠,垃圾回收器就不會回收它;如果內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒有回收它,該對象就可以被程序使用。軟引用可用來實現內存敏感的高速緩存。
下面舉個例子,假如有一個應用需要讀取大量的本地圖片,如果每次讀取圖片都從硬盤讀取,則會嚴重影響性能,但是如果全部加載到內存當中,又有可能造成內存溢出,此時使用軟引用可以解決這個問題。
設計思路是:用一個HashMap來保存圖片的路徑和相應圖片對象關聯的軟引用之間的映射關系,在內存不足時,JVM會自動回收這些緩存圖片對象所占用的空間,從而有效地避免了內存溢出的問題。
軟引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果軟引用所引用的對象被垃圾回收器回收,Java虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。
(3)弱引用(Weak Reference)
弱引用與軟引用的區別在于:只具有弱引用的對象擁有更短暫的生命周期。在垃圾回收器線程掃描它所管轄的內存區域的過程中,一旦發現了只具有弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存。不過,由于垃圾回收器是一個優先級很低的線程,因此不一定會很快發現那些只具有弱引用的對象。
(4)虛引用(Phantom Reference)
“虛引用”顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用并不會決定對象的生命周期。如果一個對象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。
虛引用主要用于檢測對象是否已經從內存中刪除,跟蹤對象被垃圾回收器回收的活動。虛引用與軟引用和弱引用的一個區別在于:虛引用必須和引用隊列 (ReferenceQueue)聯合使用。當垃圾回收器準備回收一個對象時,如果發現它還有虛引用,就會在回收對象的內存之前,把這個虛引用加入到與之關聯的引用隊列中。
最后總結一下平常遇到的比較常見的將對象判定為可回收對象的情況:
1)顯示地將某個引用賦值為null或者將已經指向某個對象的引用指向新的對象,比如下面的代碼:
Object obj = new Object(); obj = null; Object obj1 = new Object(); Object obj2 = new Object(); obj1 = obj2;2)局部引用所指向的對象,比如下面這段代碼:
void fun() {.....for(int i=0;i<10;i++) {Object obj = new Object();System.out.println(obj.getClass());} }循環每執行完一次,生成的Object對象都會成為可回收的對象。
3)只有弱引用與其關聯的對象,比如:
WeakReference<String> wr = new WeakReference<String>(new String("world"));4、JVM中的垃圾收集策略/算法
在確定了哪些垃圾可以被回收后,垃圾收集器要做的事情就是開始進行垃圾回收,但是這里面涉及到一個問題是:如何高效地進行垃圾回收。由于Java虛擬機規范并沒有對如何實現垃圾收集器做出明確的規定,因此各個廠商的虛擬機可以采用不同的方式來實現垃圾收集器,所以在此只討論幾種常見的垃圾收集算法的核心思想。
4.1 標記-清除算法(Mark-Sweep)
標記清除收集器停止所有的工作,從根掃描每個活躍的對象,然后標記掃描過的對象,標記完成以后,清除那些沒有被標記的對象。
優點:
1 解決循環引用的問題
2 不需要編譯器的配合,從而就不執行額外的指令
缺點:
1. 每個活躍的對象都要進行掃描,收集暫停的時間比較長。
2.標記-清除算法采用從根集合進行掃描,對存活的對象對象標記,標記完畢后,再掃描整個空間中未被標記的對象,進行回收,如上圖所示。
標記-清除算法不需要進行對象的移動,并且僅對不存活的對象進行處理,在存活對象比較多的情況下極為高效,但由于標記-清除算法直接回收不存活的對象,因此會造成內存碎片。
4.2 復制算法(Copying)
復制收集器將內存分為兩塊一樣大小空間,某一個時刻,只有一個空間處于活躍的狀態,當活躍的空間滿的時候,GC就會將活躍的對象復制到未使用的空間中去,原來不活躍的空間就變為了活躍的空間。
優點:
只掃描可以到達的對象,不需要掃描所有的對象,從而減少了應用暫停的時間
缺點:
1.需要額外的空間消耗,某一個時刻,總是有一塊內存處于未使用狀態
2.復制對象需要一定的開銷
復制算法采用從根集合掃描,并將存活對象復制到一塊新的,沒有使用過的空間中,這種算法當空間存活的對象比較少時,極為高效,但是帶來的成本是需要一塊內存交換空間用于進行對象的移動。
4.3 標記-整理算法(Mark-Compact)
標記整理收集器汲取了標記清除和復制收集器的優點,它分兩個階段執行,在第一個階段,首先掃描所有活躍的對象,并標記所有活躍的對象,第二個階段首先清除未標記的對象,然后將活躍的的對象復制到堆得底部
該算法極大的減少了內存碎片,并且不需要像復制算法一樣需要兩倍的空間。
標記-整理算法采用標記-清除算法一樣的方式進行對象的標記,但在清除時不同,在回收不存活的對象占用的空間后,會將所有的存活對象往左端空閑空間移動,并更新對應的指針。標記-整理算法是在標記-清除算法的基礎上,又進行了對象的移動,因此成本更高,但是卻解決了內存碎片的問題。
4.4 分代回收算法(Generational Collection)
垃圾分代回收算法(GenerationalCollecting)基于對對象生命周期分析后得出的垃圾回收算法。
因為我們前面有介紹,內存主要被分為三塊,新生代、舊生代、持久代。三代的特點不同,造就了他們所用的GC算法不同,新生代適合那些生命周期較短,頻繁創建及銷毀的對象,舊生代適合生命周期相對較長的對象,持久代在Sun HotSpot中就是指方法區(有些JVM中根本就沒有持久代這中說法)。首先介紹下新生代、舊生代、持久代的概念及特點。
(1)Young(年輕代、新生代):JVM specification中的 Heap的一部份年輕代分三個區。一個Eden區,兩個Survivor區。大部分對象在Eden區中生成。當Eden區滿時,還存活的對象將被復制到Survivor區(兩個中的一個),當這個Survivor區滿時,此區的存活對象將被復制到另外一個Survivor區,當這個Survivor去也滿了的時候,從第一個Survivor區復制過來的并且此時還存活的對象,將被復制舊生代。需要注意,Survivor的兩個區是對稱的,沒先后關系,所以同一個區中可能同時存在從Eden復制過來對象,和從前一個Survivor復制過來的對象,而復制到年老區的只有從第一個Survivor去過來的對象。而且,Survivor區總有一個是空的。
新生代使用復制算法和標記-清除垃圾收集算法,新生代中98%的對象是朝生夕死的短生命周期對象,所以不需要將新生代劃分為容量大小相等的兩部分內存,而是將新生代分為Eden區,Survivor from(Survivor 0)和Survivor to(Survivor1)三部分,其占新生代內存容量默認比例分別為8:1:1,其中Survivor from和Survivor to總有一個區域是空白,只有Eden和其中一個Survivor總共90%的新生代容量用于為新創建的對象分配內存,只有10%的Survivor內存浪費,當新生代內存空間不足需要進行垃圾回收時,仍然存活的對象被復制到空白的Survivor內存區域中,Eden和非空白的Survivor進行標記-清理回收,兩個Survivor區域是輪換的。
如果空白Survivor空間無法存放下仍然存活的對象時,使用內存分配擔保機制,直接將新生代依然存活的對象復制到年老代內存中,同時對于創建大對象時,如果新生代中無足夠的連續內存時,也直接在年老代中分配內存空間。
Java虛擬機對新生代的垃圾回收稱為Minor GC,次數比較頻繁,每次回收時間也比較短。
使用java虛擬機-Xmn參數可以指定新生代內存大小。
(2)Tenured(年老代、舊生代):JVMspecification中的 Heap的一部份年老代存放從年輕代存活的對象。一般來說年老代存放的都是生命期較長的對象。
年老代中的對象一般都是長生命周期對象,對象的存活率比較高,因此在年老代中使用標記-整理垃圾回收算法。
Java虛擬機對年老代的垃圾回收稱為MajorGC/Full GC,次數相對比較少,每次回收的時間也比較長。
java虛擬機-Xms參數可以指定最小內存大小,-Xmx參數可以指定最大內存大小,這兩個參數分別減去Xmn參數指定的新生代內存大小,可以計算出年老代最小和最大內存容量。
(3)Perm(持久代、永久代): JVM specification中的 Method area 用于存放靜態文件,如今Java類、方法等。持久代對垃圾回收沒有顯著影響,但是有些應用可能動態生成或者調用一些class,例如Hibernate等,在這種時候需要設置一個比較大的持久代空間來存放這些運行過程中新增的類。
java虛擬機內存中的方法區在SunHotSpot虛擬機中被稱為永久代,是被各個線程共享的內存區域,它用于存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯后的代碼等數據。永久代垃圾回收比較少,效率也比較低,但是也必須進行垃圾回收,否則會永久代內存不夠用時仍然會拋出OutOfMemoryError異常。
永久代也使用標記-整理算法進行垃圾回收,java虛擬機參數-XX:PermSize和-XX:MaxPermSize可以設置永久代的初始大小和最大容量。
5、垃圾回收過程
上面我們看了JVM的內存分區管理,現在我們來看JVM的垃圾回收工作是怎樣運作的。
首先當啟動J2EE應用服務器時,JVM隨之啟動,并將JDK的類和接口,應用服務器運行時需要的類和接口以及J2EE應用的類和接口定義文件也及編譯后的Class文件或JAR包中的Class文件裝載到JVM的永久存儲區。在Eden Space中創建JVM,存儲應用服務器運行時必須的JAVA對象,創建J2EE應用啟動時必須創建的JAVA對象;J2EE應用啟動完畢,可對外提供服務。
JVM在Eden區根據用戶的每次請求創建相應的JAVA對象,當Eden區的空間不足以用來創建新JAVA對象的時候,JVM的垃圾回收器執行對Eden區的垃圾回收工作,銷毀那些不再被其他對象引用的JAVA對象(如果該對象僅僅被一個沒有其他對象引用的對象引用的話,此對象也被歸為沒有存在的必要,依此類推),并將那些被其他對象所引用的JAVA對象移動到Survivor0區。
如果Survivor0區有足夠空間存放則直接放到Survivor0區;如果Survivor0區沒有足夠空間存放,則JVM的垃圾回收器執行對Survivor0區的垃圾回收工作,銷毀那些不再被其他對象引用的JAVA對象,并將那些被其他對象所引用的JAVA對象移動到Survivor1區。
如果Survivor1區有足夠空間存放則直接放到Survivor1區;如果Survivor1區沒有足夠空間存放,則JVM的垃圾回收器執行對Survivor1區的垃圾回收工作,銷毀那些不再被其他對象引用的JAVA對象,并將那些被其他對象所引用的JAVA對象移動到年老區。
如果年老區有足夠空間存放則直接放到年老區;如果年老區沒有足夠空間存放,則JVM的垃圾回收器執行對年老區的垃圾回收工作,銷毀那些不再被其他對象引用的JAVA對象,并保留那些被其他對象所引用的JAVA對象。
如果到最后年老區,Survivor1區,Survivor0區和Eden區都沒有空間的話,則JVM會報告“JVM堆空間溢出(java.lang.OutOfMemoryError: Java heap space)”,也即是在堆空間沒有空間來創建對象。
這就是JVM的內存分區管理,相比不分區來說;一般情況下,垃圾回收的速度要快很多;因為在沒有必要的時候不用掃描整片內存而節省了大量時間。
6、對象的空間分配和晉升:
(1)對象優先在Eden上分配
對象的內存分配,往大方向上講就是在堆上分配,對象主要分配在新生代的Eden Space和From Space,少數情況下會直接分配在老年代。如果新生代的Eden Space和From Space的空間不足,則會發起一次GC,如果進行了GC之后,Eden Space和From Space能夠容納該對象就放在Eden Space和From Space。在GC的過程中,會將Eden Space和From Space中的存活對象移動到To Space,然后將Eden Space和From Space進行清理。如果在清理的過程中,To Space無法足夠來存儲某個對象,就會將該對象移動到老年代中。在進行了GC之后,使用的便是Eden space和To Space了,下次GC時會將存活對象復制到From Space,如此反復循環。當對象在Survivor區躲過一次GC的話,其對象年齡便會加1,默認情況下,如果對象年齡達到15歲,就會移動到老年代中。
(2)大對象直接進入老年代
所謂的大對象是指需要大量連續存儲空間的對象,最常見的一種大對象就是大數組,比如:
byte[] data = new byte[4*1024*1024]這種一般會直接在老年代分配存儲空間。
當然分配的規則并不是百分之百固定的,這要取決于當前使用的是哪種垃圾收集器組合和JVM的相關參數。
虛擬機提供了-XX:PretenureSizeThreshold參數,大于這個參數值的對象將直接分配到老年代中。因為新生代采用的是標記-復制策略,在Eden中分配大對象將會導致Eden區和兩個Survivor區之間大量的內存拷貝。
(3)長期存活的對象將進入老年代
對象在Survivor區中每熬過一次MinorGC,年齡就增加1歲,當它的年齡增加到一定程度(默認為15歲)時,就會晉升到老年代中。
7、觸發:何時開始GC
Minor GC(新生代回收)的觸發條件比較簡單,Eden空間不足就開始進行Minor GC回收新生代。
而Full GC(老年代回收,一般伴隨一次MinorGC)則有幾種觸發條件:
(1)老年代空間不足
(2)PermSpace空間不足
(3)統計得到的MinorGC晉升到老年代的平均大小大于老年代的剩余空間
這里注意一點:PermSpace并不等同于方法區,只不過是HotspotJVM用PermSpace來實現方法區而已,有些虛擬機沒有PermSpace而用其他機制來實現方法區。
8、JVM中的回收器類型
8.1 串行回收器(Serial Collector)
單線程執行回收操作,回收期間暫停所有應用線程的執行,client模式下的默認回收器:
(1)年輕代的回收算法(Minor Collection):把Eden區的存活對象移到To區,To區裝不下直接移到年老代,把From區的移到To區,To區裝不下直接移到年老代,From區里面年齡很大的升級到年老代。 回收結束之后,Eden和From區都為空,此時把From和To的功能互換,From變To,To變From,每一輪回收之前To都是空的。設計的選型為復制。
(2)年老代的回收算法(Full Collection):年老代的回收分為三個步驟,標記(Mark)、清除(Sweep)、合并(Compact)。標記階段把所有存活的對象標記出來,清除階段釋放所有死亡的對象,合并階段把所有活著的對象合并到年老代的前部分,把空閑的片段都留到后面。設計的選型為合并,減少內存的碎片。
8.2 并行回收器(Parallel Collector)
使用多個線程同時進行垃圾回收,多核環境里面可以充分的利用CPU資源,減少回收時間,增加JVM生產率,Server模式下的默認回收器。與串行回收器相同,回收期間暫停所有應用線程的執行。
(1)年輕代的回收算法(Minor Collection):使用多個線程回收垃圾,每一個線程的算法與串行回收器相同。
(2)年老代的回收算法(Full Collection):年老代依然是單線程的,與串行回收器相同。
8.3 并行合并收集器(Parallel Compacting Collection)
年輕代和年老代的回收都是用多線程處理。與并行回收器相比,年老代的回收時間更短,從而減少了暫停時間間隔(Pause time)。
(1)年輕代的回收算法(Minor Collection):與并行回收器(ParallelCollector)相同
(2)年老代的回收算法(Full Collection) :年老代分為三個步驟,標記、統計、合并。這里用到分的思想,把年老代劃分為很多個固定大小的區(region)。標記階段,把所有存活的對象劃分為N組(應該與回收線程數相同),每一個線程獨立的負責自己那一組,標記存活對象的位置以及所在區(Region)的存活率信息,標記為并行的。統計階段,統計每一個區(Region)的存活率,原則上靠前面的存活率較高,從前到后,找到值得合并的開始位置(絕大多數對象都存活的區不值得合并),統計階段是串行的(單線程)。合并階段,依據統計階段的信息,多線程并行的把存活的對象從一個區(Region)復制到另外一個區(Region)。
8.4 并發標記清除回收器(Concurrent Mark-Sweep Collector)
又名低延時收集器(Low-latencyCollector),通過各種手段使得應用程序被掛起的時間最短。基本與應用程序并發地執行回收操作,沒有合并和復制操作。
(1)年輕代的回收算法(Minor Collection):與并行回收器(ParallelCollector)相同
(2)年老代的回收算法(Full Collection) :分為四個步驟,初始標記(Initial Mark)、并發標記(ConcurrentMark)、再次標記(Remark)、以及并發清理(Concurrent Sweep)。特別注意,沒有合并操作,所以會有碎片。
初始化階段: 暫停應用線程,找出所有存活的對象,耗時比較短,回收器使用單線程。
并發標記階段: 回收器標記操作與應用并發運行,回收器使用單線程標記存活對象。
再次標記:并發標記階段由于應用程序也在運行,這個過程中可能新增或者修改對象。所以再次暫停應用線程,找出所有修改的對象,使用多線程標記。
并發清理:回收器清理操作與應用并發運行,回收器使用單線程清理死亡對象。
8.5 幾種典型的垃圾收集器
垃圾收集算法是 內存回收的理論基礎,而垃圾收集器就是內存回收的具體實現。下面介紹一下HotSpot(JDK 7)虛擬機提供的幾種垃圾收集器,用戶可以根據自己的需求組合出各個年代使用的收集器。
(1)Serial/Serial Old
Serial/Serial Old收集器是最基本最古老的收集器,它是一個單線程收集器,并且在它進行垃圾收集時,必須暫停所有用戶線程。Serial收集器是針對新生代的收集器,采用的是Copying算法,Serial Old收集器是針對老年代的收集器,采用的是Mark-Compact算法。它的優點是實現簡單高效,但是缺點是會給用戶帶來停頓。
(2)ParNew
ParNew收集器是Serial收集器的多線程版本,使用多個線程進行垃圾收集。
(3)Parallel Scavenge
Parallel Scavenge收集器是一個新生代的多線程收集器(并行收集器),它在回收期間不需要暫停其他用戶線程,其采用的是Copying算法,該收集器與前兩個收集器有所不同,它主要是為了達到一個可控的吞吐量。
(4)Parallel Old
Parallel Old是Parallel Scavenge收集器的老年代版本(并行收集器),使用多線程和Mark-Compact算法。
(5)CMS
CMS(Current Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器,它是一種并發收集器,采用的是Mark-Sweep算法。
(6)G1
G1收集器是當今收集器技術發展最前沿的成果,它是一款面向服務端應用的收集器,它能充分利用多CPU、多核環境。因此它是一款并行與并發收集器,并且它能建立可預測的停頓時間模型。
9、使用垃圾收集器要注意的地方
下面將提出一些有關垃圾收集器要注意的地方,垃圾收集器知識很多,下面只列出一部分必要的知識:
(1)每個對象只能調用finalize( )方法一次。如果在finalize( )方法執行時產生異常(exception),則該對象仍可以被垃圾收集器收集。
(2)垃圾收集器跟蹤每一個對象,收集那些不可觸及的對象(即該對象不再被程序引用 了),回收其占有的內存空間。但在進行垃圾收集的時候,垃圾收集器會調用該對象的finalize( )方法(如果有)。如果在finalize()方法中,又使得該對象被程序引用(俗稱復活了),則該對象就變成了可觸及的對象,暫時不會被垃圾收集了。但是由于每個對象只能調用一次finalize( )方法,所以每個對象也只可能 “復活 “一次。
(3)Java語言允許程序員為任何方法添加finalize( )方法,該方法會在垃圾收集器交換回收對象之前被調用。但不要過分依賴該方法對系統資源進行回收和再利用,因為該方法調用后的執行結果是不可預知的。
(4)垃圾收集器不可以被強制執行,但程序員可以通過調研System.gc方法來建議執行垃圾收集。記住,只是建議。一般不建議自己寫System.gc,因為會加大垃圾收集工作量。
超強干貨來襲 云風專訪:近40年碼齡,通宵達旦的技術人生總結
以上是生活随笔為你收集整理的【深入理解JVM】JVM垃圾回收机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【大话Hibernate】hiberna
- 下一篇: ![CDATA[ ]]