運(yùn)行時(shí)數(shù)據(jù)區(qū)
Java虛擬機(jī)在執(zhí)行Java程序的過程中會(huì)把它所管理的內(nèi)存劃分為若干個(gè)不同的數(shù)據(jù)區(qū)域。這些區(qū)域都有各自的用途朱庆,以及創(chuàng)建和銷毀的時(shí)間榨惠,有的區(qū)域隨著虛擬機(jī)進(jìn)程的啟動(dòng)而存在,有些區(qū)域則是依賴用戶線程的啟動(dòng)和結(jié)束而建立和銷毀憾赁。
運(yùn)行時(shí)數(shù)據(jù)區(qū)大致分為共享區(qū)(方法區(qū)和堆)和私有區(qū)(VM棧和Native Method棧污朽,程序計(jì)步器)。
程序計(jì)步器
程序計(jì)數(shù)器(Program Counter Register)是一塊較小的內(nèi)存空間龙考,它的作用可以看做是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器蟆肆。
由于Java虛擬機(jī)的多線程是通過線程輪流切換并分配處理器執(zhí)行時(shí)間的方式來實(shí)現(xiàn)的矾睦,在任何一個(gè)確定的時(shí)刻,一個(gè)處理器(對(duì)于多核處理器來說是一個(gè)內(nèi)核)只會(huì)執(zhí)行一條線程中的指令炎功。因此枚冗,為了線程切換后能恢復(fù)到正確的執(zhí)行位置,每條線程都需要有一個(gè)獨(dú)立的程序計(jì)數(shù)器蛇损,各條線程之間的計(jì)數(shù)器互不影響赁温,獨(dú)立存儲(chǔ),我們稱這類內(nèi)存區(qū)域?yàn)椤熬€程私有”的內(nèi)存淤齐。
如果線程正在執(zhí)行的是一個(gè)Java方法股囊,這個(gè)計(jì)數(shù)器記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址;如果正在執(zhí)行的是Natvie方法更啄,這個(gè)計(jì)數(shù)器值則為空(Undefined)稚疹。<strong>此內(nèi)存區(qū)域是唯一一個(gè)在Java虛擬機(jī)規(guī)范中沒有規(guī)定任何OutOfMemoryError情況的區(qū)域。</strong>
VM Stack
與程序計(jì)數(shù)器一樣祭务,<strong>Java虛擬機(jī)棧(Java Virtual Machine Stacks)也是線程私有的内狗,它的生命周期與線程相同。</strong>
每個(gè)方法被執(zhí)行的時(shí)候都會(huì)同時(shí)創(chuàng)建一個(gè)<strong>棧幀</strong>用于存儲(chǔ)局部變量表待牵、操作棧其屏、動(dòng)態(tài)鏈接、方法出口等信息缨该。每一個(gè)方法被調(diào)用直至執(zhí)行完成的過程偎行,就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中從入棧到出棧的過程。
局部變量表存放了編譯期可知的各種基本數(shù)據(jù)類型(boolean贰拿、byte蛤袒、char、short膨更、int妙真、float、long荚守、double)珍德、對(duì)象引用(reference類型,它不等同于對(duì)象本身矗漾,根據(jù)不同的虛擬機(jī)實(shí)現(xiàn)锈候,它可能是一個(gè)指向?qū)ο笃鹗嫉刂返囊弥羔槪部赡苤赶蛞粋€(gè)代表對(duì)象的句柄或者其他與此對(duì)象相關(guān)的位置)和returnAddress類型(指向了一條字節(jié)碼指令的地址)敞贡。
其中64位長度的long和double類型的數(shù)據(jù)會(huì)占用2個(gè)局部變量空間(Slot)泵琳,其余的數(shù)據(jù)類型只占用1個(gè)。局部變量表所需的內(nèi)存空間在編譯期間完成分配,當(dāng)進(jìn)入一個(gè)方法時(shí)获列,這個(gè)方法需要在幀中分配多大的局部變量空間是完全確定的谷市,在方法運(yùn)行期間不會(huì)改變局部變量表的大小。
在Java虛擬機(jī)規(guī)范中击孩,對(duì)這個(gè)區(qū)域規(guī)定了兩種異常狀況:<strong>如果線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的深度迫悠,將拋出StackOverflowError異常;如果虛擬機(jī)椝莺可以動(dòng)態(tài)擴(kuò)展(當(dāng)前大部分的Java虛擬機(jī)都可動(dòng)態(tài)擴(kuò)展及皂,只不過Java虛擬機(jī)規(guī)范中也允許固定長度的虛擬機(jī)棧),當(dāng)擴(kuò)展時(shí)無法申請(qǐng)到足夠的內(nèi)存時(shí)會(huì)拋出OutOfMemoryError異常且改。</strong>
Native Method Stacks
本地方法棧(Native Method Stacks)與虛擬機(jī)棧所發(fā)揮的作用是非常相似的验烧,其區(qū)別不過是<strong>虛擬機(jī)棧為虛擬機(jī)執(zhí)行Java方法(也就是字節(jié)碼)服務(wù),而本地方法棧則是為虛擬機(jī)使用到的Native方法服務(wù)又跛。</strong>虛擬機(jī)規(guī)范中對(duì)本地方法棧中的方法使用的語言碍拆、使用方式與數(shù)據(jù)結(jié)構(gòu)并沒有強(qiáng)制規(guī)定,因此具體的虛擬機(jī)可以自由實(shí)現(xiàn)它慨蓝。甚至有的虛擬機(jī)(譬如Sun HotSpot虛擬機(jī))直接就把本地方法棧和虛擬機(jī)棧合二為一感混。與虛擬機(jī)棧一樣,本地方法棧區(qū)域也會(huì)拋出StackOverflowError和OutOfMemoryError異常礼烈。
JAVA Heap
對(duì)于大多數(shù)應(yīng)用來說弧满,Java堆(Java Heap)是Java虛擬機(jī)所管理的內(nèi)存中最大的一塊。Java堆是被所有線程共享的一塊內(nèi)存區(qū)域此熬,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建庭呜。<strong>此內(nèi)存區(qū)域的唯一目的就是存放對(duì)象實(shí)例,幾乎所有的對(duì)象實(shí)例都在這里分配內(nèi)存犀忱。</strong>這一點(diǎn)在Java虛擬機(jī)規(guī)范中的描述是:所有的對(duì)象實(shí)例以及數(shù)組都要在堆上分配募谎,但是隨著JIT編譯器的發(fā)展與逃逸分析技術(shù)的逐漸成熟,棧上分配阴汇、標(biāo)量替換優(yōu)化技術(shù)將會(huì)導(dǎo)致一些微妙的變化發(fā)生数冬,所有的對(duì)象都分配在堆上也漸漸變得不是那么“絕對(duì)”了。
Java堆是垃圾收集器管理的主要區(qū)域搀庶,因此很多時(shí)候也被稱做“GC堆”拐纱。
根據(jù)Java虛擬機(jī)規(guī)范的規(guī)定,Java堆可以處于物理上不連續(xù)的內(nèi)存空間中哥倔,只要邏輯上是連續(xù)的即可戳玫,就像我們的磁盤空間一樣。在實(shí)現(xiàn)時(shí)未斑,既可以實(shí)現(xiàn)成固定大小的,也可以是可擴(kuò)展的,不過當(dāng)前主流的虛擬機(jī)都是按照可擴(kuò)展來實(shí)現(xiàn)的(通過-Xmx和-Xms控制)蜡秽。如果在堆中沒有內(nèi)存完成實(shí)例分配府阀,并且堆也無法再擴(kuò)展時(shí),將會(huì)拋出OutOfMemoryError異常芽突。
方法區(qū)
方法區(qū)(Method Area)與Java堆一樣试浙,是各個(gè)線程共享的內(nèi)存區(qū)域,<strong>它用于存儲(chǔ)已被虛擬機(jī)加載的類信息寞蚌、常量田巴、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)挟秤。</strong>雖然Java虛擬機(jī)規(guī)范把方法區(qū)描述為堆的一個(gè)邏輯部分壹哺,但是它卻有一個(gè)別名叫做Non-Heap(非堆),目的應(yīng)該是與Java堆區(qū)分開來艘刚。
Java虛擬機(jī)規(guī)范對(duì)這個(gè)區(qū)域的限制非常寬松管宵,除了和Java堆一樣不需要連續(xù)的內(nèi)存和可以選擇固定大小或者可擴(kuò)展外,還可以選擇不實(shí)現(xiàn)垃圾收集攀甚。相對(duì)而言箩朴,垃圾收集行為在這個(gè)區(qū)域是比較少出現(xiàn)的,但并非數(shù)據(jù)進(jìn)入了方法區(qū)就如永久代的名字一樣“永久”存在了秋度。這個(gè)區(qū)域的內(nèi)存回收目標(biāo)主要是針對(duì)常量池的回收和對(duì)類型的卸載炸庞,一般來說這個(gè)區(qū)域的回收“成績”比較難以令人滿意,尤其是類型的卸載荚斯,條件相當(dāng)苛刻埠居,但是這部分區(qū)域的回收確實(shí)是有必要的。在Sun公司的BUG列表中鲸拥,曾出現(xiàn)過的若干個(gè)嚴(yán)重的BUG就是由于低版本的HotSpot虛擬機(jī)對(duì)此區(qū)域未完全回收而導(dǎo)致內(nèi)存泄漏华畏。
根據(jù)Java虛擬機(jī)規(guī)范的規(guī)定弓候,當(dāng)方法區(qū)無法滿足內(nèi)存分配需求時(shí),將拋出OutOfMemoryError異常。
運(yùn)行時(shí)常量池
運(yùn)行時(shí)常量池(Runtime Constant Pool)是方法區(qū)的一部分牡借。<strong>Class文件中除了有類的版本、字段实辑、方法不从、接口等描述等信息外,還有一項(xiàng)信息是常量池(Constant Pool Table)牵敷,用于存放編譯期生成的各種字面量和符號(hào)引用胡岔,這部分內(nèi)容將在類加載后存放到方法區(qū)的運(yùn)行時(shí)常量池中。</strong>
Java虛擬機(jī)對(duì)Class文件的每一部分(自然也包括常量池)的格式都有嚴(yán)格的規(guī)定枷餐,每一個(gè)字節(jié)用于存儲(chǔ)哪種數(shù)據(jù)都必須符合規(guī)范上的要求靶瘸,這樣才會(huì)被虛擬機(jī)認(rèn)可、裝載和執(zhí)行。但對(duì)于運(yùn)行時(shí)常量池怨咪,Java虛擬機(jī)規(guī)范沒有做任何細(xì)節(jié)的要求屋剑,不同的提供商實(shí)現(xiàn)的虛擬機(jī)可以按照自己的需要來實(shí)現(xiàn)這個(gè)內(nèi)存區(qū)域。不過诗眨,一般來說唉匾,除了保存Class文件中描述的符號(hào)引用外,還會(huì)把翻譯出來的直接引用也存儲(chǔ)在運(yùn)行時(shí)常量池中匠楚。
運(yùn)行時(shí)常量池相對(duì)于Class文件常量池的另外一個(gè)重要特征是具備動(dòng)態(tài)性巍膘,Java語言并不要求常量一定只能在編譯期產(chǎn)生,也就是并非預(yù)置入Class文件中常量池的內(nèi)容才能進(jìn)入方法區(qū)運(yùn)行時(shí)常量池芋簿,運(yùn)行期間也可能將新的常量放入池中峡懈,這種特性被開發(fā)人員利用得比較多的便是String類的intern()方法。
既然運(yùn)行時(shí)常量池是方法區(qū)的一部分益咬,自然會(huì)受到方法區(qū)內(nèi)存的限制逮诲,當(dāng)常量池?zé)o法再申請(qǐng)到內(nèi)存時(shí)會(huì)拋出OutOfMemoryError異常。
總結(jié):
程序計(jì)步器(私有):存放的只是一條指向幽告,不存在內(nèi)存溢出啥的梅鹦。
java虛擬機(jī)棧(私有):存儲(chǔ)局部變量表,操作棧冗锁,動(dòng)態(tài)鏈接齐唆、方法出口。會(huì)拋出StackOverflowError和OutOfMemoryError
本地方法棧(私有):虛擬機(jī)使用Native方法服務(wù)冻河。會(huì)拋出StackOverflowError和OutOfMemoryError
堆(共享):存放對(duì)象實(shí)例箍邮,gc主要的地方,通過-Xmx和-Xms控制叨叙,會(huì)拋出OutOfMemoryError異常锭弊。
方法區(qū)(共享):存放被虛擬機(jī)加載的類信息,常量擂错,靜態(tài)變量味滞,即時(shí)編譯器編譯后的代碼等數(shù)據(jù),會(huì)拋出OutOfMemoryError異常钮呀。
摘自《深入理解java虛擬機(jī)》