歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

深入Java虛擬機--判斷對象存活狀態

程序計數器,虛擬機棧和本地方法棧

  首先我們先來看下垃圾回收中不會管理到的內存區域,在Java虛擬機的運行時數據區我們可以看到,程序計數器,虛擬機棧,本地方法棧這三個地方是比較特別的。這個三個部分的特點就是線程私有的,它們隨著線程的創建而誕生,也因線程的結束而滅亡。棧中的棧幀隨著方法的進入和退出會有條不絮的執行著進棧和出棧。每一個棧幀中分配多少內存,基本上是在類結構確認下來的時候就已知的,因此這幾個區域的內存分配和回收都具備確定性,在這幾個區域內就不需要過多考慮回收的問題,因為方法結束或者線程結束,內存自然就跟隨著回收了。

Java堆和方法區

  我們討論的垃圾回收,主要就是關於Java堆中廢棄對象的回收。Java堆和方法區中,一個接口中的多個實現類需要的內存可能不一樣,一個方法中的多個分支需要的內存也可能不一樣,我們會在程序運行的時候動態創建對象,這部分內存的分配和回收也是動態的,垃圾收集器所關注的是這部分內存如何進行回收。

對象狀態判斷

  在研究對象的回收之前,我們需要先看一下如何進行判斷對象是否還有存活價值,即要先判斷對象是否還有被引用,這是我們進行垃圾回收的第一步,判斷對象存活狀態。接下來我會講一下幾種判斷的方法。

   1.、引用計數法

       一個比較通俗的方法就是當對象在創建的時候,就給對象創建一個對象計數器,每當有一個地方引用到這個對象的時候,計數器加一;當引用失效的時候,計數器減1;任何時候計數器為0的對象就是不可能被使用的,就是我們所認知的 --死亡對象。 客觀地說,引用計數算法的實現比較簡單,判定效率也很高,在大部分情況下它都是一個不錯的算法,也有一些比較著名的應用案例,例如微軟公司的COM(Component Obejct Mode)技術,使用ActionScript3的FlashPlayer等技術都引用了技術算法進行內存管理。 但是,至少主流的Java虛擬機裡面沒有用到引用計數算法來管理內存,之中最主要的問題就是他很難解決對象之間相互循環引用的問題:

 

public class ReferenceGc {
    public Object instance = null;
    public static void main(String[] args){
        ReferenceGc gcA = new ReferenceGc();
        ReferenceGc gcB = new ReferenceGc();
        gcA.instance=gcB;
        gcB.instance=gcA;
        gcA=null;
        gcB=null;

        System.gc();
    }
}

    這裡面如果采用的是引用計數算法來進行垃圾回收的話,這種對象明明是沒有使用,但是卻仍然占內存,在Java中,這種情況是非常常見的,所以這種算法並不能解決Java中對象相互引用的問題,所以Java虛擬機判斷對象存活狀態的算法是選擇了接下來介紹的--可達性分析算法。

    2、可達性分析算法

                

      這個算法的基本思想是,通過一系列的GC Roots 的對象作為七點,從這些節點開始的向下搜索,搜索所走過的路徑成為引用連,當一個對象到GC Roots 沒有任何引用鏈相連時,則證明此對象時不可用的。 如上圖所示,雖然Object5,Object6和Object7相互有引用,但是他們與GC Roots間是不可達的,所以它們將會被判定為使可回收的對象。

      大家可能會很疑惑,那為什麼Object5為什麼不能當GC Roots呢?

         首先,我們要規定好GC Roots的定義范圍,並不是說所有的對象都能夠成為GC Roots:

         在Java 語言中,可作為GC Roots 的對象包括下面幾種:

             - 虛擬機棧(棧幀中的本地變量表)引用的對象

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

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

             - 本地方法棧中JNI引用的對象

對象在被回收前最後的自我救贖

  

    上面我們說完了Java虛擬機中判斷對象存活狀態的算法,那麼是不是說我們判斷出對象時沒有(有效)引用之後就會被馬上回收呢?實際上並不是這樣的,即時在可達性分析分算法中不可達的對象,也並非是“非死不可”的,這時候它們暫時處於“緩刑”階段。

      對象要被宣告死亡要經歷兩個階段:

           第一個階段就是可達性分析,判斷對象是否具有有效引用

           第二個階段就是判斷對象是否有必要執行 finalize()方法。當對象沒有覆蓋finalize()方法或者finalize()方法已經被虛擬機調用過,虛擬機將這兩種情況稱為“沒有必要執行”,就是對象已經沒有任何機會完成救贖。

package com.Shop.Test;

/**
 * Created by Administrator on 2016/7/25.
 */
public class FinalizeEscapeGc {
    private static FinalizeEscapeGc SAVE_HOOK = null;
    public void isAlive(){
        System.out.println("yes, i am still alive");
    }

    @Override
    protected  void finalize() throws Throwable{
        super.finalize();
        System.out.println("finalize method executed!");
        FinalizeEscapeGc.SAVE_HOOK = this;
    }

    public static void main(String[] args) throws InterruptedException {
        SAVE_HOOK = new FinalizeEscapeGc();
        SAVE_HOOK = null;
        System.gc();
        Thread.sleep(500);
        if(SAVE_HOOK!= null){
            SAVE_HOOK.isAlive();
        }else{
            System.out.println("no ,i am dead ");
        }


        SAVE_HOOK = null;
        System.gc();
        Thread.sleep(500);
        if(SAVE_HOOK!= null){
            SAVE_HOOK.isAlive();
        }else{
            System.out.println("no ,i am dead ");
        }
    }
}

輸出結果:

finalize method executed!
yes, i am still alive
no ,i am dead

      我們可以看到,在對象“死亡”之後,並沒有馬上死亡,我們覆蓋了父類的finalized 方法,這時候就會給對象一次救贖的機會,但是也就僅此一次,當對象第二次“死亡”的時候,我們發現,即使是覆蓋了finalized方法也沒有辦法再去拯救這個對象了。

      從上面的例子我們可以很直白的理解這個finalized方法在垃圾回收中起到的作用,那就是一次且僅一次救贖的機會。

方法區的回收

   實際上方法區還是會進行垃圾回收,但是效率遠遠比不上堆中垃圾的回收,因此在垃圾回收中經常會被忽略掉。方法區中,垃圾收集的主要內容分為兩部分:廢棄常量和無用的類

     1. 廢棄常量

        這部分我們會比較容易理解,就舉一個簡單的例子,String str= "abc";這時候abc就已經進入了常量池中,當str改變類值,那麼久沒有對象引用"abc"這個常量,這時候,"abc"就會被系統清理出常量池。常量池中的其他類、方法、字段的符號引用也是如此

    

    2. 無用的類

        判定一個類是否是無用的類的條件相對苛刻許多。類需要同時滿足以下三個條件才能算是“無用的類”

            1。該類所有的實例都已經被回收,也就是Java堆中已經不存在該類的任何實例

            2。加載該類的ClassLoader已經被回收

            3。該類對象的Java.lang.Class 對象沒有在任何地方呗引用,無法再任何地方通過反射訪問該類

       虛擬機可以對滿足上述3個條件的無用類對象進行回收,這裡說的僅僅是“可以”,行不是和對象一樣,不適用就必然回收。

      

       需要對HotSpot虛擬機的參數進行設置,控制回收。

       一般情況下我們不會對方法區的無用類進行回收,但是在大量使用反射,動態代理、CGLib等ByteCode框架、這類頻繁自定義ClassLoader的場景都需要虛擬機具備類卸載的功能,以保證永久代不會溢出。

Copyright © Linux教程網 All Rights Reserved