1. 程式人生 > >詳解JVM記憶體管理與垃圾回收機制2

詳解JVM記憶體管理與垃圾回收機制2

隨著程式語言的發展,GC的功能不斷增強,效能也不斷提高,作為語言背後的無名英雄,GC離我們的工作似乎越來越遠。作為Java程式設計師,對這一點也許會有更深的體會,我們不需要了解太多與GC相關的知識,就能很好的完成工作。那還有必要深入瞭解GC嗎?學習GC的意義在哪兒?

不管效能提高到何種程度,GC都需要花費一定的時間,對於實時性要求較高的場景,就必須儘量壓低GC導致的最大暫停時間 (GC會導致應用執行緒處於暫停狀態),舉兩個例子:

  • 實時對戰遊戲:如果因為GC導致玩家頻繁卡頓,任誰都會想摔手機吧。
  • 金融交易:在某些對價格非常敏感的交易場景下(比如,外匯交易中價格的變動非常頻繁),如果因為GC導致沒有按照交易者指定的價格進行交易,相信我,這些交易者非生吃了你。

但也有許多場景,GC的最大暫停時間沒那麼重要,比如,離線分析、視訊網站等等。因此,知道這個GC演算法有這樣的特徵,所以它適合這個場景,對程式設計師來說非常有價值,這就是我們學習GC最重要的意義。接下來,我們將一步步走進GC的世界。

從誕生之初,人們就在思考GC需要完成的3件事情:何為垃圾?何時回收?如何回收?垃圾收集器在對記憶體進行回收前,第一件事就是要確定這些物件之中哪些還”活著“,哪些已經”死去“,而這些”死去“的物件,也就是我們所說的垃圾。

引用計數法

判斷物件是否存活,其中一種方法是給物件新增一個引用計數器,每當有一個地方引用它,計數器的值就加1,當引用失效時,計數器的值減1,任一時刻,如果物件的計數器值為0,那麼這個物件就不會再被使用,這種方法被稱為引用計數法。在整個回收過程中,引用計數器的值會以極快的速度更新,因而計數值的更新任務變得繁重,而且需要給計數器預留足夠大的記憶體空間,以確保它不會溢位。因此,引用計數法的演算法很簡單,但在實際運用中要考慮非常多的因素,所以它的實現往往比較複雜,更為重要的是它不能解決物件之間的迴圈引用問題。

舉個栗子,下面的程式碼片段展示了為什麼引用計數法無法解決迴圈引用的問題。

public class GcDemo {
    public static void main(String[] args) {
        // 在棧中分配記憶體空間給obj1,然後在堆中建立GcObject物件A
        // 將obj1指向A例項,這時A的引用計數值 = 1
        GcObject obj1 = new GcObject();
        // 同理,GcObject例項B的引用計數值 = 1
        GcObject obj2 = new GcObject();
        // GcObject例項2被引用,所以B引用計數值 = 2
        obj1.instance = obj2;
        // 同理A的引用計數值 = 2
        obj2.instance = obj1;
        // 棧中的obj1不再指向堆中A,這時A的計數值減1,變成1
        obj1 = null;
        // 棧中的obj2不再指向堆中B,這時B的計數值減1,變成1
        obj2 = null;
    }
}

class GcObject {
    public Object instance = null;
}

仔細閱讀程式碼中的註釋,並結合下面的記憶體結構示意圖,應該可以很好的理解其中的原因:如果JVM垃圾收集器採用引用計數法,當obj1和obj2不再指向堆中的例項A、B時,雖然A、B已經不可能再被訪問,但彼此間相互引用導致計數器的值不為0,最終導致無法回收A和B。

可達性分析

引用計數法有一個致命的問題,即無法釋放有迴圈引用的垃圾,因此,主流的Java虛擬機器都沒有選用引用計數法來管理記憶體,而是通過可達性分析 (Reachability Analysis)來判定物件是否存活。
可達性分析的基本思路是找到一系列被稱為”GC Roots“的物件引用 (Reference) 作為起始節點,通過引用關係向下搜尋,能被遍歷到的 (可到達的) 物件就被判定為存活,其餘物件 (也就是沒有被遍歷到的) 自然被判定為死亡。這裡需要著重理解的是:可達性分析本質是找出活的物件來把其餘空間判定為“無用”,而不是找出所有死掉的物件並回收它們佔用的空間,簡略的示意圖如下所示。

從圖中可以看出,經過可達性分析後,有不少物件沒有在GC Roots的引用鏈條上,其中還包含一些相互引用的物件,這些物件在不久以後都會被垃圾收集器回收,因此,可達性分析演算法可以有效解決引用計數法存在的致命問題。

但是,首次被標記的物件並一定會被回收,它還有自救的機會。一個物件真正的死亡至少需要經歷兩次標記過程:

