深入java虛擬機課件_第1頁
深入java虛擬機課件_第2頁
深入java虛擬機課件_第3頁
深入java虛擬機課件_第4頁
深入java虛擬機課件_第5頁
已閱讀5頁,還剩41頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

【深入Java虛擬機】之一:Java內存區(qū)域與內存溢出

內存區(qū)域

Java虛擬機在執(zhí)行Java程序的過程中會把他所管理的內存劃分為若干個不同的數據區(qū)域。Java

虛擬機規(guī)范將JVM所管理的內存分為以下幾個運行時數據區(qū):程序計數器、Java虛擬機棧、本地

方法棧、Java堆、方法區(qū)。下面詳細闡述各數據區(qū)所存儲的數據類型。

程序計數器(ProgramCounterRegister)

一塊較小的內存空間,它是當前線程所執(zhí)行的字節(jié)碼的行號指示器,字節(jié)碼解釋器工作時通過改

變該計數器的值來選擇下一條需要執(zhí)行的字節(jié)碼指令,分支、跳轉、循環(huán)等基礎功能都要依賴它來

實現。每條線程都有一個獨立的的程序計數器,各線程間的計數器互不影響,因此該區(qū)域是線程私

有的。

當線程在執(zhí)行一個Java方法時,該計數器記錄的是正在執(zhí)行的虛擬機字節(jié)碼指令的地址,當線

程在執(zhí)行的是Native方法(調用本地操作系統(tǒng)方法)時,該計數器的值為空。另外,該內存區(qū)域

是唯一一個在Java虛擬機規(guī)范中么有規(guī)定任何00M(內存溢出:OutOfMemoryError)情況的

區(qū)域。

Java虛擬機棧(JavaVirtualMachineStacks)

該區(qū)域也是線程私有的,它的生命周期也與線程相同。虛擬機棧描述的是Java方法執(zhí)行的內存模

型:每個方法被執(zhí)行的時候都會同時創(chuàng)建一個棧幀,棧它是用于支持續(xù)虛擬機進行方法調用和方法

執(zhí)行的數據結構。對于執(zhí)行引擎來講,活動線程中,只有棧頂的棧幀是有效的,稱為當前棧幀,這

個棧幀所關聯的方法稱為當前方法,執(zhí)行引擎所運行的所有字節(jié)碼指令都只針對當前棧幀進行操

作。棧幀用于存儲局部變量表、操作數棧、動態(tài)鏈接、方法返回地址和一些額外的附加信息。在編

譯程序代碼時,棧幀中需要多大的局部變量表、多深的操作數棧都已經完全確定了,并且寫入了方

法表的Code屬性之中。因此,一個棧幀需要分配多少內存,不會受到程序運行期變量數據的影響,

而僅僅取決于具體的虛擬機實現。

在Java虛擬機規(guī)范中,對這個區(qū)域規(guī)定了兩種異常情況:

1、如果線程請求的棧深度大于虛擬機所允許的深度,將拋出StackOverflowError異常。

2、如果虛擬機在動態(tài)擴展棧時無法申請到足夠的內存空間,則拋出OutOfMemoryError異常。

這兩種情況存在著一些互相重疊的地方:當??臻g無法繼續(xù)分配時,到底是內存太小,還是已使

用的??臻g太大,其本質上只是對同T牛事情的兩種描述而已。在單線程的操作中,無論是由于棧

幀太大,還是虛擬機??臻g太小,當??臻g無法分配時,虛擬機拋出的都是StackOverflowError

異常,而不會得到OutOfMemoryError異常。而在多線程環(huán)境下,則會拋出OutOfMemoryError

異常。

下面詳細說明棧幀中所存放的各部分信息的作用和數據紜構。

1、局部變量表

局部變量表是一組變量值存儲空間,用于存放方法參數和方法內部定義的局部變量,其中存放的

數據的類型是編譯期可知的各種基本數據類型、對象引用(reference)和returnAddress類型

(它指向了一條字節(jié)碼指令的地址)o局部變量表所需的內存空間在編譯期間完成分配,即在Java

程序被編譯成Class文件時,就確定了所需分配的最大局部變量表的容量。當進入一個方法時,這

個方法需要在棧中分配多大的局部變量空間是完全確定的,在方法運行期間不會改變局部變量表的

大小。

局部變量表的容量以變量槽(Slot)為最小單位。在虛擬機規(guī)范中并沒有明確指明一個Slot應占

用的內存空間大?。ㄔ试S其隨著處理器、操作系統(tǒng)或虛擬機的不同而發(fā)生變化),一個Slot可以存

放一個32位以內的數據類型:boolean、byte、char、short、int、floatreference和

returnAddresss。reference是對象的引用類型,returnAddress是為字節(jié)指令服務的,它執(zhí)行了

一條字節(jié)碼指令的地址。對丁64位的數據類型(long和double),虛擬機會以高位在前的方式

