Java虛擬機(jī)系列——檢視閱讀(二)

類索引,父類索引,接口索引集合——確定這個(gè)類的繼承關(guān)系

類索引(this_class)和父類索引(super_class)都是u2類型的數(shù)據(jù)熏兄,而接口索引(interfaces)是一組u2類型的數(shù)據(jù)集合,class文件中由這三項(xiàng)數(shù)據(jù)來(lái)確定這個(gè)類的繼承關(guān)系類索引用于確定這個(gè)類的全限定名,父類索引用于確定這個(gè)類的父類的全限定名。由于Java語(yǔ)言不允許多繼承,所以父類索引只有一個(gè),除了java.lang.Object之外淆党,所有的Java類都有父類,因了除了java.lang.Object之外,所有Java類的父類索引都不為0瘫里。接口索引集合用來(lái)描述這個(gè)實(shí)現(xiàn)實(shí)現(xiàn)了哪些接口玩荠,這些被實(shí)現(xiàn)的接口將按照implements語(yǔ)句后的接口順序從左到右排列在接口的索引集合中塑径。

類索引,父類索引和接口索引集合都按順序排列在訪問(wèn)標(biāo)志之后,類索引和父類索引用兩個(gè)u2類型的索引值表示,它們各自指向一個(gè)類型為CONSTANT_Class_info的類描述符常量肋拔,通過(guò)CONSTANT_Class_info類型的常量中的索引可以找到定義在CONSTANT_Utf8_info類型的常量中的全限定名稱字符串。對(duì)于接口索引集合,入口的第一項(xiàng)為u2類型的數(shù)據(jù),表示接口計(jì)數(shù)器(interfaces_count),表示索引表的容量。如果該類沒(méi)有實(shí)現(xiàn)任何接口,那么該計(jì)數(shù)器值為0,后面接口的索引表不再占用任何字節(jié)。

字段表集合

字段表(field_info)用于描述接口或類中聲明的變量和簸。字段(field)包括了類級(jí)變量或?qū)嵗兞浚话ǚ椒▋?nèi)部聲明的變量倔监。描述一個(gè)字段的信息有:字段的作用域(public,private,protected修飾符)蛾茉,是類級(jí)變量還是實(shí)例級(jí)變量(static修飾符)刺覆,可變性(final),并發(fā)可見(jiàn)性(volatile修飾符酝枢,是否強(qiáng)制從主內(nèi)存讀寫(xiě))恬偷,是否可序列化(transient修飾符),字段數(shù)據(jù)類型(基本數(shù)據(jù)類型帘睦,對(duì)象,數(shù)組)官脓,字段名稱协怒。這些信息中涝焙,各個(gè)修改符都是布爾值卑笨,要么有某個(gè)修飾符,要么沒(méi)有仑撞,很適合使用標(biāo)志位來(lái)表示赤兴。而字段叫什么名字,字段被定義為什么數(shù)據(jù)類型隧哮,這些都是無(wú)法固定的桶良,只能引用常量池中的常量來(lái)描述。下面是字段表的最終格式沮翔。

image

字段修飾符放在access_flags項(xiàng)目中陨帆,它與類的access_flags項(xiàng)目是非常相似的,都是一個(gè)u2的數(shù)據(jù)類型采蚀,其中可以設(shè)置的標(biāo)志位和含義如下表:

image

跟隨access_flags標(biāo)志的是兩項(xiàng)索引值:name_index和descriptor_index疲牵。它們都是對(duì)常量池的引用,分別代表著字段的簡(jiǎn)單名稱及字段的描述符∮苁螅現(xiàn)在需要解釋一下“簡(jiǎn)單名稱”纲爸,“描述符”及前面出現(xiàn)過(guò)多次的“全限定名”這三種特殊字符串的概念。全限定名稱和簡(jiǎn)單名稱很好理解妆够,如“org/fenixsoft/clazz/TestClass"就是一個(gè)類全限定名识啦,僅僅是把類名中的”.“替換成了”/“而已,為了使連續(xù)的多個(gè)全限定名之間不產(chǎn)生混淆神妹,在使用時(shí)最后一般會(huì)加上一個(gè)“;”號(hào)表示全限定名結(jié)束颓哮。簡(jiǎn)單名稱就是指沒(méi)有類型和參數(shù)修飾的方法或字段名稱。相對(duì)于全限定名和簡(jiǎn)單名稱來(lái)說(shuō)鸵荠,方法和字段的描述符就要復(fù)雜一些冕茅。描述符的作用是來(lái)用描述字段的數(shù)據(jù)類型,方法的參數(shù)列表(包括數(shù)量,類型及順序)和返回值嵌赠。根據(jù)描述符規(guī)則塑荒,基本數(shù)據(jù)類型(byte,char,double,float,int,long,short,boolean)及代表無(wú)返回值的void類型都使用一個(gè)大寫(xiě)字符來(lái)表示,而對(duì)象類型則用字符L加對(duì)象全限定名來(lái)表示姜挺,如下圖:

image

對(duì)于數(shù)組類型齿税,每一維度使用一個(gè)前置的 [ 字符來(lái)描述,如一定義為java.lang.String[](#)類型的二維數(shù)組炊豪,將被記錄為:“[[java/lang/String;”凌箕,一個(gè)整型數(shù)組“int[]”將被記錄為“[I”。用描述符來(lái)描述方法時(shí)词渤,按照先參數(shù)列表牵舱,后返回值的順序描述,參數(shù)列表按照參數(shù)的嚴(yán)格順序在一組小括號(hào)“()”之內(nèi)缺虐。如方法void int()描述符為:”()V“芜壁,方法java.lang.String toString()描述符為:“()java/lang/String;”字段表都包含的固定數(shù)據(jù)項(xiàng)目到descriptor_index為止就結(jié)束了,但是在descriptor_index之后跟隨著一個(gè)屬性表集合用于存儲(chǔ)一些額外的信息高氮,字段都可以在屬性表中描述0至多項(xiàng)額外的信息慧妄。字段表集合中不會(huì)列出超類或父接口中繼承而來(lái)的字段,但有可能列出原來(lái)Java代碼中不存在的字段剪芍,譬如在內(nèi)部類中為了保持對(duì)外部類的訪問(wèn)性塞淹,會(huì)自動(dòng)添加指向外部類實(shí)例的字段。另外罪裹,在Java語(yǔ)言中字段是無(wú)法重載的饱普,兩個(gè)字段的數(shù)據(jù)類型,修飾符不管是否相同状共,都必須使用不一樣的名稱(即名稱必須不同套耕,編譯器限制),但是對(duì)于字節(jié)碼來(lái)講口芍,如果兩個(gè)字段的描述符不一致箍铲,那字段重名就是合法的(即只有描述符不同就是合法的)。

疑問(wèn):

Q: 字段(field)包括了類級(jí)變量或?qū)嵗兞亏尥郑话ǚ椒▋?nèi)部聲明的變量颠猴。類級(jí)變量是指static修飾的變量么?

A: 是的小染。描述一個(gè)字段的信息有:字段的作用域(public,private,protected修飾符)翘瓮,是類級(jí)變量還是實(shí)例級(jí)變量(static修飾符),可變性(final)裤翩,并發(fā)可見(jiàn)性(volatile修飾符资盅,是否強(qiáng)制從主內(nèi)存讀寫(xiě))调榄,是否可序列化(transient修飾符),字段數(shù)據(jù)類型(基本數(shù)據(jù)類型呵扛,對(duì)象每庆,數(shù)組),字段名稱今穿。

方法表集合

方法表的結(jié)構(gòu)與字段表一樣缤灵,依次包含了訪問(wèn)標(biāo)志(access_flags),名稱索引(name_index)蓝晒,描述符索引(descriptor_index)腮出,屬性表集合(attributes)幾項(xiàng),如下表所示:

image

因?yàn)関olatile關(guān)鍵字和transient關(guān)鍵字不能修改方法芝薇,所以方法表的訪問(wèn)標(biāo)志中沒(méi)有了ACC_VOLATILE與ACC_TRANSIENT標(biāo)志胚嘲。與之相對(duì)的,synchronized, native, strictfp和abstract關(guān)鍵字可以修飾方法洛二,所以方法表的訪問(wèn)標(biāo)志中增加了ACC_SYNCHRONIZED馋劈,ACC_NATIVE,ACC_STRICTFP灭红,ACC_ABSTRACT標(biāo)志侣滩。對(duì)于方法表,所有標(biāo)志位及取值如下表:

image

方法里面的Java代碼变擒,經(jīng)過(guò)編譯器編譯成字節(jié)碼指令后,存放在方法屬性表集合中一個(gè)名為“Code”的屬性表中寝志,屬性表是class文件格式中最具擴(kuò)展性的一種數(shù)據(jù)項(xiàng)目娇斑。

與字段表集合相對(duì)應(yīng)的,如果父類方法在子類中沒(méi)有被重寫(xiě)(Override)材部,方法表集合中就不會(huì)出現(xiàn)父類的方法毫缆。但同樣的,可能**會(huì)出現(xiàn)由編譯器自動(dòng)添加的方法乐导,最典型的便是類構(gòu)造器“<client>”方法和缺省實(shí)例構(gòu)造器“<init>”方法苦丁。

在Java語(yǔ)言中,要重載(Overload)一個(gè)方法物臂,除了要與原方法具有相同的簡(jiǎn)單名稱之外旺拉,還要求必須擁有一個(gè)與原方法不同的特征簽名,特征簽名是一個(gè)方法中各個(gè)參數(shù)在常量池中的字段符號(hào)引用的集合棵磷,也就是因?yàn)?strong>返回值不會(huì)包在特征簽名之中蛾狗,因此Java語(yǔ)言里是無(wú)法僅僅依靠返回值的不同來(lái)對(duì)一個(gè)已有的方法進(jìn)行重載的。

對(duì)于JVM的Class文件格式中仪媒,特征簽名的范圍更大一些沉桌,只要描述符不是完全一致的兩個(gè)方法就可以共存。也就是說(shuō),如果兩個(gè)方法有相同的名稱和特征簽名留凭,但返回值不同佃扼,那么也是可以合法共存于同一個(gè)class文件中

疑問(wèn):

Q: 如果是默認(rèn)沒(méi)有訪問(wèn)范圍修飾符的方法,如下蔼夜,這方法訪問(wèn)標(biāo)志里的值是什么樣的呢松嘶?

<pre class="md-fences md-end-block" lang="" contenteditable="false" cid="n660" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: pre; text-align: left; break-inside: avoid; display: block; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(221, 221, 221); border-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">
void test(){
/...
}</pre>

屬性表集合——不理解

講解得并不是很好,待翻看深入理解java虛擬機(jī)的書(shū)看挎扰,還有看看R大關(guān)于這方面的講解翠订,感覺(jué)書(shū)上說(shuō)的也不是很好,每次看到這邊都不是很理解

在Class文件遵倦,字段表尽超,方法表中都可以攜帶自己的屬性表集合,以用于描述某些場(chǎng)景專有的信息梧躺。與Class文件中其它的數(shù)據(jù)項(xiàng)目要求的順序似谁、長(zhǎng)度和內(nèi)容不同,屬性表集合的限制稍微寬松一些掠哥,不再要求各個(gè)屬性表具有嚴(yán)格的順序巩踏,并且只要不與已有的屬性名重復(fù),任何人實(shí)現(xiàn)的編譯器都可以向?qū)傩员碇袑?xiě)入自己定義的屬性信息续搀,Java虛擬機(jī)運(yùn)行時(shí)會(huì)忽略掉它不認(rèn)識(shí)的屬性塞琼。為了能正確地解析Class文件,《Java虛擬機(jī)規(guī)范(第二版)》中預(yù)定義了9種虛擬機(jī)實(shí)現(xiàn)應(yīng)當(dāng)能識(shí)別的屬性禁舷,具體如下表所示:

