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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

JVM详解之:java class文件的密码本

發(fā)布時(shí)間:2024/2/28 编程问答 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JVM详解之:java class文件的密码本 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

文章目錄

  • 簡介
  • 一個(gè)簡單的class
  • ClassFile的二進(jìn)制文件
  • class文件的密碼本
    • magic
    • version
    • 常量池
    • 描述符
    • access_flags
    • this_class和super_class
    • interfaces_count和interfaces[]
    • fields_count和fields[]
  • methods_count和methods[]
  • attributes_count和attributes[]
  • 總結(jié)

簡介

一切的一切都是從javac開始的。從那一刻開始,java文件就從我們?nèi)庋劭煞直娴奈谋疚募?#xff0c;變成了冷冰冰的二進(jìn)制文件。

變成了二進(jìn)制文件是不是意味著我們無法再深入的去了解java class文件了呢?答案是否定的。

機(jī)器可以讀,人為什么不能讀?只要我們掌握java class文件的密碼表,我們可以把二進(jìn)制轉(zhuǎn)成十六進(jìn)制,將十六進(jìn)制和我們的密碼表進(jìn)行對比,就可以輕松的解密了。

下面,讓我們開始這個(gè)激動(dòng)人心的過程吧。

一個(gè)簡單的class

為了深入理解java class的含義,我們首先需要定義一個(gè)class類:

public class JavaClassUsage {private int age=18;public void inc(int number){this.age=this.age+ number;} }

很簡單的類,我想不會有比它更簡單的類了。

在上面的類中,我們定義了一個(gè)age字段和一個(gè)inc的方法。

接下來我們使用javac來進(jìn)行編譯。

IDEA有沒有?直接打開編譯后的class文件,你會看到什么?

沒錯(cuò),是反編譯過來的java代碼。但是這次我們需要深入了解的是class文件,于是我們可以選擇 view->Show Bytecode:

當(dāng)然,還是少不了最質(zhì)樸的javap命令:

javap -verbose JavaClassUsage

對比會發(fā)現(xiàn),其實(shí)javap展示的更清晰一些,我們暫時(shí)選用javap的結(jié)果。

編譯的class文件有點(diǎn)長,我一度有點(diǎn)不想都列出來,但是又一想只有對才能講述得更清楚,還是貼在下面:

public class com.flydean.JavaClassUsageminor version: 0major version: 58flags: ACC_PUBLIC, ACC_SUPER Constant pool:#1 = Methodref #2.#3 // java/lang/Object."<init>":()V#2 = Class #4 // java/lang/Object#3 = NameAndType #5:#6 // "<init>":()V#4 = Utf8 java/lang/Object#5 = Utf8 <init>#6 = Utf8 ()V#7 = Fieldref #8.#9 // com/flydean/JavaClassUsage.age:I#8 = Class #10 // com/flydean/JavaClassUsage#9 = NameAndType #11:#12 // age:I#10 = Utf8 com/flydean/JavaClassUsage#11 = Utf8 age#12 = Utf8 I#13 = Utf8 Code#14 = Utf8 LineNumberTable#15 = Utf8 LocalVariableTable#16 = Utf8 this#17 = Utf8 Lcom/flydean/JavaClassUsage;#18 = Utf8 inc#19 = Utf8 (I)V#20 = Utf8 number#21 = Utf8 SourceFile#22 = Utf8 JavaClassUsage.java {public com.flydean.JavaClassUsage();descriptor: ()Vflags: ACC_PUBLICCode:stack=2, locals=1, args_size=10: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: aload_05: bipush 187: putfield #7 // Field age:I10: returnLineNumberTable:line 7: 0line 9: 4LocalVariableTable:Start Length Slot Name Signature0 11 0 this Lcom/flydean/JavaClassUsage;public void inc(int);descriptor: (I)Vflags: ACC_PUBLICCode:stack=3, locals=2, args_size=20: aload_01: aload_02: getfield #7 // Field age:I5: iload_16: iadd7: putfield #7 // Field age:I10: returnLineNumberTable:line 12: 0line 13: 10LocalVariableTable:Start Length Slot Name Signature0 11 0 this Lcom/flydean/JavaClassUsage;0 11 1 number I } SourceFile: "JavaClassUsage.java"

ClassFile的二進(jìn)制文件

慢著,上面javap的結(jié)果好像并不是二進(jìn)制文件!

對的,javap是對二進(jìn)制文件進(jìn)行了解析,方便程序員閱讀。如果你真的想直面最最底層的機(jī)器代碼,就直接用支持16進(jìn)制的文本編譯器把編譯好的class文件打開吧。

你準(zhǔn)備好了嗎?

來吧,展示吧!

上圖左邊是16進(jìn)制的class文件代碼,右邊是對16進(jìn)制文件的適當(dāng)解析。大家可以隱約的看到一點(diǎn)點(diǎn)熟悉的內(nèi)容。

是的,沒錯(cuò),你會讀機(jī)器語言了!

class文件的密碼本