為其分配兩個連續(xù)的Slot空間。

虛擬機通過索引定位的方式使用局部變量表,索引值的范圍是從0開始到局部變量表最大的Slot

數量,對于32位數據類型的變量,索引n代表第n個Slot,對于64位的,索引n代表第n和第

兩個

n+1Sloto

在方法執(zhí)行時,虛擬機是使用局部變量表來完成參數值到參數變量列表的傳遞過程的,如果是實

例方法(非static),則局部變量表中的第0位索引的Slot默認是用于傳遞方法所屬對象實例的引

用,在方法中可以通過關鍵字"this”來訪問這個隱含的參數。其余參數則按照參數表的』礪來排

列,占用從1開始的局部變量Slot,參數表分配完畢后,再根據方法體內部定義的變量K序和作用

域分配其余的Slot.

局部變量表中的Slot是可重用的,方法體中定義的變量,作用域并不一定會覆蓋整個方法體,

如果當前字節(jié)碼PC計數器的值已經超過了某個變量的作用域,那么這個變量對應的Slot就可以交

給其他變量使用。這樣的設計不僅僅是為了節(jié)省空間,在某些情況下Slot的復用會直接影響到系

統(tǒng)的而垃圾收集行為。

2、操作數棧

操作數棧又常被稱為操作棧,操作數棧的最大深度也是在編譯的時候就確定了。32位數據類型

所占的找容量為1,64為數據類型所占的棧容量為2。當一個方法開始執(zhí)行時,它的操作棧是空的,

在方法的執(zhí)行過程中,會有各種字節(jié)碼指令(比如:加操作、賦值元算等)向操作棧中寫入和提取

內容,也就是入棧和出棧操作。

Java虛擬機的解釋執(zhí)行引擎稱為"基于棧的執(zhí)行引擎",其中所指的"棧"就是操作數棧。因

此我們也稱Java虛擬機是基于棧的,這點不同于Android虛擬機,Android虛擬機是基于寄存器

的。

基于棧的指令集最主要的優(yōu)點是可移植性強,主要的缺點是執(zhí)行速度相對會慢些;而由于寄存

器由硬件直接提供,所以基于寄存器指令集最主要的優(yōu)點是執(zhí)行速度快,主要的缺點是可移植性差。

3、動態(tài)連接

每個棧幀都包含一個指向運行時常量池(在方法區(qū)中,后面介紹)中該棧幀所屬方法的引用,持

有這個引用是為了支持方法調用過程中的動態(tài)連接。Gass文件的常量池中存在有大量的符號引用,

字節(jié)碼中的方法調用指令就以常量池中指向方法的符號引用為參數。這些符號引用,一部分會在類

加載階段或第一次使用的時候轉化為直接引用(如final、static域等),稱為靜態(tài)解析,另一部分

將在每一次的運行期間轉化為直接引用,這部分稱為動態(tài)連接。

4、方法返回地址

當一個方法被執(zhí)行后,有兩種方式退出該方法:執(zhí)行引擎遇到了任意一個方法返回的字節(jié)碼指令

或遇到了異常,并且該異常沒有在方法體內得到處理。無論采用何種退出方式,在方法退出之后,

都需要返回到方法被調用的位置,程序才能繼續(xù)執(zhí)行。方法返回時可能需要在棧幀中保存一些信息,

用來幫助恢復它的上層方法的執(zhí)行狀態(tài)。一般來說,方法正常退出時,調用者的PC計數器的值就

可以作為返回地址,棧幀中很可能保存了這個計數器值,而方法異常退出時,返回地址是要通過異

常處理器來確定的,棧幀中一般不會保存這部分信息。

方法退出的過程實際上等同于把當前棧幀出站,因此退出時可能執(zhí)行的操作有:恢復上層方法的

局部變量表和操作數棧,如果有返回值,則把它壓入調用者棧幀的操作數棧中,調整PC計數器的

值以指向方法調用指令后面的一條指令。

本地方法棧(NativeMethodStacks)

該區(qū)域與虛擬機棧所發(fā)揮的作用非常相似,只是虛擬機棧為虛擬機執(zhí)行Java方法服務,而本地

方法棧則為使用到的本地操作系統(tǒng)(Native)方法服務。

Java堆(JavaHeap)

JavaHeap是Java虛擬機所管理的內存中最大的一塊,它是所有線程共享的一塊內存區(qū)域。幾

乎所有的對象實例和數組都在這類分配內存。JavaHeap是垃圾收集器管理的主要區(qū)域,因此很多

時候也被稱為"GC堆"。

根據Java虛擬機規(guī)范的規(guī)定Java堆可以處在物理上不連續(xù)的內存空間中,只要邏輯上是連續(xù)

的即可。如果在堆中沒有內存可分配時,并且堆也無法擴展時,將會拋出OutOfMemoryError異

常。

方法區(qū)(MethodArea)

