JVM真香系列:Java文件到.Class文件
什么是JVM
JVM 全稱 Java Virtual Machine,也就是我們耳熟能詳?shù)?Java 虛擬機(jī)。它能識(shí)別 .class后綴的文件,并且能夠解析它的指令,最終調(diào)用操作系統(tǒng)上的函數(shù),完成我們想要的操作。
可能有部分小伙伴學(xué)習(xí)過(guò)C++,C++開(kāi)發(fā)出來(lái)的程序,編譯成二進(jìn)制文件后,就可以直接執(zhí)行了,操作系統(tǒng)是能夠識(shí)別的。
但是咱們開(kāi)的的Java程序就不一樣了,使用javac命令編譯出來(lái)的的.class文件之后,操作系統(tǒng)是不能識(shí)別的,需要對(duì)應(yīng)JVM去做一個(gè)轉(zhuǎn)換后,操作系統(tǒng)才能識(shí)別。
我們?yōu)槭裁床荒芟?C++ 一樣,直接在操作系統(tǒng)上運(yùn)行編譯后的二進(jìn)制文件呢?而非要搞一個(gè)處于程序與操作系統(tǒng)中間層的虛擬機(jī)呢?
這就是 JVM的過(guò)人之處了。大家都知道,Java 是一門抽象程度特別高的語(yǔ)言,提供了自動(dòng)內(nèi)存管理等一系列的特性。這些特性直接在操作系統(tǒng)上實(shí)現(xiàn)是不太可能的,所以就需要JVM 進(jìn)行做一系列的轉(zhuǎn)換。
大家一開(kāi)始學(xué)Java的時(shí)候,就知道有個(gè)Write Once, Run Everywhere。就是我們編寫了一個(gè)java文件經(jīng)過(guò)編譯成.class文件后,可以在各種系統(tǒng)中進(jìn)行運(yùn)行。
其實(shí)這里是有個(gè)前提的,我們需要在對(duì)應(yīng)操作系統(tǒng)中安裝對(duì)應(yīng)的JVM,然后我們的.class文件就能運(yùn)行了。
比如:Windows操作系統(tǒng)有對(duì)應(yīng)的JDK安裝版本、Linux也有對(duì)應(yīng)的JDK安裝版本等。
認(rèn)識(shí)JDK
Java Development Kit (JDK) 是Sun公司(已被Oracle收購(gòu))針對(duì)Java開(kāi)發(fā)員的軟件開(kāi)發(fā)工具包。自從Java推出以來(lái),JDK已經(jīng)成為使用最廣泛的Java SDK(Software development kit)。
經(jīng)非官方調(diào)查,目前JDK8是使用者最多的版本。
JDK14將在4月和7月收到安全更新,然后由9月到期的非LTS版本的JDK 15取代。JDK14包括16項(xiàng)新功能,例如JDK Flight Recorder事件流,模式匹配和開(kāi)關(guān)表達(dá)式等特征。
從JDK9之后,Oracle采用了新的發(fā)布周期:每6個(gè)月發(fā)布一個(gè)版本,每3年發(fā)布一個(gè)LTS版本。JDK14是繼JDK9之后發(fā)布的第四個(gè)版本, 該版本為非LTS版本,最新的LTS版本為JDK11。
下面是JDK版本情況

這個(gè)混個(gè)眼熟就行,隨時(shí)關(guān)注JDK版本更新和新特性。
官網(wǎng)地址:https://www.oracle.com/java/
關(guān)于JDK安裝這里就省略。
JDK、JRE、JVM的關(guān)系
上面已經(jīng)說(shuō)過(guò)JDK和JVM的相關(guān)概念,
JRE全程Java Runtime Environment,是運(yùn)行基于Java語(yǔ)言編寫的程序所不可缺少的運(yùn)行環(huán)境。也是通過(guò)它,Java的開(kāi)發(fā)者才得以將自己開(kāi)發(fā)的程序發(fā)布到用戶手中,讓用戶使用。
三者到底是什么關(guān)系呢?
關(guān)于三者關(guān)系請(qǐng)看官網(wǎng)
https://docs.oracle.com/javase/8/docs/index.html
JDK中包含JRE,也包括JDK,而JRE也包括JDK。范圍關(guān)系:JDK>JRE>JVM
".java"文件到".class"文件
`javac`命令
編寫一個(gè)HelloWorld.java文件
內(nèi)容就是一個(gè)Java入門
- public class HelloWorld {
- public static void main(String[] args) {
- System.out.println("Hello world");
- }
- }
打開(kāi)CMD,進(jìn)入當(dāng)前目錄,使用命令
- javac HelloWorld.java
就編譯出HelloWorld.class
編譯過(guò)程
這個(gè)javac命令過(guò)程到底干了些什么呢?
javac背后大致做了這些操作

