摘要:在屬性中,和分別用于存儲(chǔ)字節(jié)碼長(zhǎng)度和字節(jié)碼指令,每條指令即一個(gè)字節(jié)類(lèi)型。在虛擬機(jī)執(zhí)行時(shí),通過(guò)讀取中的一個(gè)個(gè)字節(jié)碼,并將字節(jié)碼翻譯成相應(yīng)的指令。另外,雖然是一個(gè)類(lèi)型的值,但是實(shí)際上一個(gè)方法不允許超過(guò)條字節(jié)碼指令。
最近在寫(xiě)一個(gè)私人項(xiàng)目,名字叫做SmallVM,SmallVM的目的在于通過(guò)實(shí)現(xiàn)一個(gè)輕量級(jí)的Java虛擬機(jī),加深對(duì)Java虛擬機(jī)的認(rèn)知和理解。在Java虛擬機(jī)加載類(lèi)的過(guò)程中,需要對(duì)Class文件進(jìn)行解析,我曾經(jīng)多帶帶實(shí)現(xiàn)過(guò)一個(gè)Java版的Class字節(jié)解析器ClassAnalyzer,相比于Java版,新版(Golang版)更加健壯,思路也更加清晰。本文即闡述我實(shí)現(xiàn)Class字節(jié)解析器的思路。
Class文件作為類(lèi)或者接口信息的載體,每個(gè)Class文件都完整的定義了一個(gè)類(lèi)。為了使Java程序可以“編寫(xiě)一次,處處運(yùn)行”,Java虛擬機(jī)規(guī)范對(duì)Class文件進(jìn)行了嚴(yán)格的規(guī)定。構(gòu)成Class文件的基本數(shù)據(jù)單位是字節(jié),這些字節(jié)之間不存在任何分隔符,這使得整個(gè)Class文件中存儲(chǔ)的內(nèi)容幾乎全部是程序運(yùn)行的必要數(shù)據(jù),單個(gè)字節(jié)無(wú)法表示的數(shù)據(jù)由多個(gè)連續(xù)的字節(jié)來(lái)表示。
根據(jù)Java虛擬機(jī)規(guī)范,Class文件采用一種類(lèi)似于C語(yǔ)言結(jié)構(gòu)體的偽結(jié)構(gòu)來(lái)存儲(chǔ)數(shù)據(jù),這種偽結(jié)構(gòu)中只有兩種數(shù)據(jù)類(lèi)型:無(wú)符號(hào)數(shù)和表。Java虛擬機(jī)規(guī)范定義了u1、u2、u4和u8來(lái)分別表示1個(gè)字節(jié)、2個(gè)字節(jié)、4個(gè)字節(jié)和8個(gè)字節(jié)的無(wú)符號(hào)數(shù),無(wú)符號(hào)數(shù)可以用來(lái)描述數(shù)字、索引引用、數(shù)量值或者是字符串。表是由多個(gè)無(wú)符號(hào)數(shù)或者其它表作為數(shù)據(jù)項(xiàng)構(gòu)成的復(fù)合數(shù)據(jù)類(lèi)型,表用于描述有層次關(guān)系的復(fù)合結(jié)構(gòu)的數(shù)據(jù),因此整個(gè)Class文件本質(zhì)上就是一張表。在SmallVM中u1、u2、u4和u8分別對(duì)應(yīng)于uint8、uint16、uint32和uint64,Class文件被描述為如下結(jié)構(gòu)體。
type ClassFile struct { magic uint32 minorVersion uint16 majorVersion uint16 constantPoolCount uint16 constantPool []constantpool.ConstantInfo accessFlags uint16 thisClass uint16 superClass uint16 interfacesCount uint16 interfaces []uint16 fieldsCount uint16 fields []FieldInfo methodsCount uint16 methods []MethodInfo attributesCount uint16 attributes []attribute.AttributeInfo } type FieldInfo struct { accessFlags uint16 nameIndex uint16 descriptorIndex uint16 attributesCount uint16 attributes []attribute.AttributeInfo } type MethodInfo struct { accessFlags uint16 nameIndex uint16 descriptorIndex uint16 attributesCount uint16 attributes []attribute.AttributeInfo }如何解析
組成Class文件的各個(gè)數(shù)據(jù)項(xiàng)中,例如魔數(shù)、Class文件的版本、訪(fǎng)問(wèn)標(biāo)志、類(lèi)索引和父類(lèi)索引等數(shù)據(jù)項(xiàng),它們?cè)诿總€(gè)Class文件中都占用固定數(shù)量的字節(jié),在解析時(shí)只需要讀取相應(yīng)數(shù)量的字節(jié)。除此之外,需要靈活處理的主要包括4部分:常量池、字段表集合、方法表集合和屬性表集合。字段和方法都可以具備自己的屬性,Class本身也有相應(yīng)的屬性,因此,在解析字段表集合和方法表集合的同時(shí)也包含了屬性表的解析。
常量池占據(jù)了Class文件很大一部分的數(shù)據(jù),用于存儲(chǔ)所有的常量信息,包括數(shù)字和字符串常量、類(lèi)名、接口名、字段名和方法名等。Java虛擬機(jī)規(guī)范定義了多種常量類(lèi)型,每一種常量類(lèi)型都有自己的結(jié)構(gòu)。常量池本身是一個(gè)表,在解析時(shí)有幾點(diǎn)需要注意。
每個(gè)常量類(lèi)型都通過(guò)一個(gè)u1類(lèi)型的tag來(lái)標(biāo)識(shí)。
表頭給出的常量池大小(constantPoolCount)比實(shí)際大1,例如,如果constantPoolCount等于47,那么常量池中有46項(xiàng)常量。
常量池的索引范圍從1開(kāi)始,例如,如果constantPoolCount等于47,那么常量池的索引范圍為1~46。設(shè)計(jì)者將第0項(xiàng)空出來(lái)的目的是用于表達(dá)“不引用任何一個(gè)常量池項(xiàng)目”。
如果一個(gè)CONSTANT_Long_info或CONSTANT_Double_info結(jié)構(gòu)的項(xiàng)在常量池中的索引為n,則常量池中下一個(gè)有效的項(xiàng)的索引為n+2,此時(shí)常量池中索引為n+1的項(xiàng)有效但必須被認(rèn)為不可用。
CONSTANT_Utf8_info型常量的結(jié)構(gòu)中包含u1類(lèi)型的tag、u2類(lèi)型的length和由length個(gè)u1類(lèi)型組成的bytes,這length字節(jié)的連續(xù)數(shù)據(jù)是一個(gè)使用MUTF-8(Modified UTF-8)編碼的字符串。MUTF-8與UTF-8并不兼容,主要區(qū)別有兩點(diǎn):一是null字符會(huì)被編碼成2字節(jié)(0xC0和0x80);二是補(bǔ)充字符是按照UTF-16拆分為代理對(duì)分別編碼的,相關(guān)細(xì)節(jié)可以看這里(變種UTF-8)。
屬性表用于描述某些場(chǎng)景專(zhuān)有的信息,Class文件、字段表和方法表都有相應(yīng)的屬性表集合。Java虛擬機(jī)規(guī)范定義了多種屬性,SmallVM目前實(shí)現(xiàn)了對(duì)常用屬性的解析。和常量類(lèi)型的數(shù)據(jù)項(xiàng)不同,屬性并沒(méi)有一個(gè)tag來(lái)標(biāo)識(shí)屬性的類(lèi)型,但是每個(gè)屬性都包含有一個(gè)u2類(lèi)型的attribute_name_index,attribute_name_index指向常量池中的一個(gè)CONSTANT_Utf8_info類(lèi)型的常量,該常量包含著屬性的名稱(chēng)。在解析屬性時(shí),SmallVM正是通過(guò)attribute_name_index指向的常量對(duì)應(yīng)的屬性名稱(chēng)來(lái)得知屬性的類(lèi)型。
字段表用于描述類(lèi)或者接口中聲明的變量,字段包括類(lèi)級(jí)變量以及實(shí)例級(jí)變量。字段表的結(jié)構(gòu)包含一個(gè)u2類(lèi)型的access_flags、一個(gè)u2類(lèi)型的name_index、一個(gè)u2類(lèi)型的descriptor_index、一個(gè)u2類(lèi)型的attributes_count和attributes_count個(gè)attribute_info類(lèi)型的attributes。我們已經(jīng)介紹了屬性表的解析,attributes的解析方式與屬性表的解析方式一致。
Class的文件方法表采用了和字段表相同的存儲(chǔ)格式,只是access_flags對(duì)應(yīng)的含義有所不同。方法表包含著一個(gè)重要的屬性:Code屬性。Code屬性存儲(chǔ)了Java代碼編譯成的字節(jié)碼指令,在SmallVM中,Code對(duì)應(yīng)的結(jié)構(gòu)體如下所示(僅列出了類(lèi)屬性)。
type Code struct { pool []constantpool.ConstantInfo attributeNameIndex uint16 attributeLength uint32 maxStack uint16 maxLocals uint16 codeLength uint32 code []byte exceptionTableLength uint16 exceptionTable []ExceptionInfo attributesCount uint16 attributes []AttributeInfo } type ExceptionInfo struct { startPc uint16 endPc uint16 handlerPc uint16 catchType uint16 }
在Code屬性中,codeLength和code分別用于存儲(chǔ)字節(jié)碼長(zhǎng)度和字節(jié)碼指令,每條指令即一個(gè)字節(jié)(u1類(lèi)型)。在虛擬機(jī)執(zhí)行時(shí),通過(guò)讀取code中的一個(gè)個(gè)字節(jié)碼,并將字節(jié)碼翻譯成相應(yīng)的指令。另外,雖然codeLength是一個(gè)u4類(lèi)型的值,但是實(shí)際上一個(gè)方法不允許超過(guò)65535條字節(jié)碼指令。
代碼實(shí)現(xiàn)整個(gè)Class字節(jié)解析器的源碼已放在了GitHub上,字節(jié)解析器僅僅是SmallVM的一個(gè)小模塊,對(duì)應(yīng)的目錄為src/classfile。另外,可以參考ClassAnalyzer的README,我以一個(gè)類(lèi)的Class文件為例,對(duì)該Class文件的每個(gè)字節(jié)進(jìn)行了分析,希望對(duì)大家的理解有所幫助。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/66899.html
摘要:在上篇文章實(shí)現(xiàn)簡(jiǎn)單爬蟲(chóng)框架單任務(wù)版爬蟲(chóng)中我們實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的單任務(wù)版爬蟲(chóng),對(duì)于單任務(wù)版爬蟲(chóng),每次都要請(qǐng)求頁(yè)面,然后解析數(shù)據(jù),然后才能請(qǐng)求下一個(gè)頁(yè)面。在上篇文章Golang實(shí)現(xiàn)簡(jiǎn)單爬蟲(chóng)框架(2)——單任務(wù)版爬蟲(chóng)中我們實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的單任務(wù)版爬蟲(chóng),對(duì)于單任務(wù)版爬蟲(chóng),每次都要請(qǐng)求頁(yè)面,然后解析數(shù)據(jù),然后才能請(qǐng)求下一個(gè)頁(yè)面。整個(gè)過(guò)程中,獲取網(wǎng)頁(yè)數(shù)據(jù)速度比較慢,那么我們就把獲取數(shù)據(jù)模塊做成并發(fā)執(zhí)行。在...
摘要:在上篇文章實(shí)現(xiàn)簡(jiǎn)單爬蟲(chóng)框架單任務(wù)版爬蟲(chóng)中我們實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的單任務(wù)版爬蟲(chóng),對(duì)于單任務(wù)版爬蟲(chóng),每次都要請(qǐng)求頁(yè)面,然后解析數(shù)據(jù),然后才能請(qǐng)求下一個(gè)頁(yè)面。在上篇文章Golang實(shí)現(xiàn)簡(jiǎn)單爬蟲(chóng)框架(2)——單任務(wù)版爬蟲(chóng)中我們實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的單任務(wù)版爬蟲(chóng),對(duì)于單任務(wù)版爬蟲(chóng),每次都要請(qǐng)求頁(yè)面,然后解析數(shù)據(jù),然后才能請(qǐng)求下一個(gè)頁(yè)面。整個(gè)過(guò)程中,獲取網(wǎng)頁(yè)數(shù)據(jù)速度比較慢,那么我們就把獲取數(shù)據(jù)模塊做成并發(fā)執(zhí)行。在...
摘要:它負(fù)責(zé)將的字節(jié)碼形式轉(zhuǎn)換成內(nèi)存形式的對(duì)象。先使用工具對(duì)字節(jié)碼文件進(jìn)行加密,運(yùn)行時(shí)使用定制的先解密文件內(nèi)容再加載這些解密后的字節(jié)碼。的方法是需要子類(lèi)來(lái)覆蓋的,不同的加載器將使用不同的邏輯來(lái)獲取目標(biāo)類(lèi)的字節(jié)碼。 ClassLoader 詳解 ClassLoader 做什么的? 延遲加載 各司其職 ClassLoader 傳遞性 雙親委派 Class.forName 自定義加載器 Clas...
摘要:用一張思維導(dǎo)圖盡可能囊括一下的類(lèi)加載過(guò)程的全流程。本文參考自來(lái)自周志明深入理解虛擬機(jī)第版,拓展內(nèi)容建議讀者可以閱讀下這本書(shū)。 用一張思維導(dǎo)圖盡可能囊括一下JVM的類(lèi)加載過(guò)程的全流程。 本文參考自來(lái)自周志明《深入理解Java虛擬機(jī)(第2版)》,拓展內(nèi)容建議讀者可以閱讀下這本書(shū)。 showImg(http://ocxhn1mzz.bkt.clouddn.com/class%20loadin...
摘要:一類(lèi)加載器什么是類(lèi)加載器,作用是什么類(lèi)加載器就加載字節(jié)碼文件類(lèi)加載器的種類(lèi)類(lèi)加載器有三種,不同類(lèi)加載器加載不同的引導(dǎo)類(lèi)加載器加載都是最基礎(chǔ)的文件擴(kuò)展類(lèi)加載器加載都是基礎(chǔ)的文件應(yīng)用類(lèi)加載器三方包和自己編寫(xiě)文件怎么獲得類(lèi)加載器重點(diǎn)字節(jié)碼對(duì)象二注 一、類(lèi)加載器1.什么是類(lèi)加載器,作用是什么?類(lèi)加載器就加載字節(jié)碼文件(.class) 2.類(lèi)加載器的種類(lèi)類(lèi)加載器有三種,不同類(lèi)加載器加載不同的 1...
閱讀 2007·2021-11-15 18:09
閱讀 899·2021-09-06 15:13
閱讀 2643·2021-08-23 09:43
閱讀 2024·2019-08-30 15:54
閱讀 2218·2019-08-30 13:56
閱讀 2484·2019-08-26 11:31
閱讀 3077·2019-08-26 10:56
閱讀 700·2019-08-26 10:28