方法區(qū)也是各介線程共享的內存區(qū)域,它用于存儲已經被虛擬機加教的類信息、常量、靜態(tài)變量、

即時編譯器編譯后的代碼等數據。方法區(qū)域又被稱為“永久代”,但這僅僅對于SunHotSpot來

講,JRockit和IBMJ9虛擬機中并不存在永久代的概念。Java虛擬機規(guī)范把方法區(qū)描述為Java

堆的一個邏輯部分,而且它和JavaHeap一樣不需要連續(xù)的內存,可以選擇固定大小或可擴展,另

外,虛擬機規(guī)范允許該區(qū)域可以選擇不實現垃圾回收。相對而言,垃圾收集行為在這個區(qū)域比較少

出現。該區(qū)域的內存回收目標主要針是對廢棄常量的和無用類的回收。運行時常量池是方法區(qū)的一

部分,Class文件中除了有類的版本、字段、方法、接口等描達信息外,還有一項信息是常量池(Class

文件常量池),用于存放編譯器生成的各種字面量和符號引用,這部分內容將在類加載后存放到方

法區(qū)的運行時常量池中。運行時常量池相對于Class文件常量池的另一個重要特征是具備動態(tài)性,

Java語言并不要求常量一定只能在編譯期產生,也就是并非預置入Class文件中的常量池的內容才

能進入方法區(qū)的運行時常量池,運行期間也可能將新的常量放入池中,這種特性被開發(fā)人員利用比

較多的是String類的intern()方法。

根據Java虛擬機規(guī)范的規(guī)定,當方法區(qū)無法滿足內存分配需求時,將拋出OutOfMemoryError

異常。

直接內存(DirectMemory)

直接內存并不是虛擬機運行時數據區(qū)的一部分,也不是Java虛擬機規(guī)范中定義的內存區(qū)域,

它直接從操作系統(tǒng)中分配,因此不受Java堆大小的限制,但是會受到本機總內存的大小及處理器

尋址空間的限制,因此它也可能導致OutOfMemoryError異常出現。在JDK1.4中新引入了NIO

機制,它是一種基于通道與緩沖區(qū)的新I/O方式,可以直接從操作系統(tǒng)中分配直接內存,即在堆外

分配內存,這樣能在一些場景中提高性能,因為避免了在Java堆和Native堆中來回復制數據。關

于NIO的詳細使用可以參考我的Java網絡編程系列中關于NIO的相關文章。

內存溢出

下面給出個內存區(qū)域內存溢出的簡單測試方法

內存區(qū)域內存溢出的測試方法

無限循環(huán)地new對象出來,在List中保存引用,以不被垃圾收集器回

Java堆收.另外,該區(qū)域也有可能會發(fā)生內存泄露(MemoryLeak),

出現問題時,要注意區(qū)別。

生成大量的動態(tài)類,或無線循環(huán)調用String的intern()方法產生不

方法區(qū)同的String對象實例,并在List中保存其引用,以不被垃圾收集器回

收.后者測試常量池,前者測試方法區(qū)的非常量池部分。

單線程多線程

虛擬機棧和

遞歸調用一個簡單的方法:無線循環(huán)地創(chuàng)建線程,并未每個線

本地方法棧

如不斷累積的方法。程無限循環(huán)地增加內存。

會拋出StackOverflowError會拋出OutOfMemoryError

這里有一點要重點說明,在多線程情況下,給每個線程的棧分配的內存越大,反而越容易產生內

存溢出異常。操作系統(tǒng)為每個進程分配的內存是有限制的,虛擬機提供了參數來控制Java堆和方

法區(qū)這兩部分內存的最大值,忽略掉程序計數器消耗的內存(很小),以及進程本身消耗的內存,

剩下的內存便給了虛擬機棧和本地方法棧,每個線程分配到的梭容量越大,可以建立的線程數量自

然就越少。因此,如果是建立過多的線程導致的內存溢出,在不能減少線程數的情況下,就只能通

過減少最大堆和每個線程的棧容量來換取更多的線程。

另外,由于Java堆內也可能發(fā)生內存泄露(MemoryLeak),這里簡要說明一下內存泄露和

內存溢出的區(qū)別:

內存泄露是指分配出去的內存沒有被回收回來,由于失去了對該內存區(qū)域的控制,因而造成了

資源的浪費。Java中一般不會產生內存泄露,因為有垃圾回收器自動回收垃圾,但這也不絕對,

當我們new了對象,并保存了具引用,但是后面一直沒用它,而垃圾回收器又不會去回收它,這

邊會造成內存泄露

內存溢出是指程序所需要的內存超出了系統(tǒng)所能分配的內存(包括動態(tài)擴展)的上限。

對象實例化分析

對內存分配情況分析最常見的示例便是對象實例化:

Objectobj=newObjectQ;

這段代碼的執(zhí)行會涉及java棧、Java堆、方法區(qū)三個最重要的內存區(qū)域。假設該語句出現在方

