日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

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

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

魔數:

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

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

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

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

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

版本號:

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

常量池:

字符數組的存儲方式

public

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

即String Pool,但是JVM中對應的類是StringTable,底層實現是一個hashtable,看代碼

class

Key的生成方式

  • 通過String的內容+長度生成hash值
  • 將hash值轉為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的內容是有關系的,因此下面的代碼的hashcode是相等的

public

不同方式創建字符串在JVM中的存在形式

雙引號

new String

兩個雙引號

兩個new String

拼接字符串底層是如何實現的

雙引號 + 雙引號

public

雙引號 + new String

public

1.1:字符串常量池在Java內存區域的哪個位置?

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

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

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

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

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

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

String

即執行完第一行代碼后,常量池中已存在 “hello,world!”,那么 s2不會在常量池中申請新的空間,而是直接把已存在的字符串內存地址返回給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):

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

訪問標志:

(1)作用:

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

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

(2)組成:

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

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

這三項數據主要用于確定這個類的繼承關系

(1)定義與作用:

類索引(this_class)和父類索引(super_class)都是一個 u2 類型的數據,而接口索引集合(interfaces)是一組 u2類型的數據集合,Class文件中由這三項數據來確定類的繼承關系。

  • 類索引:用來確定這個類的全限定名;
  • 父類索引:用來確定這個類的父類的全限定名。由于Java語言不允許多重繼承,所以父類索引只有一個,除了 java.lang.Object 之外,所有的Java類都有父類。因此除了java.lang.Object 外,所有Java類的父類索引都不為0。
  • 接口索引:用來描述這個類實現了哪些接口。這些被實現的接口將按 implement 語句(若此類本身是一個接口,則應當是extend語句)后的接口順序從左到右排列在接口索引集合中。

(2)類索引查找:

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

(3)接口計數器:

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

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

字段表集合:

(1)字段表結構:

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

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

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

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

(2)定義與作用:

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

變量修飾符使用標志位表示,字段數據類型和字段名稱則引用常量池中常量表示

方法表集合:

(1)結構:

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

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

這些數據項目的含義也非常類似,僅在訪問標志和屬性表集合的可選項中有區別:

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

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

(3)標志位:

對于方法表,所有的標志位及取值如下表:

行文自此,你會發現方法的定義可以通過訪問標志、名稱索引、描述符索引表達清楚,但是方法里的代碼去哪里了?

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

屬性表集合:

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

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

下表對其中的一些屬性中關鍵常用部分進行講解:

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

總結

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

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。