JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)
Java虛擬機(jī)在執(zhí)行Java程序的過(guò)程中會(huì)把它所管理的內(nèi)存劃分為若干個(gè)不同的數(shù)據(jù)區(qū)域。這些區(qū)域都有各自的用途,已經(jīng)創(chuàng)建和銷毀時(shí)間,有的區(qū)域隨著虛擬機(jī)進(jìn)程的啟動(dòng)而創(chuàng)建,有些區(qū)域則依賴用戶線程的啟動(dòng)和結(jié)束而創(chuàng)建和銷毀。根據(jù)《Java虛擬機(jī)規(guī)范(Java SE 7)》的規(guī)定,Java虛擬機(jī)所管理的內(nèi)存將會(huì)包括以下幾個(gè)運(yùn)行時(shí)數(shù)據(jù)區(qū)域,如下圖所示:

1、程序計(jì)數(shù)器
程序計(jì)數(shù)器(Program Counter Register)是一塊較小的內(nèi)存空間,它可以看做是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器。在虛擬機(jī)的概念模型里(僅是概念模型,各種虛擬機(jī)可能會(huì)通過(guò)一些更高效的方式去實(shí)現(xiàn)),字節(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è)處理器都只會(huì)執(zhí)行一條線程中的指令。因此,為了線程切換后能恢復(fù)到正確的執(zhí)行位置,每條線程都需要有一個(gè)獨(dú)立的程序計(jì)數(shù)器,各個(gè)線程之間計(jì)數(shù)器互不影響,獨(dú)立存儲(chǔ)。
如果線程正在執(zhí)行的是一個(gè)Java方法,那這個(gè)計(jì)數(shù)器記錄的是正在執(zhí)行的字節(jié)碼指令的地址;如果正在執(zhí)行的是Native方法,這個(gè)計(jì)數(shù)器值則為空(undefined)。 此內(nèi)存區(qū)域是唯一一個(gè)在Java虛擬機(jī)規(guī)范中沒(méi)有規(guī)定任何OutOfMemoryError情況的區(qū)域。
程序計(jì)數(shù)器是線程私有的,它的生命周期與線程相同(隨線程而生,隨線程而滅)。
2、Java虛擬機(jī)棧
虛擬機(jī)棧(Java Virtual Machine Stack)描述的是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ī)棧中從入棧到出棧的過(guò)程。
在Java虛擬機(jī)規(guī)范中,對(duì)這個(gè)區(qū)域規(guī)定了兩種異常情況:
- 如果線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的深度,將拋出StackOverflowError異常;
- 如果虛擬機(jī)??梢詣?dòng)態(tài)擴(kuò)展(當(dāng)前大部分的Java虛擬機(jī)都可以擴(kuò)展),如果擴(kuò)展時(shí)無(wú)法申請(qǐng)到足夠的內(nèi)存,就會(huì)拋出OutOfMemoryError異常。
與程序寄存器一樣,java虛擬機(jī)棧也是線程私有的,它的生命周期與線程相同。
3、本地方法棧
本地方法棧(Native Method Stack)與虛擬機(jī)棧所發(fā)揮的作用是非常類似,它們之間的區(qū)別在于虛擬機(jī)棧為虛擬機(jī)執(zhí)行Java方法服務(wù),而本地方法棧則是為虛擬機(jī)使用到的Native方法服務(wù)。在虛擬機(jī)規(guī)范中對(duì)本地方法棧中方法使用的語(yǔ)言、使用方式與數(shù)據(jù)結(jié)構(gòu) 并沒(méi)有強(qiáng)制規(guī)定,因此具體的虛擬機(jī)可以自由的實(shí)現(xiàn)它。
與虛擬機(jī)棧一樣,本地方法棧區(qū)域也會(huì)拋出StackOverflowError和OutOfMemoryError異常。
與虛擬機(jī)棧一樣,本地方法棧也是線程私有的。
4、Java 堆(Java Heap)
對(duì)于大多數(shù)應(yīng)用來(lái)說(shuō),Java 堆(Java Heap)是Java虛擬機(jī)所管理的內(nèi)存中最大的一塊。Java 堆是被所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機(jī)啟動(dòng)的是創(chuàng)建。此內(nèi)存區(qū)域的唯一目的就是存放對(duì)象實(shí)例,幾乎所有的對(duì)象實(shí)例以及數(shù)組都要在這里分配內(nèi)存。
Java堆是垃圾收集器管理的主要區(qū)域,因此很多時(shí)候也被稱為“GC堆”(Garbage Collected Heap)。從內(nèi)存回收的角度來(lái)看,由于現(xiàn)在收集器基本都采用分代收集算法,所以Java堆還可以細(xì)分為:新生代和老年代;新生代又可以分為:Eden 空間、From Survivor空間、To Survivor空間。
根據(jù)Java虛擬機(jī)規(guī)范的規(guī)定,Java堆可以處于物理上不連續(xù)的內(nèi)存空間中,只要邏輯上是連續(xù)的即可,就像我們的磁盤空間一樣。在實(shí)現(xiàn)時(shí),既可以實(shí)現(xiàn)成固定大小的,也可以是可擴(kuò)展的,不過(guò)當(dāng)前主流的虛擬機(jī)都是按照可擴(kuò)展來(lái)實(shí)現(xiàn)的(通過(guò)-Xms和-Xmx控制)。如果在堆中沒(méi)有內(nèi)存完成實(shí)例的分配,并且堆也無(wú)法再擴(kuò)展時(shí),將會(huì)拋出OutOfMemoryError異常。
5、方法區(qū)(Method Area)
方法區(qū)(Method Area)和Java堆一樣,是各個(gè)線程共享的內(nèi)存區(qū)域,它用于存放已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、JIT編譯后的代碼等數(shù)據(jù)。方法區(qū)在虛擬機(jī)啟動(dòng)的時(shí)候創(chuàng)建。
對(duì)于習(xí)慣在Hotspot虛擬機(jī)上開發(fā)、部署應(yīng)用程序的開發(fā)者來(lái)說(shuō),很多人都更愿意把方法區(qū)稱為“永久代”(Permanent Generation),本質(zhì)上兩者并不等價(jià),僅僅是因?yàn)镠otspot虛擬機(jī)的設(shè)計(jì)團(tuán)隊(duì)選擇把GC分代收集擴(kuò)展至方法區(qū),或者說(shuō)使用永久代來(lái)實(shí)現(xiàn)方法區(qū)而已,這樣Hotspot的垃圾收集器可用像管理Java堆一樣管理這部分內(nèi)存,能夠省去專門為方法區(qū)編寫內(nèi)存管理代碼的工作。對(duì)于其他虛擬機(jī)(如BEA JRockit、IBM J9等)來(lái)說(shuō),是不存在永久代的概念的。
Java虛擬機(jī)規(guī)范對(duì)方法區(qū)的限制非常寬松,除了和堆一樣不需要不連續(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ì)類型的卸載,一般來(lái)說(shuō),這個(gè)區(qū)域的回收"成績(jī)" 比較難以讓人滿意。
根據(jù)Java虛擬機(jī)規(guī)范的規(guī)定,如果方法區(qū)的內(nèi)存空間不能滿足內(nèi)存分配需要時(shí),將拋出OutOfMemoryError異常。
6、運(yùn)行時(shí)常量池
運(yùn)行時(shí)常量池(Runtime Constant Pool)是方法區(qū)的一部分。Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項(xiàng)信息是常量池(Constant Pool Table),用于存放編譯期生成的各種字面量和符號(hào)引用,這部分內(nèi)容將在類加載后進(jìn)入方法區(qū)的運(yùn)行時(shí)常量池中存放。
7、直接內(nèi)存
直接內(nèi)存(Direct Memory)并不是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分,也不是Java虛擬機(jī)規(guī)范中定義的內(nèi)存區(qū)域。但是這部分內(nèi)存也被頻繁使用,而且也可能導(dǎo)致OutOfMemoryError異常出現(xiàn)。
在JDK 1.4 中新加入了NIO(New Input/Output)類,引入了一種基于通道(Channel)與緩沖區(qū)(Buffer)的I/O方法,它可以使用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ù)。
JVM內(nèi)存分代

