阿里架構(gòu)師帶你深入淺出jvm

本文跟大家聊聊JVM的內(nèi)部結(jié)構(gòu)员淫,從組件中的多線程處理迅耘,JVM系統(tǒng)線程瞎颗,局部變量數(shù)組等方面進(jìn)行解析

JVM

JVM = 類加載器(classloader) + 執(zhí)行引擎(execution engine) + 運(yùn)行時(shí)數(shù)據(jù)區(qū)域(runtime data area)

下面這幅圖展示了一個(gè)典型的JVM(符合JVM Specification Java SE 7 Edition)所具備的關(guān)鍵內(nèi)部組件婉徘。

組件中的多線程處理

多線程處理”或“自由線程處理”指的是一個(gè)程序同時(shí)執(zhí)行多個(gè)操作線程的能力。

作為多線程應(yīng)用程序的一個(gè)示例敬矩,某個(gè)程序在一個(gè)線程上接收用戶輸入概行,在另一個(gè)線程上執(zhí)行多種復(fù)雜的計(jì)算,并在第三個(gè)線程上更新數(shù)據(jù)庫弧岳。

在單線程應(yīng)用程序中凳忙,用戶可能會(huì)花費(fèi)時(shí)間等待計(jì)算或數(shù)據(jù)庫更新完成。 而在多線程應(yīng)用程序中禽炬,這些進(jìn)程可以在后臺(tái)進(jìn)行涧卵,因此不會(huì)浪費(fèi)用戶時(shí)間。?

多線程處理可以是組件編程中的一個(gè)非常強(qiáng)大的工具腹尖。通過編寫多線程組件柳恐,您可以創(chuàng)建在后臺(tái)執(zhí)行復(fù)雜計(jì)算的組件,它們?cè)试S用戶界面 (UI)

在計(jì)算的過程中自由地響應(yīng)用戶輸入热幔。 雖然多線程處理是一個(gè)強(qiáng)大的工具胎撤,但是要將其正確應(yīng)用卻比較困難。

未能正確實(shí)現(xiàn)的多線程代碼可能降低應(yīng)用程序性能断凶,或甚至導(dǎo)致應(yīng)用程序凍結(jié)。 下列主題將向您介紹多線程編程的一些注意事項(xiàng)和最佳做法巫俺。.NET

Framework 提供幾個(gè)在組件中進(jìn)行多線程處理的選項(xiàng)认烁。 System.Threading 命名空間中的功能是一個(gè)選項(xiàng)。

基于事件的異步模式是另一個(gè)選項(xiàng)介汹。 BackgroundWorker 組件是對(duì)異步模式的實(shí)現(xiàn)却嗡;它提供封裝在組件中以便于使用的高級(jí)功能。

JVM系統(tǒng)線程

如果你用jconsole或者任何其他的debug工具查看嘹承,可能會(huì)看到有許多線程在后臺(tái)運(yùn)行窗价。這些運(yùn)行著的后臺(tái)線程不包含主線程,主線程是基于執(zhí)行publicstatic

void main(String[])

的需要而被創(chuàng)建的叹卷。而這些后臺(tái)線程都是被主線程所創(chuàng)建撼港。在HotspotJVM中主要的后臺(tái)系統(tǒng)線程坪它,見下表:

VM 線程該線程用于等待執(zhí)行一系列能夠使得JVM到達(dá)一個(gè)“safe-point”的操作。

而這些操作不得不發(fā)生在一個(gè)獨(dú)立的線程上的原因是:它們都要求JVM處于一個(gè)——無法修改堆的safepoint帝牡。

被這個(gè)線程執(zhí)行的該類操作都是“stop-the-world”型的垃圾回收往毡、線程棧回收靶溜、線程擱置以及有偏差的鎖定撤銷开瞭。

周期性的任務(wù)線程該線程用于響應(yīng)timer事件(例如,中斷)罩息,這些事件用于調(diào)度執(zhí)行周期性的操作

GC 線程這些線程支持在JVM中不同類型的垃圾回收

編譯器線程它們用于在運(yùn)行時(shí)將字節(jié)碼編譯為本地機(jī)器碼

信號(hào)分發(fā)線程該線程接收發(fā)送給JVM的信號(hào)嗤详,并通過調(diào)用JVM合適的方法進(jìn)行處理

單個(gè)線程

每個(gè)線程的一次執(zhí)行都包含如下的組件

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