image

對(duì)于每個(gè)屬性彪杉,它的名稱需要從常量池中引用一個(gè)CONSTANT_Utf8_info類型的常量表來(lái)表示,而屬性值的結(jié)構(gòu)則是完全自定義的牵咙,只要說(shuō)明屬性值所占用的位數(shù)長(zhǎng)度即可派近。一個(gè)符合規(guī)則的屬性表應(yīng)該滿足如下表定義的結(jié)構(gòu):

image

1.Code屬性

Java程序方法體里的代碼經(jīng)過(guò)Javac編譯器處理之后,最終變?yōu)樽止?jié)碼指令存儲(chǔ)在Code屬性內(nèi)洁桌。Code屬性出現(xiàn)在方法表的屬性集合中渴丸,但并非所有方法都必須存在這個(gè)屬性表,譬如接口或抽象類中的抽象方法就不存在Code屬性另凌,如果方法有Code屬性表存在谱轨,那么它的結(jié)構(gòu)如下表:

image

attribute_name_index是一項(xiàng)指向CONSTANT_Utf8_info常量表的索引,常量值固定為“Code”途茫,它代表了該屬性的屬性名稱碟嘴,attribute_length指示了屬性值的長(zhǎng)度,由于屬性名稱索引與屬性長(zhǎng)度一共是6個(gè)字節(jié)囊卜,所以屬性值的長(zhǎng)度固定為整個(gè)屬性表的長(zhǎng)度減去6個(gè)字節(jié)娜扇。max_stack代表了操作數(shù)棧(Operand Stacks)的最大深度错沃。在方法執(zhí)行的任意時(shí)刻,操作數(shù)棧都不會(huì)超過(guò)這個(gè)深度雀瓢。虛擬機(jī)運(yùn)行的時(shí)候需要根據(jù)這個(gè)值來(lái)分配棧幀(Frame)中的操作數(shù)棧深度枢析。max_locals代表了局部變量表所需的存儲(chǔ)空間。在這里刃麸,max_locals的單位是Slot醒叁,Slot是虛擬機(jī)為局部變量表分配內(nèi)存所使用的最小單位。對(duì)于byte,char,float,int,shot,boolean,reference和returnAddress等長(zhǎng)度不超過(guò)32位的數(shù)據(jù)類型泊业,每個(gè)局部變量占1個(gè)Slot把沼,而double與long這兩種64位的數(shù)據(jù)類型而需要2個(gè)Slot來(lái)存放。方法參數(shù)(包括實(shí)例方法中的隱藏參數(shù)“this”)吁伺,顯示異常處理器的參數(shù)(Exception Handler Parameter,即try-catch語(yǔ)句中catch塊所定義的異常)饮睬,方法體中定義的局部變量都需要使用局部表來(lái)存放。另外篮奄,并不是在方法中使用了多個(gè)局部變量捆愁,就把這些局部變量所占的Slot之和作為max_locals的值,原因是局部變量表中的Slot可以重用窟却,當(dāng)代碼執(zhí)行超出一個(gè)局部變量的作用域時(shí)昼丑,這個(gè)局部變量所在的Slot就可以被其他局部變量所使用,編譯器會(huì)根據(jù)變量的作用域來(lái)分類Slot并分配給各個(gè)變量使用夸赫,然后計(jì)算出max_locals的大小菩帝。

code_length和code用來(lái)存儲(chǔ)Java源程序編譯后生成的字節(jié)碼指令。code_length代表字節(jié)碼長(zhǎng)度憔足,code是用于存儲(chǔ)字節(jié)碼指令的一系列字節(jié)流胁附。既然名為字節(jié)碼指令,那么每個(gè)指令就是一個(gè)u1類型的單字節(jié)滓彰,當(dāng)虛擬機(jī)讀取到code中的一個(gè)字節(jié)碼時(shí),就可相應(yīng)地找出這個(gè)字節(jié)碼代表的是什么指令州袒,并且可以知道這條指令后面是否需要跟隨參數(shù)揭绑,以及參數(shù)應(yīng)該如何理解。關(guān)于code_length還有一件值得注意的事情郎哭,雖然它是一個(gè)u4類型的長(zhǎng)度值他匪,理論上最大值可以達(dá)到2的32次方減1,但虛擬機(jī)規(guī)范中限制了一個(gè)方法不允許超過(guò)65535條字節(jié)碼指令(64KB)夸研,如果超過(guò)這個(gè)限制邦蜜,Javac編譯器就會(huì)拒絕編譯。一般來(lái)講亥至,只要我們寫(xiě)Java代碼時(shí)不是刻意地編寫(xiě)超長(zhǎng)的方法悼沈,就不會(huì)超過(guò)這個(gè)最大值限制贱迟。但是,在編譯復(fù)雜的JSP文件中絮供,可以會(huì)因?yàn)檫@個(gè)原因?qū)е戮幾g失敗衣吠。Code屬性是Class文件中最重要的一個(gè)屬性,如果把一個(gè)Java程序中的信息分為代碼(Code壤靶,方法體里的Java代碼)和元數(shù)據(jù)(Metadata缚俏,包括類、字段贮乳、方法定義及其它信息)兩部分忧换,那么在整個(gè)Class文件里,Code屬性用于描述代碼向拆,其它的所有數(shù)據(jù)項(xiàng)目就都用于描述元數(shù)據(jù)亚茬。在字節(jié)碼指令之后的是這個(gè)方法的顯示異常處理表,異常表對(duì)于Code屬性表來(lái)說(shuō)不是必須存在的亲铡。異常表的格式如下表:

image

異常表它包含4個(gè)字段才写,這些字段的含義為:如果字節(jié)碼從第start_pc到end_pc行之間(不包含第end_pc)行出現(xiàn)了類型為catch_type或其子類的異常(catch_type為指向一個(gè)CONSTANT_Class_info型常量的索引),則轉(zhuǎn)到第handler_pc行繼續(xù)處理奖蔓。當(dāng)catch_type的值為0時(shí)赞草,代表任何的異常情況都需要轉(zhuǎn)向到handler_pc行行進(jìn)行處理。異常表實(shí)際上是Java代碼的一部分吆鹤,編譯器使用異常表而不是簡(jiǎn)單的跳轉(zhuǎn)命令來(lái)實(shí)現(xiàn)Java異常及finally處理機(jī)制厨疙。注:字節(jié)碼的“行”是一種形象的描述,指的是字節(jié)碼相對(duì)于方法體開(kāi)始的偏移量疑务,而不是Java源代碼的行號(hào)沾凄。

2.Exceptions屬性

這里的Exceptions屬性是在方法表中與Code屬性平級(jí)的一項(xiàng)屬性,而不是Code屬性表中的異常屬性表知允。Exceptions屬性表的作是列舉出方法中可能拋出的受查檢異常(Checked Exception)撒蟀,也就是在方法描述時(shí)在throws關(guān)鍵字后面列舉的異常。它的結(jié)構(gòu)如下表:

image

此屬性表中的number_of_exceptions項(xiàng)表示訪求可能拋出number_of_exceptions種受檢查異常温鸽,每一種受檢查異常使用一個(gè)exception_index_table項(xiàng)表示印蔗,attribute_name_index為指向常量池中CONSTANT_Class_info型常量表的索引屹逛,代表了該受檢查異常的類型飞蛹。

3.LineNumberTable屬性

LineNumberTable屬性用于描述Java源代碼行號(hào)與字節(jié)碼行號(hào)(字節(jié)碼偏移量)之間的對(duì)應(yīng)關(guān)系清焕。它并不是運(yùn)行時(shí)必須的屬性,但默認(rèn)會(huì)生成到Class文件之中蝠猬,可以在Javac中使用-g:none或-g:lines選項(xiàng)來(lái)取消或要求生成這項(xiàng)信息切蟋。如果選擇不生成LineNumberTable屬性表,對(duì)程序運(yùn)行產(chǎn)生的最主要的影響就是在拋出異常時(shí)榆芦,堆棧中將不會(huì)顯示出錯(cuò)的行號(hào)柄粹,并且在調(diào)試程序的時(shí)候無(wú)法按照源碼來(lái)設(shè)置斷點(diǎn)喘鸟。LineNumberTable屬性表結(jié)構(gòu)如下表:

image

line_number_table是一個(gè)數(shù)量為line_number_table_length,類型為line_number_info的集合镰惦,line_number_info表包括了start_pc和line_number兩個(gè)u2類型的數(shù)據(jù)項(xiàng)迷守,前者是字節(jié)碼行號(hào),后者是Java源碼行號(hào)旺入。

4.LocalVariableTable屬性

LocalVariableTable屬性表用于描述棧幀中局部變量表中的變量與Java源碼中定義的變量之間的關(guān)系兑凿,它不是運(yùn)行時(shí)必須的屬性,默認(rèn)也不會(huì)生成到Class文件之中茵瘾,可以使用-g:none或-g:vars選項(xiàng)來(lái)取消或要求生成這項(xiàng)信息礼华。如果沒(méi)有生成這項(xiàng)屬性,最大的影響就是當(dāng)其它人引用這個(gè)方法時(shí)拗秘,所有參數(shù)名稱都丟失圣絮,IDE可能會(huì)使用諸如arg0、arg1之類的占位符來(lái)替換原有的參數(shù)名稱雕旨,這對(duì)程序運(yùn)行沒(méi)有影響扮匠,但是會(huì)給代碼編寫(xiě)帶來(lái)較大的不便,而且在調(diào)試期間無(wú)法根據(jù)參數(shù)名稱從運(yùn)行上下文件中獲取參數(shù)值凡涩。LocalVariableTable屬性表結(jié)構(gòu)如下:

image

其中l(wèi)ocal_variable_info項(xiàng)目代表了一個(gè)棧幀與源碼中的局部變量的關(guān)聯(lián)棒搜,結(jié)構(gòu)如下:

image

index是這個(gè)局部變量在棧幀局部變量表中的Slot位置。當(dāng)這個(gè)變量的數(shù)據(jù)類型是64位時(shí)(double和long)活箕,它占用的Slot為index和index+1兩個(gè)位置力麸。在JDK1.5引入了泛型之后,LocalVariableTable屬性增加了一個(gè)“姐妹”屬性:LocalVaiableTypeTable育韩,這個(gè)新增加的屬性結(jié)構(gòu)與LocalVariableTable屬性非常相似克蚂,僅僅是把記錄字段描述符的descript_index替換成了字段的特征簽名(Singnature),對(duì)于非泛型類型來(lái)說(shuō)筋讨,描述符的參數(shù)化類型被擦除掉了埃叭,描述符就不能準(zhǔn)確地描述泛型類型了,因此出現(xiàn)了LocalVariableTypeTable屬性悉罕。

5.SourceFile屬性

SourceFile屬性用于記錄這生成這個(gè)Class文件的源碼文件名稱游盲。這個(gè)屬性也是可選的,可以使用-g:none或-g:source選項(xiàng)來(lái)取消或要求生成這項(xiàng)信息蛮粮。在Java中,對(duì)于大多數(shù)的類來(lái)說(shuō)谜慌,類名和文件是一致的然想,但有一些特殊情況(如內(nèi)部類)例外。如果不生成這項(xiàng)屬性欣范,當(dāng)招聘異常時(shí)变泄,堆棧中半不會(huì)顯示出錯(cuò)誤代碼所屬性文件名令哟。這個(gè)屬性是一個(gè)室長(zhǎng)的屬性,結(jié)構(gòu)如下:

