深刻了解java運行時的內存區域

對於java程序員來講,並沒必要顯示地對內存進行管理,一切都交給java虛擬機去作吧,並且,你也不必定作得比java虛擬機來得專業。好像全部內存管理都交給虛擬機去作就萬事大吉了,可是,事實有時並不是如此,可能有時你會遇到一些讓你困惑的問題,如OutOfMemoryError異常,如stackOverflowError,你開始大呼,虛擬機不是都爲咱們管理好內存了嗎?怎麼還會出現這樣的Error,其實當你真正去了解java虛擬機內存區域的分佈的時候,你就會不自覺的大呼:原來java虛擬機也不是萬能。

這是從網上找到的一個java運行時數據區域,主要包括了方法區(Method Area),java棧區(java stack),本地方法棧區(native method),堆(heap)和程序計數器(program counter register),其中,和java垃圾回收器打交道最多的就是堆了,下面咱們就它們的做用分別簡述一下:

1.程序計數器(Program Counter Register): 

每個Java線程都有一個程序計數器來用於保存程序執行到當前方法的哪個指令,對於非Native方法,這個區域記錄的是正在執行的VM原語的地址,若是正在執行的是Natvie方法,這個區域則爲空(undefined)。此內存區域是惟一一個在VM Spec中沒有規定任何OutOfMemoryError狀況的區域。

2.Java虛擬機棧(Java Virtual Machine Stacks)

與程序計數器同樣,VM棧的生命週期也是與線程相同。VM棧描述的是Java方法調用的內存模型:每一個方法被執行的時候,都會同時建立一個幀(Frame)用於存儲本地變量表、操做棧、動態連接、方法出入口等信息。每個方法的調用至完成,就意味着一個幀在VM棧中的入棧至出棧的過程。

3.本地方法棧(Native Method Stacks)

本地方法棧與VM棧所發揮做用是相似的,只不過VM棧爲虛擬機運行VM原語服務,而本地方法棧是爲虛擬機使用到的Native方法服務。它的實現的語言、方式與結構並無強制規定,甚至有的虛擬機(譬如Sun Hotspot虛擬機)直接就把本地方法棧和VM棧合二爲一。和VM棧同樣,這個區域也會拋出StackOverflowError和OutOfMemoryError異常。

對於絕大多數應用來講,Java堆是虛擬機管理最大的一塊內存。Java堆是被全部線程共享的,在虛擬機啓動時建立。Java堆的惟一目的就是存放對象實例,絕大部分的對象實例都在這裏分配。這一點在VM Spec中的描述是:全部的實例以及數組都在堆上分配(原文:The heap is the runtime data area from which memory for all class instances and arrays is allocated),可是在逃逸分析和標量替換優化技術出現後,VM Spec的描述就顯得並不那麼準確了。

Java堆內還有更細緻的劃分:新生代、老年代,再細緻一點的:eden、from survivor、to survivor,甚至更細粒度的本地線程分配緩衝(TLAB)等,不管對Java堆如何劃分,目的都是爲了更好的回收內存,或者更快的分配內存。
根據VM Spec的要求,Java堆能夠處於物理上不連續的內存空間,它邏輯上是連續的便可,就像咱們的磁盤空間同樣。實現時能夠選擇實現成固定大小的,也能夠是可擴展的,不過當前全部商業的虛擬機都是按照可擴展來實現的(經過-Xmx和-Xms控制)。若是在堆中沒法分配內存,而且堆也沒法再擴展時,將會拋出OutOfMemoryError異常。上次咱們作一個項目的時候,剛開始部署到服務器上的時候,老是出現OutOfMemoryError異常,後來發現,原來jdk1.6默認的堆空間最大是64M,後來咱們經過把最大堆值設置大了,解決了這個問題,固然最大堆值不是越大越好,java中容許直接內存進行堆外分配,若是你把堆值設置太大了,那麼當剩下的機器內存不足以直接內存的那部分程序使用的話,也會拋出OutOfMemoryError的異常。

5.方法區(Method Area)

方法區中存放了每一個Class的結構信息,包括常量池、字段描述、方法描述等等。VM Space描述中對這個區域的限制很是寬鬆,除了和Java堆同樣不須要連續的內存,也能夠選擇固定大小或者可擴展外,甚至能夠選擇不實現垃圾收集。相對來講,垃圾收集行爲在這個區域是相對比較少發生的,但並非某些描述那樣永久代不會發生GC(至少對當前主流的商業JVM實現來講是如此),這裏的GC主要是對常量池的回收和對類的卸載,雖然回收的「成績」通常也比較差強人意,尤爲是類卸載,條件至關苛刻

6.運行時常量池(Runtime Constant Pool)

Class文件中除了有類的版本、字段、方法、接口等描述等信息外,還有一項信息是常量表(constant_pool table),用於存放編譯期已可知的常量,這部份內容將在類加載後進入方法區(永久代)存放。可是Java語言並不要求常量必定只有編譯期預置入Class的常量表的內容才能進入方法區常量池,運行期間也可將新內容放入常量池(最典型的String.intern()方法)。

運行時常量池是方法區的一部分,天然受到方法區內存的限制,當常量池沒法在申請到內存時會拋出OutOfMemoryError異常。

7.本機直接內存(Direct Memory)
直接內存並非虛擬機運行時數據區的一部分,它根本就是本機內存而不是VM直接管理的區域。可是這部份內存也會致使OutOfMemoryError異常出現,所以咱們放到這裏一塊兒描述。

在JDK1.4中新加入了NIO類,引入一種基於渠道與緩衝區的I/O方式,它能夠經過本機Native函數庫直接分配本機內存,而後經過一個存儲在Java堆裏面的DirectByteBuffer對象做爲這塊內存的引用進行操做。這樣能在一些場景中顯著提升性能,由於避免了在Java堆和本機堆中來回複製數據。

顯然本機直接內存的分配不會受到Java堆大小的限制,可是即然是內存那確定仍是要受到本機物理內存(包括SWAP區或者Windows虛擬內存)的限制的,通常服務器管理員配置JVM參數時,會根據實際內存設置-Xmx等參數信息,但常常忽略掉直接內存,使得各個內存區域總和大於物理內存限制(包括物理的和操做系統級的限制),而致使動態擴展時出OutOfMemoryError異常。
相關文章
相關標籤/搜索