除非當(dāng)前指令或者操作碼是原生的,否則當(dāng)前指令或操作碼的地址都需要依賴于PC來尋址瓷炮。如果當(dāng)前方法是原生的葱色,那么該P(yáng)C即為undefined。所有的CPU都有一個(gè)PC崭别,通常PC在每個(gè)指令執(zhí)行后被增加以指向即將執(zhí)行的下一條指令的地址冬筒。JVM使用PC來跟蹤正在執(zhí)行的指令的位置。事實(shí)上茅主,PC被用來指向methodarea的一個(gè)內(nèi)存地址舞痰。

原生棧

不是所有的JVM都支持原生方法,但那些支持該特性的JVM通常會(huì)對(duì)每個(gè)線程創(chuàng)建一個(gè)原生方法棧诀姚。如果對(duì)JVM的JNI(JavaNative

Invocation)采用c鏈接模型的實(shí)現(xiàn)响牛,那么原生棧也將是一個(gè)C實(shí)現(xiàn)的棧。在這個(gè)例子中赫段,原生棧中參數(shù)的順序

呀打、返回值都將跟通常的C程序相同。一個(gè)原生方法通常會(huì)對(duì)JVM產(chǎn)生一個(gè)回調(diào)(這依賴于JVM的實(shí)現(xiàn))并執(zhí)行一個(gè)Java方法糯笙。這樣一個(gè)原生到Java的調(diào)用發(fā)生在棧上(通常在Java棧)贬丛,與此同時(shí)線程也將離開原生棧,通常在Java棧上創(chuàng)建一個(gè)新的frame给涕。

每個(gè)線程都有屬于它自己的棧豺憔,用于存儲(chǔ)在線程上執(zhí)行的每個(gè)方法的frame。棧是一個(gè)后進(jìn)先出的數(shù)據(jù)結(jié)構(gòu)够庙,這可以使得當(dāng)前正在執(zhí)行的方法位于棧的頂部恭应。對(duì)于每個(gè)方法的執(zhí)行,都會(huì)有一個(gè)新的frame被創(chuàng)建并被入棧到棧的頂部耘眨。當(dāng)方法正常的返回或在方法執(zhí)行的過程中遇到未捕獲的異常時(shí)frame會(huì)被出棧昼榛。棧不會(huì)被直接進(jìn)行操作,除了push/

pop frame

對(duì)象剔难。因此可以看出胆屿,frame對(duì)象可能會(huì)被分配在堆上奥喻,并且內(nèi)存也沒必要是連續(xù)的地址空間(請(qǐng)注意區(qū)分frame的指針跟frame對(duì)象)。

棧的限制

一個(gè)椵郝樱可以是動(dòng)態(tài)的或者是有合適大小的衫嵌。如果一個(gè)線程要求更大的棧,那么將拋出StackOverflowError異常彻秆;如果一個(gè)線程要求新創(chuàng)建一個(gè)frame楔绞,又沒有足夠的內(nèi)存空間來分配,將會(huì)拋出OutOfMemoryError異常唇兑。

Frame

對(duì)于每一個(gè)方法的執(zhí)行酒朵,一個(gè)新frame會(huì)被創(chuàng)建并被入棧到棧頂。當(dāng)方法正常返回或在方法執(zhí)行的過程中遇到未捕獲的異常扎附,frame會(huì)被出棧蔫耽。

局部變量數(shù)組

局部變量數(shù)組包含了在方法執(zhí)行期間所用到的所有的變量。包含一個(gè)對(duì)this的引用留夜,所有的方法參數(shù)匙铡,以及其他局部定義的變量。對(duì)于類方法(比如靜態(tài)方法)碍粥,方法參數(shù)的存儲(chǔ)索引從0開始鳖眼;而對(duì)于實(shí)例方法,索引為0的槽都為存儲(chǔ)this指針而保留嚼摩。

操作數(shù)棧

操作數(shù)棧在字節(jié)碼指令被執(zhí)行的過程中使用钦讳。它跟原生CPU使用的通用目的的寄存器類似。大部分的字節(jié)碼都把時(shí)間花費(fèi)在跟操作數(shù)棧打交道上枕面,通過入棧愿卒、出棧、復(fù)制潮秘、交換或者執(zhí)行那些生產(chǎn)/消費(fèi)值的操作琼开。對(duì)字節(jié)碼而言,那些在局部變量數(shù)組和操作數(shù)棧之間移動(dòng)值的指令是非常頻繁的枕荞。

動(dòng)態(tài)鏈接

每個(gè)frame都包含一個(gè)對(duì)運(yùn)行時(shí)常量池的引用稠通。該引用指向?qū)⒁粓?zhí)行的方法所屬的類的常量池。該引用也用于輔助動(dòng)態(tài)鏈接买猖。