image

sourcefile_index數(shù)據(jù)項(xiàng)是指向常量池中CONSTANT_Utf8_info型常量的索引妨蛹,常量值是源文件的文件名屏富。

6.ConstantValue屬性

ConstantValue屬性的作用是通知虛擬機(jī)自動(dòng)為靜態(tài)變量賦值。只有被static關(guān)鍵字修飾的變量才可以使用這項(xiàng)屬性蛙卤。在Java程序里類類似“int x = 123“和”static int x = 123”這樣的變量定義非常常見(jiàn)狠半,但虛擬機(jī)對(duì)這兩種變量賦值的方法和時(shí)刻有所不同。對(duì)于非static類型的變量(也就是實(shí)例變量)的賦值是在實(shí)例構(gòu)造器方法中進(jìn)行的颤难;對(duì)于類變量神年,則有兩種式可以選擇:賦值在類構(gòu)造器方法中進(jìn)行,或者使用ConstantValue屬性來(lái)賦值行嗤。目前Sun Javac編譯器的選擇是:如果同時(shí)使用final和static來(lái)修改一個(gè)變量已日,并且這個(gè)變量的數(shù)據(jù)類型是基本類型或java.lang.String的話,就生成ConstantValue屬性來(lái)進(jìn)行初始化栅屏,如果這個(gè)變量沒(méi)有被final修飾飘千,或者并非基本類型或字符串,則選擇在類構(gòu)造器中進(jìn)行初始化栈雳。ConstantValue屬性表結(jié)構(gòu)如下:

image

ConstantValue屬性是一個(gè)定長(zhǎng)屬性护奈,它的attribute_length數(shù)據(jù)項(xiàng)值必須為2。constantvalue_index數(shù)據(jù)項(xiàng)代表了常量池中一個(gè)字面常量的引用甫恩,根據(jù)字段類型不同逆济,字面量可以是CONSTANT_Long_info,CONSTANT_Float_info,CONSTANT_Double_info,CONSTANT_Integer_info和CONSTANT_String_info常量中的一種。

7.InnerClasses屬性

InnerClasses屬性表用于記錄內(nèi)部類與宿主類之間的關(guān)聯(lián)磺箕。如果一個(gè)類中定義了內(nèi)部類奖慌,那么編譯器將會(huì)為它及它所包含的內(nèi)部類生成InnerClasses屬性表。表結(jié)構(gòu)如下:

image

數(shù)據(jù)項(xiàng)number_of_classes代表需要記錄多少個(gè)內(nèi)部類信息松靡,每一個(gè)內(nèi)部類的類的信息都由一個(gè)inner_class_info表進(jìn)行描述简僧。inner_class_info表結(jié)構(gòu)如下:

image

inner_class_info_index和outer_class_info_index都是指向常量池中CONSTANT_Class_infon常量的索引,分別代表了內(nèi)部類和宿主類的符號(hào)引用雕欺。inner_name_index是指向常量池中CONSTANT_Utf8_info型常量的索引岛马,代表這個(gè)內(nèi)部類的名稱,如果是匿名內(nèi)部類屠列,則這項(xiàng)值為0啦逆。inner_class_access_flags是內(nèi)部類的訪問(wèn)標(biāo)志,類型于類的access_flags笛洛,它的取值范圍如下表:

image

8.Deprecated及Synthetic屬性

Deprecated及Synthetic屬性都屬性于標(biāo)志類型的布爾值屬性夏志,只存在有和沒(méi)有的區(qū)別,沒(méi)有屬性值的概念苛让。Deprecated屬性用于表示某個(gè)類沟蔑,字段或方法湿诊,已經(jīng)被程序作者定為不再推薦使用,它可以通過(guò)代碼中使用@Deprecated注解進(jìn)行設(shè)置瘦材。Synthetic屬代表此字段或方法并不是由Java源碼直接產(chǎn)生的厅须,而是由編譯器自行添加的,在JDK1.5之后食棕,標(biāo)識(shí)一個(gè)類朗和,字段或方法是編譯器自動(dòng)產(chǎn)生的,也可以設(shè)置它們?cè)L問(wèn)標(biāo)志中的ACC_SYNTHETIC標(biāo)志位宣蠕,其中最典型的就是Bridge Method例隆。所有非用戶代碼生產(chǎn)的類,方法及字段都應(yīng)當(dāng)至少設(shè)置Synthetic屬性和ACC_SYNTHETIC標(biāo)志位中的一項(xiàng)抢蚀,唯一的例外是實(shí)例構(gòu)造器“”方法和類構(gòu)造器“<clinit”方法镀层。

Deprecated及Synthetic屬性表結(jié)構(gòu)如下:

image

其中attribute_length數(shù)據(jù)項(xiàng)的值必須為0,因?yàn)闆](méi)有任何屬性值需要設(shè)置皿曲。

在JDK1.5和JDK1.6中一共增加了10項(xiàng)屬性唱逢,具體如下:

image

引用類型和對(duì)象是否死亡

在JDK1.2以前,Java中的引用定義得很傳統(tǒng):如果reference類型的數(shù)值代表的是另外一塊內(nèi)存的起始地址屋休,就稱這塊內(nèi)存代表著一個(gè)引用坞古。這種定義很純粹,但太過(guò)狹隘劫樟,一個(gè)對(duì)象在這種定義下只有被引用或者沒(méi)有引用兩種狀態(tài)痪枫,對(duì)于如何描述一個(gè)“食之無(wú)味,棄之可惜”的對(duì)象就顯得無(wú)能為力叠艳;如果內(nèi)存在進(jìn)行垃圾收集后還是非常緊張奶陈,則可以拋棄這些對(duì)象。很多系統(tǒng)的緩存功能都符合這樣的應(yīng)用場(chǎng)景附较。在JDK1.2之后吃粒,Java對(duì)引用的概念進(jìn)行了擴(kuò)充,將引用分為強(qiáng)引用(Strong Reference)拒课,軟引用(Soft Reference)徐勃,弱引用(Weak Reference),虛引用(Phantom Reference)四種早像,這四種引用強(qiáng)度依賴逐漸減弱僻肖。

1.強(qiáng)引用就是指在程序代碼之中普遍存在的,類似“Object obj = new Object()”這類引用卢鹦,只要強(qiáng)引用存在檐涝,垃圾收集器永遠(yuǎn)不會(huì)回收掉被引用的對(duì)象

2.軟引用用來(lái)描述一些還有用,但并非必須的對(duì)象谁榜。對(duì)于軟引用關(guān)聯(lián)著的對(duì)象,在系統(tǒng)將要發(fā)生內(nèi)存溢出之前凡纳,將會(huì)把這些對(duì)象列進(jìn)回收范圍之中窃植,并進(jìn)行第二次回收。如果這次回收還是沒(méi)有足夠的內(nèi)存荐糜,才會(huì)拋出內(nèi)存溢出錯(cuò)誤巷怜。在JDK1.2之后,提供了SoftReference來(lái)實(shí)現(xiàn)軟引用暴氏。

3.弱引用也是用來(lái)描述非必須對(duì)象的延塑,但是它的強(qiáng)度比軟引用更弱一點(diǎn),被弱引用關(guān)聯(lián)的對(duì)象只能生存到下一次垃圾回收之前答渔。當(dāng)垃圾收集器工作時(shí)关带,無(wú)論當(dāng)前內(nèi)存是否足夠,都會(huì)回收掉被弱引用關(guān)聯(lián)的對(duì)象沼撕。在JDK1.2之后宋雏,提供了WeakReference來(lái)實(shí)現(xiàn)弱引用。

4.虛引用它是最弱的一種引用關(guān)系务豺。一個(gè)對(duì)象是否有虛引用存在磨总,完全不會(huì)對(duì)其生存時(shí)間構(gòu)成影響,也無(wú)法通過(guò)虛引用來(lái)取得一個(gè)對(duì)象的實(shí)例笼沥。為一個(gè)對(duì)象設(shè)置虛引用關(guān)聯(lián)的唯一目的是希望能在這個(gè)對(duì)象被收集器回收時(shí)收到一個(gè)系統(tǒng)通知蚪燕。在JDK1.2之后,提供了PhantomReference來(lái)實(shí)現(xiàn)虛引用奔浅。

在根搜索算法中不可達(dá)的對(duì)象馆纳,也并非是“非死不可的”,這時(shí)候它們暫時(shí)處于“緩刑”階段乘凸,要真正宣告一個(gè)對(duì)象死亡厕诡,至少要經(jīng)歷兩次標(biāo)記過(guò)程:如果對(duì)象在進(jìn)行根搜索后發(fā)現(xiàn)沒(méi)有與GC Roots相連接的引用鏈,那它將會(huì)被第一次標(biāo)記并進(jìn)行一次篩選营勤,篩選的條件是此對(duì)象是否有必要執(zhí)行finalize()方法灵嫌。當(dāng)對(duì)象沒(méi)有覆蓋finallize()方法或該對(duì)象的finalize()已經(jīng)被調(diào)用過(guò),虛擬機(jī)將這兩種情況都視為“沒(méi)有必要執(zhí)行”葛作。如果一個(gè)對(duì)象被判斷為有必要執(zhí)行finalize()方法寿羞,那么這個(gè)對(duì)象將會(huì)被放置在一個(gè)名為F-Queue的隊(duì)列之中,并在稍后由一條虛擬機(jī)自動(dòng)建立的赂蠢,低優(yōu)先級(jí)的Finalizer線程去執(zhí)行绪穆。這里所謂的“執(zhí)行”是指虛擬機(jī)會(huì)觸發(fā)這個(gè)方法,但并不承諾會(huì)等待它運(yùn)行結(jié)束。這樣做的原因是玖院,如果一個(gè)對(duì)象在fianlize()方法中執(zhí)行緩慢菠红,或者發(fā)生了死循環(huán),將很可能會(huì)導(dǎo)致F-Queue隊(duì)列中的其它對(duì)象永久處于等待狀態(tài)难菌,甚至導(dǎo)致整個(gè)內(nèi)存回收系統(tǒng)崩潰试溯。finalize()方法是對(duì)象逃脫死亡命運(yùn)的最后一次機(jī)會(huì),稍后GC將對(duì)F-Queue中的對(duì)象進(jìn)行第二次小規(guī)模的標(biāo)記郊酒,如果對(duì)象在finalize()中成功拯救自己--只要重新與引用鏈上的任何一個(gè)對(duì)象建立關(guān)聯(lián)既可遇绞,那在第二次標(biāo)記時(shí)它將被移出“即將回收”集合;如果對(duì)象這時(shí)候還沒(méi)有逃脫燎窘,那它就將被回收了摹闽。

類加載時(shí)機(jī)

類從被加載到虛擬機(jī)內(nèi)存中開(kāi)始,到缷載出內(nèi)存為止褐健,它的整個(gè)生命周期為:加載(Loading)付鹿,驗(yàn)證(Verification),準(zhǔn)備(Preparation)铝量,解析(Resolution)倘屹,初始化(Initialization),使用(Using)慢叨,缷載(Unloading)七個(gè)階段纽匙。其中驗(yàn)證,準(zhǔn)備拍谐,解析三個(gè)階段統(tǒng)稱為連接(Linking)階段烛缔,這七個(gè)階段的發(fā)生順序如下圖:

