×

Java虛擬機(jī)的內(nèi)存結(jié)構(gòu)

  • 作者:新網(wǎng)
  • 來(lái)源:新網(wǎng)
  • 瀏覽:100
  • 2018-04-26 15:55:24

我們都知道虛擬機(jī)的內(nèi)存劃分了多個(gè)區(qū)域,并不是一張大餅。那么為什么要?jiǎng)澐譃槎鄩K區(qū)域呢,直接搞一塊區(qū)域,所有用到內(nèi)存的地方都往這塊區(qū)域里扔不就行了?

   t016f4f41de43840a98.jpg

         我們都知道虛擬機(jī)的內(nèi)存劃分了多個(gè)區(qū)域,并不是一張大餅。那么為什么要?jiǎng)澐譃槎鄩K區(qū)域呢,直接搞一塊區(qū)域,所有用到內(nèi)存的地方都往這塊區(qū)域里扔不就行了?
        是的,如果不進(jìn)行區(qū)域劃分,扔的時(shí)候確實(shí)痛快,可用的時(shí)候再去找怎么辦呢,這就引入了第一個(gè)問(wèn)題,分類管理,類似于衣柜,系統(tǒng)磁盤等等,為了方便查找,我們會(huì)進(jìn)行分區(qū)分類。另外如果不進(jìn)行分區(qū),內(nèi)存用盡了怎么辦呢?這里就引入了內(nèi)存劃分的第二個(gè)原因,就是為了方便內(nèi)存的回收。如果不分,回收內(nèi)存需要全部?jī)?nèi)存掃描,那就慢死了,內(nèi)存根據(jù)不同的使用功能分成不同的區(qū)域,那么內(nèi)存回收也就可以根據(jù)每個(gè)區(qū)域的特定進(jìn)行回收,比如像棧內(nèi)存中的棧幀,隨著方法的執(zhí)行棧幀進(jìn)棧,方法執(zhí)行完畢就出棧了,而對(duì)于像堆內(nèi)存的回收就需要使用經(jīng)典的回收算法來(lái)進(jìn)行回收了,所以看起來(lái)分類這么麻煩,其實(shí)是大有好處的。
  提到虛擬機(jī)的內(nèi)存結(jié)構(gòu),可能首先想起來(lái)的就是堆棧。對(duì)象分配到堆上,棧上用來(lái)分配對(duì)象的引用以及一些基本數(shù)據(jù)類型相關(guān)的值。但是·虛擬機(jī)的內(nèi)存結(jié)構(gòu)遠(yuǎn)比此要復(fù)雜的多。除了我們所認(rèn)識(shí)的(還沒(méi)有認(rèn)識(shí)完全)的堆棧以外,還有程序計(jì)數(shù)器,本地方法棧和方法區(qū)。我們平時(shí)所說(shuō)的棧內(nèi)存,一般是指的棧內(nèi)存中的局部變量表。下面是官方所給的虛擬機(jī)的內(nèi)存結(jié)構(gòu)圖
  從圖中可以看到有5大內(nèi)存區(qū)域,按照是否被線程所共享可分為兩部分,一部分是線程獨(dú)占區(qū)域,包括Java棧,本地方法棧和程序計(jì)數(shù)器。還有一部分是被線程所共享的,包括方法區(qū)和堆。什么是線程共享和線程獨(dú)占呢,非常好理解,我們知道每一個(gè)Java進(jìn)行都會(huì)有多個(gè)線程同時(shí)運(yùn)行,那么線程共享區(qū)的這片區(qū)域就是被所有線程一起使用的,不管有多少個(gè)線程,這片空間始終就這一個(gè)。而線程的獨(dú)占區(qū),是每個(gè)線程都有這么一份內(nèi)存空間,每個(gè)線程的這片空間都是獨(dú)有的,有多少個(gè)線程就有多少個(gè)這么個(gè)空間。上圖的區(qū)域的大小并不代表實(shí)際內(nèi)存區(qū)域的大小,實(shí)際運(yùn)行過(guò)程中,內(nèi)存區(qū)域的大小也是可以動(dòng)態(tài)調(diào)整的。下面來(lái)具體說(shuō)說(shuō)每一個(gè)區(qū)域的主要功能。
  程序計(jì)數(shù)器,我們?cè)趯懘a的過(guò)程中,開發(fā)工具一般都會(huì)給我們標(biāo)注行號(hào)方便查看和閱讀代碼。那么在程序在運(yùn)行過(guò)程中也有一個(gè)類似的行號(hào)方便虛擬機(jī)的執(zhí)行,就是程序計(jì)數(shù)器,在c語(yǔ)言中,我們知道會(huì)有一個(gè)goto語(yǔ)句,其實(shí)就是跳轉(zhuǎn)到了指定的行,這個(gè)行號(hào)就是程序計(jì)數(shù)器。存儲(chǔ)的就是程序下一條所執(zhí)行的指令。這部分區(qū)域是線程所獨(dú)享的區(qū)域,我們知道線程是一個(gè)順序執(zhí)行流,每個(gè)線程都有自己的執(zhí)行順序,如果所有線程共用一個(gè)程序計(jì)數(shù)器,那么程序執(zhí)行肯定就會(huì)出亂子。為了保證每個(gè)線程的執(zhí)行順序,所以程序計(jì)數(shù)器是被單個(gè)線程所獨(dú)顯的。程序計(jì)數(shù)器這塊內(nèi)存區(qū)域是唯一一個(gè)在jvm規(guī)范中沒(méi)有規(guī)定內(nèi)存溢出的。
  java虛擬機(jī)棧,java虛擬機(jī)棧是程序運(yùn)行的動(dòng)態(tài)區(qū)域,每個(gè)方法的執(zhí)行都伴隨著棧幀的入棧和出棧。 棧幀也叫過(guò)程活動(dòng)記錄,是編譯器用來(lái)實(shí)現(xiàn)過(guò)程/函數(shù)調(diào)用的一種數(shù)據(jù)結(jié)構(gòu)。棧幀中包括了局部變量表,操作數(shù)棧,方法返回地址以及額外的一些附加信息,在編譯過(guò)程中,局部變量表的大小已經(jīng)確定,操作數(shù)棧深度也已經(jīng)確定,因此棧幀在運(yùn)行的過(guò)程中需要分配多大的內(nèi)存是固定的,不受運(yùn)行時(shí)影響。對(duì)于沒(méi)有逃逸的對(duì)象也會(huì)在棧上分配內(nèi)存,對(duì)象的大小其實(shí)在運(yùn)行時(shí)也是確定的,因此即使出現(xiàn)了棧上內(nèi)存分配,也不會(huì)導(dǎo)致棧幀改變大小。
  一個(gè)線程中,可能調(diào)用鏈會(huì)很長(zhǎng),很多方法都同時(shí)處于執(zhí)行狀態(tài)。對(duì)于執(zhí)行引擎來(lái)講,活動(dòng)線程中,只有棧頂?shù)臈亲钣行У模Q為當(dāng)前棧幀,這個(gè)棧幀所關(guān)聯(lián)的方法稱為當(dāng)前方法。執(zhí)行引擎所運(yùn)行的字節(jié)碼指令僅對(duì)當(dāng)前棧幀進(jìn)行操作。
  局部變量表:我們平時(shí)所說(shuō)的棧內(nèi)存一般就是指棧內(nèi)存中的局部變量表。這里主要是存儲(chǔ)變量所用。對(duì)于基本數(shù)據(jù)類型直接存儲(chǔ)其值,對(duì)于引用數(shù)據(jù)類型則存儲(chǔ)其地址。局部變量表的最小存儲(chǔ)單位是Slot,每個(gè)Slot都能存放一個(gè)boolean、byte、char、short、int、float、reference或returnAddress類型的數(shù)據(jù)。
  既然前面提到了數(shù)據(jù)類型,在此順便說(shuō)一下,一個(gè)Slot可以存放一個(gè)32位以內(nèi)的數(shù)據(jù)類型,Java中占用32位以內(nèi)的數(shù)據(jù)類型有boolean、byte、char、short、int、float、reference和returnAddress八種類型。前面六種不需要多解釋,大家都認(rèn)識(shí),而后面的reference是對(duì)象的引用。虛擬機(jī)規(guī)范既沒(méi)有說(shuō)明它的長(zhǎng)度,也沒(méi)有明確指出這個(gè)引用應(yīng)有怎樣的結(jié)構(gòu),但是一般來(lái)說(shuō),虛擬機(jī)實(shí)現(xiàn)至少都應(yīng)當(dāng)能從此引用中直接或間接地查找到對(duì)象在Java堆中的起始地址索引和方法區(qū)中的對(duì)象類型數(shù)據(jù)。而returnAddress是為字節(jié)碼指令jsr、jsr_w和ret服務(wù)的,它指向了一條字節(jié)碼指令的地址。
  對(duì)于64位的數(shù)據(jù)類型,虛擬機(jī)會(huì)以高位在前的方式為其分配兩個(gè)連續(xù)的Slot空間。Java語(yǔ)言中明確規(guī)定的64位的數(shù)據(jù)類型只有l(wèi)ong和double兩種(reference類型則可能是32位也可能是64位)。值得一提的是,這里把long和double數(shù)據(jù)類型讀寫分割為兩次32讀寫的做法類似。不過(guò),由于局部變量表建立在線程的堆棧上,是線程私有的數(shù)據(jù),無(wú)論讀寫兩個(gè)連續(xù)的Slot是否是原子操作,都不會(huì)引起數(shù)據(jù)安全問(wèn)題。
  操作數(shù)棧是一個(gè)后入先出(Last In First Out, LIFO)棧。同局部變量表一樣,操作數(shù)棧的最大深度也在編譯的時(shí)候被寫入到字節(jié)碼文件中,關(guān)于字節(jié)碼文件,后面我會(huì)具體的來(lái)描述。操作數(shù)棧的每一個(gè)元素可以是任意的Java數(shù)據(jù)類型,包括long和double。32位數(shù)據(jù)類型所占的棧容量為1,64位數(shù)據(jù)類型所占的棧容量為2。在方法執(zhí)行的任何時(shí)候,操作數(shù)棧的深度都不會(huì)超過(guò)在max_stacks數(shù)據(jù)項(xiàng)中設(shè)定的最大值。
  當(dāng)一個(gè)方法剛剛開始執(zhí)行的時(shí)候,這個(gè)方法的操作數(shù)棧是空的,在方法的執(zhí)行過(guò)程中,會(huì)有各種字節(jié)碼指令向操作數(shù)棧中寫入和提取內(nèi)容,也就是入棧出棧操作。例如,在做算術(shù)運(yùn)算的時(shí)候是通過(guò)操作數(shù)棧來(lái)進(jìn)行的,又或者在調(diào)用其他方法的時(shí)候是通過(guò)操作數(shù)棧來(lái)進(jìn)行參數(shù)傳遞的。
  舉個(gè)例子,整數(shù)加法的字節(jié)碼指令iadd在運(yùn)行的時(shí)候要求操作數(shù)棧中最接近棧頂?shù)膬蓚€(gè)元素已經(jīng)存入了兩個(gè)int型的數(shù)值,當(dāng)執(zhí)行這個(gè)指令時(shí),會(huì)將這兩個(gè)int值和并相加,然后將相加的結(jié)果入棧。
  操作數(shù)棧中元素的數(shù)據(jù)類型必須與字節(jié)碼指令的序列嚴(yán)格匹配,在編譯程序代碼的時(shí)候,編譯器要嚴(yán)格保證這一點(diǎn),在類校驗(yàn)階段的數(shù)據(jù)流分析中還要再次驗(yàn)證這一點(diǎn)。再以上面的iadd指令為例,這個(gè)指令用于整型數(shù)加法,它在執(zhí)行時(shí),最接近棧頂?shù)膬蓚€(gè)元素的數(shù)據(jù)類型必須為int型,不能出現(xiàn)一個(gè)long和一個(gè)float使用iadd命令相加的情況。
  本地方法棧 與虛擬機(jī)棧所發(fā)揮的作用是非常相似的,其區(qū)別不過(guò)是虛擬機(jī)棧為虛擬機(jī)執(zhí)行Java方法(也就是字節(jié)碼)服務(wù),而本地方法棧則是為虛擬機(jī)使用到的Native方法服務(wù)。虛擬機(jī)規(guī)范中對(duì)本地方法棧中的方法使用的語(yǔ)言、使用方式與數(shù)據(jù)結(jié)構(gòu)并沒(méi)有強(qiáng)制規(guī)定,因此具體的虛擬機(jī)可以自由實(shí)現(xiàn)它。甚至有的虛擬機(jī)(譬如Sun HotSpot虛擬機(jī))直接就把本地方法棧和虛擬機(jī)棧合二為一。與虛擬機(jī)棧一樣,本地方法棧區(qū)域也會(huì)拋出StackOverflowError和OutOfMemoryError異常。
  方法區(qū)經(jīng)常會(huì)被人稱之為永久代,但這倆并不是一個(gè)概念。首先永久代的概念僅僅在HotSpot虛擬機(jī)中存在,不幸的是,在jdk8中,Hotspot去掉了永久代這一說(shuō)法,使用了Native Memory,也就是Metaspace空間。那么方法區(qū)是干嘛的呢?我們可以這么理解,我們要運(yùn)行Java代碼,首先需要編譯,然后才能運(yùn)行。在運(yùn)行的過(guò)程中,我們知道首先需要加載字節(jié)碼文件。也就是說(shuō)要把字節(jié)碼文件加載到內(nèi)存中。好了,問(wèn)題就來(lái)了,字節(jié)碼文件放到內(nèi)存中的什么地方呢,就是方法區(qū)中。當(dāng)然除了編譯后的字節(jié)碼之外,方法區(qū)中還會(huì)存放常量,靜態(tài)變量以及及時(shí)編譯器編譯后的代碼等數(shù)據(jù)。