當(dāng)一個(gè)Java類被編譯時(shí),所有對(duì)存儲(chǔ)在類的常量池中的變量以及方法的引用都被當(dāng)做符號(hào)引用滋尉。一個(gè)符號(hào)引用僅僅只是一個(gè)邏輯引用而不是最終指向物理內(nèi)存地址的引用玉控。JVM的實(shí)現(xiàn)可以選擇解析符號(hào)引用的時(shí)機(jī),該時(shí)機(jī)可以發(fā)生在當(dāng)類文件被驗(yàn)證后狮惜、被加載后高诺,這稱之eager或靜態(tài)分析碌识;不同的是它也可以發(fā)生在當(dāng)符號(hào)引用被首次使用的時(shí)候,稱之為lazy或延遲分析虱而。但JVM必須保證:解析發(fā)生在每個(gè)引用被首次使用前筏餐,同時(shí)在該時(shí)間點(diǎn),如果遇到分析錯(cuò)誤能夠拋出異常牡拇。綁定是一個(gè)處理過程魁瞪,它將被符號(hào)引用標(biāo)識(shí)的字段、方法或類替換為一個(gè)直接引用惠呼。這個(gè)處理過程只發(fā)生一次导俘,因?yàn)榉?hào)引用需要被完全替換。如果一個(gè)符號(hào)引用關(guān)聯(lián)著一個(gè)類剔蹋,而該類還沒有被解析旅薄,那么該類也會(huì)被立即加載。每個(gè)直接引用都被以偏移的方式存儲(chǔ)泣崩,該存儲(chǔ)結(jié)構(gòu)關(guān)聯(lián)著變量或方法的運(yùn)行時(shí)位置少梁。

線程之間共享

堆中某個(gè)節(jié)點(diǎn)的值總是不大于或不小于其父節(jié)點(diǎn)的值;

堆總是一棵完全二叉樹矫付。

將根節(jié)點(diǎn)最大的堆叫做最大堆或大根堆凯沪,根節(jié)點(diǎn)最小的堆叫做最小堆或小根堆。常見的堆有二叉堆技即、斐波那契堆等著洼。

堆的定義如下:n個(gè)元素的序列{k1,k2,ki,…,kn}當(dāng)且僅當(dāng)滿足下關(guān)系時(shí),稱之為堆而叼。

(ki <= k2i,ki <= k2i+1)或者(ki >= k2i,ki >= k2i+1), (i = 1,2,3,4...n/2)

若將和此次序列對(duì)應(yīng)的一維數(shù)組(即以一維數(shù)組作此序列的存儲(chǔ)結(jié)構(gòu))看成是一個(gè)完全二叉樹身笤,則堆的含義表明,完全二叉樹中所有非終端結(jié)點(diǎn)的值均不大于(或不小于)其左葵陵、右孩子結(jié)點(diǎn)的值液荸。由此,若序列{k1,k2,…,kn}是堆脱篙,則堆頂元素(或完全二叉樹的根)必為序列中n個(gè)元素的最小值(或最大值)

非堆式內(nèi)存

有些對(duì)象并不會(huì)創(chuàng)建在堆中娇钱,這些對(duì)象在邏輯上被認(rèn)為是JVM機(jī)制的一部分。

非堆式的內(nèi)存包括:

永久代中包含:

方法區(qū)

內(nèi)部字符串

代碼緩存:用于編譯以及存儲(chǔ)方法绊困,這些方法已經(jīng)被JIT編譯成本地代碼

內(nèi)存管理

對(duì)象和數(shù)組永遠(yuǎn)都不會(huì)被顯式釋放文搂,因此只能依靠垃圾回收器來自動(dòng)地回收它們。

通常秤朗,以如下的步驟進(jìn)行:

新對(duì)象和數(shù)組被創(chuàng)建在年輕代

次垃圾回收器將在年輕代上執(zhí)行煤蹭。那些仍然存活著的對(duì)象,將被從eden區(qū)移動(dòng)到survivor區(qū)

主垃圾回收器將會(huì)把對(duì)象在代與代之間進(jìn)行移動(dòng),主垃圾回收器通常會(huì)導(dǎo)致應(yīng)用程序的線程暫停硝皂。那些仍然存活著的對(duì)象將被從年輕代移動(dòng)到老年代

永久代會(huì)在每次老年代被回收的時(shí)候同時(shí)進(jìn)行常挚,它們?cè)趦烧咧衅湟粷M了之后都會(huì)被回收

