上篇文章介紹了JVM記憶體模型的相關知識,其實還有些內容可以更深入的介紹下,比如運行時常量池的動態插入,直接內存等,後期抽空再完善下上篇博客,今天來介紹下JVM中的一些垃圾回收策略。
一、finailize()方法
當物件沒有任何引用的時候,通常這個物件會被回收掉,但如果我們想在物件被回收前進行一些操作,例如關閉一些資源,或者讓這個物件復活,不讓他被回收怎麼辦?這時候就要用到finailize方法了。 finailize方法是Object類別中定義的方法,表示任何一個物件都有這個方法。但這個方法只會呼叫一次,如果把這個物件復活後再次讓這個物件死亡,那第2次回收該物件的時候是不會呼叫finailize方法的,而且優先順序比較低,並不能保證一定會被執行,因此不建議使用finalize方法。總結起來就是3個特性: ①、GC之前被呼叫 。 ②、只會被呼叫一次。 ③、不可靠,不能保證被執行,不建議使用。關於finalize使用方法,參考以下程式碼:
1 public class FinalizeTest { 2 3 private static FinalizeTest test; 4 /** 5 * VM参数:-XX: +PrintGCDetails -Xmx=1M -Xms=1M 6 * 7 * @param args 8 */ 9 public static void main(String[] args) { 10 //先对test对象赋值 11 test = new FinalizeTest(); 12 int _1m = 1024 * 1024; 13 //将test置为null,便于回收 14 test = null; 15 try { 16 System.gc(); 17 //模拟睡眠5s,finalize优先级较低,保证finalize能执行 18 Thread.sleep(5000); 19 } catch (InterruptedException e) { 20 e.printStackTrace(); 21 } 22 if (test != null) { 23 System.out.println("first,i am alive"); 24 }else{ 25 System.out.println("first,i am dead"); 26 } 27 //由于test在finalize方法里复活了,再次将test置为null 28 test = null; 29 try { 30 System.gc(); 31 Thread.sleep(5000);//模拟睡眠5s,让GC回收 32 } catch (InterruptedException e) { 33 e.printStackTrace(); 34 } 35 if (test != null) { 36 System.out.println("second,i am alive"); 37 }else{ 38 System.out.println("second,i am dead"); 39 } 40 41 } 42 @Override 43 protected void finalize() throws Throwable { 44 test = this ; 45 System.out.println("finalize excuted"); 46 super.finalize(); //调用父类的finailize方法 47 } 48 }
程式碼運作結果如下:
# finalize方法執行後,test物件又重新啟動了,因此印了first,i am alive。但是第二次GC的時候,finalize方法並未被執行,因此印製了second,i am dead。前面提到finalize是優先權低不可靠的,那如果沒有Thread.sleep(5000),再來看下程式碼與結果:
1 public class FinalizeTest { 2 3 private static FinalizeTest test; 4 /** 5 * VM参数:-XX: +PrintGCDetails -Xmx=1M -Xms=1M 6 * 7 * @param args 8 */ 9 public static void main(String[] args) { 10 //先对test对象赋值 11 test = new FinalizeTest(); 12 int _1m = 1024 * 1024; 13 //将test置为null,便于回收 14 test = null; 15 try { 16 System.gc(); 17 //模拟睡眠5s,finalize优先级较低,保证finalize能执行 18 //不执行睡眠操作,Thread.sleep(5000); 19 } catch (Exception e) { 20 e.printStackTrace(); 21 } 22 if (test != null) { 23 System.out.println("first,i am alive"); 24 }else{ 25 System.out.println("first,i am dead"); 26 } 27 //由于test在finalize方法里复活了,再次将test置为null 28 test = null; 29 try { 30 System.gc(); 31 //不执行睡眠操作,Thread.sleep(5000);//模拟睡眠5s,让GC回收 32 } catch (Exception e) { 33 e.printStackTrace(); 34 } 35 if (test != null) { 36 System.out.println("second,i am alive"); 37 }else{ 38 System.out.println("second,i am dead"); 39 } 40 41 } 42 @Override 43 protected void finalize() throws Throwable { 44 test = this ; 45 System.out.println("finalize excuted"); 46 super.finalize(); //调用父类的finailize方法 47 } 48 }
## 運行結果如下:
這裡清楚且有清楚地看到,finalize方法的優先順序是比較低的。 關於這個例子的反思:這個例子中第一段程式碼是參考《深入理解java虛擬機》裡的程式碼實現的,但是總感覺有2點疑問:為什麼test物件是用static修飾的成員變數方式存在?如果是static修飾,那就是存在方法區了,而方法區的GC通常效果不太好的。另一個是以成員變數的方式存在,這樣finalize回收的時候,體現不出是對當前物件本身的回收,所以感覺這個例子並不是很好。 二、引用計數法
引用計數法是一種比較早的GC回收演算法,目前一般不採用,其主要思想是:每個物件維持一個引用計數器,初始值為0,當一個物件被引用的時候,該物件的引用計數器就加1,當不被引用的時候,該物件的引用計數器就減1,如果一個物件的引用計數器變成了0,則該物件被認為是可以回收的。採用這種方式的優缺點都很明顯,優點是實現簡單,效率高,缺點是可能存在循環引用,導致記憶體溢位。
三、標記-清除法
######## ###### 標記清除法依名字分為「標記」與「清除」2個階段,其基本想法為:###先標記所有存活的對象,標記完成後,統一清除所有被標記的對象。那怎麼判斷某個物件是可以回收的呢? GC時,從一系列GC Roots根節點開始遍歷,遍歷時走過的路徑即稱為引用鏈,如果一個物件和GC Roots沒有任何引用鏈相關,那麼這個物件就不可用,就會被判定為可回收,這種演算法也叫根搜尋演算法###。那麼哪些物件可以成為GC Roots物件呢?在java語言裡,可以作為GC Roots的物件包括下面4種: ###### 虛擬機堆疊中的引用變數 ###### # 方法區中的靜態屬性中的類別靜態屬性的物件參考# 方法區中的常數所引用的物件 ####
本地方法堆疊中JNI(即native方法)的引用的物件
標記-清除法的演算法示意圖如下:
o ##o#o#的GC回收演算法圖片轉自一個網友的文章(點這裡),該網友的圖片內容也與原著一致,只是顏色不同。
四、新生代的複製法
複製法的基本想法是:將記憶體分為大小相等的2塊,每次只使用其中一塊,GC時每次將所有存活的物件複製到另一塊區域,然後清理該記憶體。 這幾種都是方法區和堆疊中的參考物件。複製法的優點是:實現簡單,回收速度快,且不會產生記憶體碎片。但由於每次只使用其中一塊,導致記憶體使用率較低。複製演算法的示意圖如下:
相關推薦:
jvm垃圾回收演算法以上是JAVA虛擬機器學習筆記:JVM記憶體模型中垃圾回收方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!