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