JVM_運(yùn)行數(shù)據(jù)區(qū)

JAVA的運(yùn)行時(shí)數(shù)據(jù)區(qū),老生常談。

我們常說的JAVA的運(yùn)行時(shí)數(shù)據(jù)區(qū)包括:程序計(jì)數(shù)器、虛擬機(jī)棧、本地方法棧、方法區(qū)、堆,其中前三者是線程私有的,后二者是線程公有的。這里為什么以線程為劃分私有/公有的依據(jù),是因?yàn)?strong>線程是JVM調(diào)度的最小單位。

線程私有

程序計(jì)數(shù)器

? 在匯編語言中,程序計(jì)數(shù)器是指CPU中的寄存器,它保存的是程序當(dāng)前執(zhí)行的指令地址(也可以說保存下一條指令的所在存儲(chǔ)單元的地址)。在JVM中的程序計(jì)數(shù)器也是類似的功能,用于記錄當(dāng)前線程執(zhí)行到的字節(jié)碼的行號(hào),字節(jié)碼的解釋器工作的時(shí)候就是通過改變這個(gè)計(jì)數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令。

? 由于在JVM中,多線程是通過線程輪流切換來獲得CPU執(zhí)行時(shí)間的,因此,在任一具體時(shí)刻,一個(gè)CPU的內(nèi)核只會(huì)執(zhí)行一條線程中的指令,因此為了能夠使得每個(gè)線程都在線程切換后能夠恢復(fù)在切換之前的程序執(zhí)行位置,每個(gè)線程都需要有自己獨(dú)立的程序計(jì)數(shù)器,并且不能相互干擾,否則就會(huì)影響到程序的正常執(zhí)行次序。因此,可以這么說,程序計(jì)數(shù)器是每個(gè)線程所私有的。

? 注意:此內(nèi)存區(qū)域是JVM里面唯一一個(gè)不會(huì)發(fā)生內(nèi)存溢出(OOM OutOfMemoryError)的區(qū)域。

虛擬機(jī)棧

? 虛擬機(jī)棧也就是我們常說的JVM的棧,棧中存放著棧幀,每一個(gè)方法執(zhí)行會(huì)產(chǎn)生一個(gè)棧幀。方法開始執(zhí)行時(shí),會(huì)在虛擬機(jī)棧中壓入一個(gè)棧幀,方法執(zhí)行結(jié)束時(shí),會(huì)將棧幀彈出棧。棧幀中包含局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈鏈接、方法出口等信息構(gòu)成。

? 局部變量表:存放編譯器可知的各種基本數(shù)據(jù)類型、對象引用類型和returnAddress類型(指向一條字節(jié)碼指令的地址:函數(shù)返回地址)。long、double、占用兩個(gè)局部變量控件的Slot。局部變量表所需要的內(nèi)存空間在編譯器確定,當(dāng)進(jìn)入一個(gè)方法時(shí),方法在棧幀中所需要分配的局部變量控件是完全確定的,不可動(dòng)態(tài)改變大小。詳細(xì)內(nèi)容可以使用JDK自帶的工具javap對class進(jìn)行反編譯查看。

? 操作數(shù)庫:后進(jìn)先出LIFO,最大深度由編譯期決定。棧幀剛建立時(shí),操作數(shù)棧為空,執(zhí)行方法操作時(shí),操作數(shù)棧用于存放JVM從局部變量表復(fù)制的常量或者變量,提供提取,及結(jié)果入棧,也用于存放調(diào)用方法需要的參數(shù)及接受方法返回的結(jié)果。操作數(shù)??梢源娣乓粋€(gè)JVM中定義的任意數(shù)據(jù)類型的值。在任意時(shí)刻,操作數(shù)棧都有一個(gè)固定的棧深度,基本類型除了long、double占用兩個(gè)深度,其他占用一個(gè)深度。

? 動(dòng)態(tài)鏈接:當(dāng)一個(gè)方法被執(zhí)行后,有兩種方式退出該方法:執(zhí)行引擎遇到任意一個(gè)方法返回的字節(jié)碼指令 或遇到了 異常 ,并且該異常沒有在方法體內(nèi)得到處理。無論采用何種方式退出,在方法退出之后,都需要返回到方法被調(diào)用的位置,程序才能繼續(xù)執(zhí)行。方法返回時(shí)可能需要在棧幀中保存一些信息,用來幫助恢復(fù)它的上層方法的執(zhí)行狀態(tài)。一般來說方法正常退出時(shí),調(diào)用者的程序計(jì)數(shù)器的值就可以作為返回地址,棧幀中很可能保存了這個(gè)計(jì)數(shù)器的值,而方法異常退出時(shí),返回地址是要通過異常處理器來確定的,棧幀中一般不保存這部分信息。

? 如果線程請求的棧深入大于虛擬機(jī)所允許的深度,將拋出StackOverflowError異常。如果虛擬機(jī)棧可以動(dòng)態(tài)擴(kuò)展(大部分虛擬機(jī)允許動(dòng)態(tài)擴(kuò)展,也可以設(shè)置固定大小的虛擬機(jī)棧),但是無法申請到足夠的內(nèi)存,會(huì)拋出OutOfMemorError。

本地方法棧

? 本地方法棧與虛擬機(jī)棧所發(fā)揮的作用很相似,他們的區(qū)別在于虛擬機(jī)棧為執(zhí)行Java代碼方法服務(wù),而本地方法棧為Native方法服務(wù)。

