欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

淺談JVM之java class文件的密碼本

 更新時間:2021年06月02日 14:49:24   作者:flydean  
一切的一切都是從javac開始的。從那一刻開始,java文件就從我們?nèi)庋劭煞直娴奈谋疚募?,變成了冷冰冰的二進制文件。變成了二進制文件是不是意味著我們無法再深入的去了解java class文件了呢?答案是否定的。本文將詳細介紹JVM之java class文件的密碼本。

簡介

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

下面,讓我們開始這個激動人心的過程吧。

一個簡單的class

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

public class JavaClassUsage {

    private int age=18;

    public void inc(int number){
        this.age=this.age+ number;
    }
}

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

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

接下來我們使用javac來進行編譯。

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

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

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

 javap -verbose JavaClassUsage

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

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

public class com.flydean.JavaClassUsage

  minor version: 0

  major version: 58

  flags: 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: ()V

    flags: ACC_PUBLIC

    Code:

      stack=2, locals=1, args_size=1

         0: aload_0

         1: invokespecial #1                  // Method java/lang/Object."<init>":()V

         4: aload_0

         5: bipush        18

         7: putfield      #7                  // Field age:I

        10: return

      LineNumberTable:

        line 7: 0

        line 9: 4

      LocalVariableTable:

        Start  Length  Slot  Name   Signature

            0      11     0  this   Lcom/flydean/JavaClassUsage;

  public void inc(int);

    descriptor: (I)V

    flags: ACC_PUBLIC

    Code:

      stack=3, locals=2, args_size=2

         0: aload_0

         1: aload_0

         2: getfield      #7                  // Field age:I

         5: iload_1

         6: iadd

         7: putfield      #7                  // Field age:I

        10: return

      LineNumberTable:

        line 12: 0

        line 13: 10

      LocalVariableTable:

        Start  Length  Slot  Name   Signature

            0      11     0  this   Lcom/flydean/JavaClassUsage;

            0      11     1 number   I

}

SourceFile: "JavaClassUsage.java"

ClassFile的二進制文件

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

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

你準(zhǔn)備好了嗎?來吧,展示吧!

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

是的,沒錯,你會讀機器語言了!

class文件的密碼本

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

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

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

下面就是密碼本,也就是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表示的是無符號的兩個字節(jié),無符號的4個字節(jié)。

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

我們對比著上面的二進制文件一個一個的來理解。

magic

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

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

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

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

來一杯咖啡吧,baby!

多么有詩意的畫面。

version

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

00 00 00 3A

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

常量池

接下來是常量池。

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

00 17

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

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

第二點,常量池數(shù)組的第0位是作為一個保留位,表示“不引用任何常量池項目”,為某些特殊的情況下使用。

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

常量池數(shù)組中存了些什么東西呢?

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

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

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

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

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

0A 00 02 00 03

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

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

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

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

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

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

先看class_index,0002=2。

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

07 00 04

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

我們再看下CONSTANT_Class的定義:

CONSTANT_Class_info {
    u1 tag;
    u2 name_index;
}

可以看到CONSTANT_Class占用3個字節(jié),第一個字節(jié)是tag,后面兩個字節(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

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

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

描述符

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

這些叫做字段描述符:

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

access_flags

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

先上密碼表:

再找一下我們16進制的access_flag:

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

21是ACC_PUBLIC和ACC_SUPER的并集。表示它有兩個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

沒錯,JavaClassUsage的父類是Object。

大家知道為什么java只能單繼承了嗎?因為class文件里面只有一個u2的位置,放不下了!

interfaces_count和interfaces[]

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

00 00

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

fields_count和fields[]

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

這里的字段包括類變量和實例變量。

每個字段信息也是一個結(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的有點不一樣:

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

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都用在什么地方呢?

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

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

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

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

總結(jié)

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

以上就是淺談JVM之java class文件的密碼本的詳細內(nèi)容,更多關(guān)于JVM之java class文件的密碼本的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Eclipse Debug模式的開啟與關(guān)閉問題簡析

    Eclipse Debug模式的開啟與關(guān)閉問題簡析

    這篇文章主要介紹了Eclipse Debug模式的開啟與關(guān)閉問題簡析,同時向大家介紹了一個簡單的debug模式啟動不起來的解決方法,希望對大家有所幫助。
    2017-10-10
  • Spring?Data?JPA關(guān)系映射@OneToOne實例解析

    Spring?Data?JPA關(guān)系映射@OneToOne實例解析

    這篇文章主要為大家介紹了Spring?Data?JPA關(guān)系映射@OneToOne實例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-08-08
  • Java中HashMap集合的6種遍歷方式詳解

    Java中HashMap集合的6種遍歷方式詳解

    這篇文章主要介紹了Java中HashMap集合的6種遍歷方式詳解,HashMap?基于哈希表的?Map?接口實現(xiàn),是以?key-value?存儲形式存在,即主要用來存放鍵值對,HashMap?的實現(xiàn)不是同步的,這意味著它不是線程安全的,我們來看一下其遍歷方式,需要的朋友可以參考下
    2023-12-12
  • 最新評論