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棧中的入棧至出棧的過程。
?????局部變量表存放了編譯期間就可知的基本數據類型(boolean byte ing char ****)、對象引用等,在編譯期間內存空間是已經分配好的,當進入一個方法需要在這其中分配多大的內存是已經完全確定的,在方法運行期間局部變量表是不可改變的。
3.本地方法棧(Native Method Stacks)
本地方法棧與VM棧所發揮作用是類似的,只不過VM棧為虛擬機運行VM原語服務,而本地方法棧是為虛擬機使用到的Native方法服務。它的實現的語言、方式與結構并沒有強制規定,甚至有的虛擬機(譬如Sun?Hotspot虛擬機)直接就把本地方法棧和VM棧合二為一。和VM棧一樣,這個區域也會拋出StackOverflowError和OutOfMemoryError異常。
4、Java堆
對于絕大多數應用來說,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異常。
轉載于:https://www.cnblogs.com/plxx/p/3712770.html
總結
- 上一篇: 表单元素对齐问题解决方案
- 下一篇: Java线程池学习