<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          玩命學(xué)JVM:認(rèn)識JVM和字節(jié)碼文件

          共 2475字,需瀏覽 5分鐘

           ·

          2020-10-05 15:29

          點擊上方「藍(lán)字」關(guān)注我們

          source:https://www.cnblogs.com/cleverziv/p/13751488.html

          本篇文章的思維導(dǎo)圖

          一、JVM的簡單介紹

          1.1 JVM是什么?

          JVM (java virtual machine),java虛擬機(jī),是一個虛構(gòu)出來的計算機(jī),但是有自己完善的硬件結(jié)構(gòu):處理器、堆棧、寄存器等。java虛擬機(jī)是用于執(zhí)行字節(jié)碼文件的。

          1.2 JAVA為什么能跨平臺?

          首先我們可以問一個這樣的問題,為什么 C 語言不能跨平臺?如下圖:

          C語言在不同平臺上的對應(yīng)的編譯器會將其編譯為不同的機(jī)器碼文件,不同的機(jī)器碼文件只能在本平臺中運行。

          而java文件的執(zhí)行過程如圖:

          java通過javac將源文件編譯為.class文件(字節(jié)碼文件),該字節(jié)碼文件遵循了JVM的規(guī)范,使其可以在不同系統(tǒng)的JVM下運行。

          小結(jié)

          • java 代碼不是直接在計算機(jī)上執(zhí)行的,而是在JVM中執(zhí)行的,不同操作系統(tǒng)下的 JVM 不同,但是會提供相同的接口。

          • javac 會先將 .java 文件編譯成二進(jìn)制字節(jié)碼文件,字節(jié)碼文件與操作系統(tǒng)平臺無關(guān),只面向 JVM, 注意同一段代碼的字節(jié)碼文件是相同的。

          • 接著JVM執(zhí)行字節(jié)碼文件,不同操作系統(tǒng)下的JVM會將同樣的字節(jié)碼文件映射為不同系統(tǒng)的API調(diào)用。

          • JVM不是跨平臺的,java是跨平臺的。

          1.3 JVM為什么跨語言

          前面提到".class文件是一種遵循了JVM規(guī)范的字節(jié)碼文件",那么不難想到,只要另一種語言也同樣了遵循了JVM規(guī)范,可將其源文件編譯為.class文件,就也能在 JVM 上運行。如下圖:

          1.4 JDK、JRE、JVM的關(guān)系

          我們看一下官方給的圖:

          1.4.1 三者定義

          • JDK:JDK(Java SE Development Kit),Java標(biāo)準(zhǔn)開發(fā)包,它提供了編譯、運行Java程序所需的各種工具和資源,包括Java編譯器(javac)、Java運行時環(huán)境(JRE),以及常用的Java類庫等。

          • JRE:JRE( Java Runtime Environment) 、Java運行環(huán)境,用于解釋執(zhí)行Java的字節(jié)碼文件。普通用戶而只需要安裝 JRE 來運行 Java 程序。而程序開發(fā)者必須安裝JDK來編譯、調(diào)試程序。

          • JVM:JVM(Java Virtual Mechinal),是JRE的一部分。負(fù)責(zé)解釋執(zhí)行字節(jié)碼文件,是可運行java字節(jié)碼文件的虛擬計算機(jī)。

          1.4.2 區(qū)別和聯(lián)系

          1. JDK 用于開發(fā),JRE 用于運行java程序 ;如果只是運行Java程序,可以只安裝JRE,無需安裝JDK。

          2. JDk包含JRE,JDK 和 JRE 中都包含 JVM。

          3. JVM 是 java 編程語言的核心并且具有平臺獨立性。

          二、字節(jié)碼文件詳解

          官方文檔地址:https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-4.html#jvms-4.1

          2.1 字節(jié)碼文件的結(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];
          }
          • "ClassFile"中的“u4、u2”等指的是每項數(shù)據(jù)的所占的長度,u4表示占4個字節(jié),u2表示占2個字節(jié),以此類推。

          • .class文件是以16進(jìn)制組織的,一個16進(jìn)制位可以用4個2進(jìn)制位表示,一個2進(jìn)制位是一個bit,所以一個16進(jìn)制位是4個bit,兩個16進(jìn)制位就是8bit = 1 byte。以Main.class文件的開頭cafe為例分析:

            因此 u4 對應(yīng)4個字節(jié),就是?cafe babe

          接下來先分析?ClassFile的結(jié)構(gòu):

          1. magic
            在 class 文件開頭的四個字節(jié), 存放著 class 文件的魔數(shù), 這個魔數(shù)是 class 文件的標(biāo)志,是一個固定的值:0xcafebabe 。也就是說他是判斷一個文件是不是 class 格式的文件的標(biāo)準(zhǔn), 如果開頭四個字節(jié)不是 0xcafebabe , 那么就說明它不是 class 文件, 不能被 JVM 識別。

          2. minor_version 和 major_version
            次版本號和主版本號決定了該
            class file文件的版本,如果 major_version 記作 M,minor_version 記作 m ,則該文件的版本號為:M.m。因此,可以按字典順序?qū)︻愇募袷降陌姹具M(jìn)行排序,例如1.5 <2.0 <2.1。當(dāng)且僅當(dāng)v處于 Mi.0≤v≤Mj.m 的某個連續(xù)范圍內(nèi)時,Java 虛擬機(jī)實現(xiàn)才能支持版本 v 的類文件格式。范圍列表如下:

          3. constant_pool_count
            constant_pool_count 項的值等于 constant_pool 表中的條目數(shù)加1。如果 constant_pool 索引大于零且小于 constant_pool_count,則該索引被視為有效,但 CONSTANT_Long_info 和CONSTANT_Double_info 類型的常量除外。

          4. constant_pool
            constant_pool 是一個結(jié)構(gòu)表,表示各種字符串常量,類和接口名稱,字段名稱以及在ClassFile 結(jié)構(gòu)及其子結(jié)構(gòu)中引用的其他常量。每個 constant_pool 表條目的格式由其第一個“標(biāo)簽”字節(jié)指示。constant_pool 表的索引從1到 constant_pool_count-1。
            Java虛擬機(jī)指令不依賴于類,接口,類實例或數(shù)組的運行時布局。相反,指令引用了constant_pool 表中的符號信息。
            所有 constant_pool 表條目均具有以下常規(guī)格式:

            cp_info {
            u1 tag;
            u1 info[];
            }

          constant_pool 表中的每個條目都必須以一個1字節(jié)的標(biāo)簽開頭,該標(biāo)簽指示該條目表示的常量的種類。常量有17種,在下表中列出,并帶有相應(yīng)的標(biāo)記。每個標(biāo)簽字節(jié)后必須跟兩個或多個字節(jié),以提供有關(guān)特定常數(shù)的信息。附加信息的格式取決于標(biāo)簽字節(jié),即info數(shù)組的內(nèi)容隨標(biāo)簽的值而變化。

          1. access_flags
            access_flags 項的值是標(biāo)志的掩碼,用于表示對該類或接口的訪問權(quán)限和屬性。設(shè)置后,每個標(biāo)志的解釋在下表中指定。

          2. this_class
            this_class 項目的值必須是指向 constant_pool 表的有效索引。該索引處的 constant_pool 條目必須是代表此類文件定義的類或接口的 CONSTANT_Class_info 結(jié)構(gòu)。

            CONSTANT_Class_info {
            u1 tag;
            u2 name_index;
            }
          3. super_class
            對于一個類,父類索引的值必須為零或必須是 constant_pool 表中的有效索引。如果super_class 項的值非零,則該索引處的 constant_pool 條目必須是 CONSTANT_Class_info 結(jié)構(gòu),該結(jié)構(gòu)表示此類文件定義的類的直接超類。直接超類或其任何超類都不能在其 ClassFile結(jié)構(gòu)的 access_flags 項中設(shè)置 ACC_FINAL 標(biāo)志。如果 super_class 項的值為零,則該類只可能是 java.lang.Object ,這是沒有直接超類的唯一類或接口。對于接口,父類索引的值必須始終是 constant_pool 表中的有效索引。該索引處的 constant_pool 條目必須是 java.lang.Object 的CONSTANT_Class_info 結(jié)構(gòu)。

          4. interfaces_count
            interfaces_count 項目的值給出了此類或接口類型的直接超接口的數(shù)量。

          5. interfaces[]
            接口表的每個值都必須是 constant_pool 表中的有效索引。interfaces [i]的每個值(其中0≤i

          6. fields_count
            字段計數(shù)器的值給出了 fields 表中 field_info 結(jié)構(gòu)的數(shù)量。field_info 結(jié)構(gòu)代表此類或接口類型聲明的所有字段,包括類變量和實例變量。

          7. fields[]
            字段表中的每個值都必須是field_info結(jié)構(gòu),以提供對該類或接口中字段的完整描述。字段表僅包含此類或接口聲明的字段,不包含從超類或超接口繼承的字段。
            字段結(jié)構(gòu)如下:

                  field_info {
            u2 access_flags;
            u2 name_index;
            u2 descriptor_index;
            u2 attributes_count;
            attribute_info attributes[attributes_count];
            }
          8. methods_count
            方法計數(shù)器的值表示方法表中 method_info 結(jié)構(gòu)的數(shù)量。

          9. methods[]
            方法表中的每個值都必須是 method_info 結(jié)構(gòu),以提供對該類或接口中方法的完整描述。如果在 method_info 結(jié)構(gòu)的 access_flags 項中均未設(shè)置 ACC_NATIVE 和 ACC_ABSTRACT 標(biāo)志,則還將提供實現(xiàn)該方法的Java虛擬機(jī)指令;
            method_info 結(jié)構(gòu)表示此類或接口類型聲明的所有方法,包括實例方法,類方法,實例初始化方法以及任何類或接口初始化的方法。方法表不包含表示從超類或超接口繼承的方法。
            方法具有如下結(jié)構(gòu):

                method_info {
            u2 access_flags;
            u2 name_index;
            u2 descriptor_index;
            u2 attributes_count;
            attribute_info attributes[attributes_count];
            }
          10. attributes_count
            屬性計數(shù)器的值表示當(dāng)前類的屬性表中的屬性數(shù)量。

          11. attributes[]
            注意,這里的屬性并不是Java代碼里面的類屬性(類字段),而是Java源文件便已有特有的一些屬性(不要與 fields 混淆),屬性的結(jié)構(gòu):
            xml attribute_info { u2 attribute_name_index; u4 attribute_length; u1 info[attribute_length]; }
            屬性列表:

          2.2 實例分析

          首先寫一段Java程序,我們熟悉的“Hello World”

          public class Main {
          public static void main(String[] args) {
          System.out.println("Hello World");
          }
          }

          使用javac Main.java編譯生成Main.class文件:

          cafe?babe?0000?0034?001d?0a00?0600?0f09
          0010?0011?0800?120a?0013?0014?0700?1507
          0016?0100?063c?696e?6974?3e01?0003?2829
          5601?0004?436f?6465?0100?0f4c?696e?654e
          756d?6265?7254?6162?6c65?0100?046d?6169
          6e01?0016?285b?4c6a?6176?612f?6c61?6e67
          2f53?7472?696e?673b?2956?0100?0a53?6f75
          7263?6546?696c?6501?0009?4d61?696e?2e6a
          6176?610c?0007?0008?0700?170c?0018?0019
          0100?0b48?656c?6c6f?2057?6f72?6c64?0700
          1a0c?001b?001c?0100?044d?6169?6e01?0010
          6a61?7661?2f6c?616e?672f?4f62?6a65?6374
          0100?106a?6176?612f?6c61?6e67?2f53?7973
          7465?6d01?0003?6f75?7401?0015?4c6a?6176
          612f?696f?2f50?7269?6e74?5374?7265?616d
          3b01?0013?6a61?7661?2f69?6f2f?5072?696e
          7453?7472?6561?6d01?0007?7072?696e?746c
          6e01?0015?284c?6a61?7661?2f6c?616e?672f
          5374?7269?6e67?3b29?5600?2100?0500?0600
          0000?0000?0200?0100?0700?0800?0100?0900
          0000?1d00?0100?0100?0000?052a?b700?01b1
          0000?0001?000a?0000?0006?0001?0000?0001
          0009?000b?000c?0001?0009?0000?0025?0002
          0001?0000?0009?b200?0212?03b6?0004?b100
          0000?0100?0a00?0000?0a00?0200?0000?0400
          0800?0500?0100?0d00?0000?0200?0e

          開始按照以上知識破譯上面的Main.class文件
          按順序解析,首先是前10個字節(jié):

          cafe babe // 魔法數(shù),標(biāo)識為.class字節(jié)碼文件
          0000 0034 //版本號 52.0
          001d //常量池長度 constant_pool_count 29-1=28

          接著開始解析常量,先查看往后的第一個字節(jié):0a,對應(yīng)的常量類型CONSTANT_Methodref,對應(yīng)的結(jié)構(gòu)為:

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

          tag占一個字節(jié),class_index 占2個字節(jié),name_and_type_index 占2個自己,依次往后數(shù),注意0a就是tag,所以往后數(shù)2個字節(jié)是 class_index

          00 06 // class_index 指向常量池中第6個常量所代表的類
          00 0f // name_and_type_index 指向常量池中第15個常量所代表的方法

          通過以上方法逐個解析,最終可得到常量池為:

          0a?//?10?CONSTANT_Methodref
          00?06?//?指向常量池中第6個常量所代表的類
          00?0f?//?指向常量池中第15個常量所代表的方法

          09?CONSTANT_Fieldref
          0010?//?指向常量池中第16個常量所代表的類
          0011?//?指向常量池中第17個常量所代表的變量

          08?//?CONSTANT_String
          00?12?//?指向常量池中第18個常量所代表的變量

          0a?//?CONSTANT_Methodref
          0013?//?指向常量池中第19個常量所代表的類
          0014?//?指向常量池中第20個常量所代表的方法

          07?//?CONSTANT_Class
          00?15?//?指向常量池中第21個常量所代表的變量

          07?//?CONSTANT_Class
          0016?//?指向常量池中第22個常量所代表的變量

          01?//?CONSTANT_Utf8?標(biāo)識字符串
          00?//?下標(biāo)為0
          06?//?6個字節(jié)
          3c?696e?6974?3e?//

          01?//CONSTANT_Utf8?表示字符串
          00?//?下標(biāo)為0
          03?//?3個字節(jié)
          2829?56?//?()v

          01?//CONSTANT_Utf8?表示字符串
          00?//?下標(biāo)為0
          04?//?4個字節(jié)
          436f?6465?//?code

          01?//CONSTANT_Utf8?表示字符串
          00?//?下標(biāo)為0
          0f?//?15個字節(jié)
          4c?696e?654e?756d?6265?7254?6162?6c65?//lineNumberTable

          01?//CONSTANT_Utf8?表示字符串
          00?//?下標(biāo)為0
          04?//?4個字節(jié)
          6d?6169?6e?//main

          01?
          00
          16?
          285b?4c6a?6176?612f?6c61?6e67?2f53?7472?696e?673b?2956?//([Ljava/lang/String;)V

          0100
          0a?//10
          53?6f75?7263?6546?696c?65?//sourceFile

          01?00
          09?
          4d61?696e?2e6a?6176?61?//Main.java

          0c?//?CONSTANT_NameAndType
          0007?//nameIndex:7
          0008?//descriptor_index:8

          07?//CONSTANT_Class
          00?17?//?第21個變量

          0c
          0018?
          0019

          0100
          0b
          48?656c?6c6f?2057?6f72?6c64?//?Hello?World

          07
          00?1a

          0c?001b?001c

          0100?
          04
          4d?6169?6e?//main

          01?00
          10
          6a61?7661?2f6c?616e?672f?4f62?6a65?6374?//java/lang/Object

          0100?
          10
          6a?6176?612f?6c61?6e67?2f53?7973?7465?6d?//?java/lang/System

          01?00
          03?
          6f75?74?//?out

          01?00
          15?
          4c6a?6176?612f?696f?2f50?7269?6e74?5374?7265?616d?3b?//Ljava/io/PrintStream;

          01?00
          13?
          6a61?7661?2f69?6f2f?5072?696e?7453?7472?6561?6d?//?java/io/PrintStrea

          01?00
          07?
          7072?696e?746c?6e?//println

          01?00
          15?
          284c?6a61?7661?2f6c?616e?672f?5374?7269?6e67?3b29?56?//?(ljava/lang/String/String;)V
          常量池往后的結(jié)構(gòu)可繼續(xù)按照這種方式進(jìn)行解析。現(xiàn)在我們采用java自帶的方法來將.class文件反編譯,并驗證我們以上的解析是正確的。

          使用javap -v Main.class可得到:

           
          ?Last?modified?2020-9-29;?size?413?bytes
          ??MD5?checksum?8b2b7cdf6c4121be8e242746b4dea946
          ??Compiled?from?"Main.java"
          public?class?Main
          ??minor?version:?0
          ??major?version:?52
          ??flags:?ACC_PUBLIC,?ACC_SUPER
          Constant?pool:
          ???#1?=?Methodref??????????#6.#15?????????//?java/lang/Object."":()V
          ???#2?=?Fieldref???????????#16.#17????????//?java/lang/System.out:Ljava/io/PrintStream;
          ???#3?=?String?????????????#18????????????//?Hello?World
          ???#4?=?Methodref??????????#19.#20????????//?java/io/PrintStream.println:(Ljava/lang/String;)V
          ???#5?=?Class??????????????#21????????????//?Main
          ???#6?=?Class??????????????#22????????????//?java/lang/Object
          ???#7?=?Utf8???????????????
          ???#8?=?Utf8???????????????()V
          ???#9?=?Utf8???????????????Code
          ??#10?=?Utf8???????????????LineNumberTable
          ??#11?=?Utf8???????????????main
          ??#12?=?Utf8???????????????([Ljava/lang/String;)V
          ??#13?=?Utf8???????????????SourceFile
          ??#14?=?Utf8???????????????Main.java
          ??#15?=?NameAndType????????#7:#8??????????//?"":()V
          ??#16?=?Class??????????????#23????????????//?java/lang/System
          ??#17?=?NameAndType????????#24:#25????????//?out:Ljava/io/PrintStream;
          ??#18?=?Utf8???????????????Hello?World
          ??#19?=?Class??????????????#26????????????//?java/io/PrintStream
          ??#20?=?NameAndType????????#27:#28????????//?println:(Ljava/lang/String;)V
          ??#21?=?Utf8???????????????Main
          ??#22?=?Utf8???????????????java/lang/Object
          ??#23?=?Utf8???????????????java/lang/System
          ??#24?=?Utf8???????????????out
          ??#25?=?Utf8???????????????Ljava/io/PrintStream;
          ??#26?=?Utf8???????????????java/io/PrintStream
          ??#27?=?Utf8???????????????println
          ??#28?=?Utf8???????????????(Ljava/lang/String;)V
          {
          ??public?Main();
          ????descriptor:?()V
          ????flags:?ACC_PUBLIC
          ????Code:
          ??????stack=1,?locals=1,?args_size=1
          ?????????0:?aload_0
          ?????????1:?invokespecial?#1??????????????????//?Method?java/lang/Object."":()V
          ?????????4:?return
          ??????LineNumberTable:
          ????????line?1:?0

          ??public?static?void?main(java.lang.String[]);
          ????descriptor:?([Ljava/lang/String;)V
          ????flags:?ACC_PUBLIC,?ACC_STATIC
          ????Code:
          ??????stack=2,?locals=1,?args_size=1
          ?????????0:?getstatic?????#2??????????????????//?Field?java/lang/System.out:Ljava/io/PrintStream;
          ?????????3:?ldc???????????#3??????????????????//?String?Hello?World
          ?????????5:?invokevirtual?#4??????????????????//?Method?java/io/PrintStream.println:(Ljava/lang/String;)V
          ?????????8:?return
          ??????LineNumberTable:
          ????????line?4:?0
          ????????line?5:?8
          }
          SourceFile:?"Main.java"

          對比下可以發(fā)現(xiàn)與我們?nèi)斯そ馕龅慕Y(jié)果是一致的。

          小結(jié)

          本文第一部分圍繞JVM的幾個常見的問題做了一些簡單介紹。第二部分詳細(xì)介紹了ClassFile的結(jié)構(gòu)及 JVM 對 ClassFile 指定的規(guī)范(更多詳細(xì)的規(guī)范有興趣的讀者可查看官方文檔),接著按照規(guī)范進(jìn)行了部分字節(jié)碼的手動解析,并與 JVM 的解析結(jié)果進(jìn)行了對比。個人認(rèn)為作為偏應(yīng)用層的programer沒必要去記憶這些“規(guī)范”,而是要跳出這些繁雜的規(guī)范掌握到以下幾點:

          1. 會借助官方文檔對字節(jié)碼文件做簡單閱讀。

          2. 理解字節(jié)碼文件在整個執(zhí)行過程的角色和作用,其實就是一個“編解碼”的過程。javac將.java文件按照J(rèn)VM的規(guī)則生成字節(jié)碼文件,JVM按照規(guī)范解析字節(jié)碼文件為機(jī)器可執(zhí)行的指令。

          掃碼二維碼

          獲取更多精彩

          Java樂園

          有用!分享+在看?
          瀏覽 58
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  中文字幕超碰在线播放 | 中文字幕亚洲有码 | 91嫩|婷婷丨入口图片 | 亚洲 欧美 乱伦 | 亚洲日本在线观看视频 |