法體中,及時對JVM虛擬機不了解的Java使用這,應該也知道obj會作為引用類型(reference)

的數據保存在Java棧的本地變量表中,而會在Java堆中保存該引用的實例化對象,但可能并不知

道,Java堆中還必須包含能查找到此對象類型數據的地址信息(如對象類型、父類、實現的接口、

方法等),這些類型數據則保存在方法區(qū)中。

另外,由于reference類型在Java虛擬機規(guī)范里面只規(guī)定了一個指向對象的引用,并沒有定義

這個引用應該通過哪種方式去定位,以及訪問到Java堆中的對象的具體位置,因此不同虛擬機實

現的對象訪問方式會有所不同,主流的訪問方式有兩種:使用句柄池和直接使用指針。

通過句柄池訪問的方式如下:

通過直接指針訪問的方式如下:

這兩種對象的訪問方式各有優(yōu)勢,使用句柄訪問方式的最大好處就是reference中存放的是穩(wěn)定

的句柄地址,在對象唄移動(垃圾收集時移動對象是非常普遍的行為)時只會改變句柄中的實例數

據指針,而reference本身不需要修改。使用直接指針訪問方式的最大好處是速度快,它節(jié)省了一

次指針定位的時間開銷。目前Java默認使用的HotSpot虛擬機采用的便是是第二種方式進行對象

訪問的。

【深入Java虛擬機】之二:Class類文件結構

平臺無關性

Java是與平臺無關的語言,這得益于Java源代碼編譯后生成的存儲字節(jié)碼的文件,即Class文

件,以及Java虛擬機的實現.不僅使用Java編譯器可以把Java代碼編譯成存儲字節(jié)碼的Class

文件使用JRuby等其他語言的編譯器也可以把程序代碼編譯成Qass文件虛擬機并不關心Class

的來源是什么語言,只要它符合一定的結構,就可以在Java中運行。Java語言中的各種變量、關

鍵字和運算符的語義最終都是由多條字節(jié)碼命令組合而成的,因此字節(jié)碼命令所能提供的語義描述

能力肯定會比Java語言本身更強大這便為其他語言實現一些有別于Java的語言特性提供了基礎,

而且這也正是在類加載時要進行安全驗證的原因。

類文件結構

Class文件是一組以8位字節(jié)為基礎單位的二進制流,各個數據項目嚴格按照順序緊湊地排列在

Class文件中,中間沒有添加任何J分隔符,這使得整個Class文件中存儲的內容幾乎全部都是程序

運行的必要數據。根據Java虛擬機規(guī)范的規(guī)定,Class文件格式采用一種類似于C語言結構體的偽

結構來存儲,這種偽結構中只有兩種數據類型:無符號數和表。無符號數屬于基本數據類型,以ul、

u2、u4、u8來分別代表1、2、4、8個字節(jié)的無符號數。表是由多個無符號數或其他表作為數據

項構成的符合數據類型,所有的表都習慣性地以"」nfo"結尾。

整個Class文件本質上就是一張表,它由如下所示的數據項構成。

從表中可以看出,無論是元符號數還是表,當需要描述同一類型但數量不定的多個數捱時,經常

會使用一個前置的容量計數器加若干個連續(xù)的該數據項的形式,稱這一系列連續(xù)的摸一個類型的數

據為某一類型的集合,比如,fields_count個fieldjnfo表數據構成了字段表集合。這里需要說明的

是:Class文件中的數據項,都是嚴格按照上表中的順序和數量被嚴格限定的,每個字節(jié)代表的含義,

長度,先后順序等都不允許改變。

下表列出了Class文件中各個數據項的具體含義:

類型名稱數量

U4magic1

U2minor_versior1

U2major_version1

U2constant_pool_count1

cpinfoconstant_poolconstant_pool_count-l

U2access_flags1

U2this_class1

U2superclass1

U2interfaces_count1

U2interfacesinterfaces_count

U2fields_count1

fiekiinfofieldsfields_count

U2methods_count1

methodjnfomethodsmethods_count

U2atrtributes_count1

attributeinfoamnburesatnribures_coum

從表中可以看出,無論是無符號數還是表,當需要描述同一類型但數量不定的多個數據時,經

常會在其前面使用一個前置的容量計數器來記錄其數量,而便跟著若干個連續(xù)的數據項,稱這一系

列連續(xù)的某一類型的數據為某一類型的集合,如:fields_count個field_info表數據便組成了方法

表集合。這里需要注意的是:Class文件中各數據項是按照上表的順序和數量被嚴格限定的,每個

字節(jié)代表的含義、長度、先后順序都不允許改變。

magic與version

每個Class文件的頭4個字節(jié)稱為魔數(magic),它的唯一作用是判斷該文件是否為一個能被

虛擬機接受的Class文件。它的值固定為OxCAFEBABE。緊接著magic的4個字節(jié)存儲的是Class

