前言
Java程序的運(yùn)行是通過Java虛擬機(jī)來實(shí)現(xiàn)的。通過類加載器將class字節(jié)碼文件加載進(jìn)JVM,然后根據(jù)預(yù)定的規(guī)則執(zhí)行。Java虛擬機(jī)在執(zhí)行Java程序的過程中會把它所管理的內(nèi)存劃分為若干個不同的數(shù)據(jù)區(qū)域。這些內(nèi)存區(qū)域被統(tǒng)一叫做運(yùn)行時數(shù)據(jù)區(qū)。Java運(yùn)行時數(shù)據(jù)區(qū)大致可以劃分為5個部分。如下圖所示。在這里要特別指出,我們現(xiàn)在說的JVM內(nèi)存劃分是概念模型。具體到每個JVM的具體實(shí)現(xiàn)可能會有所不同。具體JVM的實(shí)現(xiàn)我只會提到HotSpot虛擬機(jī)的實(shí)現(xiàn)細(xì)節(jié)。
程序計(jì)數(shù)器
程序計(jì)數(shù)器是一塊較小的內(nèi)存空間,它可以看成是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號指示器。程序計(jì)數(shù)器記錄線程當(dāng)前要執(zhí)行的下一條字節(jié)碼指令的地址。由于Java是多線程的,所以為了多線程之間的切換與恢復(fù),每一個線程都需要單獨(dú)的程序計(jì)數(shù)器,各線程之間互不影響。這類內(nèi)存區(qū)域被稱為“線程私有”的內(nèi)存區(qū)域。 由于程序計(jì)數(shù)器只存儲一個字節(jié)碼指令地址,故此內(nèi)存區(qū)域沒有規(guī)定任何OutOfMemoryError情況。
虛擬機(jī)棧
Java虛擬機(jī)棧也是線程私有的,它的生命周期與線程相同。虛擬機(jī)棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個方法在執(zhí)行時都會創(chuàng)建一個棧幀(Stack Frame)用于存儲局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口等信息。 一個棧幀就代表了一個方法執(zhí)行的內(nèi)存模型,虛擬機(jī)棧中存儲的就是當(dāng)前執(zhí)行的所有方法的棧幀(包括正在執(zhí)行的和等待執(zhí)行的)。每一個方法從調(diào)用直至執(zhí)行完成的過程,就對應(yīng)著一個棧幀在虛擬機(jī)中入棧到出棧的過程。我們平時所說的“局部變量存儲在棧中”就是指方法中的局部變量存儲在代表該方法的棧幀的局部變量表中。而方法的執(zhí)行正是從局部變量表中獲取數(shù)據(jù),放至操作數(shù)棧上,然后在操作數(shù)棧上進(jìn)行運(yùn)算,再將運(yùn)算結(jié)果放入局部變量表中,最后將操作數(shù)棧頂?shù)臄?shù)據(jù)返回給方法的調(diào)用者的過程。(關(guān)于棧幀和基于棧的方法執(zhí)行,我會在之后寫兩篇文章專門介紹。敬請期待?) 虛擬機(jī)棧可能出現(xiàn)兩種異常:由線程請求的棧深度過大超出虛擬機(jī)所允許的深度而引起的StackOverflowError異常;以及由虛擬機(jī)棧無法提供足夠的內(nèi)存而引起的OutOfMemoryError異常。
本地方法棧
本地方法棧與虛擬機(jī)棧類似,他們的區(qū)別在于:本地方法棧用于執(zhí)行本地方法(Native方法);虛擬機(jī)棧用于執(zhí)行普通的Java方法。在HotSpot虛擬機(jī)中,就將本地方法棧與虛擬機(jī)棧做在了一起。 本地方法??赡軖伋龅漠惓M摂M機(jī)棧一樣。
堆
Java堆是Java虛擬機(jī)所管理的內(nèi)存中最大的一塊。Java堆是被所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機(jī)啟動時創(chuàng)建。此內(nèi)存區(qū)域的唯一目的就是存放對象實(shí)例:所有的對象實(shí)例以及數(shù)組都要在堆上分配(The heap is the runtime data area from which memory for all class instances and arrays is allocated)。但Class對象比較特殊,它雖然是對象,但是存放在方法區(qū)里。在下面的方法區(qū)一節(jié)會介紹。Java堆是垃圾收集器(GC)管理的主要區(qū)域。現(xiàn)在的收集器基本都采用分代收集算法:新生代和老年代。而對于不同的”代“采用的垃圾回收算法也不一樣。一般新生代使用復(fù)制算法;老年代使用標(biāo)記整理算法。對于不同的”代“,一般使用不同的垃圾收集器,新生代垃圾收集器和老年代垃圾收集器配合工作。(關(guān)于垃圾收集算法、垃圾收集器以及堆中具體的分代等知識,我之后會專門寫幾篇博客來介紹。再次敬請期待?) Java堆可以是物理上不連續(xù)的內(nèi)存空間,只要邏輯上連續(xù)即可。Java堆可能拋出OutOfMemoryError異常。
方法區(qū)
方法區(qū)與Java堆一樣,是各個線程共享的內(nèi)存區(qū)域。它用于存儲已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)。 所有的字節(jié)碼被加載之后,字節(jié)碼中的信息:類信息、類中的方法信息、常量信息、類中的靜態(tài)變量等都會存放在方法區(qū)。正如其名字一樣:方法區(qū)中存放的就是類和方法的所有信息。此外,如果一個類被加載了,就會在方法區(qū)生成一個代表該類的Class對象(唯一一種不在堆上生成的對象實(shí)例)該對象將作為程序訪問方法區(qū)中該類的信息的外部接口。有了該對象的存在,才有了反射的實(shí)現(xiàn)。 在Java7之前,HotSpot虛擬機(jī)中將GC分代收集擴(kuò)展到了方法區(qū),使用永久代來實(shí)現(xiàn)了方法區(qū)。這個區(qū)域的內(nèi)存回收目標(biāo)主要是針對常量池的回收和對類型的卸載。但是在之后的HotSpot虛擬機(jī)實(shí)現(xiàn)中,逐漸開始將方法區(qū)從永久代移除。Java7中已經(jīng)將運(yùn)行時常量池從永久代移除,在Java 堆(Heap)中開辟了一塊區(qū)域存放運(yùn)行時常量池。而在Java8中,已經(jīng)徹底沒有了永久代,將方法區(qū)直接放在一個與堆不相連的本地內(nèi)存區(qū)域,這個區(qū)域被叫做元空間。 關(guān)于元空間的更多信息,請參考:Java永久代去哪兒了
運(yùn)行時常量池
運(yùn)行時常量池是方法區(qū)的一部分,關(guān)于運(yùn)行時常量池的介紹,請參考我的另一篇博文:String放入運(yùn)行時常量池的時機(jī)與String.intern()方法解惑。我還是花了些時間在理解運(yùn)行時常量池上的。
直接內(nèi)存
JDK1.4中引用了NIO,并引用了Channel與Buffer,可以使用Native函數(shù)庫直接分配堆外內(nèi)存,并通過一個存儲在Java堆里面的DirectByteBuffer對象作為這塊內(nèi)存的引用進(jìn)行操作。 如上文介紹的:Java8以及之后的版本中方法區(qū)已經(jīng)從原來的JVM運(yùn)行時數(shù)據(jù)區(qū)中被開辟到了一個稱作元空間的直接內(nèi)存區(qū)域。
轉(zhuǎn)載:http://blog.csdn.net/rainnnbow/article/details/50541079
參考: 《深入理解Java虛擬機(jī)-JVM高級特性與最佳實(shí)踐》第二版 周志明著