線程公有

? 因?yàn)槭蔷€程公有的,幾乎所有的線程都把自己產(chǎn)生的實(shí)例對象放在堆上。所以堆是JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)中所在空間最大的一塊,我們說的垃圾回收也主要發(fā)生在堆上。如果按GC的不同回收算法,堆又可以劃分為“老年代”,“新生代”,“新生代”再劃分為Eden空間、From Survivor空間、To Survivor空間。關(guān)于垃圾回收,在這里再討論。

? 堆可以是固定大小的,也可以通過設(shè)置配置文件來設(shè)置該為可擴(kuò)展的。如果堆上沒有內(nèi)存進(jìn)行分配,并無法進(jìn)行擴(kuò)展時(shí),將會(huì)拋出OutOfMemoryError異常

方法區(qū)

? 方法區(qū)中存儲(chǔ)的是每個(gè)類的信息(包括類的名稱、方法信息、字段信息)、靜態(tài)變量、常量以及編譯器編譯后的代碼等。在Class文件中除了類的字段、方法、接口等描述信息外,還有一項(xiàng)信息是常量池,用來存儲(chǔ)編譯期間生成的字面量和符號(hào)引用。雖然Java虛擬機(jī)規(guī)范把方法區(qū)描述為堆的一個(gè)邏輯部分,但是它卻又一個(gè)別名叫做Non-Heap(非堆),目的應(yīng)該是與Java堆區(qū)分開來.

原則上,如何實(shí)現(xiàn)方法區(qū)屬于虛擬機(jī)實(shí)現(xiàn)細(xì)節(jié),不受虛擬機(jī)規(guī)范約束,但是使用永久代來實(shí)現(xiàn)方法區(qū),現(xiàn)在看來并不是一個(gè)好主意,因?yàn)檫@樣更容易遇到內(nèi)存泄漏問題(永久代有-XX:MaxPermSize的上限,J9和JRockit只要沒有觸碰到進(jìn)程可用內(nèi)存的上限,例如:32位操作系統(tǒng)中的4GB,就不會(huì)出現(xiàn)問題),而且有極少的方法(例如String.intern())會(huì)因?yàn)檫@個(gè)原因?qū)е虏煌摂M機(jī)下有不同的表現(xiàn).因此,對于HotSpot虛擬機(jī),根據(jù)官方發(fā)布的路線圖信息,現(xiàn)在也有放棄永久代并逐步采用Native Memory來實(shí)現(xiàn)方法區(qū)的規(guī)劃了,在目前已經(jīng)發(fā)布的JDK1.7的HotSpot中,已經(jīng)把原本放在永久代的字符串常量池移出.

? 運(yùn)行時(shí)常量池(Runtime Constant Pool)是方法區(qū)的一部分.Class文件中除了有類的版本/字段/方法/接口等描述信息外,還有一項(xiàng)信息是常量池(Constant Pool Table),用于存放編譯期生成的各種字面量和符號(hào)引用,這部分內(nèi)容將類在加載后進(jìn)入方法區(qū)的運(yùn)行時(shí)常量池中存放.

? 運(yùn)行時(shí)常量池相對于Class文件常量池的另外一個(gè)重要特征就是具備動(dòng)態(tài)性,Java語言并不要求常量一定只有在編譯期才能產(chǎn)生,也就是并非預(yù)置入Class文件中常量池的內(nèi)容才能進(jìn)入方法區(qū)運(yùn)行時(shí)常量池,運(yùn)行期間也可能將新的常量放入池中,這種特性被開發(fā)人員利用得比較多的便是String類的intern()方法。

public String intern()
返回字符串對象的規(guī)范表示。
最初為空的字符串池由String類String 。

當(dāng)調(diào)用intern方法時(shí),如果池已經(jīng)包含與equals(Object)方法確定的相當(dāng)于此String對象的字符串,則返回來自池的字符串。 否則,此String對象將添加到池中,并返回對此String對象的引用。

由此可見,對于任何兩個(gè)字符串s和t , s.intern() == t.intern()是true當(dāng)且僅當(dāng)s.equals(t)是true 。

所有文字字符串和字符串值常量表達(dá)式都被實(shí)體化。 字符串文字在The Java? Language Specification的 3.10.5節(jié)中定義。

結(jié)果
一個(gè)字符串與該字符串具有相同的內(nèi)容,但保證來自一個(gè)唯一的字符串池。

? 注意:在JVM規(guī)范中,沒有強(qiáng)制要求方法區(qū)必須實(shí)現(xiàn)垃圾回收,很多人習(xí)慣將方法區(qū)稱為"永久代",是因?yàn)?strong>HotSpot虛擬機(jī)以永久代來實(shí)現(xiàn)方法區(qū),從而JVM的垃圾處理器可以像堆區(qū)一樣管理這部分的區(qū)域,從而不需要專門為這部分設(shè)計(jì)垃圾回收機(jī)制。不過JDK8之后,Hotspot虛擬機(jī)將運(yùn)行時(shí)常量池從永久代移除了。然后引入了一個(gè)新的概念"元空間"。

參考

Java虛擬機(jī)基礎(chǔ)——2JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)

Java虛擬機(jī)--方法區(qū)(運(yùn)行時(shí)常量池)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容