如果你要了解class文件的結(jié)構(gòu),你需要這個(gè)密碼本。

如果你想解析class文件,你需要這個(gè)密碼本。

學(xué)好這個(gè)密碼本,走遍天下都…沒啥用!

下面就是密碼本,也就是classFile的結(jié)構(gòu)。

ClassFile {u4 magic;u2 minor_version;u2 major_version;u2 constant_pool_count;cp_info constant_pool[constant_pool_count-1];u2 access_flags;u2 this_class;u2 super_class;u2 interfaces_count;u2 interfaces[interfaces_count];u2 fields_count;field_info fields[fields_count];u2 methods_count;method_info methods[methods_count];u2 attributes_count;attribute_info attributes[attributes_count]; }

其中u2,u4表示的是無符號的兩個(gè)字節(jié),無符號的4個(gè)字節(jié)。

java class文件就是按照上面的格式排列下來的,按照這個(gè)格式,我們可以自己實(shí)現(xiàn)一個(gè)反編譯器(大家有興趣的話,可以自行研究)。

我們對比著上面的二進(jìn)制文件一個(gè)一個(gè)的來理解。

magic

首先,class文件的前4個(gè)字節(jié)叫做magic word。

看一下十六進(jìn)制的第一行的前4個(gè)字節(jié):

CA FE BA BE 00 00 00 3A 00 17 0A 00 02 00 03 07

0xCAFEBABE就是magic word。所有的java class文件都是以這4個(gè)字節(jié)開頭的。

來一杯咖啡吧,baby!

多么有詩意的畫面。

version

這兩個(gè)version要連著講,一個(gè)是主版本號,一個(gè)是次版本號。

00 00 00 3A

對比一下上面的表格,我們的主版本號是3A=58,也就是我們使用的是JDK14版本。

常量池

接下來是常量池。

首先是兩個(gè)字節(jié)的constant_pool_count。對比一下,constant_pool_count的值是:

00 17

換算成十進(jìn)制就是23。也就是說常量池的大小是23-1=22。

這里有兩點(diǎn)要注意,第一點(diǎn),常量池?cái)?shù)組的index是從1開始到constant_pool_count-1結(jié)束。

第二點(diǎn),常量池?cái)?shù)組的第0位是作為一個(gè)保留位,表示“不引用任何常量池項(xiàng)目”,為某些特殊的情況下使用。

接下來是不定長度的cp_info:constant_pool[constant_pool_count-1]常量池?cái)?shù)組。

常量池?cái)?shù)組中存了些什么東西呢?

字符串常量,類和接口名字,字段名,和其他一些在class中引用的常量。

具體的constant_pool中存儲的常量類型有下面幾種:

每個(gè)常量都是以一個(gè)tag開頭的。用來告訴JVM,這個(gè)到底是一個(gè)什么常量。

好了,我們對比著來看一下。在constant_pool_count之后,我們再取一部分16進(jìn)制數(shù)據(jù):

上面我們講到了17是常量池的個(gè)數(shù),接下來就是常量數(shù)組。

0A 00 02 00 03

首先第一個(gè)字節(jié)是常量的tag, 0A=10,對比一下上面的表格,10表示的是CONSTANT_Methodref方法引用。

CONSTANT_Methodref又是一個(gè)結(jié)構(gòu)體,我們再看一下方法引用的定義:

CONSTANT_Methodref_info {u1 tag;u2 class_index;u2 name_and_type_index; }

從上面的定義我們可以看出,CONSTANT_Methodref是由三部分組成的,第一部分是一個(gè)字節(jié)的tag,也就是上面的0A。

第二部分是2個(gè)字節(jié)的class_index,表示的是類在常量池中的index。

第三部分是2個(gè)字節(jié)的name_and_type_index,表示的是方法的名字和類型在常量池中的index。

先看class_index,0002=2。

常量池的第一個(gè)元素我們已經(jīng)找到了就是CONSTANT_Methodref,第二個(gè)元素就是跟在CONSTANT_Methodref后面的部分,我們看下是什么:

07 00 04

一樣的解析步驟,07=7,查表,表示的是CONSTANT_Class。

我們再看下CONSTANT_Class的定義:

CONSTANT_Class_info {u1 tag;u2 name_index; }

可以看到CONSTANT_Class占用3個(gè)字節(jié),第一個(gè)字節(jié)是tag,后面兩個(gè)字節(jié)是name在常量池中的索引。

00 04 = 4, 表示name在常量池中的索引是4。

然后我們就這樣一路找下去,就得到了所有常量池中常量的信息。

這樣找起來,眼睛都花了,有沒有什么簡單的辦法呢?

當(dāng)然有,就是上面的javap -version, 我們再回顧一下輸出結(jié)果中的常量池部分:

