Java基礎以內存管理原理及內存區域詳解

1、概述

Java虛擬機在執行Java程序的過程當中會把它所管理的內存劃分爲若干不一樣的數據區域,這些區域都有各自的用途以及建立和銷燬的時間。Java虛擬機所管理的內存將會包括如下幾個運行時數據區域,以下圖所示:程序員

Java內存區域詳解

下面就每個區域進行闡述。算法

2、運行時數據區域

程序計數器

程序計數器,能夠看作是當前線程所執行的字節碼的行號指示器。在虛擬機的概念模型裏,字節碼解釋器工做就是經過改變程序計數器的值來選擇下一條須要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能都要依賴這個計數器來完成。安全

多線程中,爲了讓線程切換後能恢復到正確的執行位置,每條線程都須要有一個獨立的程序計數器,各條線程之間互不影響、獨立存儲,所以這塊內存是 線程私有 的。多線程

當線程正在執行的是一個Java方法,這個計數器記錄的是在正在執行的虛擬機字節碼指令的地址;當執行的是Native方法,這個計數器值爲空。函數

此內存區域是惟一一個沒有規定任何OutOfMemoryError狀況的區域 。佈局

Java虛擬機棧

Java虛擬機棧也是線程私有的 ,它的生命週期與線程相同。虛擬機棧描述的是Java方法執行的內存模型:每一個方法在執行的同時都會建立一個棧幀用於存儲局部變量表、操做數棧、動態鏈表、方法出口信息等。每個方法從調用直至執行完成的過程,就對應着一個棧幀在虛擬機棧中入棧到出棧的過程。spa

局部變量表中存放了編譯器可知的各類基本數據類型(boolean、byte、char、short、int、float、long、double)、對象引用和returnAddress類型(指向了一條字節碼指令的地址)。線程

若是擴展時沒法申請到足夠的內存,就會拋出OutOfMemoryError異常。指針

本地方法棧

本地方法棧與虛擬機的做用類似,不一樣之處在於虛擬機棧爲虛擬機執行的Java方法服務,而本地方法棧則爲虛擬機使用到的Native方法服務。有的虛擬機直接把本地方法棧和虛擬機棧合二爲一。code

會拋出stackOverflowError和OutOfMemoryError異常。

Java堆

Java堆是全部線程共享的一塊內存區域,在虛擬機啓動時建立,此內存區域的惟一目的就是存放對象實例 。

Java堆是垃圾收集器管理的主要區域。因爲如今收集器基本採用分代回收算法,因此Java堆還可細分爲:新生代和老年代。從內存分配的角度來看,線程共享的Java堆中可能劃分出多個線程私有的分配緩衝區(TLAB)。

Java堆能夠處於物理上不連續的內存空間,只要邏輯上連續的便可。在實現上,既能夠實現固定大小的,也能夠是擴展的。

若是堆中沒有內存完成實例分配,而且堆也沒法完成擴展時,將會拋出OutOfMemoryError異常。

方法區

方法區是各個線程共享的內存區域,它用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據 。

相對而言,垃圾收集行爲在這個區域比較少出現,但並不是數據進了方法區就永久的存在了,這個區域的內存回收目標主要是針對常量池的回收和對類型的卸載,

當方法區沒法知足內存分配須要時,將拋出OutOfMemoryError異常。

運行時常量池:

是方法區的一部分,它用於存放編譯期生成的各類字面量和符號引用。

直接內存

直接內存不是虛擬機運行時數據區的一部分,在NIO類中引入一種基於通道與緩衝區的IO方式,它可使用Native函數庫直接分配堆外內存,而後經過一個存儲在Java堆中的DirectByteBuffer對象做爲這塊內存的引用進行操做。

直接內存的分配不會受到Java堆大小的限制,可是會受到本機內存大小的限制,全部也可能會拋OutOfMemoryError異常。

3、對象的建立、佈局和訪問過程

對象的建立

