第6章類(lèi)文件結(jié)構(gòu)
6.1 概述
6.2 無(wú)關(guān)性基石
6.3 Class類(lèi)文件的結(jié)構(gòu)
java虛擬機(jī)不和包括java在內(nèi)的任何語(yǔ)言綁定,它只與“Class文件”這種特定的二進(jìn)制文件格式所關(guān)聯(lián)蒸矛。Class文件中包含了Java虛擬機(jī)指令集和符號(hào)表以及若干其他輔助信息录粱。
Java語(yǔ)言中的各種變量徘郭、關(guān)鍵字和運(yùn)算符號(hào)的語(yǔ)義最終都是由多條字節(jié)碼命令組合而成的。
Class文件是一組以字節(jié)(8位二進(jìn)制/8bit)為基礎(chǔ)單位的二進(jìn)制流。
整個(gè)Class文件中存儲(chǔ)的內(nèi)容幾乎全部是程序運(yùn)行的必要數(shù)據(jù)慨菱。
Class文件格式采用偽結(jié)構(gòu)來(lái)存儲(chǔ)數(shù)據(jù),這種偽結(jié)構(gòu)只有兩種數(shù)據(jù)類(lèi)型:無(wú)符號(hào)數(shù)和表戴甩。
無(wú)符號(hào)數(shù)? u1符喝、u2、u4甜孤、u8分別代表1個(gè)字節(jié)协饲、2個(gè)字節(jié)畏腕。。茉稠。無(wú)符號(hào)數(shù)可以用來(lái)描述數(shù)字描馅、索引引用、數(shù)量值或者按照utf-8編碼構(gòu)成字符串值而线。
表是由多個(gè)無(wú)符號(hào)數(shù)或者其他表作為數(shù)據(jù)項(xiàng)構(gòu)成的符合數(shù)據(jù)類(lèi)型铭污。
整個(gè)Class文件本質(zhì)上就是一張表。
高版本的jdk能向下兼容以前版本的Class文件膀篮,但不能運(yùn)行以后版本的Class文件嘹狞,即使文件格式并未發(fā)生任何變化,虛擬機(jī)也必須拒絕執(zhí)行超過(guò)其版本號(hào)的Class文件誓竿。
6.3.2 常量池
常量池可以理解為Class文件之中的資源倉(cāng)庫(kù)磅网,它是Class文件結(jié)構(gòu)中與其他項(xiàng)目關(guān)聯(lián)最多的數(shù)據(jù)類(lèi)型,也是占用Class文件空間最大的數(shù)據(jù)項(xiàng)目之一烤黍。
常量池中主要存放兩大類(lèi)常量:字面量和符號(hào)引用知市。字面量比較接近于Java語(yǔ)言層面的常量概念,如文本字符串速蕊、聲明為final的常量值等嫂丙。而符號(hào)引用則屬于編譯原理方面的概念,包括了下面三類(lèi)常量:
1.類(lèi)和接口的全限定名
2.字段的名稱(chēng)和描述符(訪問(wèn)修飾符)
3.方法的名稱(chēng)和描述符
Class文件中不會(huì)保存各個(gè)方法规哲、字段的最終內(nèi)存布局信息跟啤,因此這些字段、方法的符號(hào)引用不經(jīng)過(guò)運(yùn)行期轉(zhuǎn)換的話無(wú)法得到真正的內(nèi)存入口地址唉锌,也就無(wú)法直接被虛擬機(jī)使用隅肥。當(dāng)虛擬機(jī)運(yùn)行時(shí),需要從常量池獲得對(duì)應(yīng)的符號(hào)引用袄简,再在類(lèi)創(chuàng)建時(shí)或運(yùn)行時(shí)解析腥放、翻譯到具體的內(nèi)存地址之中。
常量池中每一項(xiàng)常量都是一個(gè)表绿语。
Class文件中方法秃症、字段等都需要引用CONSTANT_Utf8_info型常量來(lái)描述名稱(chēng)。
6.3.3 訪問(wèn)標(biāo)志
在常量池結(jié)束之后吕粹,緊接著的兩個(gè)字節(jié)代表訪問(wèn)標(biāo)志(access_flags)种柑,這個(gè)標(biāo)志用于識(shí)別一些類(lèi)或者接口層次的訪問(wèn)信息,包括:這個(gè)Class是類(lèi)還是接口匹耕;是否定義為public類(lèi)型聚请;是否定義為abstract類(lèi)型;如果是類(lèi)的話稳其,是否被聲明為final等驶赏。
6.3.4 類(lèi)索引炸卑、父類(lèi)索引與接口索引集合
類(lèi)索引(this_class)和父類(lèi)索引(super_class)都是一個(gè)u2類(lèi)型的數(shù)據(jù),而接口索引集合(interfaces)是一組u2類(lèi)型的數(shù)據(jù)的集合煤傍。Class文件中由這三項(xiàng)數(shù)據(jù)來(lái)確定這個(gè)類(lèi)的繼承關(guān)系矾兜。類(lèi)索引用于確定這個(gè)類(lèi)的全限定名,父類(lèi)索引用于確定這個(gè)類(lèi)的父類(lèi)的全限定名患久。
6.3.5 字段表集合
字段表(field_info)用于描述接口或者類(lèi)中聲明的變量椅寺。字段包括類(lèi)級(jí)變量以及實(shí)例級(jí)變量,但不包括在方法內(nèi)部聲明的局部變量蒋失。
Java中描述一個(gè)字段可以包含什么信息返帕?字段的作用域(public、private篙挽、protected修飾符)荆萤、是實(shí)例變量還是類(lèi)變量(static修飾符)、可變性(final)铣卡、并發(fā)可見(jiàn)性(volatile修飾符链韭,是否強(qiáng)制從主內(nèi)存讀寫(xiě))、可否被序列化(transient修飾符)煮落、字段數(shù)據(jù)類(lèi)型(基本類(lèi)型敞峭、對(duì)象、數(shù)組)蝉仇、字段名稱(chēng)旋讹。上述這些信息中,各個(gè)修飾符都是布爾值轿衔,要么有某個(gè)修飾符沉迹,要么沒(méi)有,很適合使用標(biāo)志位來(lái)標(biāo)識(shí)害驹。而字段叫什么名字鞭呕、字段被定義為什么數(shù)據(jù)類(lèi)型,這些都是無(wú)法固定的宛官,只能引用常量池中的常量來(lái)描述葫松。
字段表集合中不會(huì)列出從超類(lèi)或者父接口中繼承而來(lái)的字段。
6.3.6 方法表集合
方法表的結(jié)構(gòu)如同字段表一樣摘刑,依次包含了訪問(wèn)標(biāo)志(access_flags)进宝、名稱(chēng)索引(name_index)刻坊、描述符索引(descriptor_index)枷恕、屬性表集合(attributes)幾項(xiàng)。
如果父類(lèi)方法在子類(lèi)中沒(méi)有被重寫(xiě)谭胚,方法表集合中就不會(huì)出現(xiàn)來(lái)自父類(lèi)的方法信息徐块。但是未玻,有可能會(huì)出現(xiàn)由編譯器自動(dòng)添加的方法,最典型的便是類(lèi)構(gòu)造器方法和實(shí)例構(gòu)造器方法胡控。
6.3.7 屬性表集合
1.Code屬性
Java程序方法體中的代碼經(jīng)過(guò)Javac編譯器處理后扳剿,最終變?yōu)樽止?jié)碼指令存儲(chǔ)在Code屬性?xún)?nèi)。Code屬性出現(xiàn)在方法表的屬性集合之中昼激。
Code屬性是Class文件中最重要的一個(gè)屬性庇绽。一個(gè)Java程序中的信息可以分為代碼(Code,方法體里面的Java代碼)和元數(shù)據(jù)(Metadata橙困,包括類(lèi)瞧掺、字段、方法定義以及其他信息)兩部分凡傅。
了解Code屬性是學(xué)習(xí)后面關(guān)于字節(jié)碼執(zhí)行引擎內(nèi)容的必要基礎(chǔ)辟狈,能直接閱讀字節(jié)碼也是工作中分析Java代碼語(yǔ)義問(wèn)題的必要工具和基本技能。
在任何方法里面夏跷,都可以通過(guò)“this”關(guān)鍵字訪問(wèn)到此方法所屬的對(duì)象哼转。這個(gè)機(jī)制的實(shí)現(xiàn),僅僅是通過(guò)Javac編譯器編譯的時(shí)候把對(duì)this關(guān)鍵字的訪問(wèn)轉(zhuǎn)變?yōu)閷?duì)一個(gè)普通方法參數(shù)的訪問(wèn)槽华,然后在虛擬機(jī)調(diào)用實(shí)例方法時(shí)自動(dòng)傳入此參數(shù)而已壹蔓。
6.ConstantValue屬性的作用是通知虛擬機(jī)自動(dòng)為靜態(tài)變量賦值。對(duì)于非static類(lèi)型的變量(也就是實(shí)例變量)的賦值是在實(shí)例構(gòu)造器<init>方法中進(jìn)行的;而對(duì)于類(lèi)變量猿推,則有兩種方式可以選擇:在類(lèi)構(gòu)造器<clinit>方法中或者使用ConstantValue屬
性秦踪。目前Sun Javac編譯器的選擇是:如果同時(shí)使用final和static來(lái)修飾一個(gè)變量(按照習(xí)慣,這里稱(chēng)“常量”更貼切)偏螺,并且這個(gè)變量的數(shù)據(jù)類(lèi)型是基本類(lèi)型或者java.lang.String的話,就生成ConstantValue屬性來(lái)進(jìn)行初始化匆光,如果這個(gè)變量沒(méi)有被final修飾套像,或者并非基本類(lèi)型及字符串,則將會(huì)選擇在<clinit>方法中進(jìn)行初始化终息。
7.InnerClasses屬性
InnerClasses屬性用于記錄內(nèi)部類(lèi)與宿主類(lèi)之間的關(guān)聯(lián)夺巩。如果一個(gè)類(lèi)中定義了內(nèi)部類(lèi),那編譯器將會(huì)為它以及它鎖包含的內(nèi)部類(lèi)生成InnerClasses屬性周崭。
6.4 字節(jié)碼指令簡(jiǎn)介
java虛擬機(jī)的指令由一個(gè)字節(jié)長(zhǎng)度的柳譬、代表著某種特定操作含義的數(shù)字(稱(chēng)為操作碼,Opcode)以及跟隨其后的零至多個(gè)代表此操作所需參數(shù)(稱(chēng)為操作數(shù)续镇,Operands)而構(gòu)成美澳。由于Java虛擬機(jī)采用面向操作數(shù)棧而不是寄存器的架構(gòu)(這兩種架構(gòu)的區(qū)別和影響將在第8章中探討),所以大多數(shù)的指令都不包含操作數(shù),只有一個(gè)操作碼制跟。
Java虛擬機(jī)操作碼的長(zhǎng)度為一個(gè)字節(jié)舅桩,所以指令集的操作碼總數(shù)不可能超過(guò)256條。
6.4.1 字節(jié)碼與數(shù)據(jù)類(lèi)型
6.4.2 加載和存儲(chǔ)指令
加載和存儲(chǔ)指令用于將數(shù)據(jù)在棧幀中的局部變量表和操作數(shù)棧之間來(lái)回傳輸雨膨。
1.將一個(gè)局部變量加載到操作棧:iload等load相關(guān)的指令擂涛;
2.將一個(gè)數(shù)值從操作數(shù)棧存儲(chǔ)到局部變量表:istore等相關(guān)的指令;
3.將一個(gè)常量加載到操作數(shù)棧:ipush等push等等
存儲(chǔ)數(shù)據(jù)的操作數(shù)棧和局部變量表主要就是由加載和存儲(chǔ)指令進(jìn)行操作聊记。
6.4.3 運(yùn)算指令
運(yùn)算或算術(shù)指令用于對(duì)兩個(gè)操作數(shù)棧上的值進(jìn)行某種特定運(yùn)算撒妈,并把結(jié)果重新存入到操作棧頂。
加法指令:iadd排监、ladd踩身、fadd、dadd社露。
減法指令:isub挟阻、lsub、fsub峭弟、dsub附鸽。
乘法指令:imul、lmul瞒瘸、fmul坷备、dmul。
除法指令:idiv情臭、ldiv省撑、fdiv、ddiv俯在。
求余指令:irem竟秫、lrem、frem跷乐、drem肥败。
取反指令:ineg、lneg愕提、fneg馒稍、dneg。
位移指令:ishl浅侨、ishr纽谒、iushr、lshl如输、lshr鼓黔、lushr央勒。
按位或指令:ior、lor请祖。
按位與指令:iand、land脖祈。
按位異或指令:ixor肆捕、lxor。
局部變量自增指令:iinc盖高。
比較指令:dcmpg慎陵、dcmpl、fcmpg喻奥、fcmpl席纽、lcmp。
6.4.4 類(lèi)型轉(zhuǎn)換指令
類(lèi)型轉(zhuǎn)換指令可以將兩種不同的數(shù)值類(lèi)型進(jìn)行相互轉(zhuǎn)換撞蚕,這些轉(zhuǎn)換操作一般用于實(shí)現(xiàn)用戶(hù)代碼中的顯示類(lèi)型轉(zhuǎn)換操作润梯。
窄化類(lèi)型轉(zhuǎn)換指令包括::i2b、i2c甥厦、i2s纺铭、l2i、f2i刀疙、f2l舶赔、d2i、d2l和d2f谦秧。
6.4.5 對(duì)象創(chuàng)建與訪問(wèn)指令
雖然類(lèi)實(shí)例和數(shù)組都是對(duì)象竟纳,但Java虛擬機(jī)對(duì)類(lèi)實(shí)例和數(shù)組的創(chuàng)建與操作使用了不同的字節(jié)碼指令。對(duì)象創(chuàng)建后疚鲤,就可
以通過(guò)對(duì)象訪問(wèn)指令獲取對(duì)象實(shí)例或者數(shù)組實(shí)例中的字段或者數(shù)組元素锥累。
創(chuàng)建類(lèi)實(shí)例的指令:new。
創(chuàng)建數(shù)組的指令:newarray集歇、anewarray揩悄、multianewarray。
訪問(wèn)類(lèi)字段和實(shí)例字段的指令:getfield鬼悠、putfield删性、getstatic、putstatic
把一個(gè)數(shù)組元素加載到操作數(shù)棧的指令:baload焕窝、caload蹬挺、saload、iaload它掂、laload巴帮、faload溯泣、daload、aaload榕茧。
將一個(gè)操作數(shù)棧的值存儲(chǔ)到數(shù)組元素中的指令:bastore垃沦、castore、sastore用押、iastore肢簿、fastore、dastore蜻拨、aastore池充。
取數(shù)組長(zhǎng)度的指令:arraylength。
檢查類(lèi)實(shí)例類(lèi)型的指令:instanceof缎讼、checkcast收夸。
6.4.6 操作數(shù)棧管理指令
將操作數(shù)棧的棧頂一個(gè)或兩個(gè)元素出棧:pop、pop2
復(fù)制棧頂一個(gè)或兩個(gè)數(shù)值并將復(fù)制值或雙份的復(fù)制值重新壓入棧頂:dup血崭、dup2卧惜、
將棧最頂端的兩個(gè)數(shù)值互換;swap
6.4.7 控制轉(zhuǎn)移指令
從概念模型上理解夹纫,可以認(rèn)為控制轉(zhuǎn)移指令就是在有條件或無(wú)條件地修改PC寄存器的值序苏。
6.4.8 方法調(diào)用和返回指令
invokevirtual指令用于調(diào)用對(duì)象的實(shí)例方法,根據(jù)對(duì)象的實(shí)際類(lèi)型進(jìn)行分派(虛方法分派)捷凄,這也是Java語(yǔ)言中最常見(jiàn)的方法分派方式忱详。
invokeinterface指令用于調(diào)用接口方法,它會(huì)在運(yùn)行時(shí)搜索一個(gè)實(shí)現(xiàn)了這個(gè)接口方法的對(duì)象跺涤,找出適合的方法進(jìn)行調(diào)用匈睁。
invokespecial指令用于調(diào)用一些需要特殊處理的實(shí)例方法,包括實(shí)例初始化方法桶错、私有方法和父類(lèi)方法航唆。
invokestatic指令用于調(diào)用類(lèi)方法(static方法)。
方法調(diào)用指令與數(shù)據(jù)類(lèi)型無(wú)關(guān)院刁,而方法返回指令是根據(jù)返回值的類(lèi)型區(qū)分的糯钙,包括ireturn(當(dāng)返回值是boolean、byte退腥、char任岸、short和int類(lèi)型時(shí)使用)、lreturn狡刘、freturn享潜、dreturn和areturn,另外還有一條return指令供聲明為void的方法嗅蔬、實(shí)例初始化方法以及類(lèi)和接口的類(lèi)初始化方法使用剑按。
6.4.9 異常處理指令
6.4.10 同步指令
Java虛擬機(jī)可以支持方法級(jí)的同步和方法內(nèi)部一段指令序列的同步疾就,這兩種同步結(jié)構(gòu)都是使用管程(Monitor)來(lái)支持的。
第8章虛擬機(jī)字節(jié)碼執(zhí)行引擎
8.1 概述
本章將主要從概念模型的角度來(lái)講解虛擬機(jī)的方法調(diào)用和字節(jié)碼執(zhí)行艺蝴。
8.2 運(yùn)行時(shí)棧幀結(jié)構(gòu)
棧幀是用于支持虛擬機(jī)進(jìn)行方法調(diào)用和方法執(zhí)行的數(shù)據(jù)結(jié)構(gòu)猬腰,它是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)中的虛擬機(jī)棧的棧元素。
每個(gè)方法從調(diào)用開(kāi)始至執(zhí)行完成的過(guò)程猜敢,都對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧里面從入棧到出棧的過(guò)程姑荷。
每一個(gè)棧幀都包含局部變量表、操作數(shù)棧锣枝、動(dòng)態(tài)連接厢拭、方法返回地址和一些額外的附加信息兰英。
8.2.1 局部變量表
局部變量表用于存放方法參數(shù)和方法內(nèi)部定義的局部變量撇叁。
局部變量表的容量以變量槽為最小單位。
Java中占用32位以?xún)?nèi)的數(shù)據(jù)類(lèi)型有boolean畦贸、byte陨闹、char、short薄坏、int趋厉、float、reference[3]和returnAddress
8種類(lèi)型胶坠。
reference類(lèi)型表示對(duì)一個(gè)對(duì)象實(shí)例的引用君账。虛擬機(jī)實(shí)現(xiàn)至少都應(yīng)當(dāng)能通過(guò)這個(gè)引用做到兩點(diǎn),一是從此引用中直接或間接地查找到對(duì)象在Java堆中的數(shù)據(jù)存放的其實(shí)地址索引沈善,二是此引用中直接或間接地查找到對(duì)象所屬數(shù)據(jù)類(lèi)型在方法區(qū)中的存儲(chǔ)的類(lèi)型信息乡数。
在方法執(zhí)行時(shí),虛擬機(jī)是使用局部變量表完成參數(shù)值到變量列表的傳遞過(guò)程的闻牡,如果執(zhí)行的是實(shí)例方法净赴,那局部變量表中第0位索引的Slot默認(rèn)是用于傳遞方法所屬對(duì)象實(shí)例的引用,在方法中可以通過(guò)關(guān)鍵字“this”來(lái)訪問(wèn)到這個(gè)隱含的參數(shù)罩润。其余參數(shù)則按照參數(shù)表順序排列玖翅,占用從1開(kāi)始的局部變量Slot,參數(shù)表分配完畢后割以,再根據(jù)方法體內(nèi)部定義的變量順序和作用域分配其余的Slot金度。
如果一個(gè)局部變量定義了但沒(méi)有初始值是不能使用的。
8.2.2 操作數(shù)棧
操作數(shù)棧(Operand Stack)也常稱(chēng)為操作棧严沥。操作數(shù)棧的每一個(gè)元素可以是任意的Java數(shù)據(jù)類(lèi)型审姓,包括long和double。32位數(shù)據(jù)類(lèi)型所占的棧容量為1祝峻,64位數(shù)據(jù)類(lèi)型所占的棧容量為2魔吐。
在做算術(shù)運(yùn)算的時(shí)候是通過(guò)操作數(shù)棧來(lái)進(jìn)行的扎筒,又或者在調(diào)用其他方法的時(shí)候是通過(guò)操作數(shù)棧來(lái)進(jìn)行參數(shù)傳遞的。
8.2.3 動(dòng)態(tài)連接
每個(gè)棧幀都包含了一個(gè)指向運(yùn)行時(shí)常量池中該棧幀所屬方法的引用酬姆,持有這個(gè)引用是為了支持方法調(diào)用過(guò)程中的動(dòng)態(tài)連接嗜桌。
字節(jié)碼中的方法調(diào)用指令就以常量池中指向方法的符號(hào)引用作為參數(shù)。這些符號(hào)引用一部分會(huì)在類(lèi)加載階段或者第一次使用的時(shí)候就轉(zhuǎn)化為直接引用辞色,這種轉(zhuǎn)化成為靜態(tài)解析骨宠。另外一部分將在每一次運(yùn)行期間轉(zhuǎn)化為直接引用,這部分成為動(dòng)態(tài)連接相满。
8.2.4 方法返回地址
方法返回有兩種情況:1.遇到return层亿。2.發(fā)生異常。
無(wú)論何種退出方式立美,在方法退出之后匿又,都需要返回到方法被調(diào)用的位置,程序才能繼續(xù)執(zhí)行建蹄。
方法退出的過(guò)程實(shí)際上就等同于把當(dāng)前棧幀出棧碌更,因此退出時(shí)可能執(zhí)行的操作有:恢復(fù)上層方法的局部變量表和操作數(shù)棧,把返回值壓入調(diào)用者棧幀的操作數(shù)占中洞慎,調(diào)整PC計(jì)數(shù)器的值以指向方法調(diào)用指令后面的一條指令等痛单。
8.2.5 附加信息
略
8.3 方法調(diào)用
Class文件的編譯過(guò)程中不包含傳統(tǒng)編譯中的連接步驟,一切方法調(diào)用在Class文件里面存儲(chǔ)的都只是符號(hào)引用劲腿,而不是方法在實(shí)際運(yùn)行時(shí)內(nèi)存布局中的入口地址(相當(dāng)于之前說(shuō)的直接引用)旭绒。
8.3.1 解析
在Java語(yǔ)言中符合“編譯器可知,運(yùn)行期不可變”這個(gè)要求的方法焦人,主要包括靜態(tài)方法和私有方法兩大類(lèi)挥吵,前者與類(lèi)型直接關(guān)聯(lián),后者在外部不可被訪問(wèn)垃瞧,這兩種方法各自的特點(diǎn)決定了它們都不可能通過(guò)繼承或別的方式重寫(xiě)其他版本蔫劣,因此它們都適合在類(lèi)的加載階段進(jìn)行解析。
為什么需要?jiǎng)討B(tài)連接个从,是為了滿(mǎn)足多態(tài)的需求脉幢。
8.3.2 分派
分派調(diào)用過(guò)程揭示多態(tài)。如“重載”和“重寫(xiě)”在Java虛擬機(jī)中是如何實(shí)現(xiàn)的嗦锐,虛擬機(jī)如何確定正確的目標(biāo)方法嫌松。
1.靜態(tài)分派
虛擬機(jī)(準(zhǔn)確地說(shuō)是編譯器)在重載時(shí)是通過(guò)參數(shù)的靜態(tài)類(lèi)型而不是實(shí)際類(lèi)行作為判定依據(jù)的。
2.動(dòng)態(tài)分派
動(dòng)態(tài)分派和多態(tài)性的另一個(gè)重要體現(xiàn)——重寫(xiě)有著很密切的關(guān)聯(lián)奕污。
第12章 Java內(nèi)存模型與線程
12.1 概述
12.2 硬件的效率與一致性
高速緩存解決了處理器與內(nèi)存的速度矛盾萎羔,但引入了一個(gè)新的問(wèn)題:緩存一致性(Cache Coherence)。在多處理器系統(tǒng)中碳默,每個(gè)處理器都有自己的高速緩存贾陷,而它們又共享同一主內(nèi)存(Main Memory)缘眶。
為了解決一致性的問(wèn)題,需要各個(gè)處理器訪問(wèn)緩存時(shí)都遵循一些協(xié)議髓废,在讀寫(xiě)時(shí)要根據(jù)協(xié)議來(lái)進(jìn)行操作巷懈。
在本章中將會(huì)多次提到的“內(nèi)存模型”一詞,可以理解為在特定的操作協(xié)議下慌洪,對(duì)特定的內(nèi)存或高速緩存進(jìn)行讀寫(xiě)訪問(wèn)的過(guò)程抽象顶燕。
12.3 Java內(nèi)存模型
12.3.1 主內(nèi)存與工作內(nèi)存
Java內(nèi)存模型的主要目標(biāo)是定義程序中各個(gè)變量的訪問(wèn)規(guī)則,即在虛擬機(jī)中將變量存儲(chǔ)到內(nèi)存和從內(nèi)存中取出變量這樣的底層細(xì)節(jié)冈爹。這里的變量指的是共享變量涌攻,如:實(shí)例字段、靜態(tài)字段和構(gòu)成數(shù)組對(duì)象的元素频伤。
Java內(nèi)存模型定義了所有的變量都存儲(chǔ)在主內(nèi)存中恳谎。每條線程還有自己的工作內(nèi)存,線程的工作內(nèi)存中保存了被該線程使用到的變量的主內(nèi)存副本拷貝剂买,線程對(duì)變量的所有操作(讀取惠爽、賦值等)都必須在工作內(nèi)存中進(jìn)行癌蓖,而不能直接讀寫(xiě)主內(nèi)存中的變量瞬哼。不同的線程之間也無(wú)法直接訪問(wèn)對(duì)方工作內(nèi)存中的變量,線程間變量值的傳遞均需要通過(guò)主內(nèi)存來(lái)完成租副。
12.3.2 內(nèi)存間交互操作
關(guān)于主內(nèi)存與工作內(nèi)存之間具體的交互協(xié)議坐慰,即一個(gè)變量如何從主內(nèi)存拷貝到工作內(nèi)存、如何從工作內(nèi)存同步回主內(nèi)存之類(lèi)的實(shí)現(xiàn)細(xì)節(jié)用僧,Java內(nèi)存模型中定義了一下8中操作來(lái)完成结胀,虛擬機(jī)實(shí)現(xiàn)時(shí)必須保證下面提及的每一種操作都是原子的、不可再分的责循。
1.lock(鎖定):作用于主內(nèi)存的變量糟港,它把一個(gè)變量標(biāo)識(shí)為一條線程獨(dú)占的狀態(tài)。
2.unlock(解鎖):把一個(gè)處于鎖定狀態(tài)的變量釋放出來(lái)院仿,釋放后的變量才可以被其他線程鎖定秸抚。
3.read(讀取):作用于主內(nèi)存的變量歹垫,它把一個(gè)變量的值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存中剥汤,以便隨后的load動(dòng)作使用。
4.load(載入):作用于工作內(nèi)存的變量排惨,它把read操作從主內(nèi)存中得到的變量值放入到工作內(nèi)存的變量副本中吭敢。
5.use(使用):作用于工作內(nèi)存的變量,它把工作內(nèi)存中的一個(gè)變量的值傳遞給執(zhí)行引擎暮芭,每當(dāng)虛擬機(jī)遇到一個(gè)需要使用到變量的字節(jié)碼指令時(shí)將會(huì)執(zhí)行這個(gè)操作鹿驼。
6.assign(賦值):作用于工作內(nèi)存的變量欲低,它把一個(gè)從執(zhí)行引擎接收到的值賦給工作內(nèi)存的變量,每當(dāng)虛擬機(jī)遇到一個(gè)給變量賦值的字節(jié)碼指令時(shí)執(zhí)行這個(gè)操作畜晰。
7.store(存儲(chǔ)):作用于工作內(nèi)存的變量伸头,它把工作內(nèi)存中一個(gè)變量的值傳送到主內(nèi)存中,以便隨后的write操作使用舷蟀。
8.write(寫(xiě)入):作用于主內(nèi)存的變量恤磷,它把store操作從工作內(nèi)存中得到的變量值放入主內(nèi)存的變量中。
Java內(nèi)存模型規(guī)定了在執(zhí)行上述8中基本操作時(shí)必須滿(mǎn)足如下規(guī)則:
1.不允許read和load野宜、store和wirte操作之一單獨(dú)出現(xiàn)扫步。
2.不允許一個(gè)線程丟棄它的最近的assign操作,即變量在工作內(nèi)存中改變了之后必須把該變化通不會(huì)主內(nèi)存匈子。
3.不允許一個(gè)線程無(wú)原因地(沒(méi)有發(fā)生過(guò)任何assign操作)把數(shù)據(jù)從線程的工作內(nèi)存同步會(huì)主內(nèi)存中河胎。
4.一個(gè)新的變量只能在主內(nèi)存中“誕生”,不允許在工作內(nèi)存中直接使用一個(gè)未被初始化(load或assign)的變量虎敦。換句話說(shuō)游岳,就是對(duì)一個(gè)變量實(shí)施use、store操作之前其徙,必須先執(zhí)行過(guò)了assign和load操作胚迫。
5.一個(gè)變量在同一時(shí)刻只允許一條線程對(duì)其進(jìn)行l(wèi)ock操作,但lock操作可以被同一條線程重復(fù)執(zhí)行多次唾那,多次執(zhí)行l(wèi)ock后访锻,只有執(zhí)行相同次數(shù)的unlock操作,變量才會(huì)被解鎖闹获。
6.如果對(duì)一個(gè)變量執(zhí)行l(wèi)ock操作期犬,那將會(huì)清空工作內(nèi)存中此變量的值,在執(zhí)行引擎使用這個(gè)變量前避诽,需要重新執(zhí)行l(wèi)oad或assign操作初始化變量的值龟虎。
7.如果一個(gè)變量事先沒(méi)有被lock操作鎖定,那就不允許對(duì)它執(zhí)行unlock操作沙庐,也不允許去unlock一個(gè)唄其他線程鎖定住的變量鲤妥。
8.對(duì)一個(gè)變量執(zhí)行unlock操作之前,必須先把此變量同步回主內(nèi)存中(執(zhí)行store轨功、write操作)旭斥。
12.3.3 對(duì)于volatile型變量的特殊規(guī)則
當(dāng)一個(gè)變量定義為volatile之后,它將具備兩種特性:
1.保證此變量對(duì)所有線程的可見(jiàn)性古涧。這里的“可見(jiàn)性”是指當(dāng)一條線程修改了這個(gè)變量的值垂券,新值對(duì)于其他線程來(lái)說(shuō)是可以立即得知的。
由于volatile變量只能保證可見(jiàn)性,在不符合以下兩條規(guī)則的運(yùn)算場(chǎng)景中菇爪,我們?nèi)匀灰ㄟ^(guò)加鎖(使用synchronized或java.util.concurrent中的原子類(lèi))來(lái)保證原子性算芯。
a.運(yùn)算結(jié)果并不依賴(lài)變量的當(dāng)前值,或者能夠確保只有單一的線程修改變量的值凳宙。
b.變量不需要與其他的狀態(tài)變量共同參與不變約束熙揍。
2.使用volatile變量會(huì)禁止指令重排序。
指令重排序無(wú)法越過(guò)內(nèi)存屏障氏涩。
volatile變量讀操作的性能消耗和普通變量幾乎沒(méi)有什么差別届囚,但是寫(xiě)操作則可能會(huì)慢一些,因?yàn)樗枰诒镜卮a中插入許多內(nèi)存屏障指令來(lái)保證處理器不發(fā)生亂序執(zhí)行是尖。
volatile的使用場(chǎng)景意系?
Java內(nèi)存模型中對(duì)volatile變量定義的特殊規(guī)則。假定T表示一個(gè)線程饺汹,V和W分別表示兩個(gè)volatile型變量蛔添,那么在進(jìn)行read、load兜辞、use迎瞧、assign、store和write操作時(shí)需要滿(mǎn)足如下規(guī)則:
1.只有當(dāng)線程T對(duì)變量V執(zhí)行的前一個(gè)動(dòng)作是load的時(shí)候逸吵,線程T才能對(duì)變量V執(zhí)行use動(dòng)作凶硅;并且,只有當(dāng)線程T對(duì)變量V執(zhí)行的后一個(gè)動(dòng)作是use的時(shí)候胁塞,線程T才能對(duì)變量V執(zhí)行l(wèi)oad動(dòng)作咏尝。線程T對(duì)變量V的use動(dòng)作可以認(rèn)為是和線程T變量V的load压语、read動(dòng)作相關(guān)聯(lián)啸罢,必須連續(xù)一起出現(xiàn)(這條規(guī)則要求在工作內(nèi)存中,每次使用V前都必須先從主內(nèi)存刷新最新的值胎食,用于保證能看見(jiàn)其他線程對(duì)變量V所做的修改后的值)扰才。
2.只有當(dāng)線程T對(duì)變量V行的前一個(gè)動(dòng)作是assign的時(shí)候,線程T才能對(duì)變量V執(zhí)行store動(dòng)作厕怜;并且衩匣,只有當(dāng)線程T對(duì)變量V執(zhí)行的后一個(gè)動(dòng)作是store的時(shí)候,線程T才能對(duì)變量V執(zhí)行assign動(dòng)作粥航。線程T對(duì)變量V的assign動(dòng)作可以認(rèn)為是和線程T對(duì)變量V的store琅捏、write動(dòng)作相關(guān)聯(lián),必須連續(xù)一起出現(xiàn)(這條規(guī)則要求在工作內(nèi)存中递雀,每次修改V后都必須立刻同步回主內(nèi)存中柄延,用于保證其他線程可以看到自己對(duì)變量所做的修改)。
3缀程、假定動(dòng)作A是線程T對(duì)變量V實(shí)施的use或assign動(dòng)作搜吧,假定動(dòng)作F是和動(dòng)作A相關(guān)聯(lián)的load或store動(dòng)作市俊,假定動(dòng)作P是和動(dòng)作F相應(yīng)的對(duì)變量V的read或write動(dòng)作;類(lèi)似的滤奈,假定動(dòng)作B是線程T對(duì)變量W實(shí)施的use或assign動(dòng)作摆昧,假定動(dòng)作G是和動(dòng)作B相關(guān)聯(lián)的load或store動(dòng)作,假定動(dòng)作Q是和動(dòng)作G相應(yīng)的對(duì)變量W的read或write動(dòng)作蜒程。如果A先于B解取,那么P先于Q(這條規(guī)則要求volatile修飾的變量不會(huì)被指令重排序優(yōu)化敞葛,保證代碼的執(zhí)行順序與程序的順序相同)。
12.3.5 原子性、可見(jiàn)性與有序性
Java內(nèi)存模型是圍繞著在并發(fā)過(guò)程中如何處理原子性扰柠、可見(jiàn)性和有序性3個(gè)特征來(lái)建立的。
可見(jiàn)性:
除了volatile将硝,synchronized和final可以實(shí)現(xiàn)可見(jiàn)性导匣。
同步快的可見(jiàn)性是由“對(duì)一個(gè)變量執(zhí)行unlock操作之前,必須先把此變量同步回主內(nèi)存中(執(zhí)行store和write操作)”這條規(guī)則獲得的驹吮。final的可見(jiàn)性是指:被final修飾的字段在構(gòu)造器中一旦初始化完成针史,并且構(gòu)造器沒(méi)有把“this”的引用傳遞出去,那在其他線程中就能看見(jiàn)final字段的值碟狞。
有序性:
Java程序中天然的有序性可以總結(jié)為一句話:如果在本線程內(nèi)觀察啄枕,所有的操作都是有序的;如果在一個(gè)線程中觀察另一個(gè)線程族沃,所有的操作都是無(wú)序的频祝。前半句是指“線程內(nèi)表現(xiàn)為串行的語(yǔ)義”(Within-Thread As-If-Serial Semantics),后半句是指“指令重排序”現(xiàn)象和“工作內(nèi)存與主內(nèi)存同步延遲”現(xiàn)象脆淹。
Java語(yǔ)言提供了volatile和synchronized兩個(gè)關(guān)鍵字來(lái)保證線程之間操作的有序性常空,volatile關(guān)鍵字本身就包含了禁止指令重排序的語(yǔ)義,而synchronized則是由“一個(gè)變量在同一個(gè)時(shí)刻只允許一條線程對(duì)其進(jìn)行l(wèi)ock操作”這條規(guī)則獲得的盖溺,這條規(guī)則決定了持有同一個(gè)鎖的兩個(gè)同步塊只能串行地進(jìn)入漓糙。
12.3.6 先行發(fā)生原則
如果說(shuō)操作A先行發(fā)生于操作B,其實(shí)就是說(shuō)在發(fā)生操作B之前烘嘱,操作A產(chǎn)生的影響能被操作B觀察到昆禽,“影響”包括修改了內(nèi)存中共享變量的值、發(fā)送了消息蝇庭、調(diào)用了方法等醉鳖。
Java內(nèi)存模型下一些“天然的”先行發(fā)生關(guān)系:
1.程序次序規(guī)則(Program Order
Rule):在一個(gè)線程內(nèi),按照程序代碼順序哮内,書(shū)寫(xiě)在前面的操作先行發(fā)生于書(shū)寫(xiě)在后面的操作盗棵。準(zhǔn)確地說(shuō),應(yīng)該是控制流順序而不是程序代碼順序,因?yàn)橐紤]分支漾根、循環(huán)等結(jié)構(gòu)泰涂。
2.管程鎖定規(guī)則(Monitor Lock
Rule):一個(gè)unlock操作先行發(fā)生于后面對(duì)同一個(gè)鎖的lock操作。這里必須強(qiáng)調(diào)的是同一個(gè)鎖辐怕,而“后面”是指時(shí)間上的先后順序逼蒙。
3.volatile變量規(guī)則:對(duì)一個(gè)volatile變量的寫(xiě)操作先行發(fā)生于后面對(duì)這個(gè)變量的讀操作,這里的“后面”同樣是指時(shí)間上的先后順序寄疏。
4.線程啟動(dòng)規(guī)則:Thread對(duì)象的start()方法先行發(fā)生于此線程的每一個(gè)動(dòng)作是牢。
5.線程終止規(guī)則:線程中的所有操作都先行發(fā)生于對(duì)此線程的終止檢測(cè),我們可以通過(guò)Thread.join()方法結(jié)束陕截、Thread.isAlive()的返回值等手段加測(cè)到線程已經(jīng)終止執(zhí)行驳棱。
6.線程中斷規(guī)則:對(duì)線程interrupt()方法的調(diào)用先發(fā)生于被中斷線程的代碼檢測(cè)到中斷事件的發(fā)生,可以通過(guò)Thread.interrupted()方法檢測(cè)到是否有中斷發(fā)生农曲。
7.對(duì)象終結(jié)規(guī)則:一個(gè)對(duì)象的初始化完成(構(gòu)造函數(shù)執(zhí)行結(jié)束)先行發(fā)生于它的finalize()方法的開(kāi)始社搅。
8.傳遞性:如果操作A先行發(fā)生于操作B,操作B先行發(fā)生于操作C乳规,那就可以得出操作A先行發(fā)生于操作C的結(jié)論形葬。
12.4 Java與線程
12.4.1 線程的實(shí)現(xiàn)
12.4.2 Java線程調(diào)度
線程調(diào)度是指系統(tǒng)為線程分配處理器使用權(quán)的過(guò)程,主要調(diào)度方式有兩種暮的,分別是協(xié)同式線程調(diào)度和搶占式調(diào)度笙以。
Java使用的是搶占式調(diào)度。每個(gè)線程將由系統(tǒng)來(lái)分配執(zhí)行時(shí)間冻辩,線程的切換不由線程本身來(lái)決定猖腕。
12.4.3 狀態(tài)轉(zhuǎn)換
第13章線程安全與鎖優(yōu)化
13.1 概述
13.2 線程安全
線程安全的代碼都必須具備一個(gè)特征:代碼本身封裝了所有必要的正確性保障手段,令調(diào)用者無(wú)須關(guān)心多想的問(wèn)題恨闪,更無(wú)須自己采取任何措施來(lái)保證多線程的正確調(diào)用倘感。
13.2.1 Java語(yǔ)言中的線程安全
線程安全的“安全程度”由強(qiáng)到弱,不可變凛剥、絕對(duì)線程安全侠仇、相對(duì)線程安全、線程兼容和線程對(duì)立犁珠。
1.不可變
不可變的對(duì)象一定是線程安全的。
2.絕對(duì)線程安全
3.相對(duì)線程安全
4.
13.2.2 線程安全的實(shí)現(xiàn)方法
1.互斥同步
同步是指在多線程并發(fā)訪問(wèn)共享數(shù)據(jù)時(shí)互亮,保證共享數(shù)據(jù)在同一個(gè)時(shí)刻只被一個(gè)(或者是一些犁享,使用信號(hào)量的時(shí)候)線程使用。而互斥是實(shí)現(xiàn)同步的一種手段豹休,臨界區(qū)炊昆、互斥量和信號(hào)量都是主要的互斥實(shí)現(xiàn)方式。
最基本的互斥同步手段就是synchronized關(guān)鍵字。
synchronized關(guān)鍵字經(jīng)過(guò)編譯之后凤巨,會(huì)在同步塊的前后分別形成monitorenter和monitorexit這兩個(gè)字節(jié)碼指令视乐,這兩個(gè)字節(jié)碼都需要一個(gè)reference類(lèi)型的參數(shù)來(lái)指明要鎖定和解鎖的對(duì)象。
除了synchronized敢茁,還有重入鎖ReentrantLock佑淀。相比synchronized,ReentrantLock增加了一些高級(jí)功能彰檬,有3項(xiàng):等待可中斷伸刃、可實(shí)現(xiàn)公平鎖、鎖可以綁定多個(gè)條件逢倍。
等待可中斷是指當(dāng)持有鎖的線程長(zhǎng)期不釋放鎖的時(shí)候捧颅,正在等待的線程可以選擇放棄等待,改為處理其他事情较雕,可中斷特性對(duì)處理執(zhí)行時(shí)間非常長(zhǎng)的同步快很有幫助碉哑。
公平鎖是指多個(gè)線程在等待同一個(gè)鎖時(shí),必須按照申請(qǐng)鎖的時(shí)間順序來(lái)依次獲得鎖
所綁定多個(gè)條件是指一個(gè)ReentrantLock對(duì)象可以同時(shí)綁定多個(gè)Condition對(duì)象亮蒋。
2.非阻塞同步
互斥同步也稱(chēng)為阻塞同步谭梗。主要的問(wèn)題就是進(jìn)行線程阻塞和喚醒所帶來(lái)的性能問(wèn)題。
互斥同步屬于一種悲觀的并發(fā)策略宛蚓。另外一種基于沖突檢測(cè)的樂(lè)觀并發(fā)策略激捏,就是先進(jìn)行操作,如果沒(méi)有其他線程爭(zhēng)用共享數(shù)據(jù)凄吏,那操作就成功了远舅;如果共享數(shù)據(jù)有爭(zhēng)用,產(chǎn)生了沖突痕钢,那就再采取其他的補(bǔ)償措施(最常見(jiàn)的補(bǔ)償措施就是不斷地重試图柏,直到成功為止),這個(gè)稱(chēng)謂非阻塞同步任连。
CAS精髓
3.無(wú)同步方案
如果一個(gè)方法本來(lái)就不涉及共享數(shù)據(jù)蚤吹,那它自然就無(wú)須任何同步措施。
兩種天生就是線程安全的代碼:
a.可重入代碼
b.線程本地存儲(chǔ):如果一段代碼中所需要的數(shù)據(jù)必須與其他代碼共享随抠,那就看看這些共享數(shù)據(jù)的代碼是否能保證在同一個(gè)線程中執(zhí)行裁着?如果能保證,我們就可以把共享數(shù)據(jù)的可見(jiàn)范圍限制在同一個(gè)線程之內(nèi)拱她。
如果一個(gè)變量要被某個(gè)線程獨(dú)享二驰,可以通過(guò)ThreadLocal類(lèi)來(lái)實(shí)現(xiàn)線程本地存儲(chǔ)。
13.3 鎖優(yōu)化
13.3.1 自旋鎖與自適應(yīng)自旋
有些情況下秉沼,共享數(shù)據(jù)的鎖定狀態(tài)只會(huì)持續(xù)很短的一段時(shí)間桶雀,為了這段時(shí)間去掛起會(huì)恢復(fù)線程并不值得矿酵。如果物理機(jī)器有一個(gè)以上的處理器,能讓兩個(gè)或以上的線程同時(shí)并行執(zhí)行矗积,我們就可以讓后面請(qǐng)求所的那個(gè)線程“稍等一下”全肮,但不放棄處理器的執(zhí)行時(shí)間,看看持有鎖的線程是否很快就會(huì)釋放鎖棘捣。為了讓線程等待辜腺,我們只需讓線程執(zhí)行一個(gè)忙循環(huán)(自旋),這項(xiàng)技術(shù)就是所謂的自旋鎖柱锹。
如果鎖貝占用的時(shí)間很短哪自,自旋等待的效果就會(huì)非常好,反之禁熏,如果鎖被占用的時(shí)間很長(zhǎng)壤巷,那么自旋的線程只會(huì)白白消耗處理器資源。因此瞧毙,自旋等待的時(shí)間必須要有一定的限度胧华,如果自旋超過(guò)了限定的次數(shù)仍然沒(méi)有成功獲得鎖,就應(yīng)當(dāng)使用傳統(tǒng)的方式去掛起線程了宙彪。自旋次數(shù)的默認(rèn)值是10次矩动。JDK1.6中引入了自適應(yīng)的自旋鎖。
13.3.2 鎖消除
鎖消除是指虛擬機(jī)即時(shí)編譯器在運(yùn)行時(shí)释漆,對(duì)一些代碼上要求同步悲没,但是被檢測(cè)到不可能存在共享數(shù)據(jù)競(jìng)爭(zhēng)的鎖進(jìn)行消除。鎖消除的主要判定依據(jù)來(lái)源于逃逸分析的數(shù)據(jù)支持(第11章已經(jīng)講解過(guò)逃逸分析技術(shù))男图,如果判斷在一段代碼中示姿,堆上的所有數(shù)據(jù)都不會(huì)逃逸出去從而被其他線程訪問(wèn)到,那就可以把它們當(dāng)做棧上數(shù)據(jù)對(duì)待逊笆,認(rèn)為它們是線程私有的栈戳,同步加鎖自然就無(wú)須進(jìn)行。
13.3.3 鎖粗化
如果一系列的連續(xù)操作都對(duì)同一個(gè)對(duì)象反復(fù)加鎖和解鎖难裆,甚至加鎖操作時(shí)出現(xiàn)在循環(huán)體中的子檀,那即使沒(méi)有線程競(jìng)爭(zhēng),頻繁地進(jìn)行互斥同步操作也會(huì)導(dǎo)致不必要的性能損耗乃戈。
如果虛擬機(jī)探測(cè)到有這樣一串零碎的操作都對(duì)同一個(gè)對(duì)象加鎖褂痰,將會(huì)把加鎖同步的范圍擴(kuò)展(粗化)到整個(gè)操作序列的外部。
13.3.4 輕量級(jí)鎖
對(duì)象頭的Mark Word是實(shí)現(xiàn)輕量級(jí)鎖和偏向鎖的關(guān)鍵偏化。
第6章類(lèi)文件結(jié)構(gòu)
6.1 概述
6.2 無(wú)關(guān)性基石
6.3 Class類(lèi)文件的結(jié)構(gòu)
java虛擬機(jī)不和包括java在內(nèi)的任何語(yǔ)言綁定脐恩,它只與“Class文件”這種特定的二進(jìn)制文件格式所關(guān)聯(lián)。Class文件中包含了Java虛擬機(jī)指令集和符號(hào)表以及若干其他輔助信息侦讨。
Java語(yǔ)言中的各種變量驶冒、關(guān)鍵字和運(yùn)算符號(hào)的語(yǔ)義最終都是由多條字節(jié)碼命令組合而成的。
Class文件是一組以字節(jié)(8位二進(jìn)制/8bit)為基礎(chǔ)單位的二進(jìn)制流韵卤。
整個(gè)Class文件中存儲(chǔ)的內(nèi)容幾乎全部是程序運(yùn)行的必要數(shù)據(jù)骗污。
Class文件格式采用偽結(jié)構(gòu)來(lái)存儲(chǔ)數(shù)據(jù),這種偽結(jié)構(gòu)只有兩種數(shù)據(jù)類(lèi)型:無(wú)符號(hào)數(shù)和表沈条。
無(wú)符號(hào)數(shù)? u1需忿、u2、u4蜡歹、u8分別代表1個(gè)字節(jié)屋厘、2個(gè)字節(jié)。月而。汗洒。無(wú)符號(hào)數(shù)可以用來(lái)描述數(shù)字、索引引用父款、數(shù)量值或者按照utf-8編碼構(gòu)成字符串值溢谤。
表是由多個(gè)無(wú)符號(hào)數(shù)或者其他表作為數(shù)據(jù)項(xiàng)構(gòu)成的符合數(shù)據(jù)類(lèi)型。
整個(gè)Class文件本質(zhì)上就是一張表憨攒。
高版本的jdk能向下兼容以前版本的Class文件世杀,但不能運(yùn)行以后版本的Class文件,即使文件格式并未發(fā)生任何變化肝集,虛擬機(jī)也必須拒絕執(zhí)行超過(guò)其版本號(hào)的Class文件瞻坝。
6.3.2 常量池
常量池可以理解為Class文件之中的資源倉(cāng)庫(kù),它是Class文件結(jié)構(gòu)中與其他項(xiàng)目關(guān)聯(lián)最多的數(shù)據(jù)類(lèi)型杏瞻,也是占用Class文件空間最大的數(shù)據(jù)項(xiàng)目之一所刀。
常量池中主要存放兩大類(lèi)常量:字面量和符號(hào)引用。字面量比較接近于Java語(yǔ)言層面的常量概念伐憾,如文本字符串勉痴、聲明為final的常量值等。而符號(hào)引用則屬于編譯原理方面的概念树肃,包括了下面三類(lèi)常量:
1.類(lèi)和接口的全限定名
2.字段的名稱(chēng)和描述符(訪問(wèn)修飾符)
3.方法的名稱(chēng)和描述符
Class文件中不會(huì)保存各個(gè)方法蒸矛、字段的最終內(nèi)存布局信息,因此這些字段胸嘴、方法的符號(hào)引用不經(jīng)過(guò)運(yùn)行期轉(zhuǎn)換的話無(wú)法得到真正的內(nèi)存入口地址雏掠,也就無(wú)法直接被虛擬機(jī)使用。當(dāng)虛擬機(jī)運(yùn)行時(shí)劣像,需要從常量池獲得對(duì)應(yīng)的符號(hào)引用乡话,再在類(lèi)創(chuàng)建時(shí)或運(yùn)行時(shí)解析、翻譯到具體的內(nèi)存地址之中耳奕。
常量池中每一項(xiàng)常量都是一個(gè)表绑青。
Class文件中方法诬像、字段等都需要引用CONSTANT_Utf8_info型常量來(lái)描述名稱(chēng)。
6.3.3 訪問(wèn)標(biāo)志
在常量池結(jié)束之后闸婴,緊接著的兩個(gè)字節(jié)代表訪問(wèn)標(biāo)志(access_flags)坏挠,這個(gè)標(biāo)志用于識(shí)別一些類(lèi)或者接口層次的訪問(wèn)信息,包括:這個(gè)Class是類(lèi)還是接口邪乍;是否定義為public類(lèi)型降狠;是否定義為abstract類(lèi)型;如果是類(lèi)的話庇楞,是否被聲明為final等榜配。
6.3.4 類(lèi)索引、父類(lèi)索引與接口索引集合
類(lèi)索引(this_class)和父類(lèi)索引(super_class)都是一個(gè)u2類(lèi)型的數(shù)據(jù)吕晌,而接口索引集合(interfaces)是一組u2類(lèi)型的數(shù)據(jù)的集合蛋褥。Class文件中由這三項(xiàng)數(shù)據(jù)來(lái)確定這個(gè)類(lèi)的繼承關(guān)系。類(lèi)索引用于確定這個(gè)類(lèi)的全限定名聂使,父類(lèi)索引用于確定這個(gè)類(lèi)的父類(lèi)的全限定名壁拉。
6.3.5 字段表集合
字段表(field_info)用于描述接口或者類(lèi)中聲明的變量。字段包括類(lèi)級(jí)變量以及實(shí)例級(jí)變量柏靶,但不包括在方法內(nèi)部聲明的局部變量弃理。
Java中描述一個(gè)字段可以包含什么信息?字段的作用域(public屎蜓、private痘昌、protected修飾符)、是實(shí)例變量還是類(lèi)變量(static修飾符)炬转、可變性(final)辆苔、并發(fā)可見(jiàn)性(volatile修飾符,是否強(qiáng)制從主內(nèi)存讀寫(xiě))扼劈、可否被序列化(transient修飾符)驻啤、字段數(shù)據(jù)類(lèi)型(基本類(lèi)型、對(duì)象荐吵、數(shù)組)骑冗、字段名稱(chēng)。上述這些信息中先煎,各個(gè)修飾符都是布爾值贼涩,要么有某個(gè)修飾符,要么沒(méi)有薯蝎,很適合使用標(biāo)志位來(lái)標(biāo)識(shí)遥倦。而字段叫什么名字、字段被定義為什么數(shù)據(jù)類(lèi)型占锯,這些都是無(wú)法固定的袒哥,只能引用常量池中的常量來(lái)描述缩筛。
字段表集合中不會(huì)列出從超類(lèi)或者父接口中繼承而來(lái)的字段。
6.3.6 方法表集合
方法表的結(jié)構(gòu)如同字段表一樣统诺,依次包含了訪問(wèn)標(biāo)志(access_flags)歪脏、名稱(chēng)索引(name_index)疑俭、描述符索引(descriptor_index)粮呢、屬性表集合(attributes)幾項(xiàng)。
如果父類(lèi)方法在子類(lèi)中沒(méi)有被重寫(xiě)钞艇,方法表集合中就不會(huì)出現(xiàn)來(lái)自父類(lèi)的方法信息啄寡。但是,有可能會(huì)出現(xiàn)由編譯器自動(dòng)添加的方法哩照,最典型的便是類(lèi)構(gòu)造器方法和實(shí)例構(gòu)造器方法挺物。
6.3.7 屬性表集合
1.Code屬性
Java程序方法體中的代碼經(jīng)過(guò)Javac編譯器處理后,最終變?yōu)樽止?jié)碼指令存儲(chǔ)在Code屬性?xún)?nèi)飘弧。Code屬性出現(xiàn)在方法表的屬性集合之中识藤。
Code屬性是Class文件中最重要的一個(gè)屬性。一個(gè)Java程序中的信息可以分為代碼(Code次伶,方法體里面的Java代碼)和元數(shù)據(jù)(Metadata痴昧,包括類(lèi)、字段冠王、方法定義以及其他信息)兩部分赶撰。
了解Code屬性是學(xué)習(xí)后面關(guān)于字節(jié)碼執(zhí)行引擎內(nèi)容的必要基礎(chǔ),能直接閱讀字節(jié)碼也是工作中分析Java代碼語(yǔ)義問(wèn)題的必要工具和基本技能柱彻。
在任何方法里面豪娜,都可以通過(guò)“this”關(guān)鍵字訪問(wèn)到此方法所屬的對(duì)象。這個(gè)機(jī)制的實(shí)現(xiàn)哟楷,僅僅是通過(guò)Javac編譯器編譯的時(shí)候把對(duì)this關(guān)鍵字的訪問(wèn)轉(zhuǎn)變?yōu)閷?duì)一個(gè)普通方法參數(shù)的訪問(wèn)瘤载,然后在虛擬機(jī)調(diào)用實(shí)例方法時(shí)自動(dòng)傳入此參數(shù)而已。
6.ConstantValue屬性的作用是通知虛擬機(jī)自動(dòng)為靜態(tài)變量賦值卖擅。對(duì)于非static類(lèi)型的變量(也就是實(shí)例變量)的賦值是在實(shí)例構(gòu)造器<init>方法中進(jìn)行的鸣奔;而對(duì)于類(lèi)變量,則有兩種方式可以選擇:在類(lèi)構(gòu)造器<clinit>方法中或者使用ConstantValue屬
性磨镶。目前Sun Javac編譯器的選擇是:如果同時(shí)使用final和static來(lái)修飾一個(gè)變量(按照習(xí)慣溃蔫,這里稱(chēng)“常量”更貼切),并且這個(gè)變量的數(shù)據(jù)類(lèi)型是基本類(lèi)型或者java.lang.String的話琳猫,就生成ConstantValue屬性來(lái)進(jìn)行初始化伟叛,如果這個(gè)變量沒(méi)有被final修飾,或者并非基本類(lèi)型及字符串脐嫂,則將會(huì)選擇在<clinit>方法中進(jìn)行初始化统刮。
7.InnerClasses屬性
InnerClasses屬性用于記錄內(nèi)部類(lèi)與宿主類(lèi)之間的關(guān)聯(lián)紊遵。如果一個(gè)類(lèi)中定義了內(nèi)部類(lèi),那編譯器將會(huì)為它以及它鎖包含的內(nèi)部類(lèi)生成InnerClasses屬性侥蒙。
6.4 字節(jié)碼指令簡(jiǎn)介
java虛擬機(jī)的指令由一個(gè)字節(jié)長(zhǎng)度的暗膜、代表著某種特定操作含義的數(shù)字(稱(chēng)為操作碼,Opcode)以及跟隨其后的零至多個(gè)代表此操作所需參數(shù)(稱(chēng)為操作數(shù)鞭衩,Operands)而構(gòu)成学搜。由于Java虛擬機(jī)采用面向操作數(shù)棧而不是寄存器的架構(gòu)(這兩種架構(gòu)的區(qū)別和影響將在第8章中探討),所以大多數(shù)的指令都不包含操作數(shù)论衍,只有一個(gè)操作碼瑞佩。
Java虛擬機(jī)操作碼的長(zhǎng)度為一個(gè)字節(jié),所以指令集的操作碼總數(shù)不可能超過(guò)256條坯台。
6.4.1 字節(jié)碼與數(shù)據(jù)類(lèi)型
6.4.2 加載和存儲(chǔ)指令
加載和存儲(chǔ)指令用于將數(shù)據(jù)在棧幀中的局部變量表和操作數(shù)棧之間來(lái)回傳輸炬丸。
1.將一個(gè)局部變量加載到操作棧:iload等load相關(guān)的指令;
2.將一個(gè)數(shù)值從操作數(shù)棧存儲(chǔ)到局部變量表:istore等相關(guān)的指令蜒蕾;
3.將一個(gè)常量加載到操作數(shù)棧:ipush等push等等
存儲(chǔ)數(shù)據(jù)的操作數(shù)棧和局部變量表主要就是由加載和存儲(chǔ)指令進(jìn)行操作稠炬。
6.4.3 運(yùn)算指令
運(yùn)算或算術(shù)指令用于對(duì)兩個(gè)操作數(shù)棧上的值進(jìn)行某種特定運(yùn)算,并把結(jié)果重新存入到操作棧頂咪啡。
加法指令:iadd首启、ladd、fadd瑟匆、dadd闽坡。
減法指令:isub、lsub愁溜、fsub疾嗅、dsub。
乘法指令:imul冕象、lmul代承、fmul、dmul渐扮。
除法指令:idiv论悴、ldiv、fdiv墓律、ddiv膀估。
求余指令:irem、lrem耻讽、frem察纯、drem。
取反指令:ineg、lneg饼记、fneg香伴、dneg。
位移指令:ishl具则、ishr即纲、iushr、lshl博肋、lshr低斋、lushr。
按位或指令:ior束昵、lor拔稳。
按位與指令:iand、land锹雏。
按位異或指令:ixor、lxor术奖。
局部變量自增指令:iinc礁遵。
比較指令:dcmpg、dcmpl采记、fcmpg佣耐、fcmpl、lcmp唧龄。
6.4.4 類(lèi)型轉(zhuǎn)換指令
類(lèi)型轉(zhuǎn)換指令可以將兩種不同的數(shù)值類(lèi)型進(jìn)行相互轉(zhuǎn)換兼砖,這些轉(zhuǎn)換操作一般用于實(shí)現(xiàn)用戶(hù)代碼中的顯示類(lèi)型轉(zhuǎn)換操作。
窄化類(lèi)型轉(zhuǎn)換指令包括::i2b既棺、i2c讽挟、i2s、l2i丸冕、f2i耽梅、f2l、d2i胖烛、d2l和d2f眼姐。
6.4.5 對(duì)象創(chuàng)建與訪問(wèn)指令
雖然類(lèi)實(shí)例和數(shù)組都是對(duì)象,但Java虛擬機(jī)對(duì)類(lèi)實(shí)例和數(shù)組的創(chuàng)建與操作使用了不同的字節(jié)碼指令佩番。對(duì)象創(chuàng)建后众旗,就可
以通過(guò)對(duì)象訪問(wèn)指令獲取對(duì)象實(shí)例或者數(shù)組實(shí)例中的字段或者數(shù)組元素。
創(chuàng)建類(lèi)實(shí)例的指令:new趟畏。
創(chuàng)建數(shù)組的指令:newarray贡歧、anewarray、multianewarray。
訪問(wèn)類(lèi)字段和實(shí)例字段的指令:getfield艘款、putfield持际、getstatic、putstatic
把一個(gè)數(shù)組元素加載到操作數(shù)棧的指令:baload哗咆、caload蜘欲、saload、iaload晌柬、laload姥份、faload、daload年碘、aaload澈歉。
將一個(gè)操作數(shù)棧的值存儲(chǔ)到數(shù)組元素中的指令:bastore、castore屿衅、sastore埃难、iastore、fastore涤久、dastore涡尘、aastore。
取數(shù)組長(zhǎng)度的指令:arraylength响迂。
檢查類(lèi)實(shí)例類(lèi)型的指令:instanceof考抄、checkcast。
6.4.6 操作數(shù)棧管理指令
將操作數(shù)棧的棧頂一個(gè)或兩個(gè)元素出棧:pop蔗彤、pop2
復(fù)制棧頂一個(gè)或兩個(gè)數(shù)值并將復(fù)制值或雙份的復(fù)制值重新壓入棧頂:dup川梅、dup2、
將棧最頂端的兩個(gè)數(shù)值互換然遏;swap
6.4.7 控制轉(zhuǎn)移指令
從概念模型上理解贫途,可以認(rèn)為控制轉(zhuǎn)移指令就是在有條件或無(wú)條件地修改PC寄存器的值。
6.4.8 方法調(diào)用和返回指令
invokevirtual指令用于調(diào)用對(duì)象的實(shí)例方法啦鸣,根據(jù)對(duì)象的實(shí)際類(lèi)型進(jìn)行分派(虛方法分派)潮饱,這也是Java語(yǔ)言中最常見(jiàn)的方法分派方式。
invokeinterface指令用于調(diào)用接口方法诫给,它會(huì)在運(yùn)行時(shí)搜索一個(gè)實(shí)現(xiàn)了這個(gè)接口方法的對(duì)象香拉,找出適合的方法進(jìn)行調(diào)用。
invokespecial指令用于調(diào)用一些需要特殊處理的實(shí)例方法中狂,包括實(shí)例初始化方法凫碌、私有方法和父類(lèi)方法。
invokestatic指令用于調(diào)用類(lèi)方法(static方法)胃榕。
方法調(diào)用指令與數(shù)據(jù)類(lèi)型無(wú)關(guān)盛险,而方法返回指令是根據(jù)返回值的類(lèi)型區(qū)分的瞄摊,包括ireturn(當(dāng)返回值是boolean、byte苦掘、char氛魁、short和int類(lèi)型時(shí)使用)肤频、lreturn找爱、freturn清蚀、dreturn和areturn,另外還有一條return指令供聲明為void的方法递瑰、實(shí)例初始化方法以及類(lèi)和接口的類(lèi)初始化方法使用祟牲。
6.4.9 異常處理指令
6.4.10 同步指令
Java虛擬機(jī)可以支持方法級(jí)的同步和方法內(nèi)部一段指令序列的同步,這兩種同步結(jié)構(gòu)都是使用管程(Monitor)來(lái)支持的抖部。
第8章虛擬機(jī)字節(jié)碼執(zhí)行引擎
8.1 概述
本章將主要從概念模型的角度來(lái)講解虛擬機(jī)的方法調(diào)用和字節(jié)碼執(zhí)行说贝。
8.2 運(yùn)行時(shí)棧幀結(jié)構(gòu)
棧幀是用于支持虛擬機(jī)進(jìn)行方法調(diào)用和方法執(zhí)行的數(shù)據(jù)結(jié)構(gòu),它是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)中的虛擬機(jī)棧的棧元素慎颗。
每個(gè)方法從調(diào)用開(kāi)始至執(zhí)行完成的過(guò)程乡恕,都對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧里面從入棧到出棧的過(guò)程。
每一個(gè)棧幀都包含局部變量表哗总、操作數(shù)棧几颜、動(dòng)態(tài)連接、方法返回地址和一些額外的附加信息讯屈。
8.2.1 局部變量表
局部變量表用于存放方法參數(shù)和方法內(nèi)部定義的局部變量。
局部變量表的容量以變量槽為最小單位县习。
Java中占用32位以?xún)?nèi)的數(shù)據(jù)類(lèi)型有boolean涮母、byte、char躁愿、short叛本、int、float彤钟、reference[3]和returnAddress
8種類(lèi)型来候。
reference類(lèi)型表示對(duì)一個(gè)對(duì)象實(shí)例的引用。虛擬機(jī)實(shí)現(xiàn)至少都應(yīng)當(dāng)能通過(guò)這個(gè)引用做到兩點(diǎn)逸雹,一是從此引用中直接或間接地查找到對(duì)象在Java堆中的數(shù)據(jù)存放的其實(shí)地址索引营搅,二是此引用中直接或間接地查找到對(duì)象所屬數(shù)據(jù)類(lèi)型在方法區(qū)中的存儲(chǔ)的類(lèi)型信息。
在方法執(zhí)行時(shí)梆砸,虛擬機(jī)是使用局部變量表完成參數(shù)值到變量列表的傳遞過(guò)程的转质,如果執(zhí)行的是實(shí)例方法,那局部變量表中第0位索引的Slot默認(rèn)是用于傳遞方法所屬對(duì)象實(shí)例的引用帖世,在方法中可以通過(guò)關(guān)鍵字“this”來(lái)訪問(wèn)到這個(gè)隱含的參數(shù)休蟹。其余參數(shù)則按照參數(shù)表順序排列,占用從1開(kāi)始的局部變量Slot,參數(shù)表分配完畢后赂弓,再根據(jù)方法體內(nèi)部定義的變量順序和作用域分配其余的Slot绑榴。
如果一個(gè)局部變量定義了但沒(méi)有初始值是不能使用的。
8.2.2 操作數(shù)棧
操作數(shù)棧(Operand Stack)也常稱(chēng)為操作棧盈魁。操作數(shù)棧的每一個(gè)元素可以是任意的Java數(shù)據(jù)類(lèi)型翔怎,包括long和double。32位數(shù)據(jù)類(lèi)型所占的棧容量為1备埃,64位數(shù)據(jù)類(lèi)型所占的棧容量為2姓惑。
在做算術(shù)運(yùn)算的時(shí)候是通過(guò)操作數(shù)棧來(lái)進(jìn)行的,又或者在調(diào)用其他方法的時(shí)候是通過(guò)操作數(shù)棧來(lái)進(jìn)行參數(shù)傳遞的按脚。
8.2.3 動(dòng)態(tài)連接
每個(gè)棧幀都包含了一個(gè)指向運(yùn)行時(shí)常量池中該棧幀所屬方法的引用于毙,持有這個(gè)引用是為了支持方法調(diào)用過(guò)程中的動(dòng)態(tài)連接。
字節(jié)碼中的方法調(diào)用指令就以常量池中指向方法的符號(hào)引用作為參數(shù)辅搬。這些符號(hào)引用一部分會(huì)在類(lèi)加載階段或者第一次使用的時(shí)候就轉(zhuǎn)化為直接引用唯沮,這種轉(zhuǎn)化成為靜態(tài)解析。另外一部分將在每一次運(yùn)行期間轉(zhuǎn)化為直接引用堪遂,這部分成為動(dòng)態(tài)連接介蛉。
8.2.4 方法返回地址
方法返回有兩種情況:1.遇到return。2.發(fā)生異常溶褪。
無(wú)論何種退出方式币旧,在方法退出之后,都需要返回到方法被調(diào)用的位置猿妈,程序才能繼續(xù)執(zhí)行吹菱。
方法退出的過(guò)程實(shí)際上就等同于把當(dāng)前棧幀出棧,因此退出時(shí)可能執(zhí)行的操作有:恢復(fù)上層方法的局部變量表和操作數(shù)棧彭则,把返回值壓入調(diào)用者棧幀的操作數(shù)占中鳍刷,調(diào)整PC計(jì)數(shù)器的值以指向方法調(diào)用指令后面的一條指令等。
8.2.5 附加信息
略
8.3 方法調(diào)用
Class文件的編譯過(guò)程中不包含傳統(tǒng)編譯中的連接步驟俯抖,一切方法調(diào)用在Class文件里面存儲(chǔ)的都只是符號(hào)引用输瓜,而不是方法在實(shí)際運(yùn)行時(shí)內(nèi)存布局中的入口地址(相當(dāng)于之前說(shuō)的直接引用)。
8.3.1 解析
在Java語(yǔ)言中符合“編譯器可知芬萍,運(yùn)行期不可變”這個(gè)要求的方法尤揣,主要包括靜態(tài)方法和私有方法兩大類(lèi),前者與類(lèi)型直接關(guān)聯(lián)担忧,后者在外部不可被訪問(wèn)芹缔,這兩種方法各自的特點(diǎn)決定了它們都不可能通過(guò)繼承或別的方式重寫(xiě)其他版本,因此它們都適合在類(lèi)的加載階段進(jìn)行解析瓶盛。
為什么需要?jiǎng)討B(tài)連接最欠,是為了滿(mǎn)足多態(tài)的需求示罗。
8.3.2 分派
分派調(diào)用過(guò)程揭示多態(tài)。如“重載”和“重寫(xiě)”在Java虛擬機(jī)中是如何實(shí)現(xiàn)的芝硬,虛擬機(jī)如何確定正確的目標(biāo)方法蚜点。
1.靜態(tài)分派
虛擬機(jī)(準(zhǔn)確地說(shuō)是編譯器)在重載時(shí)是通過(guò)參數(shù)的靜態(tài)類(lèi)型而不是實(shí)際類(lèi)行作為判定依據(jù)的。
2.動(dòng)態(tài)分派
動(dòng)態(tài)分派和多態(tài)性的另一個(gè)重要體現(xiàn)——重寫(xiě)有著很密切的關(guān)聯(lián)拌阴。
第12章 Java內(nèi)存模型與線程
12.1 概述
12.2 硬件的效率與一致性
高速緩存解決了處理器與內(nèi)存的速度矛盾绍绘,但引入了一個(gè)新的問(wèn)題:緩存一致性(Cache Coherence)。在多處理器系統(tǒng)中迟赃,每個(gè)處理器都有自己的高速緩存陪拘,而它們又共享同一主內(nèi)存(Main Memory)。
為了解決一致性的問(wèn)題纤壁,需要各個(gè)處理器訪問(wèn)緩存時(shí)都遵循一些協(xié)議左刽,在讀寫(xiě)時(shí)要根據(jù)協(xié)議來(lái)進(jìn)行操作。
在本章中將會(huì)多次提到的“內(nèi)存模型”一詞酌媒,可以理解為在特定的操作協(xié)議下欠痴,對(duì)特定的內(nèi)存或高速緩存進(jìn)行讀寫(xiě)訪問(wèn)的過(guò)程抽象。
12.3 Java內(nèi)存模型
12.3.1 主內(nèi)存與工作內(nèi)存
Java內(nèi)存模型的主要目標(biāo)是定義程序中各個(gè)變量的訪問(wèn)規(guī)則秒咨,即在虛擬機(jī)中將變量存儲(chǔ)到內(nèi)存和從內(nèi)存中取出變量這樣的底層細(xì)節(jié)喇辽。這里的變量指的是共享變量,如:實(shí)例字段雨席、靜態(tài)字段和構(gòu)成數(shù)組對(duì)象的元素菩咨。
Java內(nèi)存模型定義了所有的變量都存儲(chǔ)在主內(nèi)存中。每條線程還有自己的工作內(nèi)存陡厘,線程的工作內(nèi)存中保存了被該線程使用到的變量的主內(nèi)存副本拷貝旦委,線程對(duì)變量的所有操作(讀取、賦值等)都必須在工作內(nèi)存中進(jìn)行雏亚,而不能直接讀寫(xiě)主內(nèi)存中的變量。不同的線程之間也無(wú)法直接訪問(wèn)對(duì)方工作內(nèi)存中的變量摩钙,線程間變量值的傳遞均需要通過(guò)主內(nèi)存來(lái)完成罢低。
12.3.2 內(nèi)存間交互操作
關(guān)于主內(nèi)存與工作內(nèi)存之間具體的交互協(xié)議,即一個(gè)變量如何從主內(nèi)存拷貝到工作內(nèi)存胖笛、如何從工作內(nèi)存同步回主內(nèi)存之類(lèi)的實(shí)現(xiàn)細(xì)節(jié)网持,Java內(nèi)存模型中定義了一下8中操作來(lái)完成,虛擬機(jī)實(shí)現(xiàn)時(shí)必須保證下面提及的每一種操作都是原子的长踊、不可再分的功舀。
1.lock(鎖定):作用于主內(nèi)存的變量,它把一個(gè)變量標(biāo)識(shí)為一條線程獨(dú)占的狀態(tài)身弊。
2.unlock(解鎖):把一個(gè)處于鎖定狀態(tài)的變量釋放出來(lái)辟汰,釋放后的變量才可以被其他線程鎖定列敲。
3.read(讀取):作用于主內(nèi)存的變量帖汞,它把一個(gè)變量的值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存中戴而,以便隨后的load動(dòng)作使用。
4.load(載入):作用于工作內(nèi)存的變量翩蘸,它把read操作從主內(nèi)存中得到的變量值放入到工作內(nèi)存的變量副本中所意。
5.use(使用):作用于工作內(nèi)存的變量,它把工作內(nèi)存中的一個(gè)變量的值傳遞給執(zhí)行引擎催首,每當(dāng)虛擬機(jī)遇到一個(gè)需要使用到變量的字節(jié)碼指令時(shí)將會(huì)執(zhí)行這個(gè)操作扶踊。
6.assign(賦值):作用于工作內(nèi)存的變量,它把一個(gè)從執(zhí)行引擎接收到的值賦給工作內(nèi)存的變量郎任,每當(dāng)虛擬機(jī)遇到一個(gè)給變量賦值的字節(jié)碼指令時(shí)執(zhí)行這個(gè)操作秧耗。
7.store(存儲(chǔ)):作用于工作內(nèi)存的變量,它把工作內(nèi)存中一個(gè)變量的值傳送到主內(nèi)存中涝滴,以便隨后的write操作使用绣版。
8.write(寫(xiě)入):作用于主內(nèi)存的變量,它把store操作從工作內(nèi)存中得到的變量值放入主內(nèi)存的變量中歼疮。
Java內(nèi)存模型規(guī)定了在執(zhí)行上述8中基本操作時(shí)必須滿(mǎn)足如下規(guī)則:
1.不允許read和load杂抽、store和wirte操作之一單獨(dú)出現(xiàn)。
2.不允許一個(gè)線程丟棄它的最近的assign操作韩脏,即變量在工作內(nèi)存中改變了之后必須把該變化通不會(huì)主內(nèi)存缩麸。
3.不允許一個(gè)線程無(wú)原因地(沒(méi)有發(fā)生過(guò)任何assign操作)把數(shù)據(jù)從線程的工作內(nèi)存同步會(huì)主內(nèi)存中。
4.一個(gè)新的變量只能在主內(nèi)存中“誕生”赡矢,不允許在工作內(nèi)存中直接使用一個(gè)未被初始化(load或assign)的變量杭朱。換句話說(shuō),就是對(duì)一個(gè)變量實(shí)施use吹散、store操作之前弧械,必須先執(zhí)行過(guò)了assign和load操作。
5.一個(gè)變量在同一時(shí)刻只允許一條線程對(duì)其進(jìn)行l(wèi)ock操作空民,但lock操作可以被同一條線程重復(fù)執(zhí)行多次刃唐,多次執(zhí)行l(wèi)ock后,只有執(zhí)行相同次數(shù)的unlock操作界轩,變量才會(huì)被解鎖画饥。
6.如果對(duì)一個(gè)變量執(zhí)行l(wèi)ock操作,那將會(huì)清空工作內(nèi)存中此變量的值浊猾,在執(zhí)行引擎使用這個(gè)變量前抖甘,需要重新執(zhí)行l(wèi)oad或assign操作初始化變量的值。
7.如果一個(gè)變量事先沒(méi)有被lock操作鎖定葫慎,那就不允許對(duì)它執(zhí)行unlock操作衔彻,也不允許去unlock一個(gè)唄其他線程鎖定住的變量薇宠。
8.對(duì)一個(gè)變量執(zhí)行unlock操作之前,必須先把此變量同步回主內(nèi)存中(執(zhí)行store米奸、write操作)昼接。
12.3.3 對(duì)于volatile型變量的特殊規(guī)則
當(dāng)一個(gè)變量定義為volatile之后,它將具備兩種特性:
1.保證此變量對(duì)所有線程的可見(jiàn)性悴晰。這里的“可見(jiàn)性”是指當(dāng)一條線程修改了這個(gè)變量的值慢睡,新值對(duì)于其他線程來(lái)說(shuō)是可以立即得知的。
由于volatile變量只能保證可見(jiàn)性铡溪,在不符合以下兩條規(guī)則的運(yùn)算場(chǎng)景中漂辐,我們?nèi)匀灰ㄟ^(guò)加鎖(使用synchronized或java.util.concurrent中的原子類(lèi))來(lái)保證原子性。
a.運(yùn)算結(jié)果并不依賴(lài)變量的當(dāng)前值棕硫,或者能夠確保只有單一的線程修改變量的值髓涯。
b.變量不需要與其他的狀態(tài)變量共同參與不變約束。
2.使用volatile變量會(huì)禁止指令重排序哈扮。
指令重排序無(wú)法越過(guò)內(nèi)存屏障纬纪。
volatile變量讀操作的性能消耗和普通變量幾乎沒(méi)有什么差別,但是寫(xiě)操作則可能會(huì)慢一些滑肉,因?yàn)樗枰诒镜卮a中插入許多內(nèi)存屏障指令來(lái)保證處理器不發(fā)生亂序執(zhí)行包各。
volatile的使用場(chǎng)景?
Java內(nèi)存模型中對(duì)volatile變量定義的特殊規(guī)則靶庙。假定T表示一個(gè)線程问畅,V和W分別表示兩個(gè)volatile型變量,那么在進(jìn)行read六荒、load护姆、use、assign掏击、store和write操作時(shí)需要滿(mǎn)足如下規(guī)則:
1.只有當(dāng)線程T對(duì)變量V執(zhí)行的前一個(gè)動(dòng)作是load的時(shí)候卵皂,線程T才能對(duì)變量V執(zhí)行use動(dòng)作;并且砚亭,只有當(dāng)線程T對(duì)變量V執(zhí)行的后一個(gè)動(dòng)作是use的時(shí)候渐裂,線程T才能對(duì)變量V執(zhí)行l(wèi)oad動(dòng)作。線程T對(duì)變量V的use動(dòng)作可以認(rèn)為是和線程T變量V的load钠惩、read動(dòng)作相關(guān)聯(lián),必須連續(xù)一起出現(xiàn)(這條規(guī)則要求在工作內(nèi)存中族阅,每次使用V前都必須先從主內(nèi)存刷新最新的值篓跛,用于保證能看見(jiàn)其他線程對(duì)變量V所做的修改后的值)。
2.只有當(dāng)線程T對(duì)變量V行的前一個(gè)動(dòng)作是assign的時(shí)候坦刀,線程T才能對(duì)變量V執(zhí)行store動(dòng)作愧沟;并且蔬咬,只有當(dāng)線程T對(duì)變量V執(zhí)行的后一個(gè)動(dòng)作是store的時(shí)候,線程T才能對(duì)變量V執(zhí)行assign動(dòng)作沐寺。線程T對(duì)變量V的assign動(dòng)作可以認(rèn)為是和線程T對(duì)變量V的store林艘、write動(dòng)作相關(guān)聯(lián),必須連續(xù)一起出現(xiàn)(這條規(guī)則要求在工作內(nèi)存中混坞,每次修改V后都必須立刻同步回主內(nèi)存中狐援,用于保證其他線程可以看到自己對(duì)變量所做的修改)。
3究孕、假定動(dòng)作A是線程T對(duì)變量V實(shí)施的use或assign動(dòng)作啥酱,假定動(dòng)作F是和動(dòng)作A相關(guān)聯(lián)的load或store動(dòng)作,假定動(dòng)作P是和動(dòng)作F相應(yīng)的對(duì)變量V的read或write動(dòng)作厨诸;類(lèi)似的镶殷,假定動(dòng)作B是線程T對(duì)變量W實(shí)施的use或assign動(dòng)作,假定動(dòng)作G是和動(dòng)作B相關(guān)聯(lián)的load或store動(dòng)作微酬,假定動(dòng)作Q是和動(dòng)作G相應(yīng)的對(duì)變量W的read或write動(dòng)作绘趋。如果A先于B,那么P先于Q(這條規(guī)則要求volatile修飾的變量不會(huì)被指令重排序優(yōu)化颗管,保證代碼的執(zhí)行順序與程序的順序相同)陷遮。
12.3.5 原子性、可見(jiàn)性與有序性
Java內(nèi)存模型是圍繞著在并發(fā)過(guò)程中如何處理原子性忙上、可見(jiàn)性和有序性3個(gè)特征來(lái)建立的拷呆。
可見(jiàn)性:
除了volatile,synchronized和final可以實(shí)現(xiàn)可見(jiàn)性疫粥。
同步快的可見(jiàn)性是由“對(duì)一個(gè)變量執(zhí)行unlock操作之前茬斧,必須先把此變量同步回主內(nèi)存中(執(zhí)行store和write操作)”這條規(guī)則獲得的。final的可見(jiàn)性是指:被final修飾的字段在構(gòu)造器中一旦初始化完成梗逮,并且構(gòu)造器沒(méi)有把“this”的引用傳遞出去项秉,那在其他線程中就能看見(jiàn)final字段的值。
有序性:
Java程序中天然的有序性可以總結(jié)為一句話:如果在本線程內(nèi)觀察慷彤,所有的操作都是有序的娄蔼;如果在一個(gè)線程中觀察另一個(gè)線程,所有的操作都是無(wú)序的底哗。前半句是指“線程內(nèi)表現(xiàn)為串行的語(yǔ)義”(Within-Thread As-If-Serial Semantics)岁诉,后半句是指“指令重排序”現(xiàn)象和“工作內(nèi)存與主內(nèi)存同步延遲”現(xiàn)象。
Java語(yǔ)言提供了volatile和synchronized兩個(gè)關(guān)鍵字來(lái)保證線程之間操作的有序性跋选,volatile關(guān)鍵字本身就包含了禁止指令重排序的語(yǔ)義涕癣,而synchronized則是由“一個(gè)變量在同一個(gè)時(shí)刻只允許一條線程對(duì)其進(jìn)行l(wèi)ock操作”這條規(guī)則獲得的,這條規(guī)則決定了持有同一個(gè)鎖的兩個(gè)同步塊只能串行地進(jìn)入前标。
12.3.6 先行發(fā)生原則
如果說(shuō)操作A先行發(fā)生于操作B坠韩,其實(shí)就是說(shuō)在發(fā)生操作B之前距潘,操作A產(chǎn)生的影響能被操作B觀察到,“影響”包括修改了內(nèi)存中共享變量的值只搁、發(fā)送了消息音比、調(diào)用了方法等。
Java內(nèi)存模型下一些“天然的”先行發(fā)生關(guān)系:
1.程序次序規(guī)則(Program Order
Rule):在一個(gè)線程內(nèi)氢惋,按照程序代碼順序洞翩,書(shū)寫(xiě)在前面的操作先行發(fā)生于書(shū)寫(xiě)在后面的操作。準(zhǔn)確地說(shuō)明肮,應(yīng)該是控制流順序而不是程序代碼順序菱农,因?yàn)橐紤]分支、循環(huán)等結(jié)構(gòu)柿估。
2.管程鎖定規(guī)則(Monitor Lock
Rule):一個(gè)unlock操作先行發(fā)生于后面對(duì)同一個(gè)鎖的lock操作循未。這里必須強(qiáng)調(diào)的是同一個(gè)鎖,而“后面”是指時(shí)間上的先后順序秫舌。
3.volatile變量規(guī)則:對(duì)一個(gè)volatile變量的寫(xiě)操作先行發(fā)生于后面對(duì)這個(gè)變量的讀操作的妖,這里的“后面”同樣是指時(shí)間上的先后順序。
4.線程啟動(dòng)規(guī)則:Thread對(duì)象的start()方法先行發(fā)生于此線程的每一個(gè)動(dòng)作足陨。
5.線程終止規(guī)則:線程中的所有操作都先行發(fā)生于對(duì)此線程的終止檢測(cè)嫂粟,我們可以通過(guò)Thread.join()方法結(jié)束、Thread.isAlive()的返回值等手段加測(cè)到線程已經(jīng)終止執(zhí)行墨缘。
6.線程中斷規(guī)則:對(duì)線程interrupt()方法的調(diào)用先發(fā)生于被中斷線程的代碼檢測(cè)到中斷事件的發(fā)生星虹,可以通過(guò)Thread.interrupted()方法檢測(cè)到是否有中斷發(fā)生。
7.對(duì)象終結(jié)規(guī)則:一個(gè)對(duì)象的初始化完成(構(gòu)造函數(shù)執(zhí)行結(jié)束)先行發(fā)生于它的finalize()方法的開(kāi)始镊讼。
8.傳遞性:如果操作A先行發(fā)生于操作B宽涌,操作B先行發(fā)生于操作C,那就可以得出操作A先行發(fā)生于操作C的結(jié)論蝶棋。
12.4 Java與線程
12.4.1 線程的實(shí)現(xiàn)
12.4.2 Java線程調(diào)度
線程調(diào)度是指系統(tǒng)為線程分配處理器使用權(quán)的過(guò)程卸亮,主要調(diào)度方式有兩種,分別是協(xié)同式線程調(diào)度和搶占式調(diào)度玩裙。
Java使用的是搶占式調(diào)度兼贸。每個(gè)線程將由系統(tǒng)來(lái)分配執(zhí)行時(shí)間,線程的切換不由線程本身來(lái)決定吃溅。
12.4.3 狀態(tài)轉(zhuǎn)換
第13章線程安全與鎖優(yōu)化
13.1 概述
13.2 線程安全
線程安全的代碼都必須具備一個(gè)特征:代碼本身封裝了所有必要的正確性保障手段溶诞,令調(diào)用者無(wú)須關(guān)心多想的問(wèn)題,更無(wú)須自己采取任何措施來(lái)保證多線程的正確調(diào)用决侈。
13.2.1 Java語(yǔ)言中的線程安全
線程安全的“安全程度”由強(qiáng)到弱很澄,不可變、絕對(duì)線程安全、相對(duì)線程安全甩苛、線程兼容和線程對(duì)立。
1.不可變
不可變的對(duì)象一定是線程安全的俏站。
2.絕對(duì)線程安全
3.相對(duì)線程安全
4.
13.2.2 線程安全的實(shí)現(xiàn)方法
1讯蒲。互斥同步
同步是指在多線程并發(fā)訪問(wèn)共享數(shù)據(jù)時(shí)肄扎,保證共享數(shù)據(jù)在同一個(gè)時(shí)刻只被一個(gè)(或者是一些墨林,使用信號(hào)量的時(shí)候)線程使用。而互斥是實(shí)現(xiàn)同步的一種手段犯祠,臨界區(qū)旭等、互斥量和信號(hào)量都是主要的互斥實(shí)現(xiàn)方式。
最基本的互斥同步手段就是synchronized關(guān)鍵字衡载。
synchronized關(guān)鍵字經(jīng)過(guò)編譯之后搔耕,會(huì)在同步塊的前后分別形成monitorenter和monitorexit這兩個(gè)字節(jié)碼指令,這兩個(gè)字節(jié)碼都需要一個(gè)reference類(lèi)型的參數(shù)來(lái)指明要鎖定和解鎖的對(duì)象痰娱。
除了synchronized弃榨,還有重入鎖ReentrantLock。相比synchronized梨睁,ReentrantLock增加了一些高級(jí)功能鲸睛,有3項(xiàng):等待可中斷、可實(shí)現(xiàn)公平鎖坡贺、鎖可以綁定多個(gè)條件官辈。
等待可中斷是指當(dāng)持有鎖的線程長(zhǎng)期不釋放鎖的時(shí)候,正在等待的線程可以選擇放棄等待遍坟,改為處理其他事情拳亿,可中斷特性對(duì)處理執(zhí)行時(shí)間非常長(zhǎng)的同步快很有幫助。
公平鎖是指多個(gè)線程在等待同一個(gè)鎖時(shí)政鼠,必須按照申請(qǐng)鎖的時(shí)間順序來(lái)依次獲得鎖
所綁定多個(gè)條件是指一個(gè)ReentrantLock對(duì)象可以同時(shí)綁定多個(gè)Condition對(duì)象风瘦。
2.非阻塞同步
互斥同步也稱(chēng)為阻塞同步。主要的問(wèn)題就是進(jìn)行線程阻塞和喚醒所帶來(lái)的性能問(wèn)題公般。
互斥同步屬于一種悲觀的并發(fā)策略万搔。另外一種基于沖突檢測(cè)的樂(lè)觀并發(fā)策略,就是先進(jìn)行操作官帘,如果沒(méi)有其他線程爭(zhēng)用共享數(shù)據(jù)瞬雹,那操作就成功了;如果共享數(shù)據(jù)有爭(zhēng)用刽虹,產(chǎn)生了沖突酗捌,那就再采取其他的補(bǔ)償措施(最常見(jiàn)的補(bǔ)償措施就是不斷地重試,直到成功為止),這個(gè)稱(chēng)謂非阻塞同步胖缤。
CAS精髓
3.無(wú)同步方案
如果一個(gè)方法本來(lái)就不涉及共享數(shù)據(jù)尚镰,那它自然就無(wú)須任何同步措施。
兩種天生就是線程安全的代碼:
a.可重入代碼
b.線程本地存儲(chǔ):如果一段代碼中所需要的數(shù)據(jù)必須與其他代碼共享哪廓,那就看看這些共享數(shù)據(jù)的代碼是否能保證在同一個(gè)線程中執(zhí)行狗唉?如果能保證,我們就可以把共享數(shù)據(jù)的可見(jiàn)范圍限制在同一個(gè)線程之內(nèi)涡真。
如果一個(gè)變量要被某個(gè)線程獨(dú)享分俯,可以通過(guò)ThreadLocal類(lèi)來(lái)實(shí)現(xiàn)線程本地存儲(chǔ)。
13.3 鎖優(yōu)化
13.3.1 自旋鎖與自適應(yīng)自旋
有些情況下哆料,共享數(shù)據(jù)的鎖定狀態(tài)只會(huì)持續(xù)很短的一段時(shí)間缸剪,為了這段時(shí)間去掛起會(huì)恢復(fù)線程并不值得。如果物理機(jī)器有一個(gè)以上的處理器东亦,能讓兩個(gè)或以上的線程同時(shí)并行執(zhí)行杏节,我們就可以讓后面請(qǐng)求所的那個(gè)線程“稍等一下”,但不放棄處理器的執(zhí)行時(shí)間讥此,看看持有鎖的線程是否很快就會(huì)釋放鎖拢锹。為了讓線程等待,我們只需讓線程執(zhí)行一個(gè)忙循環(huán)(自旋)萄喳,這項(xiàng)技術(shù)就是所謂的自旋鎖卒稳。
如果鎖貝占用的時(shí)間很短,自旋等待的效果就會(huì)非常好他巨,反之充坑,如果鎖被占用的時(shí)間很長(zhǎng),那么自旋的線程只會(huì)白白消耗處理器資源染突。因此捻爷,自旋等待的時(shí)間必須要有一定的限度,如果自旋超過(guò)了限定的次數(shù)仍然沒(méi)有成功獲得鎖份企,就應(yīng)當(dāng)使用傳統(tǒng)的方式去掛起線程了也榄。自旋次數(shù)的默認(rèn)值是10次。JDK1.6中引入了自適應(yīng)的自旋鎖司志。
13.3.2 鎖消除
鎖消除是指虛擬機(jī)即時(shí)編譯器在運(yùn)行時(shí)甜紫,對(duì)一些代碼上要求同步,但是被檢測(cè)到不可能存在共享數(shù)據(jù)競(jìng)爭(zhēng)的鎖進(jìn)行消除骂远。鎖消除的主要判定依據(jù)來(lái)源于逃逸分析的數(shù)據(jù)支持(第11章已經(jīng)講解過(guò)逃逸分析技術(shù))囚霸,如果判斷在一段代碼中,堆上的所有數(shù)據(jù)都不會(huì)逃逸出去從而被其他線程訪問(wèn)到激才,那就可以把它們當(dāng)做棧上數(shù)據(jù)對(duì)待拓型,認(rèn)為它們是線程私有的额嘿,同步加鎖自然就無(wú)須進(jìn)行。
13.3.3 鎖粗化
如果一系列的連續(xù)操作都對(duì)同一個(gè)對(duì)象反復(fù)加鎖和解鎖劣挫,甚至加鎖操作時(shí)出現(xiàn)在循環(huán)體中的册养,那即使沒(méi)有線程競(jìng)爭(zhēng),頻繁地進(jìn)行互斥同步操作也會(huì)導(dǎo)致不必要的性能損耗压固。
如果虛擬機(jī)探測(cè)到有這樣一串零碎的操作都對(duì)同一個(gè)對(duì)象加鎖捕儒,將會(huì)把加鎖同步的范圍擴(kuò)展(粗化)到整個(gè)操作序列的外部。
13.3.4 輕量級(jí)鎖
對(duì)象頭的Mark Word是實(shí)現(xiàn)輕量級(jí)鎖和偏向鎖的關(guān)鍵邓夕。