JIT編譯

JIT具體的做法是這樣的:當(dāng)載入一個(gè)類型時(shí),CLR為該類型創(chuàng)建一個(gè)內(nèi)部數(shù)據(jù)結(jié)構(gòu)和相應(yīng)的函數(shù),當(dāng)函數(shù)第一被調(diào)用時(shí),JIT將該函數(shù)編譯成機(jī)器語言.當(dāng)再次遇到該函數(shù)時(shí)則直接從cache中執(zhí)行已編譯好的機(jī)器語言.

方法區(qū)

所有的線程共享相同的方法區(qū)。所以稽物,對(duì)于方法區(qū)數(shù)據(jù)的訪問以及對(duì)動(dòng)態(tài)鏈接的處理必須是線程安全的奄毡。如果兩個(gè)線程企圖訪問一個(gè)還沒有被載入的類(該類必須只能被加載一次)的字段或者方法,直到該類被加載完成贝或,這兩個(gè)線程才能繼續(xù)執(zhí)行吼过。

類的文件結(jié)構(gòu)

一個(gè)被編譯過的類文件包含如下的結(jié)構(gòu):

ClassFile

{ u4magic; u2minor_version; u2major_version; u2constant_pool_count;

cp_infocontant_pool[constant_pool_count – 1]; u2access_flags;

u2this_class; u2super_class; u2interfaces_count;

u2interfaces[interfaces_count]; u2fields_count;

field_infofields[fields_count]; u2methods_count;

method_infomethods[methods_count]; u2attributes_count;

attribute_infoattributes[attributes_count];}

magic,

minor_version,

major_version

指定一些信息:

當(dāng)前類的版本、編譯當(dāng)前類的JDK版本

constant_pool跟符號(hào)表相似傀缩,但它包含更多的數(shù)據(jù)

access_flags為該類提供一組修改器

this_class為該類提供完全限定名在常量池中的索引那先,例如:org/jamesdbloom/foo/Bar

super_class提供對(duì)其父類的符號(hào)引用在常量池中的索引,例如:java/lang/Object

interface常量池中的數(shù)組索引赡艰,該數(shù)組提供對(duì)所有被實(shí)現(xiàn)的接口的符號(hào)引用

fields常量池中的數(shù)組索引售淡,該數(shù)組提供對(duì)每個(gè)字段的完整描述

methods常量池中的數(shù)組索引,該數(shù)組提供對(duì)每個(gè)方法簽名的完整描述慷垮,如果該方法不是抽象的或者native的揖闸,

那么也會(huì)包含字節(jié)碼

attributes不同值的數(shù)組,提供關(guān)于類的額外信息料身,包括注解:RetentionPolicy.CLASS以及RetentionPolicy.RUNTIME

可以使用javap命令查看被編譯后的java類的字節(jié)碼汤纸。

下面列出了在該類文件中,使用到的操作碼:

aload_0該操作碼是形如aload_格式的一組操作碼中其中的一個(gè)芹血。

它們都是用來加載一個(gè)對(duì)象引用到操作數(shù)棧贮泞。

而“”用于指示要被訪問的對(duì)象引用在局部變量數(shù)組中的位置,但n的值只能是0幔烛,1啃擦,2或3。

也有其他相似的操作碼用來加載非對(duì)象引用饿悬,如:iload_,lload_,fload_以及dload_

(其中令蛉,i表示int,l表示long狡恬,f表示float珠叔,而d表示double,上面n的取值范圍對(duì)這些*load_同樣適用)弟劲。

局部變量的索引如果大于3祷安,可以使用iload,lload兔乞,float汇鞭,dload和aload加載撇眯。

這些操作碼都攜帶要被加載的局部變量在數(shù)組中的索引。

ldc該操作碼用來從運(yùn)行時(shí)常量池取出一個(gè)常量壓入操作數(shù)棧

getstatic該操作碼用來從運(yùn)行時(shí)常量池的靜態(tài)字段列表入棧一個(gè)靜態(tài)值到操作數(shù)棧

invokespecial

invokevirtual

這些操作碼是一組用來執(zhí)行方法的操作碼

(總共有:invokedynamic虱咧、invokeinterface、invokespecial锚国、invokestatic腕巡、invokevirtual這幾種)。

其中血筑,本例中出現(xiàn)的invokevirtual用來執(zhí)行類的實(shí)例方法绘沉;

