日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 >

修改 class_带你探索JVM的Class文件结构

發(fā)布時間:2025/4/16 61 豆豆
生活随笔 收集整理的這篇文章主要介紹了 修改 class_带你探索JVM的Class文件结构 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

魔數(shù):

大多數(shù)情況下,我們都是通過擴展名來識別一個文件的類型的,比如我們看到一個.txt類型的文件我們就知道他是一個純文本文件。但是,擴展名是可以修改的,那一旦一個文件的擴展名被修改過,那么怎么識別一個文件的類型呢。這就用到了我們提到的“魔數(shù)”。

很多類型的文件,其起始的幾個字節(jié)的內(nèi)容是固定的(或是有意填充,或是本就如此)。因此這幾個字節(jié)的內(nèi)容也被稱為魔數(shù) (magic number),因為根據(jù)這幾個字節(jié)的內(nèi)容就可以確定文件類型。有了這些魔術(shù)數(shù)字,我們就可以很方便的區(qū)別不同的文件。

為了方便虛擬機識別一個文件是否是class類型的文件,SUN公司規(guī)定每個class文件都必須以一個word(四個字節(jié))作為開始,這個數(shù)字就是魔數(shù)。魔數(shù)是由四個字節(jié)的無符號數(shù)組成的,而class文件的名字還挺好聽的的,其魔數(shù)就是0xCAFEBABE

讀者可以隨便編譯一個class文件,然后然后用十六進(jìn)制編輯器打開編譯后的class文件,基本格式如下:

唯一的作用是用于確定這個文件是否為一個能被虛擬機接受的Class文件,在代碼中使用魔數(shù),不僅使代碼的可讀性大大降低,還可能導(dǎo)致各種問題。

版本號:

  • Class文件版本號:次版本號組成u2+主版本號u2。共占4個字節(jié)。
  • 高版本的JDK能向下兼容以前的版本的Class文件,但不能運行高版本的Class文件。
  • JDK1.1的版本號為45.0-45.65535(10進(jìn)制),之后每個大版本發(fā)布主版本號加1,如:JDK1.2:46.0~46.65535。

常量池:

字符數(shù)組的存儲方式

public

1. 字符串常量池(String Constant Pool):

即String Pool,但是JVM中對應(yīng)的類是StringTable,底層實現(xiàn)是一個hashtable,看代碼

class

Key的生成方式

  • 通過String的內(nèi)容+長度生成hash值
  • 將hash值轉(zhuǎn)為key
hashValue // Pick hashing algorithm // Bucket handling

Value的生成方式

將Java的String類的實例instanceOopDesc封裝成HashtableEntry

HashtableEntrytemplate

String.hashcode()

String類重寫了hashcode方法

public int hashCode() {int h = this.hash;if (h == 0 && this.value.length > 0) {char[] val = this.value;for(int i = 0; i < this.value.length; ++i) {h = 31 * h + val[i];}this.hash = h;}return h; }

可以看出String的hashcode與String的內(nèi)容是有關(guān)系的,因此下面的代碼的hashcode是相等的

public

不同方式創(chuàng)建字符串在JVM中的存在形式

雙引號

new String

兩個雙引號

兩個new String

拼接字符串底層是如何實現(xiàn)的

雙引號 + 雙引號

public

雙引號 + new String

public

1.1:字符串常量池在Java內(nèi)存區(qū)域的哪個位置?

  • 在JDK6.0及之前版本,字符串常量池是放在Perm Gen區(qū)(也就是方法區(qū))中;
  • 在JDK7.0版本,字符串常量池被移到了堆中了。至于為什么移到堆內(nèi),大概是由于方法區(qū)的內(nèi)存空間太小了。
  • JDK8以后也還是放在了Heap空間中,并沒有已到元空間。

1.2:字符串常量池是什么?

  • 在HotSpot VM里實現(xiàn)的string pool功能的是一個StringTable類,它是一個Hash表,默認(rèn)值大小長度是1009;這個StringTable在每個HotSpot VM的實例只有一份,被所有的類共享。字符串常量由一個一個字符組成,放在了StringTable上。
  • 在JDK6.0中,StringTable的長度是固定的,長度就是1009,因此如果放入String Pool中的String非常多,就會造成hash沖突,導(dǎo)致鏈表過長,當(dāng)調(diào)用String#intern()時會需要到鏈表上一個一個找,從而導(dǎo)致性能大幅度下降;
  • 在JDK7.0中,StringTable的長度可以通過參數(shù)指定:
