JVM(1)——JVM内存分区
一、JVM簡介
JVM,即Java虛擬機(jī)(Java Virtual Machine),一種能夠運(yùn)行Java bytecode的虛擬機(jī),是Java實(shí)現(xiàn)跨平臺(tái)的基礎(chǔ)。
引入Java語言虛擬機(jī)后,Java語言在不同平臺(tái)上運(yùn)行時(shí)不需要重新編譯。Java語言使用Java虛擬機(jī)屏蔽了與具體平臺(tái)相關(guān)的信息,使得Java語言編譯程序只需生成在Java虛擬機(jī)上運(yùn)行的目標(biāo)代碼(字節(jié)碼),就可以在多種平臺(tái)上不加修改地運(yùn)行。Java虛擬機(jī)在執(zhí)行字節(jié)碼時(shí),把字節(jié)碼解釋成具體平臺(tái)上的機(jī)器指令執(zhí)行。這就是Java的能夠“一次編譯,到處運(yùn)行”的原因。
二、JVM分區(qū)
Java虛擬機(jī)在執(zhí)行Java程序的過程中,會(huì)把它管轄的內(nèi)存劃分為若干個(gè)不同的數(shù)據(jù)區(qū)域。如下圖所示:
(1)程序計(jì)數(shù)器
程序計(jì)數(shù)器(Program Counter Register):程序計(jì)數(shù)器是一個(gè)比較小的內(nèi)存區(qū)域,用于指示當(dāng)前線程所執(zhí)行的字節(jié)碼執(zhí)行到了第幾行,可以理解為是當(dāng)前線程的行號指示器。字節(jié)碼解釋器在工作時(shí),會(huì)通過改變這個(gè)計(jì)數(shù)器的值來取下一條語句指令。每個(gè)程序計(jì)數(shù)器只用來記錄一個(gè)線程的行號,所以它是線程私有(一個(gè)線程就有一個(gè)程序計(jì)數(shù)器)的。
如果程序執(zhí)行的是一個(gè)Java方法,則計(jì)數(shù)器記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令地址;如果正在執(zhí)行的是一個(gè)本地(native,由C語言編寫完成)方法,則計(jì)數(shù)器的值為Undefined,由于程序計(jì)數(shù)器只是記錄當(dāng)前指令地址,所以不存在內(nèi)存溢出的情況,因此,程序計(jì)數(shù)器也是所有JVM內(nèi)存區(qū)域中唯一一個(gè)沒有定義OutOfMemoryError的區(qū)域。
(2)虛擬機(jī)棧
虛擬機(jī)棧(JVM Stack):一個(gè)線程的每個(gè)方法在執(zhí)行的同時(shí),都會(huì)創(chuàng)建一個(gè)棧幀(Statck Frame),棧幀中存儲(chǔ)的有局部變量表、操作棧、動(dòng)態(tài)鏈接、方法出口等,當(dāng)方法被調(diào)用時(shí),棧幀在JVM棧中入棧,當(dāng)方法執(zhí)行完成時(shí),棧幀出棧。
局部變量表中存儲(chǔ)著方法的相關(guān)局部變量,包括各種基本數(shù)據(jù)類型,對象的引用,返回地址等。在局部變量表中,只有l(wèi)ong和double類型會(huì)占用2個(gè)局部變量空間(Slot,對于32位機(jī)器,一個(gè)Slot就是32個(gè)bit),其它都是1個(gè)Slot。需要注意的是,局部變量表是在編譯時(shí)就已經(jīng)確定好的,方法運(yùn)行所需要分配的空間在棧幀中是完全確定的,在方法的生命周期內(nèi)都不會(huì)改變。
虛擬機(jī)棧中定義了兩種異常,如果線程調(diào)用的棧深度大于虛擬機(jī)允許的最大深度,則拋出StatckOverFlowError(棧溢出);不過多數(shù)Java虛擬機(jī)都允許動(dòng)態(tài)擴(kuò)展虛擬機(jī)棧的大小(有少部分是固定長度的),所以線程可以一直申請棧,直到內(nèi)存不足,此時(shí),會(huì)拋出OutOfMemoryError(內(nèi)存溢出)。
每個(gè)線程對應(yīng)著一個(gè)虛擬機(jī)棧,因此虛擬機(jī)棧也是線程私有的。
(3)本地方法棧
本地方法棧(Native Method Statck):本地方法棧在作用、運(yùn)行機(jī)制、異常類型等方面都與虛擬機(jī)棧相同,唯一的區(qū)別是:虛擬機(jī)棧是執(zhí)行Java方法的,而本地方法棧是用來執(zhí)行native方法的,在很多虛擬機(jī)中(如Sun的JDK默認(rèn)的HotSpot虛擬機(jī)),會(huì)將本地方法棧與虛擬機(jī)棧放在一起使用。
本地方法棧也是線程私有的。
(4)堆區(qū)
堆區(qū)(Heap):堆區(qū)是理解Java GC機(jī)制最重要的區(qū)域,沒有之一。在JVM所管理的內(nèi)存中,堆區(qū)是最大的一塊,堆區(qū)也是Java GC機(jī)制所管理的主要內(nèi)存區(qū)域,堆區(qū)由所有線程共享,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建。堆區(qū)的存在是為了存儲(chǔ)對象實(shí)例,原則上講,所有的對象都在堆區(qū)上分配內(nèi)存(不過現(xiàn)代技術(shù)里,也不是這么絕對的,也有棧上直接分配的)。
一般的,根據(jù)Java虛擬機(jī)規(guī)范規(guī)定,堆內(nèi)存需要在邏輯上是連續(xù)的(在物理上不需要),在實(shí)現(xiàn)時(shí),可以是固定大小的,也可以是可擴(kuò)展的,目前主流的虛擬機(jī)都是可擴(kuò)展的。如果在執(zhí)行垃圾回收之后,仍沒有足夠的內(nèi)存分配,也不能再擴(kuò)展,將會(huì)拋出OutOfMemoryError異常。
(5)方法區(qū)
方法區(qū)(Method Area):在Java虛擬機(jī)規(guī)范中,將方法區(qū)作為堆的一個(gè)邏輯部分來對待,但事實(shí)上,方法區(qū)并不是堆(Non-Heap);另外,不少人的博客中,將Java GC的分代收集機(jī)制分為3個(gè)代:青年代,老年代,永久代,這些作者將方法區(qū)定義為“永久代”,這是因?yàn)?#xff0c;對于之前的HotSpot Java虛擬機(jī)的實(shí)現(xiàn)方式中,將分代收集的思想擴(kuò)展到了方法區(qū),并將方法區(qū)設(shè)計(jì)成了永久代。不過,除HotSpot之外的多數(shù)虛擬機(jī),并不將方法區(qū)當(dāng)做永久代,HotSpot本身,也計(jì)劃取消永久代。本文中,由于筆者主要使用Oracle JDK6.0,因此仍將使用永久代一詞。
方法區(qū)是各個(gè)線程共享的區(qū)域,用于存儲(chǔ)已經(jīng)被虛擬機(jī)加載的類信息(即加載類時(shí)需要加載的信息,包括版本、field、方法、接口等信息)、final常量、靜態(tài)變量、編譯器即時(shí)編譯的代碼等。
方法區(qū)在物理上也不需要是連續(xù)的,可以選擇固定大小或可擴(kuò)展大小,并且方法區(qū)比堆還多了一個(gè)限制:可以選擇是否執(zhí)行垃圾收集。一般的,方法區(qū)上執(zhí)行的垃圾收集是很少的,這也是方法區(qū)被稱為永久代的原因之一(HotSpot),但這也不代表著在方法區(qū)上完全沒有垃圾收集,其上的垃圾收集主要是針對常量池的內(nèi)存回收和對已加載類的卸載。
在方法區(qū)上進(jìn)行垃圾收集,條件苛刻而且相當(dāng)困難,效果也不令人滿意,所以一般不做太多考慮,可以留作以后進(jìn)一步深入研究時(shí)使用。
在方法區(qū)上定義了OutOfMemoryError異常,在內(nèi)存不足時(shí)拋出。
運(yùn)行時(shí)常量池(Runtime Constant Pool)是方法區(qū)的一部分,用于存儲(chǔ)編譯期就生成的字面常量、符號引用、翻譯出來的直接引用(符號引用就是編碼是用字符串表示某個(gè)變量、接口的位置,直接引用就是根據(jù)符號引用翻譯出來的地址,將在類鏈接階段完成翻譯);運(yùn)行時(shí)常量池除了存儲(chǔ)編譯期常量外,也可以存儲(chǔ)在運(yùn)行時(shí)間產(chǎn)生的常量(比如String類的intern()方法,作用是String維護(hù)了一個(gè)常量池,如果調(diào)用的字符“abc”已經(jīng)在常量池中,則返回池中的字符串地址,否則,新建一個(gè)常量加入池中,并返回地址)。
(6)直接內(nèi)存
直接內(nèi)存(Direct Memory):直接內(nèi)存并不是JVM管理的內(nèi)存,可以這樣理解,直接內(nèi)存,就是JVM以外的機(jī)器內(nèi)存,比如,你有4G的內(nèi)存,JVM占用了1G,則其余的3G就是直接內(nèi)存,JDK中有一種基于通道(Channel)和緩沖區(qū) (Buffer)的內(nèi)存分配方式,將由C語言實(shí)現(xiàn)的native函數(shù)庫分配在直接內(nèi)存中,用存儲(chǔ)在JVM堆中的DirectByteBuffer來引用。 由于直接內(nèi)存收到本機(jī)器內(nèi)存的限制,所以也可能出現(xiàn)OutOfMemoryError的異常。
參考資料:https://segmentfault.com/a/1190000002579346
總結(jié)
以上是生活随笔為你收集整理的JVM(1)——JVM内存分区的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java Object有哪些公用方法?
- 下一篇: JVM(2)——JVM类加载机制