標記所有不可達物件,並進行篩選,篩選的標準是該物件覆蓋了finalize()方法且finalize()方法沒有被虛擬機器呼叫過,選出的物件將被放置在一個“即將被回收”的佇列中。稍後虛擬機器會建立一個低優先順序的Finalizer執行緒去遍歷佇列中的所有物件並執行finalize()方法
對佇列中的物件進行第二次標記,如果物件在finalize()方法中重新與引用鏈上的任何一個物件建立關聯,那麼這個物件將被移除佇列,而還留在佇列中的物件,就會被回收了。

要正確的實現可達性分析演算法,就必須完整地枚舉出所有的GC Roots,否則就有可能會漏掉本應存活的物件,如果垃圾收集器錯誤的回收了這些被漏掉的活物件,將會造成嚴重的bug。GC Roots作為垃圾回收的起點,必須是一些列活的引用 (Reference) 集合,那這個集合中究竟包含哪些引用?為什麼這些引用可以作為GC Roots?要回答好這兩個問題,需要對Java物件在記憶體中佈局有一些初步的瞭解,所以,在下節會對相關知識進行補充。

參考資料

周志明 著; 深入理解Java虛擬機器(第2版); 機械工業出版社,2013
知乎上關於GC ROOTS的問題


掃碼關注有驚喜