文件的次版本號和主版本號,高版本的JDK能向下兼容低版本的Class文件,但不能運行更高版本

的Class文件。

constant_pool

major_version之后是常量池(constant_pool)的入口,它是Class文件中與其他項目關聯最

多的數據類型,也是占用Class文件空間最大的數據項目之一。

常量池中主要存放兩大類常量:字面量和符號引用。字面量比較接近于Java層面的常量概念,

如文本字符串、被聲明為final的常量值等。而符號引用總結起來則包括了下面三類常量:

?類和接口的全限定名(即帶有包名的Class名,如:org.lxh.testTestClass)

?字段的名稱和描述符(private,static等描述符)

?方法的名稱和描述符(private,static等描述符)

虛擬機在加載Class文件時才會進行動態(tài)連接,也就是說,Class文件中不會保存各個方法和字

段的最終內存布局信息,因此,這些字段和方法的符號引用不經過轉換是無法直接被虛擬機使用的。

當虛擬機運行時,需要從常量池中獲得對應的符號引用,再在類加載過程中的解析階段將其替換為

直接引用,并翻譯到具體的內存地址中。

這里說明下符號引用和直接引用的區(qū)別與關聯:

?符號引用:符號引用以一組符號來描述所引用的目標,符號可以是任何形式的字面量,只

要使用時能無歧義地定位到目標即可。符號引用與虛擬機實現的內存布局無關,引用的目

標并不一定已經加載到了內存中.

?直接引用:直接引用可以是直接指向目標的指針、相對偏移量或是一個能間接定位到目標

的句柄。直接引用是與虛擬機實現的內存布局相關的,同一個符號引用在不同虛擬機實例

上翻譯出來的直接引用一般不會相同。如果有了直接引用,那說明引用的目標必定已經存

在于內存之中了。

常量池中的每一項常量者B是一個表,共有11種(JDK1.7之前)結構各不相同的表結構數據,

沒中表開始的第T立是一個ul類型的標志位(1-12,缺少2),代表當前這個常量屬于的常量類

型。11種常量類型所代表的具體含義如下表所示:

類型標志描述

CONSTANT_Utf8_info1UTF-8編碼的字符串

CONSTANTJntegerinfo3整性字面量

CONSTANT_Float_info二浮點型字面壁

CONSTANT_Long_info5長整形字面量

CONSTANTDoubleinfo6雙精度浮點型字面量

CONSTANTClassJnfO7類和接口的符號引用

CONSTANTStringinfo8字符串類型字面量

CONSTANT_Fieldref_info9字段的符號引用

CONSTANT_Methodref_info10類中方法的符號引用

CONSTANTJnterfaceMethodrefJnfo11接口中方法的符號引用

CONSTANTNameAndTypeinfo12字段或方法的部分符號引用

這11種常量類型各自均有自己的結構。仕CONSTANT_Class_info型常量的結構中有一項

namejndex屬性,該常屬性中存放一個索引值,指向常量池中一個CONSTANT_Utf8_info類型

的常量,該常量中即保存了該類的全限定名字符串。而CONSTANT_Fieldref_inf。、

CONSTANT_Methodref_infoxCONSTANTJnterfaceMethodrefJnfo型常量的結構中都有一項

index屬性,存放該字段或方法所屬的類或接口的描述符CONSTANT_Class_info的索引項。另外,

最終保存的諸如Class名、字段名、方法名、修飾符等字符串都是一個CONSTANT_Utf8_info類

型的常量,也因此,Java中方法和字段名的最大長度也即是CONSTANT_Utf8Jnfo型常量的最大

長度,在CONSTANT_Utf8_info型常量的結構中有一項length屬性,它是u2類型的,即占用2

個字節(jié),那么它的最大的length即為65535。因此,Java程序中如果定義了超過64KB英文字符

的變量或方法名,將會無法編譯。

下表給出了常量池中11種數據類型的結構:

常量項目類描述

tagu1值為1

CONSTANT_Utf8Jnfo

lengthu2UF-8編碼的字符串占用的字節(jié)數

bytesu1氏度為length的UTF-8編碼的字符用

tagu1值為3

CONSTANT^IntegerJnfo

bytesu4按照高位在前存儲的int值

tagU1值為4

CONSTANT_FloatJnfo

bytesu4按照高位在前存儲的float值

tagu1值為5

CONSTANTLonginfo

bytesu8按照高位在前存儲的long值

tagu1值為6

CONSTANT_DoubleJnfo

bytesu8按照高位在前存儲的double值

tagu1值為7

CONSTANT_Class_info

indexu2指向全限定名常量項的索引

tagu1值為8

CONSTANT_String_info

indexu2指向字符串字面量的索引

tagu1值為9

CONSTANT_Fieldref_info

indexU2指向聲明字段的類或接口描述符

CONSTANT_Class_info的索引項

indexu2指向字段名稱及類型描述符

