1.前言
在JVM內存運行時數據區中程序計數器、虛擬機棧、本地方法棧等三個區域是線程私有的,即線程結束對應的內存就會回收。而方法區和堆則是線程公有的,這部分內存只有在在程序運行期間纔會知道創建哪些對象,這部分內存是分配和回收都是動態的,而垃圾收集器所關注的就是這部分內存。
2.判斷對象是否可回收?
堆中存放的幾乎都是對象實例,在垃圾收集器對堆回收前需要先判斷對象是否已經能夠回收。
判斷對象是否可回收有兩種算法:
- 引用計數算法(java未使用)
- 根搜索算法
2.1.引用計數算法
定義:
給對象中添加一個引用計數器,每當有一個地方引用時就給計數器值加1,每當引用失效時就給計數器減1,任何時刻計數器都爲0的對象就是不可能被引用的,即對象可回收。
缺點: 無法處理相互引用的對象之間的關係。
2.2.根搜索算法
定義:
通過一系列的名爲“GC Roots”的對象作爲起始點,從這些節點開始向下搜索,搜索所有走過的路徑成爲引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連時,則證明此對象是不可用的,即可回收的對象。
2.3.java是否使用引用計數算法?
測試代碼如下:
package com.glt.gc;
/**
* 測試java是否使用引用計數算法
*/
public class ReferenceCounting {
public Object instance = null;
private static final int _1M = 1024 * 1024;
private byte[] bigSize = new byte[2 * _1M];
public static void main(String[] args) {
ReferenceCounting objA = new ReferenceCounting();
ReferenceCounting objB = new ReferenceCounting();
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;
System.gc();
}
}
輸入如下:
測試代碼中使用兩個對象相互引用,除此之外這兩個對象都無其他引用,因爲它們相互引用,導致他們的引用計數都不爲0,引用計數算法就無法通知GC收集器回收它們。但是從運行結果中看到標紅的部分,虛擬機還是進行了GC,即說明虛擬機並沒有使用引用計數算法來判斷對象是否存活。
2.4.內存回收之引用類型
java中有一類對象:當內存空間足夠時,則能保存在內存中,如果內存在垃圾回收後還非常緊張,就可以拋棄這些對象。爲了描述這些對象,java對象引用類型分爲四種,強度依次減弱:
- 強引用(Strong Reference)
強引用類似“Object obj = new Object()”,只要引用還在,永遠不會被回收。 - 軟引用(Soft Reference)
軟引用用來描述一些還有用但是非必須的對象,系統會在將要發生內存溢出之前把軟引用的對象列進回收範圍內進行第二次回收。如果回收後還沒有足夠的內存,纔會拋出內存溢出異常。 - 弱引用(Weak Reference)
若引用也是用來描述非必須對象的,強度比軟引用更弱,被弱引用關聯的對象只能生存到下一次垃圾收集發生之前。當垃圾收集器工作時,無論當前內存是否足夠,都會回收掉此類對象。 - 虛引用(Phantom Reference)
虛引用也稱爲幽靈引用或者幻影引用,是最弱的一種引用關係。一個對象是否有虛引用存在,完全不會對其生存時間造成影響,也無法通過虛引用獲取對象實例。爲一個對象設置虛引用關聯只是爲了能在這個對象唄收集器回收時收到一個系統通知。
2.5.內存回收之finalize
根搜索算法中不可達的對象,在被回收前會經歷兩次被標記的過程:
- 如果對象在進行根搜索後發現沒有與GC Roots相連的引用鏈,那麼它將會被第一次標記並且進行一次篩選,篩選條件是此對象對否有必要執行finalize()方法。如果對象沒有覆寫finalize()方法,或者finalize()方法已經調用過,則這種情況不會再執行finalize()方法。
- 如果對象被判定爲有必要執行finalize()方法,那麼這個對象將被放置在一個名爲F-Queue的隊列中,並在稍後由一條由虛擬機自動建立的、低優先級的Finalizer線程去執行(使用線程是爲了防止對象在執行finalize()時,執行太慢或者發生死鎖等佔用大量時間導致內存回收系統崩潰)。稍後GC將對F-Queue隊列進行第二次小規模標記,如果對象在finalize()方法中重新與引用鏈關聯上,則第二次標記時它將被移出“即將回收”的集合,不會被回收掉。
2.6.測試finalize()方法
package com.glt.gc;
/**
* finalize()測試
*/
public class FinalizeGC {
public static FinalizeGC inst = null;
public void isAlive() {
System.out.println("this is alive");
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize is exe! ");
inst = this;
}
public static void main(String[] args) throws Exception {
inst = new FinalizeGC();
inst = null;
System.gc();
//finalize優先級低,此處等待0.5秒
Thread.sleep(500);
//此處inst不爲null,並且finalize方法執行了
if(inst != null){
inst.isAlive();
}else{
System.out.println(" this is dead");
}
inst = null;
System.gc();
//finalize優先級低,此處等待0.5秒
Thread.sleep(500);
//此處inst爲null,並且finalize方法未執行
if(inst != null){
inst.isAlive();
}else{
System.out.println(" this is dead");
}
}
}
輸出如下:
finalize is exe!
this is alive
this is dead
以上結果中兩段代碼段完全一樣,執行結果卻是一次存活,一次死亡,證明了以下兩點:
- 對象可以在GC時自我拯救。
- 這種自救機會只有一次,因爲一個對象的finalize()方法最多隻會被系統調用一次。
2.7.回收方法區(永久代)
在堆的新生代中,常規應用進行一次垃圾收集,能收集70%-95%空間,而永久代的垃圾收集效率遠低於此。
永久代中的垃圾回收主要分爲兩部分:廢棄常量和無用的類。
- 廢棄常量
如果一個常量已經進入了常量池,但是沒有任何地方引用,則此爲廢棄常量,如果發生內存回收此常量會被回收掉。常量池中的其他類(接口)、方法、字段的符號引用也與此類似。 - 無用的類
無用的類需要滿足下面三個條件:
1.該類的所有實例都已經被回收;
2.加載類的ClassLoader已經被回收;
3.該類對應的java.lang.Class對象沒有在任何地方唄引用,無法在任何地方通過反射訪問該類的方法。
虛擬機可以選擇對滿足上面三個條件的類是否進行回收,HotSpot虛擬機可以通過-Xnoclassgc參數進行控制。
JVM查看類加載和卸載信息的相關參數如下
HotSpot虛擬
-verbose:class
-XX:+TraceClassLoading
-XX:+TraceClassUnLoading
Product版虛擬機
-verbose:class
-XX:+TraceClassLoading
fastdebug版虛擬機
-XX:+TraceClassUnLoading
參考資料:
《深入理解JAVA虛擬機》