-XX:StringTableSize=66666

1.3:字符串常量池里放的是什么?

  • 在JDK6.0及之前版本中,String Pool里放的都是字符串常量;
  • 在JDK7.0中,由于String#intern()發(fā)生了改變,因此String Pool中也可以存放放于堆內(nèi)的字符串對象的引用。

需要說明的是:字符串常量池中的字符串只存在一份!
如:

String

即執(zhí)行完第一行代碼后,常量池中已存在 “hello,world!”,那么 s2不會在常量池中申請新的空間,而是直接把已存在的字符串內(nèi)存地址返回給s2。

2. class常量池(Class Constant Pool):

2.1:class常量池簡介:

  • 我們寫的每一個Java類被編譯后,就會形成一份class文件;class文件中除了包含類的版本、字段、方法、接口等描述信息外,還有一項信息就是常量池(constant pool table),用于存放編譯器生成的各種字面量(Literal)和符號引用(Symbolic References);
  • 每個class文件都有一個class常量池。

2.2:什么是字面量和符號引用:

  • 字面量包括:1.文本字符串 2.八種基本類型的值 3.被聲明為final的常量等;
  • 符號引用包括:1.類和方法的全限定名 2.字段的名稱和描述符 3.方法的名稱和描述符。

3. 運行時常量池(Runtime Constant Pool):

  • 運行時常量池存在于內(nèi)存中,也就是class常量池被加載到內(nèi)存之后的版本,不同之處是:它的字面量可以動態(tài)的添加(String#intern()),符號引用可以被解析為直接引用
  • JVM在執(zhí)行某個類的時候,必須經(jīng)過加載、連接、初始化,而連接又包括驗證、準(zhǔn)備、解析三個階段。而當(dāng)類加載到內(nèi)存中后,jvm就會將class常量池中的內(nèi)容存放到運行時常量池中,由此可知,運行時常量池也是每個類都有一個。在解析階段,會把符號引用替換為直接引用,解析的過程會去查詢字符串常量池,也就是我們上面所說的StringTable,以保證運行時常量池所引用的字符串與字符串常量池中是一致的。
public

訪問標(biāo)志:

(1)作用:

用于識別一些類或接口層次的訪問信息,主要包括:

  • 這個Class是類還是接口;
  • 是否定義為public類型;
  • 是否定義abstract類型;
  • 如果是類的話是否被聲明為final;
  • 是否是注解
  • 是否是枚舉
  • 是否可用invokespecial字節(jié)碼指令

(2)組成:

如上所示,訪問標(biāo)志中一共有16個標(biāo)志位可以使用,當(dāng)前只制定了8個。

類索引,父類索引,接口索引集合:

這三項數(shù)據(jù)主要用于確定這個類的繼承關(guān)系

(1)定義與作用:

類索引(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)了哪些接口。這些被實現(xiàn)的接口將按 implement 語句(若此類本身是一個接口,則應(yīng)當(dāng)是extend語句)后的接口順序從左到右排列在接口索引集合中。

(2)類索引查找:

類索引、父類索引、接口索引都按照順序排列在訪問標(biāo)志之后,類索引和父類索引用兩個 u2 類型的索引值表示,它們各自指向一個類型為 CONSTANT_Class_info 的類描述符常量,通過CONSTANT_Class_info 類型常量中的索引值可以找到定義在 CONSTANT_Utf8_info類型常量中的全限定名字符串。下圖所示為類索引查找過程:

(3)接口計數(shù)器:

對于接口索引集合,入口的第一項——u2 類型的數(shù)據(jù)為接口計數(shù)器(interfaces_count),表示索引表的容量。如果該類沒有實現(xiàn)任何接口,則該計數(shù)器為0,后面接口的索引表不再占用任何字節(jié)。類索引、父類索引、接口索引的內(nèi)容如下圖:

查看上圖可知,從偏移地址 0x000000F1 開始的3個 u2 類型的值分別為 0x0001、0x0003、0x0000,代表著類索引為1、父類索引為3、接口索引集合為0。

字段表集合:

(1)字段表結(jié)構(gòu):

可以想一想在Java中描述一個字段可以包含什么信息?可以包括的信息有:

  • 字段的作用域(public、private、protected修飾符)
  • 是實例變量還是類變量(static修飾符)
  • 可變性(final)
  • 并發(fā)可見性(volatile修飾符,是否強制從主內(nèi)存讀寫)
  • 是否被序列化(transient修飾符)
  • 字段數(shù)據(jù)類型(基本類型、對象、數(shù)組)
  • 字段名稱

在上述這些信息中,各個修飾符都是布爾值,要么有某個修飾符,要么沒有,很適合使用標(biāo)志位來表示。而字段叫什么名字、被定義成什么數(shù)據(jù)類型都是無法固定的,只能引用常量池中的常量來描述。下表列出了字段表的最終格式:

  • access_flags:是一個 u2的數(shù)據(jù)類型。
  • name_index 索引值: 對常量池的引用,代表著字段的簡單名稱。
  • descriptor_index 索引值: 對常量池的引用,代表字段和方法的描述符。

(2)定義與作用:

字段表(field_info)用于描述接口或者類中聲明的變量。字段(field)包括類級變量和實例級變量,但是不包括方法內(nèi)部聲明的局部變量。簡單來說,字段表集合存儲的修飾符+名稱

變量修飾符使用標(biāo)志位表示,字段數(shù)據(jù)類型和字段名稱則引用常量池中常量表示

方法表集合:

(1)結(jié)構(gòu):

Class文件存儲格式中對方法表的描述與字段表是一致的,包括了:

  • 訪問標(biāo)志(access_flags)
  • 名稱索引(name_index)
  • 描述符索引(descriptor_index)
  • 屬性表集合(attributes)

這些數(shù)據(jù)項目的含義也非常類似,僅在訪問標(biāo)志和屬性表集合的可選項中有區(qū)別:

(2)方法表與字段表的區(qū)別:

區(qū)別在于訪問標(biāo)志的不同:在方法中不能了用volatile和transient關(guān)鍵字修飾,所以方法表中無ACC_VOLATILE、ACC_TRANSIENT。與之相對的 synchronized、native、strictfp、abstract關(guān)鍵字可修飾方法,所以在方法表中就增加了相應(yīng)的訪問標(biāo)志。

(3)標(biāo)志位:

對于方法表,所有的標(biāo)志位及取值如下表:

行文自此,你會發(fā)現(xiàn)方法的定義可以通過訪問標(biāo)志、名稱索引、描述符索引表達(dá)清楚,但是方法里的代碼去哪里了?

方法里的Java代碼,經(jīng)過編譯器編譯成字節(jié)碼指令后,存放在方法屬性表集合一個名為“Code”屬性里,屬性表作為Class文件格式中最具擴展性的一種數(shù)據(jù)項目。

屬性表集合:

屬性表(attribute_info)在Class文件、字段表、方法表都可以攜帶自己的屬性集合,用于描述某些場景專有的信息。

與Class文件中的其它數(shù)據(jù)項木要求嚴(yán)格的順序、長度和內(nèi)存不同,屬性表集合限制稍寬松,不再要求各個屬性表具有嚴(yán)格順序,只要不與已有屬性名重復(fù),任何人實現(xiàn)的二便一起都可以想屬性表中寫入自己定義的屬性信息,而Java虛擬機會忽略掉它不認(rèn)識的屬性。

下表對其中的一些屬性中關(guān)鍵常用部分進(jìn)行講解:

對于每個屬性,它的名稱需要從常量池中引入一個 CONSTANT_Utf8_info類型的常量來表示,而屬性值的結(jié)構(gòu)則是完全自定義的,只需要通過一個u4 長度屬性去說明屬性值所占用的位置即可。一個符合規(guī)則的屬性表應(yīng)滿足如下結(jié)構(gòu):

總結(jié)

以上是生活随笔為你收集整理的修改 class_带你探索JVM的Class文件结构的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。