深刻理解java虛擬機學習筆記(二)垃圾回收策略

            上篇文章介紹了JVM內存模型的相關知識,其實還有些內容能夠更深刻的介紹下,好比運行時常量池的動態插入,直接內存等,後期抽空再完善下上篇博客,今天來介紹下JVM中的一些垃圾回收策略。html

       1、finailize()方法              java

            在介紹GC策略前,先介紹下GC中的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 }

         該代碼運行結果以下:ide

         

         能夠看到,finalize方法執行後,test對象又被從新激活了,所以打印了first,i am alive。可是第二次GC的時候,finalize方法並未被執行,所以打印了second,i am dead。前面提到finalize是優先級低不可靠的,那若是沒有Thread.sleep(5000),再來看下代碼和結果:this

 
 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 }
 

        運行結果以下:  spa

       

       這裏能夠很清楚地看到,finalize方法的優先級是比較低的。3d

       關於這個例子的反思:這個例子中第一段代碼是參考《深刻理解java虛擬機》裏的代碼實現的,可是總感受有2點疑問:爲何test對象是以static修飾的成員變量方式存在?若是是static修飾,那就是存在方法區了,而方法區的GC一般效果不太好的。另外一個是以成員變量的方式存在,這樣finalize回收的時候,體現不出是對當前對象自己的回收,因此感受這個例子並非很好。code

      2、引用計數法htm

      引用計數法是一種比較早的GC回收算法,目前通常不採用,其主要思想是:每一個對象都維持一個引用計數器,初始值爲0,當一個對象被引用的時候,該對象的引用計數器就加1,當不被引用的時候,該對象的引用計數器就減1,若是一個對象的引用計數器變爲了0,則該對象被認爲是能夠回收的。採用這種方式的優缺點都很明顯,優勢是實現簡單,效率高,缺點是可能存在循環引用,致使內存溢出。對象

     3、標記-清除法 

         標記-清除法按名字分爲「標記」和「清除」2個階段,其基本思想是:首先標記出全部存活的對象,標記完成後,統一清除全部被標記的對象。那怎麼判斷某個對象是能夠回收的呢?GC時,從一系列GC Roots根節點開始遍歷,遍歷時走過的路徑即稱爲引用鏈,若是一個對象和GC Roots沒有任何引用鏈相關,那麼這個對象就不可用,就會被斷定爲可回收,這種算法也叫根搜索算法那麼哪些對象能夠成爲GC Roots對象呢?在java語言裏,能夠做爲GC Roots的對象包括下面4種: 

            虛擬機棧中的引用變量 

            方法區中的類靜態屬性引用的對象 

           方法區中的常量引用的對象 

           本地方法棧中JNI(即native方法)的引用的對象

           標記-清除法的算法示意圖以下:

 

     注:本文的GC回收算法圖片轉自一個網友的文章(點這裏),該網友的圖片內容也與原著一致,只是顏色不一樣。

     4、新生代的複製法

           複製法的基本思想是:將內存分爲大小相等的2塊,每次只使用其中一塊,GC時每次將全部存活的對象複製到另外一塊區域,而後清理該內存

           這幾種都是方法區和棧中的引用對象。複製法的優勢是:實現簡單,回收速度快,且不會產生內存碎片。但因爲每次只使用其中一塊,致使內存利用率較低。複製算法的示意圖以下:

        

        如今的商業虛擬機都採用複製法來回收新生代,因爲新生代的對象98%以上都是朝生夕死的,因此並不須要按照1:1來分配,而是將內存分爲較大的Eden區和2塊較小的Survivor區(一般Eden和Survivor區大小的比值爲8:1:1,能夠根據SurvivorRationJVM內存參數來設置比值),每次使用Eden區和其中一塊Survivor區類分配對象,GC時,將Eden區和Survivor區中的存活對象複製到另外一塊Survivor區域,這樣一來,內存利用率就高了,並且運行速度也很快。

       5、老年代的標記-整理法

           複製法在對象存活率較高時,回收效率就變低了,而在老年代中,大部分的對象都是存活期較高的對象,所以就不適宜採用複製法進行老年代的GC。根據老年代的特色,並結合標記-清除法的思路,因而提出了標記-整理法。其主要思路是:標記過程與標記-清除法一致,只是標記完成後,不直接對未存活進行清除,而是將全部存活的對象都向一端移動,而後清理掉端邊界之外的全部內存區域。這種方法的優勢是不會產生內存碎片。標記-整理法的算法示意圖以下:       

相關文章
相關標籤/搜索