運(yùn)行時(shí)的數(shù)據(jù)區(qū)域可以看以下兩張圖
一、程序計(jì)數(shù)器
程序計(jì)數(shù)器(Pregram Counter Register是塊較小的內(nèi)存空間全封, 它可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器。在虛擬機(jī)的概念模型里,字節(jié)碼解釋器工作時(shí)就是通過(guò)改變這個(gè)計(jì)數(shù)器的值來(lái)選取下一條需要執(zhí)行的字節(jié)碼指令揍诽,分支啸盏、循環(huán)弧岳、跳轉(zhuǎn)、異常處理几晤、線程恢復(fù)等基礎(chǔ)功能都需要依賴這個(gè)計(jì)數(shù)器來(lái)完成约炎。
由于Java虛擬機(jī)的多線程是通過(guò)線程輪流切換并分配處理器執(zhí)行時(shí)間的方式來(lái)實(shí)現(xiàn)的,在任何一個(gè)確定的時(shí)刻蟹瘾, 一個(gè)處理器 (對(duì)于多核處理器來(lái)說(shuō)是一 個(gè)內(nèi)核)都只會(huì)執(zhí)行 一條線程中的指令圾浅。因此,為了線程切換后能恢復(fù)到正確的執(zhí)行位置憾朴,每條線程都需要有一一個(gè)獨(dú)立的程序計(jì)數(shù)器狸捕,各條線程之間計(jì)數(shù)器互不影響,獨(dú)立存儲(chǔ)众雷,我們稱這類(lèi)內(nèi)存區(qū)域?yàn)椤熬€程私有”的內(nèi)存灸拍。
如果線程正在執(zhí)行的是一個(gè)Java方法,這個(gè)計(jì)數(shù)器記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址;如果正在執(zhí)行的是Native方法砾省,這個(gè)計(jì)數(shù)器值則為空( Undefined)鸡岗。此內(nèi)存區(qū)域是唯一一個(gè)在Java虛擬機(jī)規(guī)范中沒(méi)有規(guī)定任何OutOfMemoryError情況的區(qū)域。
二编兄、Java虛擬機(jī)棧
Java虛擬機(jī)棧(java Virtual Mechine Stacks)是線程私有的,它的生命周期與線程相同.虛擬機(jī)棧描述的是java方法執(zhí)行的內(nèi)存模型:每個(gè)方法在執(zhí)行的同時(shí)都會(huì)創(chuàng)建一個(gè)棧幀(Stack Frame)用于存儲(chǔ)局部變量表,操作數(shù)棧,動(dòng)態(tài)鏈接,方法出口等信息.每一個(gè)方法從調(diào)用直至執(zhí)行完成的過(guò)程,就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中的入棧到出棧.
局部變量表存放了編譯器可知的各種基本數(shù)據(jù)類(lèi)型(boolean,byte,char,short,int,float,long,double),對(duì)象引用(reference類(lèi)型)和returnAddress類(lèi)型(指向一條字節(jié)碼指令的地址)
在Java虛擬機(jī)規(guī)范中轩性,對(duì)這個(gè)區(qū)域規(guī)定了兩種異常狀況:如果線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的深度,將拋出StackOveFlowError異常;如果虛擬機(jī)椇菰В可以動(dòng)態(tài)擴(kuò)展(當(dāng)前大部分的Java虛擬機(jī)都可動(dòng)態(tài)擴(kuò)展揣苏,只不過(guò)Java虛擬機(jī)規(guī)范中也允許固定長(zhǎng)度的虛擬機(jī)棧)悯嗓,如果擴(kuò)展時(shí)無(wú)法申請(qǐng)到足夠的內(nèi)存,就會(huì)拋出OutOfMemoryEror異常卸察。
三脯厨、本地方法棧
本地方法棧(Native Method Stacks)與虛擬機(jī)棧作用相似,也會(huì)拋出StackOverflowError和OutOfMemoryError異常坑质。
區(qū)別在于虛擬機(jī)棧為虛擬機(jī)執(zhí)行Java方法(字節(jié)碼)服務(wù)合武,而本地方法棧是為虛擬機(jī)使用到的Native方法服務(wù)。
四洪乍、Java堆
對(duì)于大多數(shù)應(yīng)用來(lái)說(shuō)眯杏,Java堆 (Java Heap)是Java虛擬機(jī)所管理的內(nèi)存中最大的內(nèi)存塊。Java堆是被所有線程共享的一塊內(nèi)存區(qū)域壳澳, 在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建岂贩。此內(nèi)存區(qū)域的唯一作用就是存放對(duì)象實(shí)例,幾乎所有的對(duì)象實(shí)例都在這里分配內(nèi)存巷波。這一點(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堆”颈渊。從內(nèi)存回收的角度來(lái)看,由于收集器基本都采用分代收集算法终佛,所以Java堆中還可以細(xì)分為:新生代和老年代:再細(xì)致一點(diǎn)的有Eden空間俊嗽、From Survivor空間、To Survivor空間等铃彰。從內(nèi)存分配的角度來(lái)看绍豁,線程共享的Java堆中可能劃分出多個(gè)線程私有的分配緩沖區(qū)(Thread Local Alocation Buffer, TLAB).不過(guò)無(wú)論如何劃分,都與存放內(nèi)容無(wú)關(guān)牙捉,無(wú)論哪個(gè)區(qū)域竹揍,存儲(chǔ)的都仍然是對(duì)象實(shí)例,進(jìn)步劃分的目的是為了更好地回收內(nèi)存邪铲,或者更快地分配內(nèi)存芬位。
根據(jù)Java虛擬機(jī)規(guī)范的規(guī)定,Java 堆可以處于物理上不連續(xù)的內(nèi)存空間中带到,只要邏輯上是連續(xù)的即可晶衷,就像我們的磁盤(pán)空間一樣。在實(shí)現(xiàn)時(shí)阴孟,既可以實(shí)現(xiàn)成固定大小的晌纫,也可以是可擴(kuò)展的,不過(guò)當(dāng)前主流的虛擬機(jī)都是按照可擴(kuò)展來(lái)實(shí)現(xiàn)的(通過(guò)-Xmx和-Xms控制).如果在堆中設(shè)有內(nèi)存完成實(shí)例分配永丝,并且堆也無(wú)法再擴(kuò)展時(shí)锹漱,將會(huì)拋出OutOfMemoryError異常.
五、方法區(qū)
方法區(qū)(Method Area)與Java堆一樣慕嚷,是各個(gè)線程共享的內(nèi)存區(qū)城哥牍,它用于存儲(chǔ)已被虛擬機(jī)加載的類(lèi)信息、常量喝检、靜態(tài)變量嗅辣、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。
六挠说、運(yùn)行時(shí)常量池
運(yùn)行時(shí)常量池( Runtime Constant Pool)是方法區(qū)的一部分澡谭。Class 文件中除了有類(lèi)的版本、字段损俭、方法蛙奖、接口等描述信息外,還有一項(xiàng)信息是常量池(Constant Pool Table), 用于存放編譯期生成的各種字面量和符號(hào)引用杆兵,這部分內(nèi)容將在類(lèi)加載后進(jìn)人方法區(qū)的運(yùn)行時(shí)常量池中存放.
七雁仲、直接內(nèi)存
直接內(nèi)存(Direct Memory)并不是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分,也不是虛擬機(jī)規(guī)范中定義的內(nèi)存區(qū)域琐脏。但是這部分內(nèi)存也被頻繁地使用攒砖,而且也可能導(dǎo)致OutOfMemoryError異常的出現(xiàn).
在JDK 1.4中新加人了NIO (New Input/Output)類(lèi),引人了一種基于通道(Channel 與緩沖區(qū)(Buffer) 的IO方式日裙,它可以使用Native函數(shù)庫(kù)直接分配堆外內(nèi)存吹艇,然后通過(guò)一個(gè)存儲(chǔ)在Java堆中的DirectByteBuffer對(duì)象作為這塊內(nèi)存的引用進(jìn)行操作。這樣能在一些場(chǎng)景中顯著提高性能阅签,因?yàn)楸苊饬嗽贘ava堆和Native堆中來(lái)回復(fù)制數(shù)據(jù)掐暮。