Java虛擬機二、垃圾回收與內存分配(1)概述

垃圾回收概述

1.垃圾回收

  • 垃圾回收,或稱垃圾收集(Garbage Collection,GC)是指自動管理回收不再被引用的內存數據。
  • Lisp語言首次使用了動態內存分配和垃圾收集技術,可以實現垃圾回收的一個基本要求是語言是類型安全的,現在使用的包括Java、Perl、ML等。

1.1瞭解垃圾回收的必要性?

  • 1、當需要排查各種內存溢出、內存泄漏問題時;
  • 2、當垃圾收集成爲系統達到更高併發量的瓶頸時;

1.2垃圾回收到底幹了哈?

1. 哪些內存需要回收?即如何判斷對象已經死亡;
2. 什麼時候回收?即GC發生在什麼時候?需要了解GC策略,與垃圾回收器實現有關;
3. 如何回收?即需要了解垃圾回收算法,及算法的實現–垃圾回收器;

2.如何判斷一個對象是不是可以回收?

2.1引用計數法

  • 1、思路:
    給對象添加一個引用計數器,每當有一個地方引用它,計數器加1;
    當引用失效,計數器值減1;
    任何時刻計數器值爲0,則認爲對象是不再被使用的;

  • 2、優點
    實現簡單,判定高效,可以很好解決大部分場景的問題;

  • 3、缺點

  1. 很難解決對象之間相互循環引用的問題
  2. 並且開銷較大,頻繁且大量的引用變化,帶來大量的額外運算;
    主流的JVM都沒有選用引用計數算法來管理內存;

2.2 可達性分析方法(主流的實現)

  • 1、思路:
    通過一系列"GC Roots"對象作爲起始點,開始向下搜索;
    搜索所走過和路徑稱爲引用鏈(Reference Chain);
    當一個對象到GC Roots沒有任何引用鏈相連時(從GC Roots到這個對象不可達),則證明該對象是不可用的;

在這裏插入圖片描述

  • 2、GC ROOTs對象

1.虛擬機棧(棧幀中本地變量表)中引用的對象;
2. 方法區中類靜態屬性引用的對象;
3. 方法區中常量引用的對象;
4. 本地方法棧中JNI(Native方法)引用的對象;

  • 3、優點

    更加精確和嚴謹,可以分析出循環數據結構相互引用的情況;

  • 4、缺點

  1. 實現比較複雜;
  2. 需要分析大量數據,消耗大量時間;
  3. 分析過程需要GC停頓(引用關係不能發生變化),即停頓所有Java執行線程(稱爲"Stop The World",是垃圾回收重點關注的問題);

後面會針對HotSpot虛擬機實現的可達性分析算法進行介紹,看看是它如何解決這些缺點的。

2.3對象引用

  • 引用的狹義的定義(jdk1.2之前)
    如果reference類型的數據中存儲的數值代表的是另外一塊內存的起始地址,就稱這塊內存代表着一個引用;
  • 引用的分類(jdk1.2之後)
    1.強引用(Strong Reference)
    程序代碼普遍存在的,類似"Object obj=new Object()";
    只要強引用還存在,GC永遠不會回收被引用的對象;
  1. 軟引用(Soft Reference)

    用來描述還有用但並非必需的對象;
    直到內存空間不夠時(拋出OutOfMemoryError之前),纔會被垃圾回收;
    最常用於實現對內存敏感的緩存;
    SoftReference類實現;

  2. 弱引用(Weak Reference)

    用來描述非必需對象;
    只能生存到下一次垃圾回收之前,無論內存是否足夠
    WeakReference類實現;

  3. 虛引用(Phantom Reference)

    也稱爲幽靈引用或幻影引用;
    完全不會對其生存時間構成影響;
    唯一目的就是能在這個對象被回收時收到一個系統通知;
    PhantomRenference類實現;

2.4判斷對象生存或者是死亡的過程

要真正宣告一個對象死亡,至少要經歷兩次標記過程。

  1. 第一次標記

    在可達性分析後發現到GC Roots沒有任何引用鏈相連時,被第一次標記;
    並且進行一次篩選:此對象是否必要執行finalize()方法:

    1. 沒有必要執行

      沒有必要執行的情況:

      (1)對象沒有覆蓋finalize()方法;

      (2)finalize()方法已經被JVM調用過;

      這兩種情況就可以認爲對象已死,可以回收;

    1. 有必要執行

      對有必要執行finalize()方法的對象,被放入F-Queue隊列中;

      稍後在JVM自動建立、低優先級的Finalizer線程(可能多個線程)中觸發這個方法;

  1. 第二次標記

    GC將對F-Queue隊列中的對象進行第二次小規模標記

finalize() 方法

