Java基礎之詳細理解回收機制

來源:互聯網
上載者:User

標籤:

在以前從事C/C++開發的時候,記憶體的管理一直是需要被謹慎考慮的內容。在C語言中,我們使用庫函數malloc()和free()兩個庫函數來實現從堆中分配記憶體與釋放,而C++則使用操作符new和delete來實現記憶體的管理,對於這兩個方式,後者是操作符而前者是庫函數,後者能夠被編譯器處理而前者著重於對內部資料實現構造,在物件導向設計中,後者能更好的結合建構函式對自訂對象實現記憶體配置。但是,在接觸了Java之後,我們在記憶體的管理上可以輕鬆許多,關鍵是Java實現了記憶體的自動管理員模式,具體是怎麼樣的呢?


一、JVM的記憶體模型要瞭解Java是如何進行記憶體回收的,有必要先瞭解JVM內部的記憶體模型:先來看第一張圖:
這張圖基本描述了一個JVM內的運行線程所涉及的5個部分。因為JVM是基於多線程機制的並行計算模型,每個線程有自己獨立的記憶體Runspace,因此線程與線程之間不進行幹擾,另一方面虛擬機器記憶體的資料又對所有線程進行共用。是不是有點暈了?先來逐個瞭解每個組成部分:
(1)、程式計數器
程式計數器是用來指向當前的運行線程A在運行指令時,該指令的位元組碼的位置或行號。在JVM執行多線程並行時,本質是多個線程之間的輪轉機制,因為一個CPU同時只能執行一條指令,為了最大化利用CPU的運行資源,於是使用了線程的輪轉,當一條線程執行處於閑置狀態時,為了防止該線程阻塞後續的線程請求,該線程就會被撤下而替換別的線程,這樣可以使得CPU一直在執行從而提高效率。那麼當線程A被撤下時,後面肯定還會重新切換上線程A繼續執行,該如何得知之前線程被撤下前執行到什麼位置了呢?程式計數器就是為了實現這個功能的,它儲存了當前執行的位元組碼線上程記憶體中的位移量,從而實現了良好的後續執行。從這裡可以得知,每個線程有自己獨立的程式計數器記憶體空間用來指向自己的執行過程,所以這對線程而言是私人的。如果一個線程執行的是Java方法,那麼程式計數器指向的是虛擬機器位元組碼的指令地址(行號),如果執行的是本地方法,則計數器為空白。
(2)、Java虛擬機器棧Java虛擬機器棧描述的是線程中Java方法執行時的記憶體模型:每個Java方法線上程內被執行時都會建立一個棧幀,這個棧幀儲存局部變數表、操作棧等資訊。其中最關鍵的是局部變數表,該表格儲存體了在執行該Java方法時編譯期可知的基礎資料型別 (Elementary Data Type)(short、int、long、byte、char、double、float、boolean)、對象參考型別等。局部變數表的大小在編譯期就已經確定了,執行的時候不會修改該表的大小。需要注意的是該表也屬於線程私人的。
(3)、本地方法棧既然Java虛擬機器棧是為線程中的Java方法服務的,那麼本地方法棧就是為線程中執行的本地方法服務的,其儲存了本地方法使用是需要用的相關基本資料和其他資訊。
(4)、Java堆Java堆是JVM需要維護的最大記憶體地區。它是對所有的線程進行共用的記憶體地區。在該地區中,幾乎負責了所有的對象執行個體的記憶體空間分配以及數組的空間分配。Java堆的JVM進行記憶體回收的主戰場。當前主流的記憶體回收演算法分代演算法也是主要在該地區進行記憶體劃分的。該地區主要負責各個對象執行個體的分配。
(5)、方法區方法區主要用於儲存虛擬機器載入時載入的類資訊、常量資料、靜態變數、以及編譯器編譯後的相關方法的代碼。它也是被所有的線程共用的地區。就好比一個類的靜態變數在類載入時被載入,並對類共用。這個靜態變數或靜態方法就儲存在方法區中被各個線程需要時調用。在方法區中有一個主要構成部分是常量池。該池子裡儲存的是編譯器產生的各種字面量和符號引用。
說完了以上5個部分,我們再來看下面一張圖就容易理解了:
二、JVM的記憶體劃分在瞭解了以上JVM的記憶體模型之後,可以對JVM的記憶體進行有效劃分。這裡的劃分主要是對Java堆進行劃分。因為該部分地區是GC回收的主要戰場。在進行記憶體回收時,Java堆主要劃分出2個世代,方法區(非堆)劃分出一個世代。這個劃分方法是基於記憶體回收的分代回收機制。堆中劃分的兩個世代分別是Young Generation和Old Generation。而在Young Generation中,又被劃分出3個地區,分別是Eden(伊甸園..這名字我也是醉了)、From Suvivor和To Survivor。Eden當中主要是用來給建立立的對象執行個體開闢記憶體空間用的,而剩下的兩個地區From Survivor和To survivor大小一樣,主要是用來儲存進行一次記憶體回收之後剩下的對象。
在Old Genneration當中主要用來儲存存活了比較久的對象。
而在方法區,也就是非堆區,會劃分出Permanent Generation,目的是儲存一些類資訊、靜態欄位等資料,這些資料會隨著類被虛擬機器開始載入而存在。基本不參與記憶體回收。三、JVM的記憶體回收任務及原理在上面,瞭解了JVM在Java堆和非堆中進行的記憶體劃分,瞭解了各個地區下劃分的子領域及對應的儲存資料。現在就可以介紹JVM是如何執行記憶體回收的:對象執行個體的記憶體配置主要在Young Generation->Eden當中,該地區是一塊連續的閒置記憶體地區。因此在該地區進行記憶體的分配非常快速,因為不需要進行可用記憶體地區的尋找。而在Young Generation->From Survivor和To Generation中,這兩個存活區始終有一個是空的,那麼在進行記憶體回收時,JVM通過演算法尋找出Eden當中不活躍的對象執行個體,然後將活躍的對象執行個體複製到其中的一個空白的存活區中,而另一個存活去儲存了上一次記憶體回收時儲存的對象執行個體,對該地區進行搜尋將活躍的對象也複製到那個空白的存活去中,這樣,其中的存活區就儲存了活躍對象執行個體,接下來把剩下的兩個地區Eden和其中一個Survivor置空即可。如此一來,Eden又空了,就又可以進行記憶體開闢了,另一個存活區清空了,可以為下次記憶體回收提供活躍對象的儲存場所。在這個過程中有一個問題,隨著記憶體回收的不斷執行,存活區內的對象執行個體越來越多,那麼該怎麼辦呢?Java記憶體回收機制會對該存活區內的對象進行演算法統計,將存活時間偏長的對象複製到Old Generation當中,用來釋放存活區的儲存空間。以上就是幾個儲存地區的作用。
上面描述的是Young Generation當中的記憶體回收機制,那麼對於Old Generation和Permannent Generation中的對象,該如何進行記憶體回收呢?採用的回收演算法是另一種:稱之為“標記->清除->壓縮”。標記值得是標記活躍的對象,清除是回收存活過久的對象,壓縮是將記憶體進行壓縮,是所有的對象儲存在一端,留下另一端空白的記憶體地區方便開闢新的對象空間。這樣一來降低了記憶體片段,提高了記憶體的利用率。



四、JVM的記憶體回收方式JVM提供了3種記憶體回收方式,分別是串列GC、並行回收GC、並行GC。(1)、串列GC指的是在整個掃描和複製過程中採用單線程的方式來進行,適用於單CPU、新生代空間較小及對暫停時間要求不是很高的應用。該回收方法在回收時程式會被暫停,分代回收就是用的串列GC方式。(2)、並行回收GC在執行過程中採用多線程並行的方式來執行。(3)、標記回收。主要針對用於Old Generation當中的回收機制。
以上就是我對JVM記憶體回收的資料總結和自己的理解,希望對大家有所協助。

著作權聲明:本文為博主原創文章,未經博主允許不得轉載。

Java基礎之詳細理解回收機制

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.