image

加載,驗(yàn)證轩拨,準(zhǔn)備践瓷,初始化和缷載這五個(gè)階段的順序是確定的,類的加載過(guò)程必須按照這種順序按部就班地開(kāi)始亡蓉,而解析階段則不一定:在某些情況下可以在初始化階段之后再開(kāi)始晕翠,這是為了支持Java語(yǔ)言的運(yùn)行時(shí)綁定。注意這里寫(xiě)的是按部就班地“開(kāi)始”(意思是保證開(kāi)始的順序是確定的砍濒,但各個(gè)階段間是互相交叉地混合式進(jìn)行的淋肾,所以只能是開(kāi)始的順序是確定的),而不是按部就班地“進(jìn)行”或“完成”爸邢,因?yàn)檫@些階段通常都是互相交叉地混合式進(jìn)行的樊卓,通常會(huì)在一個(gè)階段執(zhí)行的過(guò)程調(diào)用或激活另外一個(gè)階段。

在什么情況下需要開(kāi)始類的加載過(guò)程的第一個(gè)階段:加載杠河。虛擬機(jī)規(guī)范中并沒(méi)有進(jìn)行強(qiáng)制約束碌尔。但是對(duì)于初始化階段浇辜,虛擬機(jī)規(guī)范則是嚴(yán)格規(guī)定了有且只有四種情況必須立即對(duì)類進(jìn)行“初始化”(而加載,驗(yàn)證唾戚,準(zhǔn)備階段自然需要在此之前開(kāi)始):

  1. 遇到new,getstatic,putstatic或invokestatic這4條字節(jié)碼指令時(shí)柳洋,如果類沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)其初始化颈走。生成這4條指令最常見(jiàn)的Java代碼場(chǎng)景是:使用new關(guān)鍵字實(shí)例化對(duì)象的時(shí)候膳灶,讀取或設(shè)置一個(gè)類的靜態(tài)字段(被final修飾、已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外)的時(shí)候立由,以及調(diào)用一個(gè)類的靜態(tài)方法的時(shí)候。

  2. 使用java.lang.reflect包的方法對(duì)類進(jìn)行反射調(diào)用的時(shí)候序厉,如果類沒(méi)有進(jìn)行過(guò)初始化锐膜,則需要先觸發(fā)其初始化。

  3. 當(dāng)初始化一個(gè)類的時(shí)候弛房,如果發(fā)現(xiàn)其父類還沒(méi)有進(jìn)行過(guò)初始化道盏,則需要先觸發(fā)其父類的初始化。

  4. 當(dāng)虛擬機(jī)啟動(dòng)時(shí)文捶,用戶需要指定一個(gè)要執(zhí)行的主類(包含main()方法的那個(gè)類)荷逞,虛擬機(jī)會(huì)先初始化這個(gè)主類。

對(duì)于這四種會(huì)觸發(fā)類進(jìn)行初始化的場(chǎng)景粹排,虛擬機(jī)規(guī)范中使用了一個(gè)很強(qiáng)烈的限定語(yǔ):“有且只有”种远,這四種場(chǎng)景中的行為稱為對(duì)一個(gè)類的主動(dòng)引用。除此之外所有引用類的方法顽耳,都不會(huì)觸發(fā)初始化坠敷,稱為被動(dòng)引用。下面是三個(gè)被動(dòng)引用的例子:

a.通過(guò)子類引用父類中的靜態(tài)字段射富,不會(huì)初始化子類

<pre class="md-fences md-end-block" lang="java" contenteditable="false" cid="n808" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: pre; text-align: left; break-inside: avoid; display: block; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(221, 221, 221); border-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">
package com.xtayfjpk.jvm.chapter7;
?
/**

  • 被動(dòng)使用類字段演示一:
  • 通過(guò)子類引用父類的靜態(tài)字段膝迎,不會(huì)導(dǎo)致子類初始化
  • @author zj

*/
public class SuperClass {
public static int value = 123;
?
static {
System.out.println("SuperClass Init");
}
}</pre>

<pre class="md-fences md-end-block" lang="java" contenteditable="false" cid="n809" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: pre; text-align: left; break-inside: avoid; display: block; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(221, 221, 221); border-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">
package com.xtayfjpk.jvm.chapter7;
?
public class SubClass extends SuperClass {
?
static {
System.out.println("SubClass Init");
}
}</pre>

<pre class="md-fences md-end-block" lang="java" contenteditable="false" cid="n810" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: pre; text-align: left; break-inside: avoid; display: block; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(221, 221, 221); border-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">
package com.xtayfjpk.jvm.chapter7;
?
public class NotInitialization {
?
/**

  • @param args
    */
    public static void main(String[] args) {
    //通過(guò)子類引用父類的靜態(tài)字段,不會(huì)導(dǎo)致子類初始化
    System.out.println(SubClass.value);
    }
    ?
    }</pre>

上述代碼運(yùn)行之后胰耗,只會(huì)輸出“SuperClass init!”限次,而不會(huì)輸出“SubClass init!”。對(duì)于靜態(tài)字段柴灯,只有直接定義這個(gè)字段的類才會(huì)初始化卖漫,因此通過(guò)子類來(lái)引用父類中的靜態(tài)字段,只會(huì)觸發(fā)父類初始化而不不會(huì)觸發(fā)子類的初始化弛槐。至于是否要觸發(fā)子類的加載和驗(yàn)證懊亡,在虛擬機(jī)規(guī)范中并沒(méi)有規(guī)定,這點(diǎn)取決于虛擬機(jī)的具體實(shí)現(xiàn)乎串。對(duì)于Sun HotSpot虛擬機(jī)來(lái)說(shuō)店枣,可以通過(guò)-XX:+TraceClassLoading參數(shù)看到此操作是否會(huì)導(dǎo)致子類的加載(事實(shí)上SubClass被加載了)速警。

b.通過(guò)定義數(shù)組來(lái)引用類,不會(huì)觸發(fā)此類的初始化

<pre class="md-fences md-end-block" lang="java" contenteditable="false" cid="n815" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: pre; text-align: left; break-inside: avoid; display: block; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(221, 221, 221); border-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">
package com.xtayfjpk.jvm.chapter7;
?
public class NotInitialization {
?
/**

  • @param args
    */
    public static void main(String[] args) {
    ?
    //通過(guò)數(shù)組定義來(lái)引用類鸯两,不會(huì)觸發(fā)此類的初始化
    SuperClass[] sca = new SuperClass[10];
    ?
    }
    ?
    }</pre>

上述代碼運(yùn)行后(SuperClass重用上一例子代碼)右遭,并沒(méi)有輸出“SuperClass init!”,說(shuō)明并沒(méi)有觸發(fā)SuperClass的初始化階段槽华。但是這段代碼觸發(fā)另一個(gè)名為“[Lcom.xtayfjpk.jvm.chapter7.SuperClass;”的類的初始化階段寞酿,對(duì)于用戶代碼來(lái)說(shuō),這并不是一個(gè)合法的類名稱钝侠,它是一個(gè)由虛擬機(jī)自動(dòng)生成该园,直接繼承于java.lang.Object的子類,創(chuàng)建動(dòng)作由字節(jié)碼指令newarray觸發(fā)帅韧。

c.引用常量池中的常量里初,不會(huì)觸發(fā)定義常量類的初始化(static final修飾的字段)

<pre class="md-fences md-end-block" lang="java" contenteditable="false" cid="n820" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: pre; text-align: left; break-inside: avoid; display: block; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(221, 221, 221); border-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">
package com.xtayfjpk.jvm.chapter7;
?
public class ConstClass {
?
public static final String HELLOWORLD = "hello world";
?
static {
System.out.println("ConstClass Inited");
}
?
}</pre>

<pre class="md-fences md-end-block" lang="java" contenteditable="false" cid="n821" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: Consolas, "Liberation Mono", Courier, monospace; font-size: 0.9em; white-space: pre; text-align: left; break-inside: avoid; display: block; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(221, 221, 221); border-radius: 3px; padding: 8px 1em 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: normal; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">
package com.xtayfjpk.jvm.chapter7;
?
public class NotInitialization {
?
/**

  • @param args
    */
    public static void main(String[] args) {
    ?
    //常量在編譯階段會(huì)存入調(diào)用類的常量池中,本質(zhì)上沒(méi)有直接引用到定義常量的類忽舟,不會(huì)導(dǎo)致該類初始化
    System.out.println(ConstClass.HELLOWORLD);
    }
    ?
    }</pre>

上述代碼運(yùn)行后双妨,也沒(méi)有輸出“ConstClass init!”,這是因?yàn)殡m然在Java源碼中引用了ConstClass類中的常量HELLOWORLD叮阅,但是編譯階段將此常量值“hello world”存儲(chǔ)到了NotInitialization類的常量池中刁品,對(duì)象常量ConstClass.HELLOWORLD的引用實(shí)際都轉(zhuǎn)化為NotInitialization類對(duì)自身常量池的引用。也就是說(shuō)實(shí)際上NotInitialization的Class文件之中并沒(méi)有ConstClass類的符號(hào)引用入口浩姥,這兩個(gè)類在編譯成Class之后就不存在任何聯(lián)系了挑随。

接口的加載過(guò)程與類的加載過(guò)程稍有一些不同,針對(duì)接口需要做一些特殊說(shuō)明:接口也有初始化過(guò)程及刻,這點(diǎn)與類是一致的镀裤,上面的代碼都是使用靜態(tài)語(yǔ)句塊“static {}”來(lái)輸出初始化信息的,而接口中不能有“static {}”靜態(tài)語(yǔ)句塊缴饭,但編譯器仍然會(huì)為接口生成“”類構(gòu)造器暑劝,用于初始化接口中所定義的成員變量(也是常量)。接口與類真正有所區(qū)別的是前面講述的四有“有且僅有”需要開(kāi)始初始化階段場(chǎng)景中的第三種:當(dāng)一個(gè)類在初始化時(shí)颗搂,要求其父類全都已經(jīng)初始化過(guò)了担猛,但是一個(gè)接口在初始化時(shí),并不要求其父接口也全部初始完成了丢氢,只有在真正用到父接口的時(shí)候(如引用到接口中定義的常量)才會(huì)初始化傅联。

疑問(wèn):

Q: 加載,驗(yàn)證疚察,準(zhǔn)備蒸走,初始化和缷載這五個(gè)階段的順序是確定的,類的加載過(guò)程必須按照這種順序按部就班地開(kāi)始貌嫡,而解析階段則不一定:在某些情況下可以在初始化階段之后再開(kāi)始比驻,這是為了支持Java語(yǔ)言的運(yùn)行時(shí)綁定该溯。那使用階段不是確定的么?是指不一定使用别惦?

類加載過(guò)程

一狈茉、加載

“加載”(Loading)階段是“類加載”(Class Loading)過(guò)程的一個(gè)階段。在加載階段掸掸,虛擬機(jī)需要完成以下三件事情:a.通過(guò)一個(gè)類的全限制名來(lái)獲取定義此類的二進(jìn)制字節(jié)流氯庆。b.將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)。c.在Java堆中生成一個(gè)代表這個(gè)類的java.lang.Class對(duì)象扰付,作為方法區(qū)這些數(shù)據(jù)的訪問(wèn)入口堤撵。