這個(gè)流程

1、詞法分析
讀取源代碼,一個(gè)字節(jié)一個(gè)字節(jié)的讀取,找出其中我們定義好的關(guān)鍵字(如Java中的if、else、for、while等關(guān)鍵詞,識(shí)別哪些if是合法的關(guān)鍵詞,哪些不是),這就是詞法分析器進(jìn)行詞法分析的過(guò)程,其結(jié)果是從源代碼中找出規(guī)范化的Token流。
2、語(yǔ)法分析
通過(guò)語(yǔ)法分析器對(duì)詞法分析后Token流進(jìn)行語(yǔ)法分析,這一步檢查這些關(guān)鍵字組合再一次是否符合Java語(yǔ)言規(guī)范(如在if后面是不是緊跟著一個(gè)布爾判斷表達(dá)式),詞法分析的結(jié)果是形成一個(gè)符合Java語(yǔ)言規(guī)范的抽象語(yǔ)法樹(shù)。
3、語(yǔ)義分析
通過(guò)語(yǔ)義分析器進(jìn)行語(yǔ)義分析。語(yǔ)音分析主要是將一些難懂的、復(fù)雜的語(yǔ)法轉(zhuǎn)化成更加簡(jiǎn)單的語(yǔ)法,結(jié)果形成最簡(jiǎn)單的語(yǔ)法(如將foreach轉(zhuǎn)換成for循環(huán) ,好有注解等),最后形成一個(gè)注解過(guò)后的抽象語(yǔ)法樹(shù),這個(gè)語(yǔ)法樹(shù)更為接近目標(biāo)語(yǔ)言的語(yǔ)法規(guī)則。
4、生成字節(jié)碼
通過(guò)字節(jié)碼生產(chǎn)器生成字節(jié)碼,根據(jù)經(jīng)過(guò)注解的語(yǔ)法抽象樹(shù)生成字節(jié)碼,也就是將一個(gè)數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)化為另一個(gè)數(shù)據(jù)結(jié)構(gòu)。最后生成我們想要的.class文件。
使用十六進(jìn)制查看class文件內(nèi)容
我只用的是Notepad++,選中文本→插件→Converter→ASCII->HEX