(轉載本站文章請註明作者和出處 方誌朋的部落格

相關推薦

JVM記憶體管理垃圾回收機制2 - 何為垃圾

隨著程式語言的發展,GC的功能不斷增強,效能也不斷提高,作為語言背後的無名英雄,GC離我們的工作似乎越來越遠。作為Java程式設計師,對這一點也許會有更深的體會,我們不需要了解太多與GC相關的知識,就能很好的完成工作。那還有必要深入瞭解GC嗎?學習GC的意義在哪兒? 不管效能提高到何種程

JVM記憶體管理垃圾回收機制2

隨著程式語言的發展,GC的功能不斷增強,效能也不斷提高,作為語言背後的無名英雄,GC離我們的工作似乎越來越遠。作為Java程式設計師,對這一點也許會有更深的體會,我們不需要了解太多與GC相關的知識,就能很好的完成工作。那還有必要深入瞭解GC嗎?學習GC的意義在哪

JVM記憶體管理垃圾回收機制1 - 記憶體管理

Java應用程式是執行在JVM上的,得益於JVM的記憶體管理和垃圾收集機制,開發人員的效率得到了顯著提升,也不容易出現記憶體溢位和洩漏問題。但正是因為開發人員把記憶體的控制權交給了JVM,一旦出現記憶體方面的問題,如果不瞭解JVM的工作原理,將很難排查錯誤。本文將從理論角度介紹虛擬機器的記憶

JVM記憶體管理垃圾回收機制1

Java應用程式是執行在JVM上的,得益於JVM的記憶體管理和垃圾收集機制,開發人員的效率得到了顯著提升,也不容易出現記憶體溢位和洩漏問題。但正是因為開發人員把記憶體的控制權交給了JVM,一旦出現記憶體方面的問題,如果不瞭解JVM的工作原理,將很難排查錯誤。本文

Java效能優化三:記憶體管理垃圾回收機制,開發必備優化技巧!

一、Java 類載入機制的特點: (1)基於父類的委託機制:執行一個程式時,總是由 AppClass Loader (系統類載入器)開始載入指定的類,在載入類時,每個類載入器會將載入任務上交給其父,如果其父找不到,再由自己去載入, Bootstrap Loader (啟動類載入器)是最頂級的類載

java記憶體管理垃圾回收機制

        看了很多java記憶體管理的文章或者部落格,寫的要麼籠統,要麼劃分的不正確,且很多文章都千篇一律。例如部分地方將jvm籠統的分為堆、棧、程式計數器,這麼分太過於籠統,無法清晰的闡述java的記憶體管理模型;部分地方將jvm分為堆、棧、程式計數器、常量池、

JVM內存管理垃圾回收機制 (上)

JVM 內存結構Java應用程序是運行在JVM上的,得益於JVM的內存管理和垃圾收集機制,開發人員的效率得到了顯著提升,也不容易出現內存溢出和泄漏問題。但正是因為開發人員把內存的控制權交給了JVM,一旦出現內存方面的問題,如果不了解JVM的工作原理,將很難排查錯誤。本文將從理論角度介紹虛擬機的內存管理和垃圾回

JVM記憶體模型垃圾回收

一、JVM體系結構 二、JVM Heap Memory 1.新生代(Young Generation)  - Eden Space  - Survivor FromSpace (S1)  - Survivor ToSpace (S2)

Java虛擬機器 記憶體管理垃圾回收

java和C++之間有一堵由記憶體自動分配與垃圾回收所圍成的高牆,外面的人想進來,裡面的人想出去 主要內容 記憶體分佈 垃圾回收機制 垃圾收集器 Java記憶體分佈 當java虛擬機器執行程式時,會把由虛擬機器管理的記憶體劃分為不同的區域,他們的作用不同,建立和銷燬時間也不同,有的是虛擬

python垃圾回收機制 Java記憶體管理垃圾回收

語言的記憶體管理是語言設計的一個重要方面。它是決定語言效能的重要因素。無論是C語言的手工管理,還是Java的垃圾回收,都成為語言最重要的特徵。這裡以Python語言為例子,說明一門動態型別的、面向物件的語言的記憶體管理方式。 物件的記憶體使用 賦值語句是語言最常見的功能了。但即使是最簡單的賦值語句,也可以

JVM記憶體結構垃圾回收總結

1、JVM記憶體模型   JVM只不過是執行在你係統上的另一個程序而已,這一切的魔法始於一個java命令。正如任何一個作業系統程序那樣,JVM也需要記憶體來完成它的執行時操作。記住:JVM本身是硬體的一層軟體抽象,在這之上才能夠執行Java程式,也才有了我們所吹噓的平臺獨立性以及“一

深入JVM記憶體模型JVM引數詳細配置

深入詳解JVM記憶體模型與JVM引數詳細配置 JVM記憶體結構 堆記憶體(Heap) 方法區(Method Area) 虛擬機器棧(JVM Stack) 棧幀的含義? 棧幀的資料結構? 區域性變量表 運算元棧

探祕Java虛擬機器——記憶體管理垃圾回收

本文主要是基於Sun JDK 1.6 Garbage Collector(作者:畢玄)的整理與總結,原文請讀者在網上搜索。 1、Java虛擬機器執行時的資料區 2、常用的記憶體區域調節引數 -Xms:初始堆大小,預設為實體記憶體的1/64(<1GB);預設(MinHeapFreeRatio引數可以調

Java之美[從菜鳥到高手演變]之JVM記憶體管理垃圾回收

很多Java面試的時候,都會問到有關Java垃圾回收的問題,提到垃圾回收肯定要涉及到JVM記憶體管理機制,Java語言的執行效率一直被C、C++程式設計師所嘲笑,其實,事實就是這樣,Java在執行效率方面確實很低,一方面,Java語言採用面向物件思想,這也決定了其必然是開發效

Java進階10 記憶體管理垃圾回收

整個教程中已經不時的出現一些記憶體管理和垃圾回收的相關知識。這裡進行一個小小的總結。Java是在JVM所虛擬出的記憶體環境中執行的。記憶體分為棧(stack)和堆(heap)兩部分。我們將分別考察這兩個區域。棧許多語言利用棧資料結構來記錄函式呼叫的次序和相關變數。在Java中

JVM記憶體管理垃圾回收

無論對於Java程式設計師還是大資料研發人員,JVM是必須掌握的技能之一。既是面試中經常問的問題,也是在實際業務中對程式進行調優、排查類似於記憶體溢位、棧溢位、記憶體洩漏等問題的關鍵。筆者將按下圖分多篇文章詳細闡述JVM:   本篇文章主要敘述JVM記憶體管理、直接記憶體、垃圾回收和常見的垃圾回

記憶體管理垃圾回收機制

垃圾回收機制是每個公司進行技術面試必問的問題之一,掌握垃圾回收機制至關重要,下面是某篇部落格中的內容 感覺不錯,單獨拉出來作為儲存,請大家關注原連結:  https://blog.csdn.net/rabbit_in_android/article/details/5038695

jvm記憶體分配和垃圾回收機制

問題: 1、垃圾回收目標物件? 2、什麼時間進行垃圾回收?(面試最常見的問題之一) 3、jvm怎樣進行垃圾回收? jvm記憶體分配 執行緒共享區域 1、 堆 2、方法區 執行緒私有區域 1、jvm棧 2、本地方法棧 3、程式計數器 由於虛擬機器棧,

JVM架構和GC垃圾回收機制

JVM架構圖分析 下圖:參考網路+書籍,如有侵權請見諒 (想了解Hadoop記憶體溢位請看: JVM被分為三個主要的子系統 (1)類載入器子系統(2)執行時資料區(3)執行引擎 1. 類載入器子系統 Java的動態類載入功能是由類載入器子系統處理。當它在執行時(

JVM自動記憶體管理垃圾回收技術

前言:java和C++之間有著記憶體自動管理和垃圾回收技術的高牆,牆裡面的人想出來,牆外面的想進去。(引用自周大師) 上一篇主要介紹了jvm的記憶體分配,沒看過的同學可以移步上篇部落格jvm記憶體分配。程式猿比較關注的記憶體的堆疊應用,這篇主要講jvm中記憶體