CONSTANT_NamcAndTypc_info的索引項

tagu1值為10

CONSTANT_Mcthodrcf_info

indexu2指向聲明方法的類描述符

CONSTANT_Class_info的索引項

indexu2指向方法名稱及類型描述符

CONSTANT_NameAndType_info的索引項

tagu1值為11

CONSTANTInrerfaceMethcdrefJnfo

indexu2指向聲明方法的接口描述符

CONSTANT_Class_info的索引項

indexu2指向方法名稱及類型描述符

CONSTANT_NameAndType_info的索引項

tagu1值為12

CONSTANT_NameAndType_info

indexu2指向字段或方法名稱常量項目的索引

indexu2指向該字段或方法描述符常量項的索引

access_flag

在常量池結束之后,緊接著的2個字節(jié)代表訪問標志(accessJlag),這個標志用于識別一些

類或接口層次的訪問信息,包括:這個Class是類還是接口,是否定義為public類型,abstract類

型,如果是類的話,是否聲明為final,等等。每種訪問信息都由一個十六進制的標志值表示,如果

同時具有多種訪問信息,則得到的標志值為這幾種訪問信息的標志值的邏輯或。

this_class%superclass、interfaces

類索引(this_class)和父類索引(superclass)都是一個u2類型的數據,而接口索引集合

(interfaces)則是一組u2類型的數據集合,Class文件中由這三項數據來確定這個類的繼承關系。

類索引、父類索引和接口索引集合都按照順序排列在訪問標志之后,類索引和父類索引兩個u2類

型的索引值表示,它們各自指向一個類型為COMNSTANT_Class_info的類描述符常量,通過該常

量中的索引值找到定義在C0MNSTANT_Utf8」nf。類型的常量中的全限定名字符串。而接口索引

集合就用來描述這個類實現了哪些接口,這些被實現的接口將按implements語句(如果這個類本

身是個接口,則應當是extend語句)后的接口順序從左到右排列在接口的索引集合中。

fields

字段表(fieldjnfo)用于描述接口或類中聲明的變量。字段包括了類級變量或實例級變量,但

不包括在方法內聲明的變量。字段的名字、數據類型、修飾符等都是無法固定的,只能引用常量池

中的常量來描述。下面是字段表的最種格式:

類型名稱數量

u2access_flags1

u2nanie_index1

u2descriptor_index1

u2attributes_count1

Attribute_infoattributesattributes_count

其中的accessjlags與類中的access_flagsfei類似,是表示數據類型的修飾符,如public,static,

volatile等。后面的namejndex和descriptojindex都是對常量池的引用,分別代表字段的簡單

名稱及字段和方法的描述符。這里簡單解釋下"簡單名稱"、"描述符"和"全限定名”這三種特

殊字符串的概念。

前面有所提及,全限定名即指一個事物的完整的名稱,如在org.lxh.test包下的TestClass類的

全限定名為:org/lxh/test/TestClass,即把包名中的改為7”,為了使連續(xù)的多個全限定名

之間不產生混淆,在使用時最后一般會加入一個",”來表示全限定名結束。簡單名稱則是指沒有

類型或參數修飾的方法或字段名稱,如果一個類中有這樣一個方法booleanget(mtname)和

一個變量privatefinalstaticintm,則他們的簡單名稱則分別為get()和m。

而描述符的作用則是用來描述字段的數據類型、方法的參數列表(包括數量、類型以及順序等)

和返回值的。根據描述符規(guī)則,詳細的描述符標示字的含義如下表所示:

標示字符含義■

B基本類型byte

C基本類型char

s基本類型shot

I基本類型int

F基本類型float

D基本類型double

J基本類型long

Z基本類型boolean

V無返回值的void

L.如LorgIxhtestTestClass對象類型

對于數組類型,每一維度將使用一個前置的字符來描述,如一個整數數組"int口口”將為記

