摘要:本文已收錄修煉內(nèi)功躍遷之路學(xué)習(xí)語(yǔ)言的時(shí)候,需要在不同的目標(biāo)操作系統(tǒng)上或者使用交叉編譯環(huán)境,使用正確的指令集編譯成對(duì)應(yīng)操作系統(tǒng)可運(yùn)行的執(zhí)行文件,才可以在相應(yīng)的系統(tǒng)上運(yùn)行,如果使用操作系統(tǒng)差異性的庫(kù)或者接口,還需要針對(duì)不同的系統(tǒng)做不同的處理宏的
本文已收錄【修煉內(nèi)功】躍遷之路
學(xué)習(xí)C語(yǔ)言的時(shí)候,需要在不同的目標(biāo)操作系統(tǒng)上(或者使用交叉編譯環(huán)境),(使用正確的CPU指令集)編譯成對(duì)應(yīng)操作系統(tǒng)可運(yùn)行的執(zhí)行文件,才可以在相應(yīng)的系統(tǒng)上運(yùn)行,如果使用操作系統(tǒng)差異性的庫(kù)或者接口,還需要針對(duì)不同的系統(tǒng)做不同的處理(宏)
Java的出現(xiàn)也正是為了解決"平臺(tái)無關(guān)性","Write Once, Run Anywhere"的口號(hào)也充分表達(dá)了軟件開發(fā)人員對(duì)沖破平臺(tái)接線的渴求
"與平臺(tái)無關(guān)"的最終實(shí)現(xiàn)還是要在操作系統(tǒng)的應(yīng)用層上,這便是JVM的存在,不同的平臺(tái)有不同的JVM,而所有的JVM都可以載入與平臺(tái)無關(guān)的字節(jié)碼,從而實(shí)現(xiàn)程序的"一次編寫,到處運(yùn)行"
JVM并非只為Java設(shè)計(jì),而字節(jié)碼也并非只有Java才可以編譯得到,早在Java發(fā)展之初,設(shè)計(jì)者便將Java規(guī)范拆分為Java語(yǔ)言規(guī)范及Java虛擬機(jī)規(guī)范,同時(shí)也承諾,對(duì)JVM做適當(dāng)?shù)臄U(kuò)展,以便更好地支持其他語(yǔ)言運(yùn)行于JVM之上,而這一切的基礎(chǔ)便是Class文件(字節(jié)碼文件),Class文件中存放了JVM可以理解運(yùn)行的字節(jié)碼命令
In the future, we will consider bounded extensions to th Java virtual machine to provide better support for other languages
JVM并不關(guān)心Class的來源是何種語(yǔ)言,在JVM發(fā)展到1.7~1.8的時(shí)候,設(shè)計(jì)者通過JSR-292基本兌現(xiàn)了以上承諾
本篇不會(huì)去詳細(xì)地介紹如何去解析Class文件,目的是為了了解Class文件的結(jié)構(gòu),Class文件中都包含哪些內(nèi)容
Class文件可以由JVM加載并執(zhí)行,其中記錄了類信息、變量信息、方法信息、字節(jié)碼指令等等,雖然JVM加載Class之后(在JIT之前)進(jìn)行的是解釋執(zhí)行,但Class文件并不是文本文件,而是被嚴(yán)格定義的二進(jìn)制流文件
接下來,均會(huì)以這段代碼為示例進(jìn)行分析
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進(jìn)行編譯,編譯后的文件可以使用$ javap -p -v ClassStruct查看Class文件的內(nèi)容(見文章末尾)
魔數(shù)很多文件存儲(chǔ)都會(huì)使用魔數(shù)來進(jìn)行身份識(shí)別,比如圖片文件,即使將圖片文件改為不正確的后綴,絕大多數(shù)圖片預(yù)覽器也會(huì)正確解析
同樣Class文件也不例外,使用二進(jìn)制模式打開Class文件,會(huì)發(fā)現(xiàn)所有Class文件的前四個(gè)字節(jié)均為OxCAFEBABE,這個(gè)魔術(shù)在Java還被稱為"Oak"語(yǔ)言的時(shí)候就已經(jīng)確定下來了
版本號(hào)緊接著魔術(shù)的四個(gè)字節(jié)(接下來不再對(duì)照二進(jìn)制進(jìn)行查看,而是直接查看javap幫我們解析出來的結(jié)果,見文章末尾)存儲(chǔ)的是Class文件的版本號(hào),前兩個(gè)字節(jié)為次版本號(hào)(minor version),后兩個(gè)字節(jié)為主版本號(hào)(major version)
Java版本號(hào)從45開始,高版本的JDK可以向下兼容低版本的Class文件,但無法向上兼容高版本,即使文件格式并未發(fā)生變化
常量池緊接著主次版本號(hào)之后的是常量池入口
常量池中主要存放兩大類常量:字面量(Literal)和符號(hào)引用(Symbolic Reference)
字面量:如文本字符串、被聲明為final的常量值等
符號(hào)引用:類和接口的全限定名(Fully Qualified Name)、字段的名稱和描述(Descriptor)、方法的名稱和描述符
Java代碼在進(jìn)行編譯時(shí),并不像C或C++那樣有"連接"這一步驟,而是在虛擬機(jī)加載Class文件時(shí)進(jìn)行動(dòng)態(tài)連接,Class文件中不會(huì)保存各方法和字段的內(nèi)存布局,在虛擬機(jī)運(yùn)行時(shí),需要從常量池中獲得對(duì)應(yīng)的符號(hào)引用,再在類創(chuàng)建或運(yùn)行時(shí)解析并翻譯到具體的內(nèi)存地址中,才能被虛擬機(jī)使用
訪問標(biāo)志訪問標(biāo)志用于識(shí)別一些類或接口層次的訪問信息
訪問標(biāo)志用于識(shí)別這個(gè)Class是類還是接口;是否定義為public;是否為abstract類型;是否聲明為final;等等,具體標(biāo)志含義如下
標(biāo)志 | 名稱 |
---|---|
ACC_PUBLIC | 是否為public類型 |
ACC_FINAL | 是否被聲明為final |
ACC_SUPER | 是否允許使用invokespecial字節(jié)碼指令 |
ACC_INTERFACE | 是否為接口 |
ACC_ABSTRACT | 是否為abstract |
ACC_SYNTHETIC | 標(biāo)識(shí)這個(gè)類并非由用戶代碼生成 |
ACC_ANNOTATION | 標(biāo)識(shí)這是一個(gè)注解 |
ACC_ENUM | 標(biāo)識(shí)這是一個(gè)枚舉 |
Class文件中由類索引(this_class)、父類索引(super_class)及接口索引集合(interfaces)三項(xiàng)數(shù)據(jù)確定這個(gè)類的繼承關(guān)系
父類索引只有一個(gè)(對(duì)應(yīng)extends語(yǔ)句),而接口索引則是一個(gè)集合(對(duì)應(yīng)implements語(yǔ)句)
字段表集合字段表(field_info)用于描述類或者接口中聲明的變量
字段(field)包括了類級(jí)變量(如static)及實(shí)例級(jí)變量,但不包括在方法內(nèi)部聲明的變量
字段包含的信息有:作用域(public、private、protected)、類級(jí)還是實(shí)例級(jí)(static)、可變性(final)、并發(fā)可見性(volatile)、可否序列化(transient)、數(shù)據(jù)類型、字段名等
描述符這里簡(jiǎn)單解釋一下描述符(descriptor)
描述符用來描述字段數(shù)據(jù)類型、方法參數(shù)列表和返回值,根據(jù)描述符規(guī)則,基本數(shù)據(jù)類型及代表無返回值的void類型都用一個(gè)大寫字符表示,對(duì)象類型則用字符L加對(duì)象全限定名來表示
標(biāo)識(shí)字符 | 含義 |
---|---|
B | byte |
C | char |
D | double |
F | float |
I | int |
J | long |
S | short |
Z | boolean |
V | void |
L | 對(duì)象類型,如 Ljava/lang/Object; |
對(duì)于數(shù)組,每一個(gè)維度使用一個(gè)前置的[來描述,如java.lang.String[][]將被記錄為[[java/lang/String;,int[]將被記錄為[I
描述方法時(shí),按照先參數(shù)列表,后返回值的順序描述,參數(shù)列表放在()內(nè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文件中對(duì)方法的描述與對(duì)字段的描述幾乎采用了完全一致的方式,方法表的結(jié)構(gòu)依次包括了訪問標(biāo)志(access_flags)、名稱索引(name_index)、描述符索引(descriptor_index)、屬性表集合(attributes),但是方法內(nèi)部的代碼并不在方法表中,而是經(jīng)過編譯器編譯成字節(jié)碼指令后,存放在屬性表集合中一個(gè)名為"Code"的屬性中
屬性表集合在Class文件、字段表、方法表中都可以攜帶自己的屬性表集合,用于描述某些場(chǎng)景專有的信息,Java虛擬機(jī)規(guī)范中預(yù)定義了9種虛擬機(jī)實(shí)現(xiàn)應(yīng)當(dāng)能識(shí)別的屬性
屬性名稱 | 使用位置 | 含義 |
---|---|---|
Code | 方法表 | Java代碼編譯成的字節(jié)碼指令 |
ConstantValue | 字段表 | final關(guān)鍵自定義的常量值 |
Deprecated | 類、方法表、字段表 | 被聲明為deprecated的方法和字段 |
Exceptions | 方法表 | 方法拋出的異常 |
InnerClasses | 類文件 | 內(nèi)部類列表 |
LineNumberTable | Code屬性 | Java源碼的行號(hào)與字節(jié)碼指令的對(duì)應(yīng)關(guān)系 |
LocalVariableTable | Code屬性 | 方法的局部變量描述 |
SourceFile | 類文件 | 原文件名稱 |
Synthetic | 類、方法表、字段表 | 標(biāo)識(shí)方法或字段為編譯器自動(dòng)生成的 |
關(guān)于屬性表,會(huì)在之后的文章中穿插介紹
附:Class文件Classfile ~/articles/【修煉內(nèi)功】躍遷之路/JVM/[JVM] 類文件結(jié)構(gòu)/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虛擬機(jī)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/74750.html
摘要:本文已收錄修煉內(nèi)功躍遷之路在誕生之初便提出,各提供商發(fā)布很多不同平臺(tái)的虛擬機(jī),這些虛擬機(jī)都可以載入并執(zhí)行同平臺(tái)無關(guān)的字節(jié)碼。設(shè)計(jì)者在第一版虛擬機(jī)規(guī)范中便承諾,時(shí)至今日,商業(yè)機(jī)構(gòu)和開源機(jī)構(gòu)已在之外發(fā)展出一大批可以在上運(yùn)行的語(yǔ)言,如等。 本文已收錄【修煉內(nèi)功】躍遷之路 Java在誕生之初便提出 Write Once, Run Anywhere,各提供商發(fā)布很多不同平臺(tái)的虛擬機(jī),這些虛擬機(jī)...
摘要:本文已收錄修煉內(nèi)功躍遷之路在淺談虛擬機(jī)內(nèi)存模型一文中有簡(jiǎn)單介紹過,虛擬機(jī)棧是線程私有的,每個(gè)方法在執(zhí)行的同時(shí)都會(huì)創(chuàng)建一個(gè)棧幀,方法執(zhí)行時(shí)棧幀入棧,方法結(jié)束時(shí)棧幀出棧,虛擬機(jī)中棧幀的入棧順序就是方法的調(diào)用順序?qū)懥撕芏辔淖?,但都不盡如意,十分慚 本文已收錄【修煉內(nèi)功】躍遷之路 showImg(https://segmentfault.com/img/bVbtSi5?w=1654&h=96...
摘要:本文已收錄修煉內(nèi)功躍遷之路初次接觸的時(shí)候感覺表達(dá)式很神奇表達(dá)式帶來的編程新思路,但又總感覺它就是匿名類或者內(nèi)部類的語(yǔ)法糖而已,只是語(yǔ)法上更為簡(jiǎn)潔罷了,如同以下的代碼匿名類內(nèi)部類編譯后會(huì)產(chǎn)生三個(gè)文件雖然從使用效果來看,與匿名類或者內(nèi)部類有相 本文已收錄【修煉內(nèi)功】躍遷之路 showImg(https://segmentfault.com/img/bVbui4o?w=800&h=600)...
摘要:本文已收錄修煉內(nèi)功躍遷之路我們寫的方法在被編譯為文件后是如何被虛擬機(jī)執(zhí)行的對(duì)于重寫或者重載的方法,是在編譯階段就確定具體方法的么如果不是,虛擬機(jī)在運(yùn)行時(shí)又是如何確定具體方法的方法調(diào)用不等于方法執(zhí)行,一切方法調(diào)用在文件中都只是常量池中的符號(hào)引 本文已收錄【修煉內(nèi)功】躍遷之路 showImg(https://segmentfault.com/img/bVbuesq?w=2114&h=12...
摘要:也正是因此,一旦出現(xiàn)內(nèi)存泄漏或溢出問題,如果不了解的內(nèi)存管理原理,那么將會(huì)對(duì)問題的排查帶來極大的困難。 本文已收錄【修煉內(nèi)功】躍遷之路 showImg(https://segmentfault.com/img/bVbsP9I?w=1024&h=580); 不論做技術(shù)還是做業(yè)務(wù),對(duì)于Java開發(fā)人員來講,理解JVM各種原理的重要性不必再多言 對(duì)于C/C++而言,可以輕易地操作任意地址的...
閱讀 1432·2021-11-15 11:38
閱讀 3582·2021-11-09 09:47
閱讀 1984·2021-09-27 13:36
閱讀 3230·2021-09-22 15:17
閱讀 2565·2021-09-13 10:27
閱讀 2878·2019-08-30 15:44
閱讀 1193·2019-08-27 10:53
閱讀 2721·2019-08-26 14:00