class文件的開(kāi)頭就是
CAFEBABE
想要學(xué)習(xí)這里的十六進(jìn)制的字節(jié)碼的含義可以參考
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html
javap查看class文件內(nèi)容
javap是 Java class文件分解器,可以反編譯(即對(duì)javac編譯的文件進(jìn)行反編譯),也可以查看java編譯器生成的字節(jié)碼。
新建一個(gè)User.java源文件,經(jīng)過(guò)javac編譯后,生成User.classs。
- package com.tian.demo.test;
- public class User {
- private int age = 22;
- private String name = "tian";
- public int addAge() {
- return age = age + 1;
- }
- public static void main(String[] args) {
- }
- }
使用javap命令
- javap -v User.class >log.txt
打開(kāi)log.txt
- Classfile /D:/workspace/new/demo/src/main/java/com/tian/demo/test/User.class
- Last modified 2020-11-5; size 441 bytes
- MD5 checksum 2fa72d3f53bd9f138e0bfae82aba67e3
- Compiled from "User.java"
- public class com.tian.demo.test.User
- minor version: 0
- major version: 52
- flags: ACC_PUBLIC, ACC_SUPER
- Constant pool:
- #1 = Methodref #6.#21 // java/lang/Object."<init>":()V
- #2 = Fieldref #5.#22 // com/tian/demo/test/User.age:I
- #3 = String #23 // tian
- #4 = Fieldref #5.#24 // com/tian/demo/test/User.name:Ljava/lang/String;
- #5 = Class #25 // com/tian/demo/test/User
- #6 = Class #26 // java/lang/Object
- #7 = Utf8 age
- #8 = Utf8 I
- #9 = Utf8 name
- #10 = Utf8 Ljava/lang/String;
- #11 = Utf8 <init>
- #12 = Utf8 ()V
- #13 = Utf8 Code
- #14 = Utf8 LineNumberTable
- #15 = Utf8 addAge
- #16 = Utf8 ()I
- #17 = Utf8 main
- #18 = Utf8 ([Ljava/lang/String;)V
- #19 = Utf8 SourceFile
- #20 = Utf8 User.java
- #21 = NameAndType #11:#12 // "<init>":()V
- #22 = NameAndType #7:#8 // age:I
- #23 = Utf8 tian
- #24 = NameAndType #9:#10 // name:Ljava/lang/String;
- #25 = Utf8 com/tian/demo/test/User
- #26 = Utf8 java/lang/Object
- {
- public com.tian.demo.test.User();
- 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 22
- 7: putfield #2 // Field age:I
- 10: aload_0
- 11: ldc #3 // String tian
- 13: putfield #4 // Field name:Ljava/lang/String;
- 16: return
- LineNumberTable:
- line 3: 0
- line 4: 4
- line 5: 10
- public int addAge();
- descriptor: ()I
- flags: ACC_PUBLIC
- Code:
- stack=3, locals=1, args_size=1
- 0: aload_0
- 1: aload_0
- 2: getfield #2 // Field age:I
- 5: iconst_1
- 6: iadd
- 7: dup_x1
- 8: putfield #2 // Field age:I
- 11: ireturn
- LineNumberTable:
- line 8: 0
- public static void main(java.lang.String[]);
- descriptor: ([Ljava/lang/String;)V
- flags: ACC_PUBLIC, ACC_STATIC
- Code:
- stack=0, locals=1, args_size=1
- 0: return
- LineNumberTable:
- line 13: 0
- }
- SourceFile: "User.java"
- 魔數(shù)與class文件版本
- 常量池
- 訪問(wèn)標(biāo)志
- 類索引、父類索引、接口索引
- 字段表集合
- 方法表集合
- 屬性表集合
然后JVM就可以讀取這個(gè)User.class文件進(jìn)行解析等一系列的操作。

以上就是我們的Java文件到class文件。
IT技術(shù)分享社區(qū)
個(gè)人博客網(wǎng)站:https://programmerblog.xyz認(rèn)識(shí)JVM
什么是JVM
JVM 全稱 Java Virtual Machine,也就是我們耳熟能詳?shù)?Java 虛擬機(jī)。它能識(shí)別 .class后綴的文件,并且能夠解析它的指令,最終調(diào)用操作系統(tǒng)上的函數(shù),完成我們想要的操作。
可能有部分小伙伴學(xué)習(xí)過(guò)C++,C++開(kāi)發(fā)出來(lái)的程序,編譯成二進(jìn)制文件后,就可以直接執(zhí)行了,操作系統(tǒng)是能夠識(shí)別的。
但是咱們開(kāi)的的Java程序就不一樣了,使用javac命令編譯出來(lái)的的.class文件之后,操作系統(tǒng)是不能識(shí)別的,需要對(duì)應(yīng)JVM去做一個(gè)轉(zhuǎn)換后,操作系統(tǒng)才能識(shí)別。
我們?yōu)槭裁床荒芟?C++ 一樣,直接在操作系統(tǒng)上運(yùn)行編譯后的二進(jìn)制文件呢?而非要搞一個(gè)處于程序與操作系統(tǒng)中間層的虛擬機(jī)呢?
這就是 JVM的過(guò)人之處了。大家都知道,Java 是一門抽象程度特別高的語(yǔ)言,提供了自動(dòng)內(nèi)存管理等一系列的特性。這些特性直接在操作系統(tǒng)上實(shí)現(xiàn)是不太可能的,所以就需要JVM 進(jìn)行做一系列的轉(zhuǎn)換。
大家一開(kāi)始學(xué)Java的時(shí)候,就知道有個(gè)Write Once, Run Everywhere。就是我們編寫了一個(gè)java文件經(jīng)過(guò)編譯成.class文件后,可以在各種系統(tǒng)中進(jìn)行運(yùn)行。
其實(shí)這里是有個(gè)前提的,我們需要在對(duì)應(yīng)操作系統(tǒng)中安裝對(duì)應(yīng)的JVM,然后我們的.class文件就能運(yùn)行了。
比如:Windows操作系統(tǒng)有對(duì)應(yīng)的JDK安裝版本、Linux也有對(duì)應(yīng)的JDK安裝版本等。