虛擬機(jī)規(guī)范的這三點(diǎn)要求實(shí)際上并不具體,因此虛擬機(jī)實(shí)現(xiàn)與具體應(yīng)用的靈活度相當(dāng)大羽莺。例如“通過(guò)一個(gè)類的全限制名來(lái)獲取定義此類的二進(jìn)制字節(jié)流”粒督,并沒(méi)有指明二進(jìn)制字節(jié)流要從一個(gè)Class文件中獲取,準(zhǔn)確地說(shuō)是根本沒(méi)有指明要從哪里獲取及怎樣獲取禽翼。虛擬機(jī)設(shè)計(jì)團(tuán)隊(duì)加載階段搭建了一個(gè)相當(dāng)開(kāi)放的,廣闊的舞臺(tái)族跛,Java發(fā)展歷程中闰挡,許多舉足輕重的Java技術(shù)都建立在這一基礎(chǔ)上,例如:a.從ZIP包中讀取礁哄,這很常見(jiàn)长酗,最終成為日后JAR,EAR,WAR格式的基礎(chǔ)b.從網(wǎng)絡(luò)中獲取,這種場(chǎng)景最典型的應(yīng)用就是Appletc.運(yùn)行時(shí)計(jì)算生成桐绒,這種場(chǎng)景使用得最多的就是動(dòng)態(tài)代理技術(shù)夺脾,在java.lang.reflect.Proxy中,就是用了ProxyGenerator.generatProxyClass來(lái)為特定接口生成 $Proxy的代理類的二進(jìn)制字節(jié)流茉继。d.由其它文件生成咧叭,典型場(chǎng)景:JSP應(yīng)用。e.從數(shù)據(jù)庫(kù)中讀取*烁竭,這種場(chǎng)景相對(duì)少見(jiàn)些菲茬,有些中間件服務(wù)器(如SAP Netweaver)可以選擇把程序安裝到數(shù)據(jù)庫(kù)中來(lái)完成程序代碼在集群間的分發(fā)。f. .......

加載階段與連接階段的部分內(nèi)容(如一部分字節(jié)碼文件格式驗(yàn)證動(dòng)作)是交叉進(jìn)行的派撕,加載階段尚未完成婉弹,連接階段可能已經(jīng)開(kāi)始,但這些夾在加載階段之中進(jìn)行的動(dòng)作终吼,仍然屬性連接階段的內(nèi)容镀赌,這兩階段的開(kāi)始時(shí)間仍然保持著固的先后順序。

二际跪、驗(yàn)證

驗(yàn)證是連接階段的第一步商佛,這一階段的目的是為了確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求喉钢,并且不會(huì)危害虛擬機(jī)自身的安全。

盡管驗(yàn)證階段是非常重要的威彰,并且驗(yàn)證階段的工作量在虛擬機(jī)的類加載子系統(tǒng)中占了很大一部分出牧。如果驗(yàn)證到輸入的字節(jié)流不符合Class文件的存儲(chǔ)格式,就拋出一個(gè)java.lang.VerifyError錯(cuò)誤或者其子錯(cuò)誤歇盼。具體應(yīng)當(dāng)檢查哪些方面舔痕,如何檢查,何時(shí)檢查豹缀,虛擬機(jī)規(guī)范都沒(méi)有明確說(shuō)明伯复,所以不同的虛擬機(jī)對(duì)驗(yàn)證的實(shí)現(xiàn)可能會(huì)有所不同,但大致上都會(huì)完成四個(gè)階段的驗(yàn)證過(guò)程:文件格式驗(yàn)證邢笙,元數(shù)據(jù)驗(yàn)證啸如,字節(jié)碼驗(yàn)證和符號(hào)引用驗(yàn)證

三氮惯、準(zhǔn)備

準(zhǔn)備階段是正式為類變量分配內(nèi)存并設(shè)置類變量初始值的階段叮雳,這些內(nèi)存都將在方法區(qū)中進(jìn)行分配(注意是類變量即static修飾的字段,不是示例變量)妇汗。需要強(qiáng)調(diào)的是:首先是這時(shí)候進(jìn)行內(nèi)存分配的僅包括類變量帘不,而不包括實(shí)例變量,實(shí)例變量將會(huì)在對(duì)象實(shí)例化時(shí)隨著對(duì)象一起分配是Java堆中杨箭。其次是這里所說(shuō)的初始值“通常情況“下是數(shù)據(jù)類型的零值寞焙,假設(shè)一個(gè)類變量定義為:public static int value = 123;那么變量value在準(zhǔn)備階段過(guò)后的初始值是0而不是123,因?yàn)檫@時(shí)候尚未開(kāi)始執(zhí)行任何Java方法互婿,而把value賦值為123的putstatic指令是在程序被編譯后捣郊,存放于類構(gòu)造器”<clinit>“方法中的,所以把value賦值為123的動(dòng)作將在初始化階段才會(huì)被執(zhí)行慈参。

上面提到的”通常情況“下初始值為零值呛牲,但是,如果類字段的字段屬性表中存在ConstantValue屬性懂牧,那在準(zhǔn)備階段變量value就會(huì)初始初始化為ConstantValue屬性所指定的值侈净,假設(shè)上面類變量value被定義為:public static final int value = 123;編譯時(shí)Javac將會(huì)為value生成ConstantValue屬性表,在準(zhǔn)備階段虛擬會(huì)就會(huì)根據(jù)ConstantValue的設(shè)置將value賦值為123僧凤。(靜態(tài)常量即static final 修飾的字段直接賦值)

四畜侦、解析

解析階段是虛擬機(jī)將常量池內(nèi)的符號(hào)引用替換為直接引用的過(guò)程,在Class文件中它以CONSTANT_Class_info躯保,CONSTANT_Fieldref_info旋膳,CONSTANT_Methodref_info等類型的常量出現(xiàn)。直接引用與符號(hào)引用的關(guān)聯(lián)是:

a.符號(hào)引用(Symbolic References)以一組符號(hào)來(lái)描述所引用的目標(biāo)途事,符號(hào)可以是任何形式的字面量验懊,只要使用時(shí)能無(wú)歧義地定位到目標(biāo)即可擅羞。符號(hào)引用與虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局無(wú)關(guān),引用的目標(biāo)并不一定已經(jīng)加載到了內(nèi)存中义图。

b.直接引用可以是直接指向目標(biāo)的指針减俏,相對(duì)偏移量或是一個(gè)能間接定位到目標(biāo)的句柄直接引用與虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局相關(guān)碱工,同一個(gè)符號(hào)引用在不同的虛擬機(jī)實(shí)例上翻譯出來(lái)的直接引用一般不會(huì)相同娃承。如果有了直接引用,那引用的目標(biāo)就必須已經(jīng)在內(nèi)存中存在怕篷。

虛擬機(jī)規(guī)范中并未規(guī)定解析階段發(fā)生的具體時(shí)間历筝,只要求在執(zhí)行了newarray,heckcast廊谓,getfield梳猪,etstatic,instanceof蒸痹,invokeinterface春弥,invokespecial,nvokestatic叠荠,invokevritual惕稻,multianewarray,new蝙叛,putfield和putstatic這13個(gè)用于操作符號(hào)引用的字節(jié)碼指令之前,先對(duì)它們所使用的符號(hào)引用進(jìn)行解析公给。所以虛擬機(jī)實(shí)現(xiàn)會(huì)根據(jù)需要來(lái)判斷借帘,到底是在類加載器加載時(shí)就對(duì)常量池中的符號(hào)引用進(jìn)行解析,還是等到一個(gè)符號(hào)引用將要被使用時(shí)才去解析它淌铐。

同一個(gè)符號(hào)引用可能會(huì)進(jìn)行多次解析請(qǐng)求肺然,虛擬機(jī)實(shí)現(xiàn)可能會(huì)對(duì)第一次解析的結(jié)果進(jìn)行緩存從而避免解析動(dòng)作重復(fù)進(jìn)行。無(wú)論是否真正執(zhí)行了多次解析操作腿准,虛擬機(jī)需要保證的都是在同一個(gè)實(shí)體中际起,如果一個(gè)符號(hào)引用被成功解析過(guò),那么后續(xù)的解析請(qǐng)求就應(yīng)當(dāng)一直成功吐葱;同樣的街望,如果第一次解析失敗了,其它指令對(duì)這個(gè)符號(hào)引用的解析請(qǐng)求也應(yīng)該收到相同的異常弟跑。

解析動(dòng)作主要針對(duì)的是類或接口灾前,字段,方法孟辑,接口方法四類符號(hào)引用進(jìn)行的哎甲,分別對(duì)應(yīng)于常量池的CONSTANT_Class_info蔫敲,CONSTANT_Fieldref_info,CONSTANT_Methodref_info及CONSTANT_InterfaceMetodref_info四種常量類型炭玫。下面是這四種引用的解析過(guò)程奈嘿。

1.類或接口的解析過(guò)程假設(shè)當(dāng)前代碼所處的類為D,如果要把一個(gè)從未解析過(guò)的符號(hào)引用N解析為一個(gè)類或接口C的直接引用吞加,那虛擬機(jī)完成整個(gè)解析過(guò)程需要包括以下3個(gè)步驟:

a.如果C不是一個(gè)數(shù)組類型裙犹,那虛擬機(jī)將會(huì)把代表N的全限定名傳遞給D類的加載器去加載這個(gè)類C。在加載過(guò)程中榴鼎,由于元數(shù)據(jù)驗(yàn)證伯诬,字節(jié)碼驗(yàn)證的需要,又將可能觸發(fā)其它相關(guān)類的加載動(dòng)作巫财,例如加載這個(gè)類的父類或?qū)崿F(xiàn)的接口盗似。一旦這個(gè)加載過(guò)程出現(xiàn)了任何異常,解析過(guò)程就將宣告失敗平项。