而invokespecial用于執(zhí)行實(shí)例的初始化方法,同時(shí)也用于執(zhí)行私有方法以及屬于超類但被當(dāng)前類繼承的方法

(超類方法動(dòng)態(tài)綁定到子類)豺总。

return該操作碼是一組操作碼(ireturn,lreturn,freturn,dreturn,areturn以及return)中的其中一個(gè)车伞。

每個(gè)操作碼,都是類型相關(guān)的返回語句喻喳。

其中i代表int另玖,l表示long,f表示float表伦,d表示double而a表示一個(gè)對(duì)象的引用谦去。

沒有標(biāo)識(shí)符作為首字母的return語句,僅會(huì)返回void

就像在其他通用的字節(jié)碼中那樣蹦哼,以上這些操作碼主要用于跟本地變量鳄哭、操作數(shù)棧以及運(yùn)行時(shí)常量池打交道。

構(gòu)造器有兩個(gè)指令纲熏,第一個(gè)將“this”壓入到操作數(shù)棧妆丘,接下來該構(gòu)造器的父構(gòu)造器被執(zhí)行,這一操作將導(dǎo)致this被“消費(fèi)”局劲,因此this將從操作數(shù)棧出棧勺拣。

而對(duì)于sayHello()方法,它的執(zhí)行將更為復(fù)雜容握。因?yàn)樗坏貌煌ㄟ^運(yùn)行時(shí)常量池宣脉,解析符號(hào)引用到真實(shí)的引用。第一個(gè)操作數(shù)getstatic剔氏,用來入棧一個(gè)指向System類的靜態(tài)字段out的引用到操作數(shù)棧塑猖。接下來的操作數(shù)ldc,入棧一個(gè)字符串字面量“Hello”到操作數(shù)棧谈跛。最后羊苟,invokevirtual操作數(shù),執(zhí)行System.out的println方法感憾,這將使得“Hello”作為一個(gè)參數(shù)從操作數(shù)棧出棧蜡励,并為當(dāng)前線程創(chuàng)建一個(gè)新的frame。

類加載器

JVM的啟動(dòng)是通過bootstrap類加載器來加載一個(gè)用于初始化的類。在publicstatic void main(String[])被執(zhí)行前凉倚,該類會(huì)被鏈接以及實(shí)例化兼都。main方法的執(zhí)行,將順序經(jīng)歷加載稽寒,鏈接扮碧,以及對(duì)額外必要的類跟接口的初始化。

加載:

加載是這樣一個(gè)過程:查找表示該類或接口類型的類文件杏糙,并把它讀到一個(gè)字節(jié)數(shù)組中慎王。接著,這些字節(jié)會(huì)被解析以確認(rèn)它們是否表示一個(gè)Class對(duì)象以及是否有正確的主宏侍、次版本號(hào)赖淤。任何被當(dāng)做直接superclass的類或接口也一同被加載。一旦這些工作完成谅河,一個(gè)類或接口對(duì)象將會(huì)從二進(jìn)制表示中創(chuàng)建咱旱。

鏈接: 鏈接包含了對(duì)該類或接口的驗(yàn)證,準(zhǔn)備類型以及該類的直接父類跟父接口旧蛾。簡而言之莽龟,鏈接包含三個(gè)步驟:驗(yàn)證、準(zhǔn)備以及解析(optional)

驗(yàn)證:該階段會(huì)確認(rèn)類以及接口的表示形式在結(jié)構(gòu)上的正確性锨天,同時(shí)滿足Java編程語言以及JVM語義上的要求毯盈。

在驗(yàn)證階段執(zhí)行這些檢查意味著在運(yùn)行時(shí)可以免去在鏈接階段進(jìn)行這些動(dòng)作,雖然拖慢了類的加載速度病袄,然而它避免了在執(zhí)行字節(jié)碼的時(shí)候執(zhí)行這些檢查搂赋。

準(zhǔn)備:包含了對(duì)靜態(tài)存儲(chǔ)的內(nèi)存分配以及JVM所使用的任何數(shù)據(jù)結(jié)構(gòu)(比如方法表)。靜態(tài)字段都被創(chuàng)建以及實(shí)例化為它們的默認(rèn)值益缠。然而脑奠,沒有任何實(shí)例化器或代碼在這個(gè)階段被執(zhí)行,因?yàn)檫@些任務(wù)將會(huì)發(fā)生在實(shí)例化階段幅慌。

解析:是一個(gè)可選的階段宋欺。該階段通過加載引用的類或接口來檢查符號(hào)引用是否正確。如果在這個(gè)點(diǎn)這些檢查沒發(fā)生胰伍,那么對(duì)符號(hào)引用的解析會(huì)被推遲到直到它們被字節(jié)碼指令使用之前齿诞。