認(rèn)識(shí)JDK
Java Development Kit (JDK) 是Sun公司(已被Oracle收購(gòu))針對(duì)Java開(kāi)發(fā)員的軟件開(kāi)發(fā)工具包。自從Java推出以來(lái),JDK已經(jīng)成為使用最廣泛的Java SDK(Software development kit)。
經(jīng)非官方調(diào)查,目前JDK8是使用者最多的版本。
JDK14將在4月和7月收到安全更新,然后由9月到期的非LTS版本的JDK 15取代。JDK14包括16項(xiàng)新功能,例如JDK Flight Recorder事件流,模式匹配和開(kāi)關(guān)表達(dá)式等特征。
從JDK9之后,Oracle采用了新的發(fā)布周期:每6個(gè)月發(fā)布一個(gè)版本,每3年發(fā)布一個(gè)LTS版本。JDK14是繼JDK9之后發(fā)布的第四個(gè)版本, 該版本為非LTS版本,最新的LTS版本為JDK11。
下面是JDK版本情況

這個(gè)混個(gè)眼熟就行,隨時(shí)關(guān)注JDK版本更新和新特性。
官網(wǎng)地址:https://www.oracle.com/java/
關(guān)于JDK安裝這里就省略。
JDK、JRE、JVM的關(guān)系
上面已經(jīng)說(shuō)過(guò)JDK和JVM的相關(guān)概念,
JRE全程Java Runtime Environment,是運(yùn)行基于Java語(yǔ)言編寫的程序所不可缺少的運(yùn)行環(huán)境。也是通過(guò)它,Java的開(kāi)發(fā)者才得以將自己開(kāi)發(fā)的程序發(fā)布到用戶手中,讓用戶使用。
三者到底是什么關(guān)系呢?
關(guān)于三者關(guān)系請(qǐng)看官網(wǎng)
https://docs.oracle.com/javase/8/docs/index.html

JDK中包含JRE,也包括JDK,而JRE也包括JDK。范圍關(guān)系:JDK>JRE>JVM
".java"文件到".class"文件
`javac`命令
編寫一個(gè)HelloWorld.java文件

內(nèi)容就是一個(gè)Java入門
- public class HelloWorld {
- public static void main(String[] args) {
- System.out.println("Hello world");
- }
- }
打開(kāi)CMD,進(jìn)入當(dāng)前目錄,使用命令
- javac HelloWorld.java
就編譯出HelloWorld.class
編譯過(guò)程
這個(gè)javac命令過(guò)程到底干了些什么呢?
javac背后大致做了這些操作

這個(gè)流程

1、詞法分析
讀取源代碼,一個(gè)字節(jié)一個(gè)字節(jié)的讀取,找出其中我們定義好的關(guān)鍵字(如Java中的if、else、for、while等關(guān)鍵詞,識(shí)別哪些if是合法的關(guān)鍵詞,哪些不是),這就是詞法分析器進(jìn)行詞法分析的過(guò)程,其結(jié)果是從源代碼中找出規(guī)范化的Token流。
2、語(yǔ)法分析
通過(guò)語(yǔ)法分析器對(duì)詞法分析后Token流進(jìn)行語(yǔ)法分析,這一步檢查這些關(guān)鍵字組合再一次是否符合Java語(yǔ)言規(guī)范(如在if后面是不是緊跟著一個(gè)布爾判斷表達(dá)式),詞法分析的結(jié)果是形成一個(gè)符合Java語(yǔ)言規(guī)范的抽象語(yǔ)法樹(shù)。
3、語(yǔ)義分析
通過(guò)語(yǔ)義分析器進(jìn)行語(yǔ)義分析。語(yǔ)音分析主要是將一些難懂的、復(fù)雜的語(yǔ)法轉(zhuǎn)化成更加簡(jiǎn)單的語(yǔ)法,結(jié)果形成最簡(jiǎn)單的語(yǔ)法(如將foreach轉(zhuǎn)換成for循環(huán) ,好有注解等),最后形成一個(gè)注解過(guò)后的抽象語(yǔ)法樹(shù),這個(gè)語(yǔ)法樹(shù)更為接近目標(biāo)語(yǔ)言的語(yǔ)法規(guī)則。
4、生成字節(jié)碼
通過(guò)字節(jié)碼生產(chǎn)器生成字節(jié)碼,根據(jù)經(jīng)過(guò)注解的語(yǔ)法抽象樹(shù)生成字節(jié)碼,也就是將一個(gè)數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)化為另一個(gè)數(shù)據(jù)結(jié)構(gòu)。最后生成我們想要的.class文件。
使用十六進(jìn)制查看class文件內(nèi)容
我只用的是Notepad++,選中文本→插件→Converter→ASCII->HEX