b.如果C是一個(gè)數(shù)組類型赫舒,并且數(shù)組的元素類型為對(duì)象,也就是N的描述符會(huì)是類似”[Ljava.lang.Integer"的形式闽瓢,那么會(huì)按照第a點(diǎn)的規(guī)則加載數(shù)組元素類型接癌。如果N的描述符如前面所假設(shè)的形式,需要加載的元素類型就是“java.lang.Integer”扣讼,接著由虛擬機(jī)生成一個(gè)代表此數(shù)組和元素的數(shù)組對(duì)象缺猛。

c.如果上面的步驟沒(méi)有出現(xiàn)任何異常,那么C在虛擬機(jī)中實(shí)際上已經(jīng)成為一個(gè)有效的類或接口了椭符,但在解析完成之前還要進(jìn)行符號(hào)引用驗(yàn)證荔燎,確認(rèn)D是否具備對(duì)C的訪問(wèn)權(quán)限,如果發(fā)現(xiàn)不具體訪問(wèn)權(quán)限销钝,將拋出java.lang.IllegalAccessError錯(cuò)誤有咨。

2.字段解析——多態(tài)要解析一個(gè)未被解析過(guò)的字段符號(hào)引用,首先將會(huì)對(duì)字段表內(nèi)class_index項(xiàng)中索引的CONSTANT_Class_info符號(hào)引用進(jìn)行解析蒸健,也就是字段所屬性的類或接口的符號(hào)引用座享。如果在解析這個(gè)類或接口符號(hào)引用過(guò)程中出現(xiàn)了任何異常,都會(huì)導(dǎo)致字段符號(hào)引用解析失敗似忧。如果解析成功完成渣叛,那么這個(gè)字段所屬性的類或接口用C表示,虛擬機(jī)規(guī)范要求如下步驟對(duì)C進(jìn)行后續(xù)字段的搜索:a.如果C本身就包含了簡(jiǎn)單名稱和字段描述符都與目標(biāo)相匹配的字段盯捌,則返回這個(gè)字段的直接引用诗箍,查找結(jié)束。b.否則,如果C中實(shí)現(xiàn)了接口滤祖,將會(huì)按照繼承關(guān)系從上往下遞歸搜索各個(gè)接口和它的父接口筷狼,如果接口中包含了簡(jiǎn)單名稱答字段描述符都與目標(biāo)相匹配的字段,則返回該字段的直接引用匠童,查找結(jié)束埂材。c.否則,如果C不是java.lang.Object的話汤求,將會(huì)按照繼承關(guān)系從上往下遞歸搜索其父類俏险,如果在父類中包含了簡(jiǎn)單名稱和字段描述符都與目標(biāo)匹配的字段,則返回這個(gè)字段的直接引用扬绪,查找結(jié)束竖独。d.否則,查找失敗挤牛,招拋出java.lang.NoSuchFieldError錯(cuò)誤莹痢。

3.類方法解析類方法解析的第一個(gè)步驟與字段解析一樣,也是需要先解析出方法表的class_index項(xiàng)中索引的方法所屬性類或接口的符號(hào)引用墓赴,如果解析成功竞膳,依然用C表示這個(gè)類,接下來(lái)虛擬機(jī)將會(huì)按照如下步驟進(jìn)行后續(xù)的類方法搜索:a.類方法和接口方法符號(hào)引用的常量類型定義是分開(kāi)的诫硕,如果在類方法表中發(fā)現(xiàn)了class_index中索引的C是個(gè)接口坦辟,那么直接就拋出java.lang.IncompatibleClassChangeError錯(cuò)誤。b.如果通了第a步章办,在類C中查找是否有簡(jiǎn)單名稱和描述符與目標(biāo)相匹配的方法锉走,如果有則返回這個(gè)方法的直接引用,查找結(jié)束藕届。c.否則挠日,在類C的父類中遞歸查找是否有簡(jiǎn)單名稱和字段描述符都與目標(biāo)匹配的方法,則返回這個(gè)方法的直接引用翰舌,查找結(jié)束。d.否則冬骚,在類C實(shí)現(xiàn)的接口列表及它們的父接口中遞歸查找否有簡(jiǎn)單名稱和字段描述符都與目標(biāo)匹配的方法椅贱,說(shuō)明類C是一個(gè)抽象類,這時(shí)候查找結(jié)束只冻,拋出java.lang.AbstractMethodError錯(cuò)誤庇麦。e.否則,宣告查找失敗喜德,拋出java.lang.NoSuchMethodError錯(cuò)誤山橄。最后,如果查找過(guò)程成功返回了直接引用舍悯,將會(huì)對(duì)這個(gè)方法進(jìn)行權(quán)限驗(yàn)證航棱;如果發(fā)現(xiàn)不具務(wù)對(duì)此方法的訪問(wèn)權(quán)限睡雇,將拋出java.lang.IllegalAccessError錯(cuò)誤。

4.接口方法解析接口方法也需要先解析出接口方法表中的class_index項(xiàng)中索引的方法所屬性的類或接口的符號(hào)引用饮醇,如果解析成功它抱,依然用C表示這個(gè)接口,接下來(lái)虛擬機(jī)將會(huì)按照如下步驟進(jìn)行后續(xù)的接口訪求搜索:a.與類方法解析相反朴艰,如果在接口方法表中發(fā)現(xiàn)了class_index中索引的C是個(gè)類而不是接口观蓄,那么直接就拋出java.lang.IncompatibleClassChangeError錯(cuò)誤。b.否則祠墅,在接口C中查找是否有簡(jiǎn)單名稱的描述符都與目標(biāo)相匹配的方法侮穿,如果有則返回這個(gè)方法的直接引用,查找結(jié)束毁嗦。c.否中亲茅,在接口C的父接口中遞歸查找,直到j(luò)ava.lang.Object類為止金矛,看是否有簡(jiǎn)單名稱和描述符都與目標(biāo)相匹配的方法芯急,如果有則返回這個(gè)方法的直接引用,查找結(jié)束驶俊。d.否則娶耍,宣告查找失敗,拋出java.lang.NoSuchMethodError錯(cuò)誤饼酿。由于接口中的所有方法都默認(rèn)是public的榕酒,所以不存在訪問(wèn)權(quán)限問(wèn)題,因此接口方法的符號(hào)解析應(yīng)該不會(huì)拋出java.lang.IllegalAccessError錯(cuò)誤故俐。

五想鹰、初始化

類的初始化是類加載過(guò)程的最后一步,前面的類加載動(dòng)作药版,除了在加載階段用戶應(yīng)用程序可以通過(guò)自定義類加載器參與之外辑舷,其余動(dòng)作完全由虛擬機(jī)主導(dǎo)和控制。到了初始化階段槽片,才真正執(zhí)行類中定義的Java程序代碼(或者說(shuō)是字節(jié)碼)何缓。

在準(zhǔn)備階段,變量已經(jīng)賦過(guò)一次系統(tǒng)要求的初始值还栓,而在初始化階段碌廓,則是根據(jù)程序員通過(guò)程序制定的主觀計(jì)劃去初始化類變量和其它資源,或者可以從另外一個(gè)角度來(lái)表達(dá):初始化階段是執(zhí)行類構(gòu)造器<clinit>()方法的過(guò)程剩盒。<clinit>()方法執(zhí)行過(guò)程可能會(huì)影響程序運(yùn)行行為的一些特點(diǎn)與細(xì)節(jié)谷婆,如下:

  1. <clinit>()方法是由編譯器自動(dòng)收集類中所有類變量的賦值動(dòng)作和靜態(tài)語(yǔ)句塊(static{})中的語(yǔ)句合并產(chǎn)生的,編譯器收集的順序是由語(yǔ)句在源文件中出現(xiàn)的順序所決定的,靜態(tài)語(yǔ)句塊中只能訪問(wèn)到定義在靜態(tài)語(yǔ)句塊之前的變量纪挎,定義在它之后變量期贫,在前面的靜態(tài)語(yǔ)句塊中可以賦值,但是不能訪問(wèn)廷区。

  2. <clinit>()方法與類的構(gòu)造器<init>()不同唯灵,它不需要顯示地調(diào)用父類類構(gòu)造器,虛擬機(jī)會(huì)保證在子類的<clinit>()方法執(zhí)行之前隙轻,父類的<clinit>()方法已經(jīng)執(zhí)行完畢埠帕。因此在虛擬機(jī)中第一個(gè)被執(zhí)行<clinit>()方法的類肯定是java.lang.Object。

  3. 由于父類的<clinit>()方法先執(zhí)行玖绿,所就意味著父類中定義的靜態(tài)語(yǔ)句塊要優(yōu)先于子類的類變量賦值操作敛瓷。

  4. <clinit>()方法對(duì)于類或接口來(lái)說(shuō)并不是必須的俺附,如果一個(gè)類中沒(méi)有靜態(tài)語(yǔ)句塊办龄,也沒(méi)有對(duì)類變量的賦值操作信夫,那么編譯器可以不為這類生成<clinit>()方法铐料。

  5. 接口中不能使用靜態(tài)語(yǔ)句塊,但仍然可以有變量初始化的賦值操作剂碴,因此接口與類一樣都會(huì)生成<clinit>()方法咸这,但接口與類不同的是痒蓬,執(zhí)行接口的<clinit>()方法不需要先執(zhí)行父接口的<clinit>()方法贮勃。只有當(dāng)父接口中定義的變量被使用時(shí)贪惹,父接口才會(huì)初始化。另外寂嘉,接口的實(shí)現(xiàn)類在初始化時(shí)也不會(huì)執(zhí)行接口的<clinit>()方法奏瞬。

  6. 虛擬機(jī)會(huì)保證一個(gè)類的<clinit>()方法在多線程環(huán)境中被正確地加鎖同步,如果多個(gè)線程同時(shí)去初始化一個(gè)類泉孩,那么只會(huì)有一個(gè)線程去執(zhí)行這個(gè)類的<clinit>()方法硼端,其它線程都需要阻塞等待,直到活動(dòng)線程執(zhí)行<clinit>()方法完畢寓搬。如果在一個(gè)類的<clinit>()方法中有很耗時(shí)的操作珍昨,那就可能造成多個(gè)線程阻塞,在實(shí)際應(yīng)用中這種阻塞往往是很隱蔽的句喷。

疑問(wèn):

Q: JVM的符號(hào)引用替換為直接引用什么意思镣典?

A: 參考

在JVM中類加載過(guò)程中,在解析階段脏嚷,Java虛擬機(jī)會(huì)把類的二進(jìn)制數(shù)據(jù)中的符號(hào)引用替換為直接引用。

1.符號(hào)引用(Symbolic References):

符號(hào)引用以一組符號(hào)來(lái)描述所引用的目標(biāo)瞒御,符號(hào)可以是任何形式的字面量父叙,只要使用時(shí)能夠無(wú)歧義的定位到目標(biāo)即可。例如,在Class文件中它以CONSTANT_Class_info趾唱、CONSTANT_Fieldref_info涌乳、CONSTANT_Methodref_info等類型的常量出現(xiàn)。符號(hào)引用與虛擬機(jī)的內(nèi)存布局無(wú)關(guān)甜癞,引用的目標(biāo)并不一定加載到內(nèi)存中夕晓。在Java中,一個(gè)java類將會(huì)編譯成一個(gè)class文件悠咱。在編譯時(shí)蒸辆,java類并不知道所引用的類的實(shí)際地址,因此只能使用符號(hào)引用來(lái)代替析既。比如org.simple.People類引用了org.simple.Language類躬贡,在編譯時(shí)People類并不知道Language類的實(shí)際內(nèi)存地址,因此只能使用符號(hào)org.simple.Language(假設(shè)是這個(gè)眼坏,當(dāng)然實(shí)際中是由類似于CONSTANT_Class_info的常量來(lái)表示的)來(lái)表示Language類的地址拂玻。各種虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局可能有所不同,但是它們能接受的符號(hào)引用都是一致的宰译,因?yàn)榉?hào)引用的字面量形式明確定義在Java虛擬機(jī)規(guī)范的Class文件格式中檐蚜。

2.直接引用

直接引用可以是:

(1)直接指向目標(biāo)的指針(比如,指向“類型”【Class對(duì)象】沿侈、類變量闯第、類方法的直接引用可能是指向方法區(qū)的指針)

(2)相對(duì)偏移量(比如,指向?qū)嵗兞坷呒帷?shí)例方法的直接引用都是偏移量)

(3)一個(gè)能間接定位到目標(biāo)的句柄

直接引用是和虛擬機(jī)的布局相關(guān)的乡括,同一個(gè)符號(hào)引用在不同的虛擬機(jī)實(shí)例上翻譯出來(lái)的直接引用一般不會(huì)相同。如果有了直接引用智厌,那引用的目標(biāo)必定已經(jīng)被加載入內(nèi)存中了诲泌。

雙親委派模型

