??Java虛擬機在執(zhí)行Java程序時會把它所管理的內(nèi)存劃分為幾塊區(qū)域,分別用于負責不同的任務(wù)。有些區(qū)域的創(chuàng)建和銷毀依賴于虛擬機進程,有的則依賴于用戶線程的創(chuàng)建和銷毀。廢話不多說,我們來看看JVM運行時內(nèi)存區(qū)域具體是個什么鬼,先看張圖:

JVM運行時數(shù)據(jù)區(qū)
??原諒我不會畫圖,湊合著看看哈~
??我們從圖上可以看到,Java虛擬機在運行程序時,并非所有的內(nèi)存數(shù)據(jù)都同等對待,分為線程共享和線程私有的兩種區(qū)域。線程共享即所有的線程都可以訪問該區(qū)域的數(shù)據(jù),而私有內(nèi)存是伴隨線程的創(chuàng)建而創(chuàng)建的區(qū)域,只對當前線程可見,并伴隨線程的銷毀而回收。
??我們接著來看兩種區(qū)域下更細致的劃分:
一、共享內(nèi)存
-
Java堆(Java Heap)
??在絕大多數(shù)情況下,Java堆算得上是JVM中空間最大的區(qū)域,因為這里的唯一作用就是存放程序中實例化的對象,幾乎所有的對象實例都會在這里分配內(nèi)存空間。它被所有線程共享,伴隨著Java虛擬機的啟動而創(chuàng)建。
??Java堆可以是物理上不連續(xù)的內(nèi)存空間,但要求必須保證邏輯連續(xù)。在實現(xiàn)方面,可以設(shè)置為固定大小,也可以是可擴展的,主流的虛擬機是通過 -Mmx和 -Mms 進行配置實現(xiàn)。如果堆的剩余空間不足以分配實例對象需要的空間,且無法繼續(xù)擴展,則會拋出OutOfMemoryError異常。
??那Java虛擬機怎么解決空間不足的問題呢?答案是內(nèi)存回收 - 垃圾收集機制,即依賴垃圾收集器回收已經(jīng)“死掉”的對象所占用的內(nèi)存,以供后續(xù)新創(chuàng)建的對象使用。Java堆是垃圾收集器主要管理的區(qū)域,所以也叫做“GC堆”(Garbage Collected Heap)。目前主流的虛擬機實現(xiàn)收集器都采用分代收集算法,大致做法就是將Java堆細分為Eden區(qū)、From Survivor區(qū)、To Survivor區(qū),新創(chuàng)建的對象優(yōu)先會在Eden區(qū)分配空間,有的虛擬機還支持開啟本地線程緩沖區(qū)(Thread Local Allocation Buffer. TLAB),(WTF,什么鬼玩意兒!)別慌,這個緩沖區(qū)只是為了幫助更快速地分配和回收內(nèi)存,可以先不了解具體原理,但要知道如果開啟了這個緩沖區(qū),則優(yōu)先在TLAB區(qū)域分配空間。當然一些例外的大對象,會直接在老年代分配內(nèi)存空間,這么做是為了避免對象老年化的時候遷移大量數(shù)據(jù)。
??對象的回收算法還有標記-清除算法、復(fù)制算法、標記-整理算法,關(guān)于GC的收集算法,后續(xù)會單獨介紹。 -
方法區(qū)(Method Area)
??方法區(qū)中存儲的是已被虛擬機加載完畢的類信息、常量、靜態(tài)變量以及即時編譯器編譯后的代碼等數(shù)據(jù)。不同的虛擬機對方法區(qū)的實現(xiàn)方式各不相同,HotSpot虛擬機上采用永久代的方式來實現(xiàn)方法區(qū),但這樣容易遇到內(nèi)存溢出的問題(永久代有 -XX:MaxPremSize的上限)。現(xiàn)在HotSpot虛擬機已經(jīng)逐步采用Native Memory的方式來代替永久代,JDK1.7版本的HotSpot中,已經(jīng)把原本放在永久代的字符串常量池移出。注意,這里指的是字符串常量池將不屬于永久代,但仍屬于方法區(qū)的一部分,即永久代并不等價于方法區(qū)。
??方法區(qū)和堆區(qū)一樣,是所有線程共享的區(qū)域,并且Java虛擬機規(guī)范認為方法區(qū)是堆的一個邏輯部分,但實際上和堆是有區(qū)分的,它還有一個別名“Non-Heap”,即非堆。最大的不同之處是內(nèi)存回收方面,Java虛擬機規(guī)范不要求方法區(qū)必須實現(xiàn)垃圾收集,這不代表方法區(qū)的數(shù)據(jù)會“長生不死”,方法區(qū)的回收目標主要是常量池的回收和類型的卸載,嚴格意義來講,這些區(qū)域的內(nèi)存回收也很有必要。 -
運行時常量池(Runtime Constant Pool)
??運行時常量池是方法區(qū)的一部分,它的作用是存放編譯期生成的字面量和符號引用,這些數(shù)據(jù)來自于Class文件的常量池(Constant Pool Table),出這些之外,運行時常量池還會存放翻譯后的直接引用。相較于Class文件的常量池,運行時常量池還具有動態(tài)性,這意味著其中存放的常量不僅僅是在編譯期產(chǎn)生,還會在運行期存放新的常量。
二、線程私有內(nèi)存
-
Java虛擬機棧(Java Virtual Machine Stacks)
??虛擬機棧是對Java方法執(zhí)行過程中的內(nèi)存模型的描述??梢赃@么描述:每個方法在執(zhí)行的時候會在虛擬機棧中創(chuàng)建一個對應(yīng)的棧幀(Stack Frame)用于存儲方法中的局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口等信息。該方法的調(diào)用、執(zhí)行、完成的過程,對應(yīng)著棧幀在虛擬機棧中的入棧到出棧的過程。局部變量表存放的數(shù)據(jù)有以下幾種:
?a. 了編譯期可知的各種基本數(shù)據(jù)類型(boolean、byte、char、short、int、float、long、double)
?b. 對象引用(reference,是一個指向?qū)ο笃鹗嫉刂返囊弥羔樆蛘呤侵赶虼韺ο蟮木浔?br> ?c. returnAddress類型(指向了一條字節(jié)碼指令的地址)
值得關(guān)注的是,局部變量表所需要的內(nèi)存大小是在編譯期確定并且完成分配,運行期不會動態(tài)改變局部變量表的大小。如果線程請求的棧深度大于虛擬機所允許的深度,會拋出StackOverFlowError異常;如果無法申請到足夠的內(nèi)存,則會拋出OutOfMemoryError異常。 -
本地方法棧(Native Method Stack)
??本地方法棧和虛擬機棧的作用幾乎一致,但本地方法棧的服務(wù)對象是虛擬機所使用到的Native方法,而虛擬機棧是服務(wù)于虛擬機執(zhí)行的Java方法。在Sun公司的HotSpot虛擬機的實現(xiàn)中,本地方法棧和虛擬機棧被放到了一起實現(xiàn)。 -
程序計數(shù)器(Program Counter Register)
??這是一塊比較小的內(nèi)存空間,它用于指定當前線程正在執(zhí)行的字節(jié)碼的行號,也叫行號指示器?;谶@一點作用,就決定了程序計數(shù)器必須是線程私有的內(nèi)存區(qū)域,因為在處理器的每個內(nèi)核中,任意時刻都只會執(zhí)行一個線程中的一個指令,只有保證程序計數(shù)器是線程獨立的,線程之間的執(zhí)行才不會因為處理器切換線程而互相影響。注意一點,如果當前線程正在執(zhí)行Java方法,則計數(shù)器記錄的正在執(zhí)行的虛擬機字節(jié)碼指令的地址;如果正在執(zhí)行Native方法,則計數(shù)器值為空(Undifined)。
三、直接內(nèi)存(Direct Memory)
??首先聲明一點,直接內(nèi)存并不屬于Java虛擬機運行時內(nèi)存區(qū)域。那這塊區(qū)域是干什么的呢?它是JDK1.4引入NIO以后,基于通道(Channel)和緩沖區(qū)(Buffer)的I/O方式,使用Native函數(shù)庫直接分配的堆外內(nèi)存區(qū)域。在Java堆中會存儲著一個對應(yīng)的DirectByteBuffer對象作為這塊堆外內(nèi)存的引用,避免在Java堆和Native堆中來回復(fù)制數(shù)據(jù)。
??直接內(nèi)存的大小不會收到Java堆的內(nèi)存限制,但是會受到本機總內(nèi)存大小和處理器尋址空間的限制。