1. 程式人生 > >GC工作機制詳解

GC工作機制詳解

一、JVM結構

根據《java虛擬機器規範》規定,JVM的基本結構一般如下圖所示:


從左圖可知,JVM主要包括四個部分:

1.類載入器(ClassLoader):在JVM啟動時或者在類執行時將需要的class載入到JVM中。(右圖表示了從java原始檔到JVM的整個過程,可配合理解。 關於類的載入機制,可以參考http://blog.csdn.net/tonytfjing/article/details/47212291

2.執行引擎:負責執行class檔案中包含的位元組碼指令(執行引擎的工作機制,這裡也不細說了,這裡主要介紹JVM結構);

3.記憶體區(也叫執行時資料區):是在JVM執行的時候操作所分配的記憶體區。執行時記憶體區主要可以劃分為5個區域,如圖:


  • 方法區(Method Area):用於儲存類結構資訊的地方,包括常量池、靜態變數、建構函式等。雖然JVM規範把方法區描述為堆的一個邏輯部分, 但它卻有個別名non-heap(非堆),所以大家不要搞混淆了。方法區還包含一個執行時常量池。
  • java堆(Heap):儲存java例項或者物件的地方。這塊是GC的主要區域(後面解釋)。從儲存的內容我們可以很容易知道,方法區和堆是被所有java執行緒共享的。
  • java棧(Stack):java棧總是和執行緒關聯在一起,每當建立一個執行緒時,JVM就會為這個執行緒建立一個對應的java棧。在這個java棧中又會包含多個棧幀,每執行一個方法就建立一個棧幀,用於儲存區域性變量表、操作棧、方法返回值等。每一個方法從呼叫直至執行完成的過程,就對應一個棧幀在java棧中入棧到出棧的過程。所以java棧是現成私有的。
  • 程式計數器(PC Register):用於儲存當前執行緒執行的記憶體地址。由於JVM程式是多執行緒執行的(執行緒輪流切換),所以為了保證執行緒切換回來後,還能恢復到原先狀態,就需要一個獨立的計數器,記錄之前中斷的地方,可見程式計數器也是執行緒私有的。
  • 本地方法棧(Native Method Stack):和java棧的作用差不多,只不過是為JVM使用到的native方法服務的。
4.本地方法介面:主要是呼叫C或C++實現的本地方法及返回結果。

二、記憶體分配

我覺得了解垃圾回收之前,得先了解JVM是怎麼分配記憶體的,然後識別哪些記憶體是垃圾需要回收,最後才是用什麼方式回收。

Java的記憶體分配原理與C/C++不同,C/C++每次申請記憶體時都要malloc進行系統呼叫,而系統呼叫發生在核心空間,每次都要中斷進行切換,這需要一定的開銷,而Java虛擬機器是先一次性分配一塊較大的空間,然後每次new時都在該空間上進行分配和釋放,減少了系統呼叫的次數,節省了一定的開銷,這有點類似於記憶體池的概念;二是有了這塊空間過後,如何進行分配和回收就跟GC機制有關了。

java一般記憶體申請有兩種:靜態記憶體和動態記憶體。很容易理解,編譯時就能夠確定的記憶體就是靜態記憶體,即記憶體是固定的,系統一次性分配,比如int型別變數;動態記憶體分配就是在程式執行時才知道要分配的儲存空間大小,比如java物件的記憶體空間。根據上面我們知道,java棧、程式計數器、本地方法棧都是執行緒私有的,執行緒生就生,執行緒滅就滅,棧中的棧幀隨著方法的結束也會撤銷,記憶體自然就跟著回收了。所以這幾個區域的記憶體分配與回收是確定的,我們不需要管的。但是java堆和方法區則不一樣,我們只有在程式執行期間才知道會建立哪些物件,所以這部分記憶體的分配和回收都是動態的。一般我們所說的垃圾回收也是針對的這一部分。

總之Stack的記憶體管理是順序分配的,而且定長,不存在記憶體回收問題;而Heap 則是為java物件的例項隨機分配記憶體,不定長度,所以存在記憶體分配和回收的問題;

三、垃圾檢測、回收演算法

垃圾收集器一般必須完成兩件事:檢測出垃圾;回收垃圾。怎麼檢測出垃圾?一般有以下幾種方法:

引用計數法:給一個物件新增引用計數器,每當有個地方引用它,計數器就加1;引用失效就減1。

好了,問題來了,如果我有兩個物件A和B,互相引用,除此之外,沒有其他任何物件引用它們,實際上這兩個物件已經無法訪問,即是我們說的垃圾物件。但是互相引用,計數不為0,導致無法回收,所以還有另一種方法:

可達性分析演算法:以根集物件為起始點進行搜尋,如果有物件不可達的話,即是垃圾物件。這裡的根集一般包括java棧中引用的物件、方法區常良池中引用的物件

本地方法中引用的物件等。

總之,JVM在做垃圾回收的時候,會檢查堆中的所有物件是否會被這些根集物件引用,不能夠被引用的物件就會被垃圾收集器回收。一般回收演算法也有如下幾種:

1.標記-清除(Mark-sweep)

演算法和名字一樣,分為兩個階段:標記和清除。標記所有需要回收的物件,然後統一回收。這是最基礎的演算法,後續的收集演算法都是基於這個演算法擴充套件的。

不足:效率低;標記清除之後會產生大量碎片。效果圖如下:


2.複製(Copying)

此演算法把記憶體空間劃為兩個相等的區域,每次只使用其中一個區域。垃圾回收時,遍歷當前使用區域,把正在使用中的物件複製到另外一個區域中。此演算法每次只處理正在使用中的物件,因此複製成本比較小,同時複製過去以後還能進行相應的記憶體整理,不會出現“碎片”問題。當然,此演算法的缺點也是很明顯的,就是需要兩倍記憶體空間。效果圖如下:


3.標記-整理(Mark-Compact)

此演算法結合了“標記-清除”和“複製”兩個演算法的優點。也是分兩階段,第一階段從根節點開始標記所有被引用物件,第二階段遍歷整個堆,把清除未標記物件並且把存活物件“壓縮”到堆的其中一塊,按順序排放。此演算法避免了“標記-清除”的碎片問題,同時也避免了“複製”演算法的空間問題。效果圖如下:


4.分代收集演算法

這是當前商業虛擬機器常用的垃圾收集演算法。分代的垃圾回收策略,是基於這樣一個事實:不同的物件的生命週期是不一樣的。因此,不同生命週期的物件可以採取不同的收集方式,以便提高回收效率。

為什麼要運用分代垃圾回收策略?在java程式執行的過程中,會產生大量的物件,因每個物件所能承擔的職責不同所具有的功能不同所以也有著不一樣的生命週期,有的物件生命週期較長,比如Http請求中的Session物件,執行緒,Socket連線等;有的物件生命週期較短,比如String物件,由於其不變類的特性,有的在使用一次後即可回收。試想,在不進行物件存活時間區分的情況下,每次垃圾回收都是對整個堆空間進行回收,那麼消耗的時間相對會很長,而且對於存活時間較長的物件進行的掃描工作等都是徒勞。因此就需要引入分治的思想,所謂分治的思想就是因地制宜,將物件進行代的劃分,把不同生命週期的物件放在不同的代上使用不同的垃圾回收方式。

如何劃分?將物件按其生命週期的不同劃分成:年輕代(Young Generation)、年老代(Old Generation)、持久代(Permanent Generation)。其中持久代主要存放的是類資訊,所以與java物件的回收關係不大,與回收息息相關的是年輕代和年老代。這裡有個比喻很形象

“假設你是一個普通的 Java 物件,你出生在 Eden 區,在 Eden 區有許多和你差不多的小兄弟、小姐妹,可以把 Eden 區當成幼兒園,在這個幼兒園裡大家玩了很長時間。Eden 區不能無休止地放你們在裡面,所以當年紀稍大,你就要被送到學校去上學,這裡假設從小學到高中都稱為 Survivor 區。開始的時候你在 Survivor 區裡面劃分出來的的“From”區,讀到高年級了,就進了 Survivor 區的“To”區,中間由於學習成績不穩定,還經常來回折騰。直到你 18 歲的時候,高中畢業了,該去社會上闖闖了。於是你就去了年老代,年老代裡面人也很多。在年老代裡,你生活了 20 年 (每次 GC 加一歲),最後壽終正寢,被 GC 回收。有一點沒有提,你在年老代遇到了一個同學,他的名字叫愛德華 (慕光之城裡的帥哥吸血鬼),他以及他的家族永遠不會死,那麼他們就生活在永生代。”

具體區域可以通過VisualVM中的VisaulGC外掛檢視,如圖(openjdk 1.7):


年輕代:是所有新物件產生的地方。年輕代被分為3個部分——Enden區和兩個Survivor區(From和to)當Eden區被物件填滿時,就會執行Minor GC。並把所有存活下來的物件轉移到其中一個survivor區(假設為from區)。Minor GC同樣會檢查存活下來的物件,並把它們轉移到另一個survivor區(假設為to區)。這樣在一段時間內,總會有一個空的survivor區。經過多次GC週期後,仍然存活下來的物件會被轉移到年老代記憶體空間。通常這是在年輕代有資格提升到年老代前通過設定年齡閾值來完成的。需要注意,Survivor的兩個區是對稱的,沒先後關係,from和to是相對的。

年老代:在年輕代中經歷了N次回收後仍然沒有被清除的物件,就會被放到年老代中,可以說他們都是久經沙場而不亡的一代,都是生命週期較長的物件。對於年老代和永久代,就不能再採用像年輕代中那樣搬移騰挪的回收演算法,因為那些對於這些回收戰場上的老兵來說是小兒科。通常會在老年代記憶體被佔滿時將會觸發Full GC,回收整個堆記憶體。

持久代:用於存放靜態檔案,比如java類、方法等。持久代對垃圾回收沒有顯著的影響。 

分代回收的效果圖如下:


我這裡之所以最後講分代,是因為分代裡涉及了前面幾種演算法。年輕代:涉及了複製演算法;年老代:涉及了“標記-整理(Mark-Sweep)”的演算法。

四、垃圾收集器

垃圾收集演算法是記憶體回收的方法論,而實現這些方法論的則是垃圾收集器。不同廠商不同版本JVM所提供的垃圾收集器可能不同,這裡參照《深入理解Java虛擬機器》說的是JDK1.7版本Hotspot虛擬機器,關於垃圾收集器有篇博文總結的不錯,我就不說了,詳見:http://blog.csdn.net/java2000_wl/article/details/8030172

相關推薦

JVM結構、GC工作機制

固定 本地方法棧 內存池 為什麽 water aml 並且 兩種 數據區 轉自:http://blog.csdn.net/tonytfjing/article/details/44278233 JVM結構、內存分配、垃圾回收算法、垃圾收集器。下面我們一一來看。 一、JVM結

JVM、Gc工作機制

相同 生命 棧幀 VM 每次 失效 劃分 地址 .com JVM主要包括四個部分: 類加載器(ClassLoad) 執行引擎 內存區: 本地方法接口:類似於jni調本地native方法 內存區包括四個部分: 1.方法區:包含了靜態變量、常量池、構造函數等

業余草分享面試題,JVM結構、GC工作機制

影響 根節點 tac 關註 共享 產生 我想 tar 效果 題外話:最近在應聘阿裏2015暑期實習,感觸頗多。機會總是留給有準備的人的,所以平常一定要註意知識的鞏固和積累。知識的深度也要有一定的理解,不比別人知道的多,公司幹嘛選你?關於JVM和GC,我相信學java的絕大部

GC工作機制

一、JVM結構 根據《java虛擬機器規範》規定,JVM的基本結構一般如下圖所示: 從左圖可知,JVM主要包括四個部分: 1.類載入器(ClassLoader):在JVM啟動時或者在類執行時將需要的class載入到JVM中。(右圖表示了從java原始檔到JVM的整

JVM 和 GC工作機制

一些基礎的知識結構和底層的原理性的東西還是需要好好進行研究的,這樣就更有助於理解 JAVA 的很多知識;本文章是在檢視<<深入理解Java 虛擬機器>>後所得心得,希望對大家有所幫助,也歡迎技術大咖批評指教; 一、JVM結構、記憶體分配、垃圾回收演算

Java垃圾回收(GC機制

nbsp 引用計數 維護 png 對象 最新 新的 com 前沿 垃圾回收算法有兩種,根據不同的虛擬機策略不同 1、引用計數法 2、可達性分析法 由於我們平常使用的hotspot虛擬機用的是第二種。 那哪些是可達的呢? 這個算法的基本思想是通過一系列稱為“GC Roots”

MapReduce工作機制

memory 傳遞 等待 mapper 臨時文件 相等 water tsp 以及 1.MapTask工作機制整個Map階段流程大體如上圖所示。簡單概述:input File通過split被邏輯切分為多個split文件,通過Record按行讀取內容給map(用戶自己實現的)進

Java虛擬機器 :Java垃圾回收(GC機制

轉自:http://www.importnew.com/28413.html 哪些記憶體需要回收? 哪些記憶體需要回收是垃圾回收機制第一個要考慮的問題,所謂“要回收的垃圾”無非就是那些不可能再被任何途徑使用的物件。那麼如何找到這些物件? 1、引用計數法 這個演算法的實現是,給物件中新

NIO元件Selector工作機制(上)

在使用Java進行相關網路程式的的設計時,出身C/C++的人,首先想到的框架就是多路複用,想到多路複用,Unix/Linux下馬上就能讓從想到select, poll, epoll系統呼叫。於是,在看到Java的NIO中的Selector類時必然會倍感親切。稍加查閱一下SDK手冊以及相關例程,不一會兒,一個

TCP的工作機制

TCP的特點及其目的 為了通過IP資料報實現可靠性傳輸,需要考慮很多事情,例如資料的破壞、丟包、重複以及分片順序混亂等,TCP通過校驗和、序列號、確認應答、重發控制、連線管理以及視窗控制等機制等實現可靠性傳輸。 通過序列號與確認應答提高可靠性 在TCP

【Spark工作機制】 執行機制

Spark主要包括  排程與任務分配、I/O模組、通訊控制模組、容錯模組  、 Shuffle模組。 Spark 按照   ①應用  application  ②作業 job   ③ stage  ④ task   四個層次進行排程,採用經典的FIFO和FAIR等排程演

MapReduce工作機制(MapTask和ReduceTask)

MapTask:1.maptask0負責切片0 ,maptask1負責切片1,maptask2負責切片2。2.maptask0通過一個元件TextinputFormat讀切片0,這個元件封裝一個LineRecordReader,裡面有next方法,每調一次方法從切片0裡讀一行

struts2工作機制

讀者如果曾經學習過Struts1.x或者有過Struts1.x的開發經驗,那麼千萬不要想當然地以為這一章可以跳過。實際上Struts1.x與Struts2並無我們想象的血緣關係。雖然Struts2的開發小組極力保留Struts1.x的習慣,但因為Struts2的核心設計完

【網路程式設計】深入理解TCP的工作機制

TCP的特點及其目的 為了通過IP資料報實現可靠性傳輸,需要考慮很多事情,例如資料的破壞、丟包、重複以及分片順序混亂等,TCP通過校驗和、序列號、確認應答、重發控制、連線管理以及視窗控制等機制等實現可靠性傳輸。 通過序列號與確認應答提高可靠性 在TCP中,當傳送端的

Hadoop框架:NameNode工作機制

本文原始碼:[GitHub·點這裡](https://github.com/cicadasmile/big-data-parent) || [GitEE·點這裡](https://gitee.com/cicadasmile/big-data-parent) # 一、儲存機制 ## 1、基礎描述 Nam

Hadoop框架:DataNode工作機制

本文原始碼:[GitHub·點這裡](https://github.com/cicadasmile/big-data-parent) || [GitEE·點這裡](https://gitee.com/cicadasmile/big-data-parent) # 一、工作機制 ## 1、基礎描述 ![]

中斷底半部機制工作佇列

工作佇列的使用方法和tasklet 非常相似,下面的程式碼用於定義一個工作佇列和一個底半部執行函式。 struct work_struct my_wq; /*定義一個工作佇列*/ void my_wq_func(unsigned long); /*定義一個處理函式*/ 通過INIT_W

JVM架構和GC垃圾回收機制

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

Java GC機制

垃圾收集 Garbage Collection 通常被稱為“GC”,即就是Java垃圾回收機制。 導讀: 1、什麼是GC 2、GC常用演算法 3、垃圾收集器 4、finalize()方法詳解 5、總結--根據GC原理來優化程式碼 正式閱讀之前需要了解相關概念: Java 堆記憶體分為新生代和老年代,新生

Golang GC 垃圾回收機制

摘要 在實際使用 go 語言的過程中,碰到了一些看似奇怪的記憶體佔用現象,於是決定對go語言的垃圾回收模型進行一些研究。本文對研究的結果進行一下總結。 什麼是垃圾回收? 曾幾何時,記憶體管理是程式設計師開發應用的一大難題。傳統的系統級程式語言(主要指C