實(shí)例化 類或接口,包含執(zhí)行類或接口的實(shí)例化方法:

在JVM中存在多個(gè)不同職責(zé)的類加載器骂租。每一個(gè)類加載器都代理其已被加載的父加載器(除了bootstrap類加載器祷杈,因?yàn)樗歉虞d器)。

Bootstrap類加載器:當(dāng)java程序運(yùn)行時(shí)渗饮,java虛擬機(jī)需要裝載java類但汞,這個(gè)過程需要一個(gè)類裝載器來完成宿刮。而類裝載器本身也是一個(gè)java類,這就出現(xiàn)了類似人類的第一位母親是如何產(chǎn)生出來的問題私蕾。

其實(shí)僵缺,java虛擬機(jī)中內(nèi)嵌了一個(gè)稱為Bootstrap的類裝載器,它是用特定于操作系統(tǒng)的本地代碼實(shí)現(xiàn)的踩叭,屬于java虛擬機(jī)的內(nèi)核谤饭,這個(gè)Bootstrap類裝載器不用專門的類裝載器去裝載。Bootstrap類裝載器負(fù)責(zé)加載java核心包中的類懊纳。

Extension 類加載器:從標(biāo)準(zhǔn)的Java擴(kuò)展API中加載類。例如亡容,安全的擴(kuò)展功能集嗤疯。

System 類加載器:這是應(yīng)用程序默認(rèn)的類加載器。它從classpath中加載應(yīng)用程序類闺兢。

用戶定義的類加載器:可以額外得定義類加載器來加載應(yīng)用程序類茂缚。用戶定義的類加載器可用于一些特殊的場景,比如:在運(yùn)行時(shí)重新加載類或?qū)⒁恍┨厥獾念惛綦x為多個(gè)不同的分組(通常web服務(wù)器中都會(huì)有這樣的需求屋谭,比如Tomcat)脚囊。

更快的類加載

一個(gè)稱之為類數(shù)據(jù)共享(CDS)的特性自HotspotJVM

5.0開始被引進(jìn)。在安裝JVM期間桐磁,安裝器加載一系列的Java核心類(如rt.jar)到一個(gè)經(jīng)過映射過的內(nèi)存區(qū)進(jìn)行共享存檔悔耘。CDS減少了加載這些類的時(shí)間從而提升了JVM的啟動(dòng)速度,同時(shí)允許這些類在不同的JVM實(shí)例之間共享我擂。這大大減少了內(nèi)存碎片衬以。

方法區(qū)的位置

JVM

Specification Java SE 7

Edition清楚地聲明:盡管方法區(qū)是堆的一個(gè)邏輯組成部分,但最簡單的實(shí)現(xiàn)可能是既不對(duì)它進(jìn)行垃圾回收也不壓縮它校摩。然而矛盾的是利用jconsole查看Oracle的JVM的方法區(qū)(以及CodeCache)是非堆形式的看峻。OpenJDK代碼顯示CodeCache相對(duì)ObjectHeap而言是VM中一個(gè)獨(dú)立的域。

類加載器引用

類通常是按需加載衙吩,即第一次使用該類時(shí)才加載互妓。由于有了類加載器,Java運(yùn)行時(shí)系統(tǒng)不需要知道文件與文件系統(tǒng)坤塞。

運(yùn)行時(shí)常量池

JVM對(duì)每個(gè)類型維護(hù)著一個(gè)常量池冯勉,它是一個(gè)跟符號(hào)表相似的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu),但它包含了更多的數(shù)據(jù)尺锚。Java的字節(jié)碼需要一些數(shù)據(jù)珠闰,通常這些數(shù)據(jù)會(huì)因?yàn)樘蠖y以直接存儲(chǔ)在字節(jié)碼中。取而代之的一種做法是將其存儲(chǔ)在常量池中瘫辩,字節(jié)碼包含一個(gè)對(duì)常量池的引用伏嗜。運(yùn)行時(shí)常量池主要用來進(jìn)行動(dòng)態(tài)鏈接坛悉。

幾種類型的數(shù)據(jù)會(huì)存儲(chǔ)在常量池中,它們是:

數(shù)值字面量

字符串字面量

類的引用

字段的引用

方法的引用

如果你編譯下面的這個(gè)簡單的類:

package org.jvminternals;public class SimpleClass { public void sayHello() {System.out.println("Hello");}}

