深刻理解JVM —— Java運行時數據區域

引一句周志明老師對 Java 中的內存管理機制的描述:算法

Java 與 C++ 之間有一堵有內存動態分配和垃圾收集技術所圍成的「高牆」,牆外面的人想進去,牆裏面的人卻想出來。數組

運行時數據區域

Java虛擬機在執行Java程序的過程當中把它所管理的內存劃分爲若干個不一樣的數據區域。這些區域都有各自的用途,以及建立和銷燬的時間,有的區域隨着虛擬機進程的啓動而存在,有些區域則依賴用戶線程的啓動和結束而創建和銷燬。緩存

image.png


線程私有的:

  • 程序計數器
  • 虛擬機棧
  • 本地方法棧

線程共享的:微信

  • 方法區
  • 直接內存

程序計數器

程序計數器是一塊較小的內存空間,能夠看做是當前線程所執行的字節碼的行號指示器。字節碼解釋器工做時經過改變這個計數器的值來選取下一條須要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等功能都須要依賴這個計數器來完。函數

爲了線程切換後能恢復到正確的執行位置,每條線程都須要有一個獨立的程序計數器,各線程之間計數器互不影響,獨立存儲,咱們稱這類內存區域爲「線程私有」的內存。性能

程序計數器也是惟一一個不會出現OutOfMemoryError的內存區域,它的生命週期隨着線程的建立而建立,隨着線程的結束而死亡。線程

虛擬機棧

Java虛擬機棧也是線程私有的,它的生命週期和線程相同,描述的是 Java 方法執行的內存模型。咱們能夠把Java 內存能夠粗糙的區分爲堆內存(Heap)棧內存(Stack),其中棧就是如今說的虛擬機棧,或者說是虛擬機棧中局部變量表部分。 而實際上,Java虛擬機棧是由一個個棧幀組成,描述的是Java方法執行的內存模型:每一個方法在執行的同時,都會建立一個棧幀,用於存儲局部變量表、操做數棧、動態連接、方法出口信息。)設計

局部變量表主要存放了編譯器可知的基本數據類型(boolean、byte、char、short、int、float、long、double)、對象引用(reference類型,它不一樣於對象自己,多是一個指向對象起始地址的引用指針,也多是指向一個表明對象的句柄或其餘與此對象相關的位置)和 returnAddress類型(指向了一條字節碼命令地址)。指針

Java 虛擬機棧會出現兩種異常:StackOverFlowError 和 OutOfMemoryError。code

  • StackOverFlowError: 若Java虛擬機棧的內存大小不容許動態擴展,那麼當線程請求棧的深度超過當前Java虛擬機棧的最大深度的時候,就拋出StackOverFlowError異常。
  • OutOfMemoryError: 若 Java 虛擬機棧的內存大小容許動態擴展,且當線程請求棧時內存用完了,沒法再動態擴展了,此時拋出OutOfMemoryError異常。

本地方法棧

和虛擬機棧所發揮的做用很是類似,區別是: 虛擬機棧爲虛擬機執行 Java 方法 (也就是字節碼)服務,而本地方法棧則爲虛擬機使用到的 Native 方法服務。 在 HotSpot 虛擬機中和 Java 虛擬機棧合二爲一。與虛擬機棧同樣,本地方法棧區域也會拋出StackOverFlowError 和 OutOfMemoryError異常。
**

Java 虛擬機所管理的內存中最大的一塊,Java 堆是全部線程共享的一塊內存區域,在虛擬機啓動時建立。此內存區域的惟一目的就是存放對象實例,幾乎全部的對象實例以及數組都在這裏分配內存。

Java 堆是垃圾收集器管理的主要區域,所以也被稱做GC堆(Garbage Collected Heap).從垃圾回收的角度,因爲如今收集器基本都採用分代垃圾收集算法,因此Java堆還能夠細分爲:新生代和老年代:在細緻一點有:Eden空間、From Survivor、To Survivor空間等。

Java堆能夠處於物理上不連續的內存空間中,只要邏輯上是連續的便可。若是在堆中咩有內存完成實例分配,而且堆也沒法在拓展時,將會拋出OutOfMemoryError異常。

方法區

方法區與 Java 堆同樣,是各個線程共享的內存區域,它用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。雖然Java虛擬機規範把方法區描述爲堆的一個邏輯部分,可是它卻有一個別名叫作 Non-Heap(非堆),目的應該是與 Java 堆區分開來。

HotSpot 虛擬機中方法區也常被稱爲 「永久代」,本質上二者並不等價。僅僅是由於 HotSpot 虛擬機設計團隊用永久代來實現方法區而已,這樣 HotSpot 虛擬機的垃圾收集器就能夠像管理 Java 堆同樣管理這部份內存了。可是這並非一個好主意,由於這樣更容易遇到內存溢出問題。相對而言,垃圾收集行爲在方法區是比較少出現的,但並不是數據進入方法區後就「永久存在」了


根據Java 虛擬機規範的規定,當方法區沒法知足內存分配需求時,將拋出OutOfMemoryError 異常。

運行時常量池

運行時常量池是方法區的一部分。Class 文件中除了有類的版本、字段、方法、接口等描述信息外,還有常量池信息(用於存放編譯期生成的各類字面量和符號引用)
既然運行時常量池時方法區的一部分,天然受到方法區內存的限制,當常量池沒法再申請到內存時會拋出 OutOfMemoryError 異常。

JDK1.7及以後版本的 JVM 已經將運行時常量池從方法區中移了出來,在 Java 堆(Heap)中開闢了一塊區域存放運行時常量池。
**

直接內存

直接內存並非虛擬機運行時數據區的一部分,也不是虛擬機規範中定義的內存區域,可是這部份內存也被頻繁地使用。並且也可能致使OutOfMemoryError異常出現。
JDK1.4中新加入的 NIO(New Input/Output) 類,引入了一種基於通道(Channel)緩存區(Buffer) 的 I/O 方式,它能夠直接使用Native函數庫直接分配堆外內存,而後經過一個存儲在 Java 堆中的 DirectByteBuffer 對象做爲這塊內存的引用進行操做。這樣就能在一些場景中顯著提升性能,由於避免了在 Java 堆和 Native 堆之間來回複製數據
本機直接內存的分配不會收到 Java 堆的限制,可是,既然是內存就會受到本機總內存大小以及處理器尋址空間的限制。

參考文獻《深刻理解Java虛擬機》 周志明 著


微信公衆號:

相關文章
相關標籤/搜索