本系列主要記錄筆者在學(xué)習(xí) [深入理解Java虛擬機(jī)] 一書時(shí)的理解
我們都知道在Java中赊级,我們并不需要過多的在意內(nèi)存的管理斩个,這一切都交給了虛擬機(jī)自動(dòng)管理爬虱,我們并不需要操心何時(shí)需要去釋放一個(gè)對(duì)象的內(nèi)存讳推。
當(dāng)然析砸,如果出現(xiàn)了內(nèi)存溢出或泄漏,我們就必須去了解一下Java虛擬機(jī)的內(nèi)存管理機(jī)制以便于我們解決問題
[筆者仍為Android初學(xué)者垮庐。如有解釋錯(cuò)誤的地方,歡迎評(píng)論區(qū)指正探討]
本篇為該系列第四篇坞琴,概述類文件結(jié)構(gòu)哨查。
概述
想要深入的了解Jvm,那么解析class文件是必不可少的剧辐,Class文件格式采用一種類似于C語言結(jié)構(gòu)體的偽結(jié)構(gòu)來存儲(chǔ)數(shù)據(jù)寒亥,這種偽結(jié)構(gòu)中只有兩種數(shù)據(jù)類型:無符號(hào)數(shù)和表邮府。
- 無符號(hào)數(shù)屬于基本的數(shù)據(jù)類型,以u(píng)1溉奕、u2褂傀、u4、u8來分別代表1個(gè)字節(jié)加勤、2個(gè)字節(jié)仙辟、4個(gè)字節(jié)和8個(gè)字節(jié)的無符號(hào)數(shù),無符號(hào)數(shù)可以用來描述數(shù)字鳄梅、索引引用叠国、數(shù)量值或者按照UTF-8編碼構(gòu)成字符串值。
- 表是由多個(gè)無符號(hào)數(shù)或者其他表作為數(shù)據(jù)項(xiàng)構(gòu)成的復(fù)合數(shù)據(jù)類型戴尸,所有表都習(xí)慣性地以“_info”結(jié)尾粟焊。表用于描述有層次關(guān)系的復(fù)合結(jié)構(gòu)的數(shù)據(jù),整個(gè)Class文件本質(zhì)上就是一張表孙蒙。
接下來我們依照這張圖项棠,一步一步的解析類文件的結(jié)構(gòu):
魔數(shù)Magic Number
每個(gè)Class文件的頭4個(gè)字節(jié)稱為魔數(shù)(Magic Number),它的唯一作用是確定這個(gè)文件是否為一個(gè)能被虛擬機(jī)接受的Class文件挎峦。很多文件存儲(chǔ)標(biāo)準(zhǔn)中都使用魔數(shù)來進(jìn)行*身份識(shí)別香追,譬如圖片格式,如gif或者jpeg等在文件頭中都存有魔數(shù)浑测。
使用魔數(shù)而不是擴(kuò)展名來進(jìn)行識(shí)別主要是基于安全方面的考慮翅阵,因?yàn)槲募U(kuò)展名可以隨意地改動(dòng)。
版本號(hào)
緊接著魔數(shù)的是版本號(hào)迁央,分為次版本號(hào)和主版本號(hào)掷匠,高版本的JDK能向下兼容以前版本的Class文件,但不能運(yùn)行以后版本的Class文件岖圈,即使文件格式并未發(fā)生任何變化讹语,虛擬機(jī)也必須拒絕執(zhí)行超過其版本號(hào)的Class文件。
常量池
接下來是常量池蜂科,常量池可以理解為Class文件之中的資源倉(cāng)庫(kù)顽决,
它是Class文件結(jié)構(gòu)中與其他項(xiàng)目關(guān)聯(lián)最多的數(shù)據(jù)類型,也是占用Class文件空間最大的數(shù)據(jù)項(xiàng)目之一导匣,同時(shí)它還是在Class文件中第一個(gè)出現(xiàn)的表類型數(shù)據(jù)項(xiàng)目才菠。
由于常量池?cái)?shù)量是不確定的,所以需要有一個(gè)u2類型的數(shù)據(jù)用來存儲(chǔ)常量池?cái)?shù)量贡定。
常量池主要存放兩大類常量:字面量和符號(hào)引用赋访。字面量指的是文本字符串、聲明為final的常量值等。而而符號(hào)引用則屬于編譯原理方面的概念蚓耽,包括:類和接口的全限定名渠牲,字段的名稱和描述符,方法的名稱和描述符步悠。
Java代碼在進(jìn)行Javac編譯的時(shí)候签杈,并不像C和C++那樣有“連接”這一步驟,而是在虛擬機(jī)加載Class文件的時(shí)候進(jìn)行動(dòng)態(tài)連接鼎兽。也就是說答姥,在Class文件中不會(huì)保存各個(gè)方法、字段的最終內(nèi)存布局信息接奈,因此這些字段踢涌、方法的符號(hào)引用不經(jīng)過運(yùn)行期轉(zhuǎn)換的話無法得到真正的內(nèi)存入口地址,也就無法直接被虛擬機(jī)使用序宦。當(dāng)虛擬機(jī)運(yùn)行時(shí)睁壁,需要從常量池獲得對(duì)應(yīng)的符號(hào)引用,再在類創(chuàng)建時(shí)或運(yùn)行時(shí)解析互捌、翻譯到具體的內(nèi)存地址之中潘明。
常量池中每一項(xiàng)常量都是一個(gè)表,看一下圖
表里存儲(chǔ)的各種類型的數(shù)據(jù)的一些信息秕噪,這里就不深入展開了钳降。
訪問標(biāo)志
接下來的兩個(gè)直接表示訪問標(biāo)志,這個(gè)標(biāo)志用來識(shí)別一些類或接口層次的訪問信息腌巾,包括:這個(gè)class是類還是接口遂填,是不是public,是不是abstract澈蝙,是不是final等等信息吓坚。
access_flags中一共有16個(gè)標(biāo)志位可以使用,1為真灯荧,0為假礁击。
類索引,父類索引逗载,接口索引集合
類索引(this_class)和父類索引(super_class)都是一個(gè)u2類型的數(shù)據(jù)哆窿,而接口索引集合(interfaces)是一組u2類型的數(shù)據(jù)的集合。
Class文件中由這三項(xiàng)數(shù)據(jù)來確定這個(gè)類的繼承關(guān)系厉斟。
- 類索引用于確定這個(gè)類的全限定名.
- 父類索引用于確定這個(gè)類的父類的全限定名挚躯。由于Java語言不允許多重繼承,所以父類索引只有一個(gè)擦秽,除了
java.lang.Object
之外秧均,所有的Java類都有父類食侮,因此除了java.lang.Object
外,所有Java類的父類索引都不為0目胡。 - 接口索引集合就用來描述這個(gè)類實(shí)現(xiàn)了哪些接口,接口索引集合還有一個(gè)標(biāo)記(interface_count)链快,用來標(biāo)記實(shí)現(xiàn)了多少個(gè)接口誉己。
這些索引值各自指向一個(gè)類型為CONSTANT_Class_info
的類描述符常量,通過CONSTANT_Class_info
類型的常量中的索引值可以找到定義在CONSTANT_Utf8_info
類型的常量中的全限定名字符串域蜗。
字段表集合
字段表(field_info)用于描述接口或者類中聲明的變量巨双。字段(field)包括類級(jí)變量以及實(shí)例級(jí)變量,但不包括在方法內(nèi)部聲明的局部變量霉祸。
一個(gè)字段表中包含了字段的作用域(public筑累、private、protected修飾
符)丝蹭、是實(shí)例變量還是類變量(static修飾符)慢宗、可變性(final)、并發(fā)可見性(volatile修飾符奔穿,是否強(qiáng)制從主內(nèi)存讀寫)镜沽、可否被序列化(transient修飾符)、字段數(shù)據(jù)類型(基本類型贱田、對(duì)象缅茉、數(shù)組)、字段名稱等等信息男摧。
大部分信息都是用bool值來表示的蔬墩,是或者否。
而字段叫什么名字耗拓、字段被定義為什么數(shù)據(jù)類型拇颅,這些都是無法固定的,只能引用常量池中的常量來描述帆离。
方法表集合
類似于字段表蔬蕊,方法表依次包括了訪問標(biāo)志(access_flags)、名稱索引(name_index)哥谷、描述符索引(descriptor_index)岸夯、屬性表集合(attributes)等幾項(xiàng)。
常規(guī)的標(biāo)志都和字段表的標(biāo)志一樣们妥,用bool值來表示猜扮。
不過不同于字段的是,方法還應(yīng)該有方法代碼监婶,而這部分存放在屬性表的code字段里旅赢。屬性表是Class文件格式中最具擴(kuò)展性的一種數(shù)據(jù)項(xiàng)目齿桃,下面我們來了解一下屬性表。
屬性表
屬性表(attribute_info)在前面的講解之中已經(jīng)出現(xiàn)過數(shù)次煮盼,在Class文件短纵、字段表、方法表都可以攜帶自己的屬性表集合僵控,以用于描述某些場(chǎng)景專有的信息香到。
與Class文件中其他的數(shù)據(jù)項(xiàng)目要求嚴(yán)格的順序、長(zhǎng)度和內(nèi)容不同报破,屬性表集合的限制稍微寬松了一些悠就,不再要求各個(gè)屬性表具有嚴(yán)格順序,并且只要不與已有屬性名重復(fù)充易,任何人實(shí)現(xiàn)的編譯器都可以向?qū)傩员碇袑懭胱约憾x的屬性信息
屬性表中可以包括很多信息梗脾,比如方法代碼(code),常量值(ConstantValue)盹靴,異常(Exceptions)炸茧,源文件名稱(SourceFile)等等信息,對(duì)于每個(gè)屬性鹉究,它的名稱需要從常量池中引用一個(gè)CONSTANT_Utf8_info類型的常量來表示宇立,而屬性值的結(jié)構(gòu)則是完全自定義的,只需要通過一個(gè)u4的長(zhǎng)度屬性去說明屬性值所占用的位數(shù)即可自赔。
小結(jié)
類文件結(jié)構(gòu)大概就介紹到這里妈嘹,每一種表都有各自的屬性,但是無外乎都記錄了我們?cè)瓉韏ava文件中寫的一些屬性绍妨,變量润脸,方法的信息。這些信息多種多樣他去,這里就不深入闡述毙驯,簡(jiǎn)單了解。