摘要:本文已收錄修煉內功躍遷之路學習語言的時候,需要在不同的目標操作系統上或者使用交叉編譯環境,使用正確的指令集編譯成對應操作系統可運行的執行文件,才可以在相應的系統上運行,如果使用操作系統差異性的庫或者接口,還需要針對不同的系統做不同的處理宏的
本文已收錄【修煉內功】躍遷之路
學習C語言的時候,需要在不同的目標操作系統上(或者使用交叉編譯環境),(使用正確的CPU指令集)編譯成對應操作系統可運行的執行文件,才可以在相應的系統上運行,如果使用操作系統差異性的庫或者接口,還需要針對不同的系統做不同的處理(宏)
Java的出現也正是為了解決"平臺無關性","Write Once, Run Anywhere"的口號也充分表達了軟件開發人員對沖破平臺接線的渴求
"與平臺無關"的最終實現還是要在操作系統的應用層上,這便是JVM的存在,不同的平臺有不同的JVM,而所有的JVM都可以載入與平臺無關的字節碼,從而實現程序的"一次編寫,到處運行"
JVM并非只為Java設計,而字節碼也并非只有Java才可以編譯得到,早在Java發展之初,設計者便將Java規范拆分為Java語言規范及Java虛擬機規范,同時也承諾,對JVM做適當的擴展,以便更好地支持其他語言運行于JVM之上,而這一切的基礎便是Class文件(字節碼文件),Class文件中存放了JVM可以理解運行的字節碼命令
In the future, we will consider bounded extensions to th Java virtual machine to provide better support for other languages
JVM并不關心Class的來源是何種語言,在JVM發展到1.7~1.8的時候,設計者通過JSR-292基本兌現了以上承諾
本篇不會去詳細地介紹如何去解析Class文件,目的是為了了解Class文件的結構,Class文件中都包含哪些內容
Class文件可以由JVM加載并執行,其中記錄了類信息、變量信息、方法信息、字節碼指令等等,雖然JVM加載Class之后(在JIT之前)進行的是解釋執行,但Class文件并不是文本文件,而是被嚴格定義的二進制流文件
接下來,均會以這段代碼為示例進行分析
import java.io.Serializable; public class ClassStruct implements Serializable { private static final String HELLO = "hello"; private String name; public ClassStruct(String name) { this.name = name; } public void print() { System.out.println(HELLO + ": " + name); } public static void main(String[] args) { ClassStruct classStruct = new ClassStruct("ManerFan"); classStruct.print(); } }
使用$ javac ClassStruct.java進行編譯,編譯后的文件可以使用$ javap -p -v ClassStruct查看Class文件的內容(見文章末尾)
魔數很多文件存儲都會使用魔數來進行身份識別,比如圖片文件,即使將圖片文件改為不正確的后綴,絕大多數圖片預覽器也會正確解析
同樣Class文件也不例外,使用二進制模式打開Class文件,會發現所有Class文件的前四個字節均為OxCAFEBABE,這個魔術在Java還被稱為"Oak"語言的時候就已經確定下來了
版本號緊接著魔術的四個字節(接下來不再對照二進制進行查看,而是直接查看javap幫我們解析出來的結果,見文章末尾)存儲的是Class文件的版本號,前兩個字節為次版本號(minor version),后兩個字節為主版本號(major version)
Java版本號從45開始,高版本的JDK可以向下兼容低版本的Class文件,但無法向上兼容高版本,即使文件格式并未發生變化
常量池緊接著主次版本號之后的是常量池入口
常量池中主要存放兩大類常量:字面量(Literal)和符號引用(Symbolic Reference)
字面量:如文本字符串、被聲明為final的常量值等
符號引用:類和接口的全限定名(Fully Qualified Name)、字段的名稱和描述(Descriptor)、方法的名稱和描述符
Java代碼在進行編譯時,并不像C或C++那樣有"連接"這一步驟,而是在虛擬機加載Class文件時進行動態連接,Class文件中不會保存各方法和字段的內存布局,在虛擬機運行時,需要從常量池中獲得對應的符號引用,再在類創建或運行時解析并翻譯到具體的內存地址中,才能被虛擬機使用
訪問標志訪問標志用于識別一些類或接口層次的訪問信息
訪問標志用于識別這個Class是類還是接口;是否定義為public;是否為abstract類型;是否聲明為final;等等,具體標志含義如下
標志 | 名稱 |
---|---|
ACC_PUBLIC | 是否為public類型 |
ACC_FINAL | 是否被聲明為final |
ACC_SUPER | 是否允許使用invokespecial字節碼指令 |
ACC_INTERFACE | 是否為接口 |
ACC_ABSTRACT | 是否為abstract |
ACC_SYNTHETIC | 標識這個類并非由用戶代碼生成 |
ACC_ANNOTATION | 標識這是一個注解 |
ACC_ENUM | 標識這是一個枚舉 |
Class文件中由類索引(this_class)、父類索引(super_class)及接口索引集合(interfaces)三項數據確定這個類的繼承關系
父類索引只有一個(對應extends語句),而接口索引則是一個集合(對應implements語句)
字段表集合字段表(field_info)用于描述類或者接口中聲明的變量
字段(field)包括了類級變量(如static)及實例級變量,但不包括在方法內部聲明的變量
字段包含的信息有:作用域(public、private、protected)、類級還是實例級(static)、可變性(final)、并發可見性(volatile)、可否序列化(transient)、數據類型、字段名等
描述符這里簡單解釋一下描述符(descriptor)
描述符用來描述字段數據類型、方法參數列表和返回值,根據描述符規則,基本數據類型及代表無返回值的void類型都用一個大寫字符表示,對象類型則用字符L加對象全限定名來表示
標識字符 | 含義 |
---|---|
B | byte |
C | char |
D | double |
F | float |
I | int |
J | long |
S | short |
Z | boolean |
V | void |
L | 對象類型,如 Ljava/lang/Object; |
對于數組,每一個維度使用一個前置的[來描述,如java.lang.String[][]將被記錄為[[java/lang/String;,int[]將被記錄為[I
描述方法時,按照先參數列表,后返回值的順序描述,參數列表放在()內,如void inc()描述符為()V,方法java.lang.String toString()描述符為()Ljava/lang/String;,方法int indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex的描述符為([CII[CIII)I
方法表集合Class文件中對方法的描述與對字段的描述幾乎采用了完全一致的方式,方法表的結構依次包括了訪問標志(access_flags)、名稱索引(name_index)、描述符索引(descriptor_index)、屬性表集合(attributes),但是方法內部的代碼并不在方法表中,而是經過編譯器編譯成字節碼指令后,存放在屬性表集合中一個名為"Code"的屬性中
屬性表集合在Class文件、字段表、方法表中都可以攜帶自己的屬性表集合,用于描述某些場景專有的信息,Java虛擬機規范中預定義了9種虛擬機實現應當能識別的屬性
屬性名稱 | 使用位置 | 含義 |
---|---|---|
Code | 方法表 | Java代碼編譯成的字節碼指令 |
ConstantValue | 字段表 | final關鍵自定義的常量值 |
Deprecated | 類、方法表、字段表 | 被聲明為deprecated的方法和字段 |
Exceptions | 方法表 | 方法拋出的異常 |
InnerClasses | 類文件 | 內部類列表 |
LineNumberTable | Code屬性 | Java源碼的行號與字節碼指令的對應關系 |
LocalVariableTable | Code屬性 | 方法的局部變量描述 |
SourceFile | 類文件 | 原文件名稱 |
Synthetic | 類、方法表、字段表 | 標識方法或字段為編譯器自動生成的 |
關于屬性表,會在之后的文章中穿插介紹
附:Class文件Classfile ~/articles/【修煉內功】躍遷之路/JVM/[JVM] 類文件結構/src/ClassStruct.class Last modified 2019-6-2; size 829 bytes MD5 checksum 9f7454acd0455837a33ff8e03edffdb3 Compiled from "ClassStruct.java" public class ClassStruct implements java.io.Serializable minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #14.#31 // java/lang/Object."":()V #2 = Fieldref #6.#32 // ClassStruct.name:Ljava/lang/String; #3 = Fieldref #33.#34 // java/lang/System.out:Ljava/io/PrintStream; #4 = Class #35 // java/lang/StringBuilder #5 = Methodref #4.#31 // java/lang/StringBuilder." ":()V #6 = Class #36 // ClassStruct #7 = String #37 // hello: #8 = Methodref #4.#38 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #9 = Methodref #4.#39 // java/lang/StringBuilder.toString:()Ljava/lang/String; #10 = Methodref #40.#41 // java/io/PrintStream.println:(Ljava/lang/String;)V #11 = String #42 // ManerFan #12 = Methodref #6.#43 // ClassStruct." ":(Ljava/lang/String;)V #13 = Methodref #6.#44 // ClassStruct.print:()V #14 = Class #45 // java/lang/Object #15 = Class #46 // java/io/Serializable #16 = Utf8 HELLO #17 = Utf8 Ljava/lang/String; #18 = Utf8 ConstantValue #19 = String #47 // hello #20 = Utf8 name #21 = Utf8 #22 = Utf8 (Ljava/lang/String;)V #23 = Utf8 Code #24 = Utf8 LineNumberTable #25 = Utf8 print #26 = Utf8 ()V #27 = Utf8 main #28 = Utf8 ([Ljava/lang/String;)V #29 = Utf8 SourceFile #30 = Utf8 ClassStruct.java #31 = NameAndType #21:#26 // " ":()V #32 = NameAndType #20:#17 // name:Ljava/lang/String; #33 = Class #48 // java/lang/System #34 = NameAndType #49:#50 // out:Ljava/io/PrintStream; #35 = Utf8 java/lang/StringBuilder #36 = Utf8 ClassStruct #37 = Utf8 hello: #38 = NameAndType #51:#52 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #39 = NameAndType #53:#54 // toString:()Ljava/lang/String; #40 = Class #55 // java/io/PrintStream #41 = NameAndType #56:#22 // println:(Ljava/lang/String;)V #42 = Utf8 ManerFan #43 = NameAndType #21:#22 // " ":(Ljava/lang/String;)V #44 = NameAndType #25:#26 // print:()V #45 = Utf8 java/lang/Object #46 = Utf8 java/io/Serializable #47 = Utf8 hello #48 = Utf8 java/lang/System #49 = Utf8 out #50 = Utf8 Ljava/io/PrintStream; #51 = Utf8 append #52 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder; #53 = Utf8 toString #54 = Utf8 ()Ljava/lang/String; #55 = Utf8 java/io/PrintStream #56 = Utf8 println { private static final java.lang.String HELLO; descriptor: Ljava/lang/String; flags: ACC_PRIVATE, ACC_STATIC, ACC_FINAL ConstantValue: String hello private java.lang.String name; descriptor: Ljava/lang/String; flags: ACC_PRIVATE public ClassStruct(java.lang.String); descriptor: (Ljava/lang/String;)V flags: ACC_PUBLIC Code: stack=2, locals=2, args_size=2 0: aload_0 1: invokespecial #1 // Method java/lang/Object." ":()V 4: aload_0 5: aload_1 6: putfield #2 // Field name:Ljava/lang/String; 9: return LineNumberTable: line 7: 0 line 8: 4 line 9: 9 public void print(); descriptor: ()V flags: ACC_PUBLIC Code: stack=3, locals=1, args_size=1 0: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 3: new #4 // class java/lang/StringBuilder 6: dup 7: invokespecial #5 // Method java/lang/StringBuilder." ":()V 10: ldc #7 // String hello: 12: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 15: aload_0 16: getfield #2 // Field name:Ljava/lang/String; 19: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 22: invokevirtual #9 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 25: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 28: return LineNumberTable: line 12: 0 line 13: 28 public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=2, args_size=1 0: new #6 // class ClassStruct 3: dup 4: ldc #11 // String ManerFan 6: invokespecial #12 // Method " ":(Ljava/lang/String;)V 9: astore_1 10: aload_1 11: invokevirtual #13 // Method print:()V 14: return LineNumberTable: line 16: 0 line 17: 10 line 18: 14 } SourceFile: "ClassStruct.java"
參考:
深入理解Java虛擬機
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/74750.html
摘要:本文已收錄修煉內功躍遷之路在誕生之初便提出,各提供商發布很多不同平臺的虛擬機,這些虛擬機都可以載入并執行同平臺無關的字節碼。設計者在第一版虛擬機規范中便承諾,時至今日,商業機構和開源機構已在之外發展出一大批可以在上運行的語言,如等。 本文已收錄【修煉內功】躍遷之路 Java在誕生之初便提出 Write Once, Run Anywhere,各提供商發布很多不同平臺的虛擬機,這些虛擬機...
摘要:本文已收錄修煉內功躍遷之路在淺談虛擬機內存模型一文中有簡單介紹過,虛擬機棧是線程私有的,每個方法在執行的同時都會創建一個棧幀,方法執行時棧幀入棧,方法結束時棧幀出棧,虛擬機中棧幀的入棧順序就是方法的調用順序寫了很多文字,但都不盡如意,十分慚 本文已收錄【修煉內功】躍遷之路 showImg(https://segmentfault.com/img/bVbtSi5?w=1654&h=96...
摘要:本文已收錄修煉內功躍遷之路初次接觸的時候感覺表達式很神奇表達式帶來的編程新思路,但又總感覺它就是匿名類或者內部類的語法糖而已,只是語法上更為簡潔罷了,如同以下的代碼匿名類內部類編譯后會產生三個文件雖然從使用效果來看,與匿名類或者內部類有相 本文已收錄【修煉內功】躍遷之路 showImg(https://segmentfault.com/img/bVbui4o?w=800&h=600)...
摘要:本文已收錄修煉內功躍遷之路我們寫的方法在被編譯為文件后是如何被虛擬機執行的對于重寫或者重載的方法,是在編譯階段就確定具體方法的么如果不是,虛擬機在運行時又是如何確定具體方法的方法調用不等于方法執行,一切方法調用在文件中都只是常量池中的符號引 本文已收錄【修煉內功】躍遷之路 showImg(https://segmentfault.com/img/bVbuesq?w=2114&h=12...
摘要:也正是因此,一旦出現內存泄漏或溢出問題,如果不了解的內存管理原理,那么將會對問題的排查帶來極大的困難。 本文已收錄【修煉內功】躍遷之路 showImg(https://segmentfault.com/img/bVbsP9I?w=1024&h=580); 不論做技術還是做業務,對于Java開發人員來講,理解JVM各種原理的重要性不必再多言 對于C/C++而言,可以輕易地操作任意地址的...
閱讀 1422·2021-11-15 11:38
閱讀 3566·2021-11-09 09:47
閱讀 1969·2021-09-27 13:36
閱讀 3211·2021-09-22 15:17
閱讀 2547·2021-09-13 10:27
閱讀 2862·2019-08-30 15:44
閱讀 1158·2019-08-27 10:53
閱讀 2702·2019-08-26 14:00