站在虛擬機(jī)的角度上,只存在兩種不同的類加載器:一種是啟動(dòng)類加載器(Bootstrap ClassLoader)铣鹏,這個(gè)類加載器使用C++語(yǔ)言實(shí)現(xiàn)敷扫,是虛擬機(jī)自身的一部分;另外一種就是其它所有的類加載器诚卸,這些類加載器都由Java語(yǔ)言實(shí)現(xiàn)葵第,獨(dú)立于虛擬機(jī)外部,并且全部繼承自java.lang.ClassLoader合溺。從Java開(kāi)發(fā)人員的角度看卒密,類加載器還可以劃分得更細(xì)一些,如下:

  1. 啟動(dòng)類加載器(Bootstrap ClassLoader):這個(gè)類加載器負(fù)責(zé)將放置在<JAVA_HOME>\lib目錄中的棠赛,或者被-Xbootclasspath參數(shù)所指定路徑中的哮奇,并且是虛擬機(jī)能識(shí)別的(僅按照文件名識(shí)別膛腐,如rt.jar,名字不符合的類庫(kù)即使放置在lib目錄中也不會(huì)被加載)類庫(kù)加載到虛擬機(jī)內(nèi)存中鼎俘。啟動(dòng)類加載器無(wú)法被Java程序直接使用哲身。

  2. 擴(kuò)展類加載器(Extension ClassLoader):這個(gè)類加載器由sun.misc.Launcher$ExtClassLoader實(shí)現(xiàn),它負(fù)責(zé)加載<JAVA_HOME>\lib\ext目錄中的贸伐,或者被java.ext.dirs系統(tǒng)變量所指定的路徑中的所有類庫(kù)勘天,開(kāi)發(fā)者可以直接使用擴(kuò)展類加載器。

  3. 應(yīng)用程序類加載器(Application ClassLoader):這個(gè)類加載器由sum.misc.Launcher.$AppClassLoader來(lái)實(shí)現(xiàn)捉邢。由于這個(gè)類加載器是ClassLoader中的getSystemClassLoader()方法的返回值脯丝,所以一般也被稱為系統(tǒng)類加載器它負(fù)責(zé)加載用戶類路徑上所指定的類庫(kù)歌逢,開(kāi)發(fā)者可以直接使用這個(gè)類加載器巾钉,如果應(yīng)用程序中沒(méi)有自定義過(guò)自己的類加載器,一般情況下這個(gè)就是程序中默認(rèn)的類加載器秘案。

應(yīng)用程序由這三種類加載器互相配合進(jìn)行加載的砰苍,如果有需要,還可以加入自己定義的類加載器阱高。這些類加載器之間的關(guān)系一般如下圖:

image

上圖中展示的類加載器之間的層次關(guān)系赚导,就稱為類加載器的雙親委派模型(Parents Delegation Model)。雙親委派模型要求除了頂層的啟動(dòng)類加載器之外赤惊,其余的類加載器都應(yīng)當(dāng)有自己的父類加載器吼旧。這里的類加載器之間的父子關(guān)系一般不會(huì)以繼承的關(guān)系來(lái)實(shí)現(xiàn),而是使用組合關(guān)系來(lái)復(fù)用父加載器的代碼未舟。雙親委派模型的工作過(guò)程是:如果一個(gè)類加載器收到了類加載的請(qǐng)求圈暗,它首先不會(huì)自己去嘗試加載這個(gè)類,而是把這個(gè)請(qǐng)求委派給父類加載器去完成裕膀,每一個(gè)層次的類加載器都是如此员串,因此所有的加載請(qǐng)求最終都應(yīng)該傳送到頂層的啟動(dòng)類加載器中,只有當(dāng)父加載器反饋?zhàn)约簾o(wú)法完全這個(gè)加載請(qǐng)求時(shí)昼扛,子加載器才會(huì)嘗試自己去加載寸齐。

雙親委派模型的破壞雙親委派模型的第一次“被破壞”其實(shí)發(fā)生在雙親委派模型出現(xiàn)之前--即JDK1.2發(fā)布之前。由于雙親委派模型是在JDK1.2之后才被引入的抄谐,而類加載器和抽象類java.lang.ClassLoader則是JDK1.0時(shí)候就已經(jīng)存在渺鹦,面對(duì)已經(jīng)存在 的用戶自定義類加載器的實(shí)現(xiàn)代碼,Java設(shè)計(jì)者引入雙親委派模型時(shí)不得不做出一些妥協(xié)蛹含。為了向前兼容毅厚,JDK1.2之后的java.lang.ClassLoader添加了一個(gè)新的protected方法findClass(),在此之前浦箱,用戶去繼承java.lang.ClassLoader的唯一目的就是重寫(xiě)loadClass()方法吸耿,因?yàn)樘摂M在進(jìn)行類加載的時(shí)候會(huì)調(diào)用加載器的私有方法loadClassInternal()殴边,而這個(gè)方法的唯一邏輯就是去調(diào)用自己的loadClass()。

JDK1.2之后已不再提倡用戶再去覆蓋loadClass()方法珍语,應(yīng)當(dāng)把自己的類加載邏輯寫(xiě)到findClass()方法中,在loadClass()方法的邏輯里竖幔,如果父類加載器加載失敗板乙,則會(huì)調(diào)用自己的findClass()方法來(lái)完成加載,這樣就可以保證新寫(xiě)出來(lái)的類加載器是符合雙親委派模型的拳氢。

雙親委派模型的第二次“被破壞”是這個(gè)模型自身的缺陷所導(dǎo)致的募逞,雙親委派模型很好地解決了各個(gè)類加載器的基礎(chǔ)類統(tǒng)一問(wèn)題(越基礎(chǔ)的類由越上層的加載器進(jìn)行加載),基礎(chǔ)類之所以被稱為“基礎(chǔ)”馋评,是因?yàn)樗鼈兛偸亲鳛楸徽{(diào)用代碼調(diào)用的API放接。

但是,如果基礎(chǔ)類又要調(diào)用用戶的代碼留特,那該怎么辦呢纠脾。

這并非是不可能的事情,一個(gè)典型的例子便是JNDI(Java 命名與目錄接口 )服務(wù)蜕青,它的代碼由啟動(dòng)類加載器去加載(在JDK1.3時(shí)放進(jìn)rt.jar)苟蹈,但JNDI的目的就是對(duì)資源進(jìn)行集中管理和查找,它需要調(diào)用獨(dú)立廠商實(shí)現(xiàn)部部署在應(yīng)用程序的classpath下的JNDI接口提供者(SPI, Service Provider Interface)的代碼右核,但啟動(dòng)類加載器不可能“認(rèn)識(shí)”之些代碼慧脱,該怎么辦?

為了解決這個(gè)困境贺喝,Java設(shè)計(jì)團(tuán)隊(duì)只好引入了一個(gè)不太優(yōu)雅的設(shè)計(jì):線程上下文類加載器(Thread Context ClassLoader)菱鸥。這個(gè)類加載器可以通過(guò)java.lang.Thread類的setContextClassLoader()方法進(jìn)行設(shè)置,如果創(chuàng)建線程時(shí)還未設(shè)置躏鱼,它將會(huì)從父線程中繼承一個(gè)氮采;如果在應(yīng)用程序的全局范圍內(nèi)都沒(méi)有設(shè)置過(guò),那么這個(gè)類加載器默認(rèn)就是應(yīng)用程序類加載器挠他。有了線程上下文類加載器扳抽,JNDI服務(wù)使用這個(gè)線程上下文類加載器去加載所需要的SPI代碼,也就是父類加載器請(qǐng)求子類加載器去完成類加載動(dòng)作殖侵,這種行為實(shí)際上就是打通了雙親委派模型的層次結(jié)構(gòu)來(lái)逆向使用類加載器贸呢,已經(jīng)違背了雙親委派模型,但這也是無(wú)可奈何的事情拢军。Java中所有涉及SPI的加載動(dòng)作基本上都采用這種方式楞陷,例如JNDI,JDBC,JCE,JAXB和JBI等。

雙親委派模型的第三次“被破壞”是由于用戶對(duì)程序的動(dòng)態(tài)性的追求導(dǎo)致的茉唉,例如OSGi的出現(xiàn)固蛾。在OSGi環(huán)境下结执,類加載器不再是雙親委派模型中的樹(shù)狀結(jié)構(gòu),而是進(jìn)一步發(fā)展為網(wǎng)狀結(jié)構(gòu)艾凯。

疑問(wèn):

Q: 啟動(dòng)類加載器(Bootstrap ClassLoader):這個(gè)類加載器負(fù)責(zé)將放置在<JAVA_HOME>\lib目錄中的献幔,或者被-Xbootclasspath參數(shù)所指定路徑中的,并且是虛擬機(jī)能識(shí)別的(僅按照文件名識(shí)別趾诗,如rt.jar蜡感,名字不符合的類庫(kù)即使放置在lib目錄中也不會(huì)被加載)類庫(kù)加載到虛擬機(jī)內(nèi)存中.什么叫能被虛擬機(jī)識(shí)別的類庫(kù),都有哪些?

運(yùn)行時(shí)棧幀結(jié)構(gòu)

棧幀(Stack Frame)是用于支持虛擬機(jī)進(jìn)行方法調(diào)用和方法執(zhí)行的數(shù)據(jù)結(jié)構(gòu)恃泪,它是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的虛擬機(jī)棧(Virtual Machine Stack)的棧元素郑兴。棧幀存儲(chǔ)了方法的局部變量表,操作數(shù)棧贝乎,動(dòng)態(tài)連接和方法返回地址等信息情连。第一個(gè)方法從調(diào)用開(kāi)始到執(zhí)行完成,就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中從入棧到出棧的過(guò)程览效。每一個(gè)棧幀都包括了局部變量表却舀,操作數(shù)棧,動(dòng)態(tài)連接锤灿,方法返回地址和一些額外的附加信息禁筏。在編譯代碼的時(shí)候,棧幀中需要多大的局部變量表衡招,多深的操作數(shù)棧都已經(jīng)完全確定了篱昔,并且寫(xiě)入到了方法表的Code屬性中,因此一個(gè)棧幀需要分配多少內(nèi)存始腾,不會(huì)受到程序運(yùn)行期變量數(shù)據(jù)的影響州刽,而僅僅取決于具體虛擬機(jī)的實(shí)現(xiàn)。

一個(gè)線程中的方法調(diào)用鏈可能會(huì)很長(zhǎng)浪箭,很多方法都同時(shí)處理執(zhí)行狀態(tài)穗椅。對(duì)于執(zhí)行引擎來(lái)講,活動(dòng)線程中奶栖,只有虛擬機(jī)棧頂?shù)臈攀怯行У钠ケ恚Q為當(dāng)前棧幀(Current Stack Frame),這個(gè)棧幀所關(guān)聯(lián)的方法稱為當(dāng)前方法(Current Method)宣鄙。執(zhí)行引用所運(yùn)行的所有字節(jié)碼指令都只針對(duì)當(dāng)前棧幀進(jìn)行操作袍镀。棧幀的概念結(jié)構(gòu)如下圖所示:

image

1.局部變量表——存放方法參數(shù)和方法內(nèi)部定義的局部變量

局部變量表是一組變量值存儲(chǔ)空間,用于存放方法參數(shù)和方法內(nèi)部定義的局部變量冻晤。在Java程序編譯為Class文件時(shí)苇羡,就在方法表的Code屬性的max_locals數(shù)據(jù)項(xiàng)中確定了該方法需要分配的最大局部變量表的容量

在方法執(zhí)行時(shí)鼻弧,虛擬機(jī)是使用局部變量表完成參數(shù)變量列表的傳遞過(guò)程设江,如果是實(shí)例方法锦茁,那么局部變量表中的每0位索引的Slot默認(rèn)是用于傳遞方法所屬對(duì)象實(shí)例的引用,在方法中可以通過(guò)關(guān)鍵字“this”來(lái)訪問(wèn)這個(gè)隱含的參數(shù)叉存,其余參數(shù)則按照參數(shù)列表的順序來(lái)排列码俩,占用從1開(kāi)始的局部變量Slot,參數(shù)表分配完畢后歼捏,再根據(jù)方法體內(nèi)部定義的變量順序和作用域來(lái)分配其余的Slot握玛。局部變量表中的Slot是可重用的,方法體中定義的變量甫菠,其作用域并不一定會(huì)覆蓋整個(gè)方法,如果當(dāng)前字節(jié)碼PC計(jì)算器的值已經(jīng)超出了某個(gè)變量的作用域冕屯,那么這個(gè)變量對(duì)應(yīng)的Slot就可以交給其它變量使用寂诱。