生成的類文件的常量池承绸,看起來會(huì)像下圖所示:

Constant

pool: #1 = Methodref #6.#17 // java/lang/Object."":()V#2 =

Fieldref #18.#19 // java/lang/System.out:Ljava/io/PrintStream;#3 =

String #20 // "Hello"#4 = Methodref #21.#22 //

java/io/PrintStream.println:(Ljava/lang/String;)V#5 = Class #23 //

org/jvminternals/SimpleClass#6 = Class #24 // java/lang/Object#7 = Utf8

#8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11

= Utf8 LocalVariableTable #12 = Utf8 this #13 = Utf8

Lorg/jvminternals/SimpleClass; #14 = Utf8 sayHello #15 = Utf8 SourceFile

#16 = Utf8 SimpleClass.java #17 = NameAndType #7:#8 //

"":()V#18 = Class #25 // java/lang/System#19 = NameAndType

#26:#27 // out:Ljava/io/PrintStream;#20 = Utf8 Hello #21 = Class #28 //

java/io/PrintStream#22 = NameAndType #29:#30 //

println:(Ljava/lang/String;)V#23 = Utf8 org/jvminternals/SimpleClass #24

= Utf8 java/lang/Object#25 = Utf8 java/lang/System #26 = Utf8 out#27 =

Utf8 Ljava/io/PrintStream; #28 = Utf8 java/io/PrintStream #29 = Utf8

println #30 = Utf8 (Ljava/lang/String;)V

常量池中包含了下面的這些類型:

Integer一個(gè)4字節(jié)的int常量

Long一個(gè)8字節(jié)的long常量

Float一個(gè)4字節(jié)的float常量

Double一個(gè)8字節(jié)的double常量

String一個(gè)String字面值常量指向常量池中另一個(gè)包含最終字節(jié)的UTF8記錄

Utf8一個(gè)字節(jié)流表示一個(gè)Utf8編碼的字串序列

Class一個(gè)Class字面值常量指向常量池中的另一個(gè)Utf8記錄裸影,它包含JVM內(nèi)部格式的完全限定名

(它用于動(dòng)態(tài)鏈接)

NameAndType用一個(gè)冒號(hào)區(qū)分一對(duì)值,每個(gè)值都指向常量池中得其他記錄军熏。

冒號(hào)前的第一個(gè)值指向一個(gè)utf8字符串字面量表示方法名或者字段名轩猩。

第二個(gè)值指向一個(gè)utf8字符串字面量表示類型。

舉一個(gè)字段的例子是完全限定的類名荡澎;

舉一個(gè)方法的例子是: 它是一個(gè)列表均践,該列表中每個(gè)參數(shù)都是完全限定的類名

Fieldref,

Methodref,

InterfaceMethodref

用點(diǎn)來分隔的一對(duì)值,每個(gè)值指向常量池中的另一個(gè)記錄摩幔。

點(diǎn)前的第一個(gè)值指向一個(gè)Class記錄彤委。第二個(gè)值指向一個(gè)NameAndType記錄

異常表

異常表存儲(chǔ)了每個(gè)異常處理器的信息:

起始點(diǎn)

終止點(diǎn)

處理代碼的PC偏移量

被捕獲的異常類的常量池索引

如果一個(gè)方法定義了try-catch或try-finally異常處理器,那么一個(gè)異常表將會(huì)被創(chuàng)建或衡。它包含了每個(gè)異常處理器的信息或者finally塊以及正在被處理的異常類型跟處理器代碼的位置焦影。

當(dāng)一個(gè)異常被拋出,JVM會(huì)為當(dāng)前方法尋找一個(gè)匹配的處理器封断。如果沒有找到斯辰,那么該方法最終會(huì)唐突地出棧當(dāng)前stackframe而異常會(huì)被重新拋出到調(diào)用鏈(新的frame)。如果在所有的frame都出棧之前還是沒有找到異常處理器坡疼,那么當(dāng)前線程將會(huì)被終止彬呻。當(dāng)然這也可能會(huì)導(dǎo)致JVM被終止,如果異常被拋出到最后一個(gè)非后臺(tái)線程的話柄瑰,比如該線程就是主線程废岂。

最終異常處理器會(huì)匹配所有的異常類型并且無論什么時(shí)候該類型的異常被拋出總是會(huì)得到執(zhí)行。在沒有異常拋出的例子中狱意,finally塊仍然會(huì)在方法的最后被執(zhí)行湖苞。一旦return語句被執(zhí)行就會(huì)立即跳轉(zhuǎn)到finally代碼塊繼續(xù)執(zhí)行。