Constant pool:#1 = Methodref #2.#3 // java/lang/Object."<init>":()V#2 = Class #4 // java/lang/Object#3 = NameAndType #5:#6 // "<init>":()V#4 = Utf8 java/lang/Object#5 = Utf8 <init>#6 = Utf8 ()V#7 = Fieldref #8.#9 // com/flydean/JavaClassUsage.age:I#8 = Class #10 // com/flydean/JavaClassUsage#9 = NameAndType #11:#12 // age:I#10 = Utf8 com/flydean/JavaClassUsage#11 = Utf8 age#12 = Utf8 I#13 = Utf8 Code#14 = Utf8 LineNumberTable#15 = Utf8 LocalVariableTable#16 = Utf8 this#17 = Utf8 Lcom/flydean/JavaClassUsage;#18 = Utf8 inc#19 = Utf8 (I)V#20 = Utf8 number#21 = Utf8 SourceFile#22 = Utf8 JavaClassUsage.java

以第一行為例,直接告訴你常量池中第一個(gè)index的類型是Methodref,它的classref是index=2,它的NameAndType是index=3。

并且直接在后面展示出了具體的值。

描述符

且慢,在常量池中我好像看到了一些不一樣的東西,這些I,L是什么東西?

這些叫做字段描述符:

上圖是他們的各項(xiàng)含義。除了8大基礎(chǔ)類型,還有2個(gè)引用類型,分別是對象的實(shí)例,和數(shù)組。

access_flags

常量池后面就是access_flags:訪問描述符,表示的是這個(gè)class或者接口的訪問權(quán)限。

先上密碼表:

再找一下我們16進(jìn)制的access_flag:

沒錯(cuò),就是00 21。 參照上面的表格,好像沒有21,但是別怕:

21是ACC_PUBLIC和ACC_SUPER的并集。表示它有兩個(gè)access權(quán)限。

this_class和super_class

接下來是this class和super class的名字,他們都是對常量池的引用。

00 08 00 02

this class的常量池index=8, super class的常量池index=2。

看一下2和8都代表什么:

#2 = Class #4 // java/lang/Object#8 = Class #10 // com/flydean/JavaClassUsage

沒錯(cuò),JavaClassUsage的父類是Object。

大家知道為什么java只能單繼承了嗎?因?yàn)閏lass文件里面只有一個(gè)u2的位置,放不下了!

interfaces_count和interfaces[]

接下來就是接口的數(shù)目和接口的具體信息數(shù)組了。

00 00

我們沒有實(shí)現(xiàn)任何接口,所以interfaces_count=0,這時(shí)候也就沒有interfaces[]了。

fields_count和fields[]

然后是字段數(shù)目和字段具體的數(shù)組信息。

這里的字段包括類變量和實(shí)例變量。

每個(gè)字段信息也是一個(gè)結(jié)構(gòu)體:

field_info {u2 access_flags;u2 name_index;u2 descriptor_index;u2 attributes_count;attribute_info attributes[attributes_count]; }

字段的access_flag跟class的有點(diǎn)不一樣:

這里我們就不具體對比解釋了,感興趣的小伙伴可以自行體驗(yàn)。

methods_count和methods[]

接下來是方法信息。

method結(jié)構(gòu)體:

method_info {u2 access_flags;u2 name_index;u2 descriptor_index;u2 attributes_count;attribute_info attributes[attributes_count]; }

method訪問權(quán)限標(biāo)記:

attributes_count和attributes[]

attributes被用在ClassFile, field_info, method_info和Code_attribute這些結(jié)構(gòu)體中。

先看下attributes結(jié)構(gòu)體的定義:

attribute_info {u2 attribute_name_index;u4 attribute_length;u1 info[attribute_length]; }

都有哪些attributes, 這些attributes都用在什么地方呢?

其中有六個(gè)屬性對于Java虛擬機(jī)正確解釋類文件至關(guān)重要,他們是:
ConstantValue,Code,StackMapTable,BootstrapMethods,NestHost和NestMembers。

九個(gè)屬性對于Java虛擬機(jī)正確解釋類文件不是至關(guān)重要的,但是對于通過Java SE Platform的類庫正確解釋類文件是至關(guān)重要的,他們是:

Exceptions,InnerClasses,EnclosingMethod,Synthetic,Signature,SourceFile,LineNumberTable,LocalVariableTable,LocalVariableTypeTable。

其他13個(gè)屬性,不是那么重要,但是包含有關(guān)類文件的元數(shù)據(jù)。

總結(jié)

最后留給大家一個(gè)問題,java class中常量池的大小constant_pool_count是2個(gè)字節(jié),兩個(gè)字節(jié)可以表示2的16次方個(gè)常量。很明顯已經(jīng)夠大了。

但是,萬一我們寫了超過2個(gè)字節(jié)大小的常量怎么辦?歡迎大家留言給我討論。

本文鏈接:http://www.flydean.com/jvm-class-file-structure/

最通俗的解讀,最深刻的干貨,最簡潔的教程,眾多你不知道的小技巧等你來發(fā)現(xiàn)!

歡迎關(guān)注我的公眾號:「程序那些事」,懂技術(shù),更懂你!

總結(jié)

以上是生活随笔為你收集整理的JVM详解之:java class文件的密码本的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

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