finalize()方法是對象逃脫死亡的最後一次機會
(A)如果對象在其finalize()方法中重新與引用鏈上任何一個對象建立關聯,第二次標記時會將其移出"即將回收"的集合;
(B、如果對象沒有,也可以認爲對象已死,可以回收了;
一個對象的finalize()方法只會被系統自動調用一次,經過finalize()方法逃脫死亡的對象,第二次不會再調用

上面已經說到finalize()方法與垃圾回收第二次標記相關,下面瞭解下在Java語言層面有哪些需要注意的。

finalize()是Object類的一個方法,是Java剛誕生時爲了使C/C++程序員容易接受它所做出的一個妥協,但不要當作類似C/C++的析構函數;
因爲它執行的時間不確定,甚至是否被執行也不確定(Java程序的不正常退出),而且運行代價高昂,無法保證各個對象的調用順序(甚至有不同線程中調用);

如果需要"釋放資源",可以定義顯式的終止方法,並在"try-catch-finally"的**finally{}**塊中保證及時調用,如File相關類的close()方法;
此外,finalize()方法主要有兩種用途:

  1. 充當"安全網"

    當顯式的終止方法沒有調用時,在finalize()方法中發現後發出警告;
    但要考慮是否值得付出這樣的代價;
    如FileInputStream、FileOutputStream、Timer和Connection類中都有這種應用;

  2. 與對象的本地對等體有關

    本地對等體:普通對象調用本地方法(JNI)委託的本地對象;
    本地對等體不會被GC回收;
    如果本地對等體不擁有關鍵資源,finalize()方法裏可以回收它(如C/C++中malloc(),需要調用free());
    如果有關鍵資源,必須顯式的終止方法;

一般情況下,應儘量避免使用它,甚至可以忘掉它。(前面這麼多白看了。。。。)

可達性分析算法的實現(以hotpot虛擬機爲例)

1. 可達性分析所帶來的問題
  1. 消耗大量時間

    從前面可達性分析知道,GC Roots主要在全局性的引用(常量或靜態屬性)和執行上下文中(棧幀中的本地變量表);
    要在這些大量的數據中,逐個檢查引用,會消耗很多時間;

  2. GC停頓

    可達性分析期間需要保證整個執行系統的一致性對象的引用關係不能發生變化
    導致GC進行時必須停頓所有Java執行線程(稱爲"Stop The World");
    (幾乎不會發生停頓的CMS收集器中,枚舉根節點時也是必須要停頓的)

    Stop The World: 是JVM在後臺自動發起和自動完成的;
    在用戶不可見的情況下,把用戶正常的工作線程全部停掉;

2. 枚舉根節點(查找GC Roots)
  • 目前主流JVM都是準確式GC,可以直接得知哪些地方存放着對象引用,所以執行系統停頓下來後,並不需要全部、逐個檢查完全局性的和執行上下文中的引用位置;
  • 在HotSpot中,是使用一組稱爲OopMap的數據結構來達到這個目的的;
    在類加載時,計算對象內什麼偏移量上是什麼類型的數據;
    在JIT編譯時,也會記錄棧和寄存器中的哪些位置是引用;
    這樣GC掃描時就可以直接得知這些信息;
3.安全點
  • 安全點是幹哈的?
    OopMap數據結構可以有助於快而準地完成GC Roots的枚舉,但有一個問題:
    指令導致對象間的引用關係發生變化,如果爲每一條指令都生成一個OopMap,需要的空間成本太高
  • 解決方法:
    並非爲每一條指令都生成OopMap,只在特定的位置(安全點)記錄這些信息。即:程序並非在所有的地方都會停頓下來開始GC,只有在安全點彩可以
  • 安全點的選取:
    選擇標準:是否具備讓程序長時間執行的特徵
    "長時間執行"最明顯的特徵就是指令序列複用,如:方法調用、循環跳轉、循環的末尾、異常跳轉等;
    只有具有這些功能的指令纔會產生Safepoint;
* 安全點的停頓

即:如何讓所有的線程都跑到最近的安全點上在停頓

    1. 搶先式中斷(現在幾乎沒有JVM實現採用這種方式)
      不需要線程主動配合,實現如下:
      (1)在GC發生時,首先中斷所有線程;
      (2)如果發現不在Safepoint上的線程,就恢復讓其運行到Safepoint上;
    1. 主動式中斷(Voluntary Suspension)
      (1)在GC發生時,不直接操作線程中斷,而是僅簡單設置一個標誌
      (2)讓各線程執行時主動去輪詢這個標誌,發現中斷標誌爲真時就自己中斷掛起;
      而輪詢標誌的地方和Safepoint是重合的;
4. 安全區域
  • 出現的原因:
    程序在沒有佔用CPU時(sleep或是block), 無法運行到安全點上掛起

  • 啥是安全區域
    指一段代碼片段中,引用關係不會發生變化;在這個區域中的任意地方開始GC都是安全的;

  • 安全區域解決問題的思路:

    1. 線程執行進入Safe Region,首先標識自己已經進入Safe Region;
    2. 線程被喚醒離開Safe Region時,其需要檢查系統是否已經完成根節點枚舉(或整個GC);
      如果已經完成,就繼續執行;
      否則必須等待,直到收到可以安全離開Safe Region的信號通知;這樣就不會影響標記結果;

雖然HotSpot虛擬機中採用了這些方法來解決對象可達性分析的問題,但只是大大減少了這些問題影響,並不能完全解決,如GC停頓"Stop The World"是垃圾回收重點關注的問題,後面介紹垃圾回收器時應注意:低GC停頓是其一個關注。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章