建立一個對象一般是須要new關鍵字,當虛擬機遇到一條new指令時,首先檢查這個指令的參數是否在常量池中定位到一個類的符號引用,而且檢查這個符號引用表明的類是否已被加載、解析和初始化過。若是那麼執行相應的類加載過程。

類加載檢查經過後,虛擬機將爲新生對象分配內存。爲對象分配空間的任務等同於把一塊肯定大小的內存從Java堆中劃分出來。分配的方式有兩種: 一種叫 指針碰撞 ,假設Java堆中內存是絕對規整的,用過的和空閒的內存各在一邊,中間放着一個指針做爲分界點的指示器,分配內存就是把那個指針向空閒空間的那邊挪動一段與對象大小相等的距離。 另外一種叫 空閒列表 :若是Java堆中的內存不是規整的,虛擬機就須要維護一個列表,記錄哪一個內存塊是可用的,在分配的時候從列表中找到一塊足夠大的空間劃分給對象實例,並更新列表上的記錄。 採用哪一種分配方式是由Java堆是否規整決定的,而Java堆是否規整是由所採用的垃圾收集器是否帶有壓縮整理功能決定的。 另 外一個須要考慮的問題就是對象建立時的線程安全問題,有兩種解決方案:一是對分配內存空間的動做進行同步處理;另外一種是吧內存分配的動做按照線程劃分在不 同的空間之中進行,即每一個線程在Java堆中預先分配一小塊內存(TLAB),哪一個線程要分配內存就在哪一個線程的TLAB上分配,只有TLAB用完並分配 新的TLAB時才須要同步鎖定。

內存分配完成後,虛擬機須要將分配到的內存空間初始化爲零值。這一步操做保證了對象的實例字段在Java代碼中能夠不賦初始值就能夠直接使用。

接下來虛擬機要對對象進行必要的設置,例如這個對象是哪一個類的實例、如何才能找到類的元數據信息等,這些信息存放在對象的對象頭中。

上面的工做都完成之後,從虛擬機的角度來看一個新的對象已經產生了。可是從Java程序的角度,還須要執行init方法,把對象按照程序員的意願進行初始化,這樣一個真正可用的對象纔算徹底產生出來。

對象的內存佈局

在HotSpot虛擬機中,對象在內存中存儲的佈局可分爲三個部分: 對象頭、實例數據和對齊填充。

對象頭包括兩個部分:第一部分用於存儲對象自身的運行時數據,如哈希碼、GC分代年齡、線程所持有的鎖等。官方稱之爲「Mark Word」。第二個部分爲是類型指針,即對象指向它的類元數據的指針,虛擬機經過這個指針來肯定這個對象是哪一個類的實例。

實例數據是對象真正存儲的有效信息,也是程序代碼中所定義的各類類型的字段內容。

對齊填充並非必然存在的,僅僅起着佔位符的做用。、Hotpot VM要求對象起始地址必須是8字節的整數倍,對象頭部分正好是8字節的倍數,因此當實例數據部分沒有對齊時,須要經過對齊填充來對齊。

對象的訪問定位

Java程序經過棧上的reference數據來操做堆上的具體對象。主要的訪問方式有使用句柄和直接指針兩種:

句柄:Java堆將會劃出一塊內存來做爲句柄池,引用中存儲的就是對象的句柄地址,而句柄中包含了對象實例數據與類型數據各自的具體地址信息 。如圖所示:

Java內存區域詳解

直接指針:Java堆對象的佈局要考慮如何放置訪問類型數據的相關信息,引用中存儲的就是對象地址 。如圖所示:

Java內存區域詳解

兩個方式各有優勢,使用句柄最大的好處是引用中存儲的是穩定的句柄地址,對象被移動時只會改變句柄中實例的地址,引用不須要修改、使用直接指針訪問的好處是速度更快,它節省了一次指針定位的時間開銷。

相關文章
相關標籤/搜索