字符比較

字符比較(character comparison)是指按照字典次序?qū)蝹€(gè)字符或字符串進(jìn)行比較大小的操作详囤,一般都是以ASCII碼值的大小作為字符比較的標(biāo)準(zhǔn)财骨。

符號(hào)表

符號(hào)表在編譯程序工作的過程中需要不斷收集、記錄和使用源程序中一些語法符號(hào)的類型和特征等相關(guān)信息藏姐。這些信息一般以表格形式存儲(chǔ)于系統(tǒng)中隆箩。如常數(shù)表、變量名表羔杨、數(shù)組名表捌臊、過程名表、標(biāo)號(hào)表等等兜材,統(tǒng)稱為符號(hào)表理澎。對(duì)于符號(hào)表組織逞力、構(gòu)造和管理方法的好壞會(huì)直接影響編譯系統(tǒng)的運(yùn)行效率。

在JVM中糠爬,內(nèi)部字符串被存儲(chǔ)在字符串表中寇荧。字符串表是一個(gè)hashtable映射對(duì)象指針到符號(hào)(比如:Hashtable),它被存儲(chǔ)在永久代里执隧。

當(dāng)類被加載時(shí)揩抡,字符串字面量會(huì)被編譯器自動(dòng)“內(nèi)部化”并且被加入到字符表。另外字符串類的實(shí)例可以通過調(diào)用String.intern()來明確地內(nèi)部化镀琉。當(dāng)String.intern()被調(diào)用峦嗤,如果符號(hào)表里已經(jīng)包含該字符串,那么指向該字符串的引用將被返回屋摔。如果該字符串沒有包含在字符表寻仗,則會(huì)被加入到字符串表同時(shí)返回其引用。

在此我向大家推薦一個(gè)架構(gòu)學(xué)習(xí)交流群凡壤。交流學(xué)習(xí)群號(hào):744642380,里面會(huì)分享一些資深架構(gòu)師錄制的視頻錄像:有Spring耙替,MyBatis亚侠,Netty源碼分析,高并發(fā)俗扇、高性能硝烂、分布式、微服務(wù)架構(gòu)的原理铜幽,JVM性能優(yōu)化滞谢、分布式架構(gòu)等這些成為架構(gòu)師必備的知識(shí)體系。還能領(lǐng)取免費(fèi)的學(xué)習(xí)資源除抛,目前受益良

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末狮杨,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子到忽,更是在濱河造成了極大的恐慌橄教,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,718評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件喘漏,死亡現(xiàn)場離奇詭異护蝶,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)翩迈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門持灰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人负饲,你說我怎么就攤上這事堤魁∥沽矗” “怎么了?”我有些...
    開封第一講書人閱讀 158,207評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵姨涡,是天一觀的道長衩藤。 經(jīng)常有香客問我,道長涛漂,這世上最難降的妖魔是什么赏表? 我笑而不...
    開封第一講書人閱讀 56,755評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮匈仗,結(jié)果婚禮上瓢剿,老公的妹妹穿的比我還像新娘。我一直安慰自己悠轩,他們只是感情好间狂,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著火架,像睡著了一般鉴象。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上何鸡,一...
    開封第一講書人閱讀 50,050評(píng)論 1 291
  • 那天纺弊,我揣著相機(jī)與錄音,去河邊找鬼骡男。 笑死淆游,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的隔盛。 我是一名探鬼主播犹菱,決...
    沈念sama閱讀 39,136評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼吮炕!你這毒婦竟也來了腊脱?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,882評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤龙亲,失蹤者是張志新(化名)和其女友劉穎虑椎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體俱笛,經(jīng)...
    沈念sama閱讀 44,330評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡捆姜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了迎膜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泥技。...
    茶點(diǎn)故事閱讀 38,789評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖磕仅,靈堂內(nèi)的尸體忽然破棺而出珊豹,到底是詐尸還是另有隱情簸呈,我是刑警寧澤,帶...
    沈念sama閱讀 34,477評(píng)論 4 333
  • 正文 年R本政府宣布店茶,位于F島的核電站蜕便,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏贩幻。R本人自食惡果不足惜轿腺,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望丛楚。 院中可真熱鬧族壳,春花似錦、人聲如沸趣些。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽坏平。三九已至拢操,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間舶替,已是汗流浹背令境。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評(píng)論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留坎穿,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,598評(píng)論 2 362
  • 正文 我出身青樓返劲,卻偏偏與公主長得像玲昧,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子篮绿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容