Java的內存管理機制以內存區域劃分

各位,很久不見。先作個預告,因爲最近主要在作Java服務端開發,最近一段時間會更新Java服務端開發相關的一些知識,包括但不限於一些讀書筆記、框架的學習筆記、和最近一段時間的思考和沉澱。先從Java虛擬機的內存開始吧。web

Java虛擬機在執行Java程序的過程當中會把它所管理的內存劃分爲若干個不一樣的數據區域。根據《Java虛擬機規範(第2版)》規定,Java虛擬機所管理的內存包括如下幾個運行區域。以下圖所示:緩存

   

這些區域有各自的用途,各自的建立和銷燬時間(各自的生命週期)。微信

 

1. 程序記數器 (線程私有,內存區域惟一在Java虛擬機規範中沒有規定任何OutOfMemoryError狀況的區域)多線程

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

因爲Java虛擬機的多線程是經過線程輪流切換分配處理器執行時間的方式來實現的,在任何一個肯定的時刻,一個處理器(對於多核處理器來講是一個內核)只會執行一條線程中的指令。所以爲了線程切換後能恢復到正確的位置,每條線程都須要有一個獨立的程序技術器,各條線程之間的計數器互不影響,獨立存儲,這類內存區域爲「線程私有」的內存。函數

若是線程正在執行的是一個Java方法,程序計數器記錄的是正在執行的虛擬機字節碼指令的地址;若是是正在執行的是Nativie方法,則記錄的值爲空(Undefined)。性能

 

2.Java虛擬機棧(線程私有,生命週期與線程相同)學習

虛擬機棧描述的是Java方法執行的內存模型:每一個方法被執行的時候都會同時建立一個棧幀(Stack Frame)用於存儲局部變量表、操做棧、動態連接、方法出口等信息。每個方法被調用直至執行完成的過程,就對應着一個棧幀在虛擬機中從入棧到出棧的過程。spa

局部變量表存放的是啥?它存放了編譯期可知的各類基本數據類型 (boolean、byte、char、short、int、long、double)、對象引用(reference類型,根據不一樣的虛構機實現,它多是一個指向對象起始地址的引用指針,也多是表明對象的句柄或者其餘與此對象相關的位置)和returnAddress類型(提向了一條字節碼指令的地址)線程

該區域會產生的兩種異常狀況

(1)StackOverflowError異常:請求的棧深度大於虛擬機所容許的深度

(2)OutOfMemoryError 異常:當擴展時沒法申請到足夠的內存

 

3.本地方法棧(線程私有)

本地方法棧與虛擬機棧做用類似,區別是虛擬機執行Java方法(字節碼)服務,而本地方法棧則是爲虛擬機使用到的Native方法服務。

該區域和虛擬機棧同樣也會產生的StackOverflowError異常、OutOfMemoryError 異常

 

4.Java堆(線程共享,內存中最大的一塊)

在虛擬機啓動時建立,惟一目的就是存放對象實例,Java堆能夠處於物理上不連續的內存空間中,只要邏輯上連續的便可。能夠實現成固定的,也能夠是擴展的,當前主流的虛構機都是按照可擴展來實現的(經過-Xmx 和-Xms 控制),若是堆中沒有內存完成實例分配,而且堆也沒法擴展,將會拋出OutOfMemoryError異常。

垃圾收集器的主要區域,因此也被稱爲」GC堆「。

從內存回收的角度看,分爲了新生代和老年代;再細緻一點的有Eden空間、From Survivor空間、To Survivor空間等。

從內存分配的角度看,線程共享的Java堆中可能劃分出多個線程私有的分配緩衝區(Thread Local  Allocation Buffer,TLAB)。

 

5.方法區(別名:Non-Heap (非堆),線程共享)

它用於存儲已被虛擬機加載的類信息常量、靜態變量、即時編譯後的代碼等數據。

方法區和永久代:本質上二者並不等價,僅僅是由於HotSpot虛擬機的設計團隊選擇把GC分代收集擴展至方法區,或者說使用永久代來實現方法區而已。對於其餘虛擬機(如BEA Jrockit、IBM J9等)來講是不存在永久代的概念的。即便HotSpot虛擬機自己,根據官方發佈的路線圖信息,也有放棄永久代的並」搬家「至Native Memory來實現方法區的規劃了(Java 1.8 已放棄永久代,替代者元空間(Metaspace):與Oracle JRockit 和 IBM JVM相似,JDK 8.HotSpot JVM開始使用本地化的內存存放類的元數據,這個空間叫作元空間(Metaspace)

方法區能夠選擇不實現垃圾收集,這個區域的內存回收主要是針對常量池的回收和對類型的卸載。

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

 運行時常量池,是方法區的一部分。Class文件中除了有類的版本、字段方法、接口等描述信息外,還有一項信息是常量池,用於存放編譯器生成的各類字面量和符號引用,這部份內容將在類加載後存放到方法區的運行時常量池中。

直接內存

直接內存(Direct Memory)並非虛擬機運行時的一部分,也不是Java虛擬機規範中定義的內存區域,可是這部分也被頻繁的使用,並且也可能致使OutOfMemoryError異常出現。那什麼是直接內存?

在JDK1.4中新加入了NIO(New Input/Output)類,引入了一種基於通道(Channnel)與緩存區(Buffer ) 的I/O方式,它可使用Native函數直接分配堆外內存,而後經過一個存儲在Java堆裏的DirectByteBuffer對象做爲這塊內存的引用進行操做。目的是在一些場景中提升性能,由於避免了Java堆和Native堆中來回複製數據。本機直接內存不會受到Java堆大小的限制,但會受到本機總內存的限制。這個地方是須要配置虛擬機參數一併考慮進去的,動態擴展時可能會出現OutofMemoryError異常。

 

參考:《深刻理解Java虛擬機:JVM高級特性與最佳實踐》

本文首發於我的微信公衆號:webguan ;歡迎您的關注

相關文章
相關標籤/搜索