錄為"皿",而一個String類型的數組"String1]"將被記錄為”[Ljava/lang/String"

用方法描述符描述方法時,按照先參數后返回值的順序描述,參數要按照嚴格的II頁序放在一組小

括號內,如方法的描述符為

intgetIndex(Stringnamezchar[]tgcjntstartjntend,chartarget)

"(Ljava/lang/String[CIIC)f。

字段表包含的固定數據項目到descriptorjndex為止就結束了,但是在它之后還緊跟著一個屬

性表集合用于存儲一些額外的信息。比如,如果在類中有如下字段的聲明:staticfinalintm=2;

那就可能會存在一項名為Constantvalue的屬性,它指向常量2.關于attributejnfo的詳細內容,

在后面關于屬性表的項目中會有詳細介紹。

最后需要注意一點:字段表集合中不會列出從父類或接口中繼承而來的字段,但有可能列出原本

Java代碼中不存在的字段。比如在內部類中為了保持對外部類的訪問性,會自動添加指向外部類

實例的字段。

methods

方法表(methodjnfo)的結構與屬性表的結構相同,不過多贅述。方法里的Java代碼,經過

編譯器編譯成字節(jié)碼指令后,存放在方法屬性表集合中一個名為“Code”的屬性里,關于屬性表

的項目,同樣會在后面詳細介紹。

與字段表集合相對應,如果父類方法在子類中沒有被覆寫,方法表集合中就不會出現來自父類的

方法信息。但同樣,有可能會出現由編譯器自動添加的方法,最典型的便是類構造器"<clinit>"

方法和實例構造器方法。

在Java語言中,要重載一個方法,除了要與原方法具有相同的簡單名稱外,還要求必須擁有一

個與原方法不同的特征簽名,特征簽名就是一個方法中各個參數在常量池中的字段符號引用的集

合,也就是因為返回值不會包含在特征簽名之中,因此Java語言里無法僅僅依靠返回值的不同來

對一個已有方法進行重載。

attributes

屬性表(attributejnfo)在前面已經出現過多系,在Class文件、字段表、方法表中都可以攜

帶自己的屬性表集合,以用于描述某些場景專有的信息。

屬性表集合的限制沒有那么嚴格,不再要求各個屬性表具有嚴格的順序,并且只要不與已有的屬

性名重復,任何人實現的編譯器都可以向屬性表中寫入自己定義的屬性信息,但Java虛擬機運行

時會忽略掉它不認識的屬性。Java虛擬機規(guī)范中預定義了9項虛擬機應當能識別的屬性(JDK1.5

后又增加了一些新的特性,因此不止下面9項,但下面9項是最基本也是必要,出現頻率最高的),

如下表所示:

屬性名稱使用位置含義

Code方法表Java代碼澧譯成的字節(jié)碼指令

ConstantXalue字段表fnal關建字定義的常量值

Deprecated類、方法表、字段表被他羽為deprecated的方法和手段

Exceptions方法表方法拋出的異常

InnerClasses類文件內部類列表

LineNumberTableCode屬性Java源碼由行號與字節(jié)嗎指令間的對它關系

LocalVariableTableC。加理性方法的局部變量描述

SourceFile類文件源文件名稱

|標至類、方法或子段三是編譯器目單生成的

Synthetic類、且法表、字線表

對于每個屬性,它的名稱都需要從常量池中引用一個CONSTANT_Utf8_info類型的常量來表示,

每個屬性值的結構是完全可以自定義的,只需說明屬性值所占用的位數長度即可。一個符合規(guī)則的

屬性表至少應具有"attribute_name_info"、"attributejength"和至少一項信息屬性。

1)Code屬性

前面已經說過,Java程序方法體中的代碼講過Javac編譯后,生成的字節(jié)碼指令便會存儲在

Code屬性中,但并非所有的方法表都必須存在這個屬性,比如接口或抽象類中的方法就不存在

Code屬性。如果方法表有Code屬性存在,那么它的結構將如下表所示:

■類型名稱數量!■

u2attribute_name_indexi

u4attribute_lengthi

u2max_stacki

u2niax_localsi

u4code_lengthi

ulcodecodejength

u2exception_table_lenglh1

exception_infoexception_tableexception_table_length

u2attributes_count1

arrtibute_infoattributesattributes_count

attribute_name_index是一項指向CONSTANT_Utf8_info型常量的索弓|,常量值固定為

"Code”,它代表了該屬性的名稱。attribute」ength指示了屬性值的長度,由于屬性名稱索引

與屬性長度一共是6個字節(jié),所以屬性值的長度固定為整個屬性表的長度減去6個字節(jié)。

max_stack代表了操作數棧深度的最大值,max」ocals代表了局部變量表所需的存儲空間,它

的單位是Slot,并不是在方法中用到了多少個局部變量,就把這些局部變量所占Slot之和作為

maxjocals的值,原因是局部變量表中的Slot可以重用。

code」ength和code用來存儲Java源程序編譯后生成的字節(jié)碼指令。code用于存儲字節(jié)碼指

令的一系列字節(jié)流,它是U1類型的單字節(jié),因此取值范圍為0x00到OxFF,那么一共可以表達256

條指令,目前,Java虛擬機規(guī)范已經定義了其中200條編碼值而應的指令含義。code」ength雖然

是一個u4類型的長度值,理論上可以達到2A32-1,但是虛擬機規(guī)范中限制了一個方法不允許超過

65535條字節(jié)碼指令,如果超過了這個限制,Javac編譯器將會拒絕編譯。

類型名稱數量

u2start_pc1

u2end_pc1

u2handler_pc1

u2catch_pc1

字節(jié)碼指令之后是這個方法的顯式異常處理表集合(exceptionjable),它對于Code屬性來說

并不是必須存在的。它的格式如下表所示;