局部變量不像前面介紹的類變量那樣存在“準(zhǔn)備階段”。類變量有兩次賦初始值的過(guò)程安聘,一次在準(zhǔn)備階段痰洒,賦予系統(tǒng)初始值;另外一次在初始化階段浴韭,賦予程序員定義的值丘喻。因此即使在初始化階段程序員沒(méi)有為類變量賦值也沒(méi)有關(guān)系,類變量仍然具有一個(gè)確定的初始值念颈。但局部變量就不一樣了泉粉,如果一個(gè)局部變量定義了但沒(méi)有賦初始值是不能使用的。

2.操作數(shù)椓穹迹——用于執(zhí)行運(yùn)算的后入先出棧

操作數(shù)棧也常被稱為操作棧嗡靡,它是一個(gè)后入先出棧。同局部變量表一樣窟感,操作數(shù)棧的最大深度也是編譯的時(shí)候被寫(xiě)入到方法表的Code屬性的max_stacks數(shù)據(jù)項(xiàng)中讨彼。操作數(shù)棧的每一個(gè)元素可以是任意Java數(shù)據(jù)類型,包括long和double柿祈。32位數(shù)據(jù)類型所占的棧容量為1哈误,64位數(shù)據(jù)類型所占的棧容量為2。棧容量的單位為“字寬”躏嚎,對(duì)于32位虛擬機(jī)來(lái)說(shuō)蜜自,一個(gè)”字寬“占4個(gè)字節(jié),對(duì)于64位虛擬機(jī)來(lái)說(shuō)卢佣,一個(gè)”字寬“占8個(gè)字節(jié)袁辈。

當(dāng)一個(gè)方法剛剛執(zhí)行的時(shí)候,這個(gè)方法的操作數(shù)棧是空的珠漂,在方法執(zhí)行的過(guò)程中晚缩,會(huì)有各種字節(jié)碼指向操作數(shù)棧中寫(xiě)入和提取值尾膊,也就是入棧與出棧操作。例如荞彼,在做算術(shù)運(yùn)算的時(shí)候就是通過(guò)操作數(shù)棧來(lái)進(jìn)行的冈敛,又或者調(diào)用其它方法的時(shí)候是通過(guò)操作數(shù)棧來(lái)進(jìn)行參數(shù)傳遞的。

另外鸣皂,在概念模型中抓谴,兩個(gè)棧幀作為虛擬機(jī)棧的元素,相互之間是完全獨(dú)立的寞缝,但是大多數(shù)虛擬機(jī)的實(shí)現(xiàn)里都會(huì)作一些優(yōu)化處理癌压,令兩個(gè)棧幀出現(xiàn)一部分重疊。讓下棧幀的部分操作數(shù)棧與上面棧幀的部分局部變量表重疊在一起荆陆,這樣在進(jìn)行方法調(diào)用返回時(shí)就可以共用一部分?jǐn)?shù)據(jù)滩届,而無(wú)須進(jìn)行額外的參數(shù)復(fù)制傳遞了,重疊過(guò)程如下圖:

image

3.動(dòng)態(tài)連接——保存棧幀所屬方法的引用

每個(gè)棧幀都包含一個(gè)指向運(yùn)行時(shí)常量池中該棧幀所屬方法的引用被啼,持有這個(gè)引用是為了支持方法調(diào)用過(guò)程中的動(dòng)態(tài)連接帜消。在Class文件的常量池中存有大量的符號(hào)引用,字節(jié)碼中的方法調(diào)用指令就以常量池中指向方法的符號(hào)引用為參數(shù)浓体。這些符號(hào)引用一部分會(huì)在類加載階段或第一次使用的時(shí)候轉(zhuǎn)化為直接引用泡挺,這種轉(zhuǎn)化稱為靜態(tài)解析。另外一部分將在每一次的運(yùn)行期期間轉(zhuǎn)化為直接引用命浴,這部分稱為動(dòng)態(tài)連接娄猫。

4.方法返回地址——調(diào)用方法的程序計(jì)數(shù)器的值作為返回地址

當(dāng)一個(gè)方法被執(zhí)行后,有兩種方式退出這個(gè)方法生闲。第一種方式是執(zhí)行引擎遇到任意一個(gè)方法返回的字節(jié)碼指令稚新,這時(shí)候可能會(huì)有返回值傳遞給上層的方法調(diào)用者(調(diào)用當(dāng)前方法的的方法稱為調(diào)用者),是否有返回值和返回值的類型將根據(jù)遇到何種方法返回指令來(lái)決定跪腹,這種退出方法方式稱為正常完成出口(Normal Method Invocation Completion)褂删。

另外一種退出方式是,在方法執(zhí)行過(guò)程中遇到了異常冲茸,并且這個(gè)異常沒(méi)有在方法體內(nèi)得到處理屯阀,無(wú)論是Java虛擬機(jī)內(nèi)部產(chǎn)生的異常,還是代碼中使用athrow字節(jié)碼指令產(chǎn)生的異常轴术,只要在本方法的異常表中沒(méi)有搜索到匹配的異常處理器难衰,就會(huì)導(dǎo)致方法退出,這種退出方式稱為異常完成出口(Abrupt Method Invocation Completion)逗栽。一個(gè)方法使用異常完成出口的方式退出盖袭,是不會(huì)給它的調(diào)用都產(chǎn)生任何返回值的。

無(wú)論采用何種方式退出,在方法退出之前鳄虱,都需要返回到方法被調(diào)用的位置弟塞,程序才能繼續(xù)執(zhí)行,方法返回時(shí)可能需要在棧幀中保存一些信息拙已,用來(lái)幫助恢復(fù)它的上層方法的執(zhí)行狀態(tài)决记。一般來(lái)說(shuō),方法正常退出時(shí)倍踪,調(diào)用者PC程序計(jì)數(shù)器的值就可以作為返回地址系宫,棧幀中很可能會(huì)保存這個(gè)計(jì)數(shù)器值。而方法異常退出時(shí)建车,返回地址是要通過(guò)異常處理器來(lái)確定的扩借,棧幀中一般不會(huì)保存這部分信息。

方法退出的過(guò)程實(shí)際上等同于把當(dāng)前棧幀出棧缤至,因此退出時(shí)可能執(zhí)行的操作有:恢復(fù)上層方法的局部變量表和操作數(shù)棧潮罪,把返回值(如果有的話)壓入調(diào)用都棧幀的操作數(shù)棧中,調(diào)用PC計(jì)數(shù)器的值以指向方法調(diào)用指令后面的一條指令等凄杯。

5. 附加信息

虛擬機(jī)規(guī)范允許具體的虛擬機(jī)實(shí)現(xiàn)增加一些規(guī)范里沒(méi)有描述的信息到棧幀中,例如與高度相關(guān)的信息秉宿,這部分信息完全取決于具體的虛擬機(jī)實(shí)現(xiàn)戒突。在實(shí)際開(kāi)發(fā)中,一般會(huì)把動(dòng)態(tài)連接描睦,方法返回地址與其它附加信息全部歸為一類膊存,稱為棧幀信息。

疑問(wèn):

Q: 32位數(shù)據(jù)類型所占的棧容量為1忱叭,64位數(shù)據(jù)類型所占的棧容量為2隔崎。棧容量的單位為“字寬”,對(duì)于32位虛擬機(jī)來(lái)說(shuō)韵丑,一個(gè)”字寬“占4個(gè)字節(jié)爵卒,對(duì)于64位虛擬機(jī)來(lái)說(shuō),一個(gè)”字寬“占8個(gè)字節(jié)撵彻。意思是說(shuō)钓株,如果是64位虛擬機(jī),對(duì)于像long這種數(shù)據(jù)類型陌僵,它的實(shí)際所占的棧容量為2轴合,則占有2* 8 =16個(gè)字節(jié)。是么碗短?那32位虛擬機(jī)是8個(gè)字節(jié)受葛?

Q: 每個(gè)棧幀都包含一個(gè)指向運(yùn)行時(shí)常量池中該棧幀所屬方法的引用,持有這個(gè)引用是為了支持方法調(diào)用過(guò)程中的動(dòng)態(tài)連接。也就是說(shuō)每個(gè)棧幀都會(huì)持有這個(gè)棧幀對(duì)應(yīng)調(diào)用方法的引用总滩,這樣在線程切換或者方法的連續(xù)調(diào)用中當(dāng)要切換或者調(diào)用到這個(gè)棧幀時(shí)可以根據(jù)程序計(jì)數(shù)器(偏移量)結(jié)合這個(gè)引用纲堵,再次找到這個(gè)方法在內(nèi)存中上次執(zhí)行到的位置,繼續(xù)執(zhí)行代碼咳秉。 是這樣么婉支?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市澜建,隨后出現(xiàn)的幾起案子向挖,更是在濱河造成了極大的恐慌,老刑警劉巖炕舵,帶你破解...
    沈念sama閱讀 212,816評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件何之,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡咽筋,警方通過(guò)查閱死者的電腦和手機(jī)溶推,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)晓淀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)窃蹋,“玉大人,你說(shuō)我怎么就攤上這事身冀《媚停” “怎么了辐赞?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,300評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)硝训。 經(jīng)常有香客問(wèn)我响委,道長(zhǎng),這世上最難降的妖魔是什么窖梁? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,780評(píng)論 1 285
  • 正文 為了忘掉前任赘风,我火速辦了婚禮,結(jié)果婚禮上纵刘,老公的妹妹穿的比我還像新娘邀窃。我一直安慰自己,他們只是感情好假哎,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布蛔翅。 她就那樣靜靜地躺著,像睡著了一般位谋。 火紅的嫁衣襯著肌膚如雪山析。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 50,084評(píng)論 1 291
  • 那天掏父,我揣著相機(jī)與錄音笋轨,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛爵政,可吹牛的內(nèi)容都是我干的仅讽。 我是一名探鬼主播,決...
    沈念sama閱讀 39,151評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼钾挟,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼洁灵!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起掺出,我...
    開(kāi)封第一講書(shū)人閱讀 37,912評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤徽千,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后汤锨,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體双抽,經(jīng)...
    沈念sama閱讀 44,355評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評(píng)論 2 327
  • 正文 我和宋清朗相戀三年闲礼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了牍汹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,809評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡柬泽,死狀恐怖慎菲,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情锨并,我是刑警寧澤露该,帶...
    沈念sama閱讀 34,504評(píng)論 4 334
  • 正文 年R本政府宣布,位于F島的核電站琳疏,受9級(jí)特大地震影響有决,放射性物質(zhì)發(fā)生泄漏闸拿。R本人自食惡果不足惜空盼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望新荤。 院中可真熱鬧揽趾,春花似錦、人聲如沸苛骨。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)痒芝。三九已至俐筋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間严衬,已是汗流浹背澄者。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,121評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人粱挡。 一個(gè)月前我還...
    沈念sama閱讀 46,628評(píng)論 2 362
  • 正文 我出身青樓赠幕,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親询筏。 傳聞我的和親對(duì)象是個(gè)殘疾皇子榕堰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評(píng)論 2 351