堆,一般來(lái)講堆內(nèi)存是Java虛擬機(jī)中最大的一塊內(nèi)存區(qū)域,同方法區(qū)一樣,是被所有線程所共享的區(qū)域。此區(qū)域所存在的唯一目的就存放對(duì)象的實(shí)例(對(duì)象實(shí)例并不一定全部在堆中創(chuàng)建)。堆內(nèi)存是垃圾收集器主要光顧的區(qū)域,一般來(lái)講根據(jù)使用的垃圾收集器的不同,堆中還會(huì)劃分為一些區(qū)域,比如新生代和老年代。新生代還可以再劃分為Eden,Survivor等區(qū)域。另外為了性能和安全性的角度,在堆中還會(huì)為線程劃分單獨(dú)的區(qū)域,稱之為線程分配緩沖區(qū)。更細(xì)致的劃分是為了讓垃圾收集器能夠更高效的工作,提高垃圾收集的效率。
以上就是虛擬機(jī)的內(nèi)存結(jié)構(gòu)的內(nèi)容了。
 

免責(zé)聲明:本文內(nèi)容由互聯(lián)網(wǎng)用戶自發(fā)貢獻(xiàn)自行上傳,本網(wǎng)站不擁有所有權(quán),也不承認(rèn)相關(guān)法律責(zé)任。如果您發(fā)現(xiàn)本社區(qū)中有涉嫌抄襲的內(nèi)容,請(qǐng)發(fā)送郵件至:operations@xinnet.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),本站將立刻刪除涉嫌侵權(quán)內(nèi)容。

免費(fèi)咨詢獲取折扣

Loading