本系列主要記錄筆者在學習 [深入理解Java虛擬機] 一書時的理解
我們都知道在Java中,我們并不需要過多的在意內(nèi)存的管理,這一切都交給了虛擬機自動管理,我們并不需要操心何時需要去釋放一個對象的內(nèi)存。
當然,如果出現(xiàn)了內(nèi)存溢出或泄漏,我們就必須去了解一下Java虛擬機的內(nèi)存管理機制以便于我們解決問題
[筆者仍為Android初學者。如有解釋錯誤的地方,歡迎評論區(qū)指正探討]
本篇為該系列第四篇,概述類文件結(jié)構(gòu)。
概述
想要深入的了解Jvm,那么解析class文件是必不可少的,Class文件格式采用一種類似于C語言結(jié)構(gòu)體的偽結(jié)構(gòu)來存儲數(shù)據(jù),這種偽結(jié)構(gòu)中只有兩種數(shù)據(jù)類型:無符號數(shù)和表。
- 無符號數(shù)屬于基本的數(shù)據(jù)類型,以u1、u2、u4、u8來分別代表1個字節(jié)、2個字節(jié)、4個字節(jié)和8個字節(jié)的無符號數(shù),無符號數(shù)可以用來描述數(shù)字、索引引用、數(shù)量值或者按照UTF-8編碼構(gòu)成字符串值。
- 表是由多個無符號數(shù)或者其他表作為數(shù)據(jù)項構(gòu)成的復合數(shù)據(jù)類型,所有表都習慣性地以“_info”結(jié)尾。表用于描述有層次關(guān)系的復合結(jié)構(gòu)的數(shù)據(jù),整個Class文件本質(zhì)上就是一張表。
接下來我們依照這張圖,一步一步的解析類文件的結(jié)構(gòu):
魔數(shù)Magic Number
每個Class文件的頭4個字節(jié)稱為魔數(shù)(Magic Number),它的唯一作用是確定這個文件是否為一個能被虛擬機接受的Class文件。很多文件存儲標準中都使用魔數(shù)來進行*身份識別,譬如圖片格式,如gif或者jpeg等在文件頭中都存有魔數(shù)。
使用魔數(shù)而不是擴展名來進行識別主要是基于安全方面的考慮,因為文件擴展名可以隨意地改動。
版本號
緊接著魔數(shù)的是版本號,分為次版本號和主版本號,高版本的JDK能向下兼容以前版本的Class文件,但不能運行以后版本的Class文件,即使文件格式并未發(fā)生任何變化,虛擬機也必須拒絕執(zhí)行超過其版本號的Class文件。
常量池
接下來是常量池,常量池可以理解為Class文件之中的資源倉庫,
它是Class文件結(jié)構(gòu)中與其他項目關(guān)聯(lián)最多的數(shù)據(jù)類型,也是占用Class文件空間最大的數(shù)據(jù)項目之一,同時它還是在Class文件中第一個出現(xiàn)的表類型數(shù)據(jù)項目。
由于常量池數(shù)量是不確定的,所以需要有一個u2類型的數(shù)據(jù)用來存儲常量池數(shù)量。
常量池主要存放兩大類常量:字面量和符號引用。字面量指的是文本字符串、聲明為final的常量值等。而而符號引用則屬于編譯原理方面的概念,包括:類和接口的全限定名,字段的名稱和描述符,方法的名稱和描述符。
Java代碼在進行Javac編譯的時候,并不像C和C++那樣有“連接”這一步驟,而是在虛擬機加載Class文件的時候進行動態(tài)連接。也就是說,在Class文件中不會保存各個方法、字段的最終內(nèi)存布局信息,因此這些字段、方法的符號引用不經(jīng)過運行期轉(zhuǎn)換的話無法得到真正的內(nèi)存入口地址,也就無法直接被虛擬機使用。當虛擬機運行時,需要從常量池獲得對應的符號引用,再在類創(chuàng)建時或運行時解析、翻譯到具體的內(nèi)存地址之中。
常量池中每一項常量都是一個表,看一下圖
表里存儲的各種類型的數(shù)據(jù)的一些信息,這里就不深入展開了。
訪問標志
接下來的兩個直接表示訪問標志,這個標志用來識別一些類或接口層次的訪問信息,包括:這個class是類還是接口,是不是public,是不是abstract,是不是final等等信息。
access_flags中一共有16個標志位可以使用,1為真,0為假。
類索引,父類索引,接口索引集合
類索引(this_class)和父類索引(super_class)都是一個u2類型的數(shù)據(jù),而接口索引集合(interfaces)是一組u2類型的數(shù)據(jù)的集合。
Class文件中由這三項數(shù)據(jù)來確定這個類的繼承關(guān)系。
- 類索引用于確定這個類的全限定名.
- 父類索引用于確定這個類的父類的全限定名。由于Java語言不允許多重繼承,所以父類索引只有一個,除了
java.lang.Object
之外,所有的Java類都有父類,因此除了java.lang.Object
外,所有Java類的父類索引都不為0。 - 接口索引集合就用來描述這個類實現(xiàn)了哪些接口,接口索引集合還有一個標記(interface_count),用來標記實現(xiàn)了多少個接口。
這些索引值各自指向一個類型為CONSTANT_Class_info
的類描述符常量,通過CONSTANT_Class_info
類型的常量中的索引值可以找到定義在CONSTANT_Utf8_info
類型的常量中的全限定名字符串。
字段表集合
字段表(field_info)用于描述接口或者類中聲明的變量。字段(field)包括類級變量以及實例級變量,但不包括在方法內(nèi)部聲明的局部變量。
一個字段表中包含了字段的作用域(public、private、protected修飾
符)、是實例變量還是類變量(static修飾符)、可變性(final)、并發(fā)可見性(volatile修飾符,是否強制從主內(nèi)存讀寫)、可否被序列化(transient修飾符)、字段數(shù)據(jù)類型(基本類型、對象、數(shù)組)、字段名稱等等信息。
大部分信息都是用bool值來表示的,是或者否。
而字段叫什么名字、字段被定義為什么數(shù)據(jù)類型,這些都是無法固定的,只能引用常量池中的常量來描述。
方法表集合
類似于字段表,方法表依次包括了訪問標志(access_flags)、名稱索引(name_index)、描述符索引(descriptor_index)、屬性表集合(attributes)等幾項。
常規(guī)的標志都和字段表的標志一樣,用bool值來表示。
不過不同于字段的是,方法還應該有方法代碼,而這部分存放在屬性表的code字段里。屬性表是Class文件格式中最具擴展性的一種數(shù)據(jù)項目,下面我們來了解一下屬性表。
屬性表
屬性表(attribute_info)在前面的講解之中已經(jīng)出現(xiàn)過數(shù)次,在Class文件、字段表、方法表都可以攜帶自己的屬性表集合,以用于描述某些場景專有的信息。
與Class文件中其他的數(shù)據(jù)項目要求嚴格的順序、長度和內(nèi)容不同,屬性表集合的限制稍微寬松了一些,不再要求各個屬性表具有嚴格順序,并且只要不與已有屬性名重復,任何人實現(xiàn)的編譯器都可以向?qū)傩员碇袑懭胱约憾x的屬性信息
屬性表中可以包括很多信息,比如方法代碼(code),常量值(ConstantValue),異常(Exceptions),源文件名稱(SourceFile)等等信息,對于每個屬性,它的名稱需要從常量池中引用一個CONSTANT_Utf8_info類型的常量來表示,而屬性值的結(jié)構(gòu)則是完全自定義的,只需要通過一個u4的長度屬性去說明屬性值所占用的位數(shù)即可。
小結(jié)
類文件結(jié)構(gòu)大概就介紹到這里,每一種表都有各自的屬性,但是無外乎都記錄了我們原來java文件中寫的一些屬性,變量,方法的信息。這些信息多種多樣,這里就不深入闡述,簡單了解。