class文件的開(kāi)頭就是
CAFEBABE
想要學(xué)習(xí)這里的十六進(jìn)制的字節(jié)碼的含義可以參考
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html
javap查看class文件內(nèi)容
javap是 Java class文件分解器,可以反編譯(即對(duì)javac編譯的文件進(jìn)行反編譯),也可以查看java編譯器生成的字節(jié)碼。
新建一個(gè)User.java源文件,經(jīng)過(guò)javac編譯后,生成User.classs。
- package com.tian.demo.test;
- public class User {
- private int age = 22;
- private String name = "tian";
- public int addAge() {
- return age = age + 1;
- }
- public static void main(String[] args) {
- }
- }
使用javap命令
- javap -v User.class >log.txt
打開(kāi)log.txt
- Classfile /D:/workspace/new/demo/src/main/java/com/tian/demo/test/User.class
- Last modified 2020-11-5; size 441 bytes
- MD5 checksum 2fa72d3f53bd9f138e0bfae82aba67e3
- Compiled from "User.java"
- public class com.tian.demo.test.User
- minor version: 0
- major version: 52
- flags: ACC_PUBLIC, ACC_SUPER
- Constant pool:
- #1 = Methodref #6.#21 // java/lang/Object."<init>":()V
- #2 = Fieldref #5.#22 // com/tian/demo/test/User.age:I
- #3 = String #23 // tian
- #4 = Fieldref #5.#24 // com/tian/demo/test/User.name:Ljava/lang/String;
- #5 = Class #25 // com/tian/demo/test/User
- #6 = Class #26 // java/lang/Object
- #7 = Utf8 age
- #8 = Utf8 I
- #9 = Utf8 name
- #10 = Utf8 Ljava/lang/String;
- #11 = Utf8 <init>
- #12 = Utf8 ()V
- #13 = Utf8 Code
- #14 = Utf8 LineNumberTable
- #15 = Utf8 addAge
- #16 = Utf8 ()I
- #17 = Utf8 main
- #18 = Utf8 ([Ljava/lang/String;)V
- #19 = Utf8 SourceFile
- #20 = Utf8 User.java
- #21 = NameAndType #11:#12 // "<init>":()V
- #22 = NameAndType #7:#8 // age:I
- #23 = Utf8 tian
- #24 = NameAndType #9:#10 // name:Ljava/lang/String;
- #25 = Utf8 com/tian/demo/test/User
- #26 = Utf8 java/lang/Object
- {
- public com.tian.demo.test.User();
- 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 22
- 7: putfield #2 // Field age:I
- 10: aload_0
- 11: ldc #3 // String tian
- 13: putfield #4 // Field name:Ljava/lang/String;
- 16: return
- LineNumberTable:
- line 3: 0
- line 4: 4
- line 5: 10
- public int addAge();
- descriptor: ()I
- flags: ACC_PUBLIC
- Code:
- stack=3, locals=1, args_size=1
- 0: aload_0
- 1: aload_0
- 2: getfield #2 // Field age:I
- 5: iconst_1
- 6: iadd
- 7: dup_x1
- 8: putfield #2 // Field age:I
- 11: ireturn
- LineNumberTable:
- line 8: 0
- public static void main(java.lang.String[]);
- descriptor: ([Ljava/lang/String;)V
- flags: ACC_PUBLIC, ACC_STATIC
- Code:
- stack=0, locals=1, args_size=1
- 0: return
- LineNumberTable:
- line 13: 0
- }
- SourceFile: "User.java"
- 魔數(shù)與class文件版本
- 常量池
- 訪問(wèn)標(biāo)志
- 類索引、父類索引、接口索引
- 字段表集合
- 方法表集合
- 屬性表集合
然后JVM就可以讀取這個(gè)User.class文件進(jìn)行解析等一系列的操作。

以上就是我們的Java文件到class文件。