它包含四個字段,這些字段的含義為:如果字節(jié)碼從第start_pc行到第end_pc行之間(不含

end_pc行)出現了類型為catch.type或其子類的異常(catch_type為指向一個

CONSTANT_Class_info型常量的索引),則轉到第handler_pc行繼續(xù)處理,當catch_pc的值為

0時,代表人和的異常情況都要轉到handler_pc處進行處理。異常表實際上是Java代碼的一部分,

編譯器使用異常表而不是簡單的跳轉命令來實現Java異常即finally處理機制,也因此,finally

中的內容會在try或catch中的return語句之前執(zhí)行,并且在try或catch跳轉到finally之前,

會將其內部需要返回的變量的值復制一份副本到最后一個本地表量表的Slot中.Code屬性是

Class文件中最重要的一個屬性,如果把一個Java程序中的信息分為代碼和元數據兩部分,那么在

整個Class文件里,Code屬性用于描述代碼,所有的其他數據項目都用于描述元數據。

2)Exception屬性

這里的Exception屬性的作用是列舉出方法中可能拋出的受查異常,也就是方法描述時在

throws關鍵字后面列舉的異常。它的結構很簡單,只有attribute_name_indexxattribute」ength、

number_of_exceptionsxexception_index_table四項,從字面上便很容易理解,這里不再詳述。

3)LineNumberTable屬性

它用于描述Java源碼行號與字節(jié)碼行號之間的對應關系。

4)LocalVariableTable屬性

它用于描述棧幀中局部變量表中的變量與Java源碼中定義的變量之間的對應關系。

5)SourceFile屬性

它用于記錄生成這個Class文件的源碼文件名稱。

6)Constantvalue屬性

Constantvalue屬性的作用是通知虛擬機自動為靜態(tài)變量賦值,只有被static修飾的變量才可

以使用這項屬性。在Java中,對非static類型的變量(也就是實例變量)的賦值是在實例構造器

<init>方法中進行的;而對于類變量(static變量),則有兩種方式可以選擇:在類構造其中賦值,

或使用Constantvalue屬性賦值。

目前SunJavac編譯器的選擇是:如果同時使用final和static修飾一個變量(即全局常量),

并且這個變量的數據類型是基本類型或String的話,就生成ConstantValue屬性來進行初始做編

譯時Javac將會為該常量生成Constantvalue屬性,在類加載的準備階段虛擬機便會根據

Constantvalue為常量設置相應的值),如果該變量沒有被final修飾,或者并非基本類型及字符

串,則選擇在<clinit>方法中進行初始化。

雖然有final關鍵字才更符合"Constantvalue”的含義,但在虛擬機規(guī)范中并沒有強制要求字

段必須用final修飾,只要求了字段必須用static修飾,對final關鍵字的要求是Javac編譯器自己

加入的限制。因此,在實際的程序中,只有同時被final和static修飾的字段才有ConstantValue

屬性。而且ConstantValue的屬性值只限于基本類型和String,很明顯這是因為它從常量池中也

只能夠引用到基本類型和String類型的字面量。

下面簡要說明下final、static,staticfinal修飾的字段賦值的區(qū)別:

?static修飾的字段在類加載過程中的準備階段被初始化為0或null等默認值,而后在初始

化階段(觸發(fā)類構造器<dinit>)才會被賦予代碼中設定的值,如果沒有設定值,那么它的

值就為默認值。

?final修飾的字段在運行時被初始化(可以直接賦值,也可以在實例構造器中賦值),一旦

賦值便不可更改;

?staticfinal修飾的字:殳在Javac時生成ConstantValue屬性,在類加載的準備階段根據

ConstantValue的值為該字段賦值,它沒有默認值,必須顯式地賦值,否則Javac時會報

錯??梢岳斫鉃樵诰幾g期即把結果放入了常量池中。

7)InnerClasses屬性

該屬性用于記錄內部類與宿主類之間的關聯。如果一個類中定義了內部類,那么編譯器將會為它

及它所包含的內部類生成InnerClasses屬性。

8)Deprecated屬性和Synthetic屬性

該屬性用于表示某個類、字段和方法,已經被程序作者定為不再推薦使用,它可以通過在代碼中

使用@Deprecated注釋進行設置。

9)Synthetic屬性

該屬性代表此字段或方法并不是Java源代碼直接生成的,而是由編譯器自行添加的,如this字

段和實例構造器、類構造器等。

【深入Java虛擬機】之三:類初始化

類初始化是類加載過程的最后一個階段,到初始化階段,才真正開始執(zhí)行類中的Java程序代

碼。虛擬機規(guī)范嚴格規(guī)定了有且只有四種情況必須立即對類進行初始化:

?遇到new、getstatic,putstatic、invokestatic這四條字節(jié)碼指令時,如果類還沒有進行

過初始化,則需要先觸發(fā)其初始化。生成這四條指令最常見的Java代碼場景是:使用new

關鍵字實例化對象時、讀取或設置一個類的靜態(tài)字段(static)時

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論