JVM堆內(nèi)存被分為兩部分:年輕代(Young Generation)和老年代(Old Generation)。
JVM 常用的參數(shù)
| 參數(shù)名稱 | 說(shuō)明 |
|---|---|
| -Xms | 設(shè)置JVM啟動(dòng)時(shí)堆的初始化大小。 |
| -Xmx | 設(shè)置堆最大值。 |
| -Xmn | 設(shè)置年輕代的空間大小 |
| -XX:PermGen | 設(shè)置永久代內(nèi)存的初始化大小。 |
| XX:MaxPermGen | 設(shè)置永久代的最大值。 |
| XX:SurvivorRatio | 提供Eden區(qū)和survivor區(qū)的空間比例。比如,如果年輕代的大小為10m并且VM開關(guān)是-XX:SurvivorRatio=2,那么將會(huì)保留5m內(nèi)存給Eden區(qū)和每個(gè)Survivor區(qū)分配2.5m內(nèi)存。默認(rèn)比例是8。 |
| -XX:NewRatio | 提供年老代和年輕代的比例大小。默認(rèn)值是2。 |
參考資料
深入理解Java虛擬機(jī):JVM高級(jí)特性與最佳實(shí)踐(第2版)
Java內(nèi)存與垃圾回收調(diào)優(yōu):http://www.importnew.com/14086.html