1. 程式人生 > 其它 >JVM真香系列:java檔案到.class檔案

JVM真香系列:java檔案到.class檔案

什麼是JVM

JVM全稱Java Virtual Machine,也就是我們耳熟能詳的 Java 虛擬機器。它能識別 .class字尾的檔案,並且能夠解析它的指令,最終呼叫作業系統上的函式,完成我們想要的操作。

可能有部分小夥伴學習過C++,C++開發出來的程式,編譯成二進位制檔案後,就可以直接執行了,作業系統是能夠識別的。

但是咱們開的的Java程式就不一樣了,使用javac命令編譯出來的的.class檔案之後,作業系統是不能識別的,需要對應JVM去做一個轉換後,作業系統才能識別。

我們為什麼不能像 C++ 一樣,直接在作業系統上執行編譯後的二進位制檔案呢?而非要搞一個處於程式與作業系統中間層的虛擬機器呢?

這就是JVM的過人之處了。大家都知道,Java 是一門抽象程度特別高的語言,提供了自動記憶體管理等一系列的特性。這些特性直接在作業系統上實現是不太可能的,所以就需要JVM進行做一系列的轉換。

大家一開始學Java的時候,就知道有個Write Once, Run Everywhere。就是我們編寫了一個java檔案經過編譯成.class檔案後,可以在各種系統中進行執行。

其實這裡是有個前提的,我們需要在對應作業系統中安裝對應的JVM,然後我們的.class檔案就能運行了。

比如:Windows作業系統有對應的JDK安裝版本、Linux也有對應的JDK安裝版本等。

認識JDK

Java Development Kit (JDK) 是Sun公司(已被Oracle收購)針對Java開發員的軟體開發工具包。自從Java推出以來,JDK已經成為使用最廣泛的JavaSDK(Software development kit)。

經非官方調查,目前JDK8是使用者最多的版本。

JDK14將在4月和7月收到安全更新,然後由9月到期的非LTS版本的JDK 15取代。JDK14包括16項新功能,例如JDK Flight Recorder事件流,模式匹配和開關表示式等特徵。

從JDK9之後,Oracle採用了新的釋出週期:每6個月釋出一個版本,每3年釋出一個LTS版本。JDK14是繼JDK9之後釋出的第四個版本, 該版本為非LTS版本,最新的LTS版本為JDK11。

下面是JDK版本情況

這個混個眼熟就行,隨時關注JDK版本更新和新特性。

官網地址:
https://www.oracle.com/java/

關於JDK安裝這裡就省略。

JDK、JRE、JVM的關係

上面已經說過JDK和JVM的相關概念,

JRE全程Java Runtime Environment,是執行基於Java語言編寫的程式所不可缺少的執行環境。也是通過它,Java的開發者才得以將自己開發的程式釋出到使用者手中,讓使用者使用。

三者到底是什麼關係呢?

關於三者關係請看官網

https://docs.oracle.com/javase/8/docs/index.html

JDK中包含JRE,也包括JDK,而JRE也包括JDK。範圍關係:JDK>JRE>JVM

".java"檔案到".class"檔案

`javac`命令

編寫一個HelloWorld.java檔案

內容就是一個Java入門

publicclassHelloWorld{
publicstaticvoidmain(String[]args){
System.out.println("Helloworld");
}
}

開啟CMD,進入當前目錄,使用命令

javacHelloWorld.java

就編譯出HelloWorld.class

編譯過程

這個javac命令過程到底幹了些什麼呢?

javac背後大致做了這些操作

這個流程

1、詞法分析

讀取原始碼,一個位元組一個位元組的讀取,找出其中我們定義好的關鍵字(如Java中的if、else、for、while等關鍵詞,識別哪些if是合法的關鍵詞,哪些不是),這就是詞法分析器進行詞法分析的過程,其結果是從原始碼中找出規範化的Token流。

2、語法分析

通過語法分析器對詞法分析後Token流進行語法分析,這一步檢查這些關鍵字組合再一次是否符合Java語言規範(如在if後面是不是緊跟著一個布林判斷表示式),詞法分析的結果是形成一個符合Java語言規範的抽象語法樹。

3、語義分析

通過語義分析器進行語義分析。語音分析主要是將一些難懂的、複雜的語法轉化成更加簡單的語法,結果形成最簡單的語法(如將foreach轉換成for迴圈 ,好有註解等),最後形成一個註解過後的抽象語法樹,這個語法樹更為接近目標語言的語法規則。

4、生成位元組碼

通過位元組碼生產器生成位元組碼,根據經過註解的語法抽象樹生成位元組碼,也就是將一個數據結構轉化為另一個數據結構。最後生成我們想要的.class檔案。

使用十六進位制檢視class檔案內容

我只用的是Notepad++,選中文字→外掛→Converter→ASCII->HEX

class檔案的開頭就是

CAFEBABE

想要學習這裡的十六進位制的位元組碼的含義可以參考

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html

javap檢視class檔案內容

javap是 Java class檔案分解器,可以反編譯(即對javac編譯的檔案進行反編譯),也可以檢視java編譯器生成的位元組碼。

新建一個User.java原始檔,經過javac編譯後,生成User.classs。

packagecom.tian.demo.test;

publicclassUser{
privateintage=22;
privateStringname="tian";

publicintaddAge(){
returnage=age+1;
}

publicstaticvoidmain(String[]args){

}
}

使用javap命令

javap-vUser.class>log.txt

開啟log.txt

Classfile/D:/workspace/new/demo/src/main/java/com/tian/demo/test/User.class
Lastmodified2020-11-5;size441bytes
MD5checksum2fa72d3f53bd9f138e0bfae82aba67e3
Compiledfrom"User.java"
publicclasscom.tian.demo.test.User
minorversion:0
majorversion:52
flags:ACC_PUBLIC,ACC_SUPER
Constantpool:
#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=Utf8age
#8=Utf8I
#9=Utf8name
#10=Utf8Ljava/lang/String;
#11=Utf8<init>
#12=Utf8()V
#13=Utf8Code
#14=Utf8LineNumberTable
#15=Utf8addAge
#16=Utf8()I
#17=Utf8main
#18=Utf8([Ljava/lang/String;)V
#19=Utf8SourceFile
#20=Utf8User.java
#21=NameAndType#11:#12//"<init>":()V
#22=NameAndType#7:#8//age:I
#23=Utf8tian
#24=NameAndType#9:#10//name:Ljava/lang/String;
#25=Utf8com/tian/demo/test/User
#26=Utf8java/lang/Object
{
publiccom.tian.demo.test.User();
descriptor:()V
flags:ACC_PUBLIC
Code:
stack=2,locals=1,args_size=1
0:aload_0
1:invokespecial#1//Methodjava/lang/Object."<init>":()V
4:aload_0
5:bipush22
7:putfield#2//Fieldage:I
10:aload_0
11:ldc#3//Stringtian
13:putfield#4//Fieldname:Ljava/lang/String;
16:return
LineNumberTable:
line3:0
line4:4
line5:10

publicintaddAge();
descriptor:()I
flags:ACC_PUBLIC
Code:
stack=3,locals=1,args_size=1
0:aload_0
1:aload_0
2:getfield#2//Fieldage:I
5:iconst_1
6:iadd
7:dup_x1
8:putfield#2//Fieldage:I
11:ireturn
LineNumberTable:
line8:0

publicstaticvoidmain(java.lang.String[]);
descriptor:([Ljava/lang/String;)V
flags:ACC_PUBLIC,ACC_STATIC
Code:
stack=0,locals=1,args_size=1
0:return
LineNumberTable:
line13:0
}
SourceFile:"User.java"

魔數與class檔案版本
常量池
訪問標誌
類索引、父類索引、介面索引
欄位表集合
方法表集合
屬性表集合

然後JVM就可以讀取這個User.class檔案進行解析等一系列的操作。

以上就是我們的Java檔案到class檔案。

IT技術分享社群

個人部落格網站:
https://programmerblog.xyz認識JVM

什麼是JVM

JVM全稱Java Virtual Machine,也就是我們耳熟能詳的 Java 虛擬機器。它能識別 .class字尾的檔案,並且能夠解析它的指令,最終呼叫作業系統上的函式,完成我們想要的操作。

可能有部分小夥伴學習過C++,C++開發出來的程式,編譯成二進位制檔案後,就可以直接執行了,作業系統是能夠識別的。

但是咱們開的的Java程式就不一樣了,使用javac命令編譯出來的的.class檔案之後,作業系統是不能識別的,需要對應JVM去做一個轉換後,作業系統才能識別。

我們為什麼不能像 C++ 一樣,直接在作業系統上執行編譯後的二進位制檔案呢?而非要搞一個處於程式與作業系統中間層的虛擬機器呢?

這就是JVM的過人之處了。大家都知道,Java 是一門抽象程度特別高的語言,提供了自動記憶體管理等一系列的特性。這些特性直接在作業系統上實現是不太可能的,所以就需要JVM進行做一系列的轉換。

大家一開始學Java的時候,就知道有個Write Once, Run Everywhere。就是我們編寫了一個java檔案經過編譯成.class檔案後,可以在各種系統中進行執行。

其實這裡是有個前提的,我們需要在對應作業系統中安裝對應的JVM,然後我們的.class檔案就能運行了。

比如:Windows作業系統有對應的JDK安裝版本、Linux也有對應的JDK安裝版本等。

認識JDK

Java Development Kit (JDK) 是Sun公司(已被Oracle收購)針對Java開發員的軟體開發工具包。自從Java推出以來,JDK已經成為使用最廣泛的JavaSDK(Software development kit)。

經非官方調查,目前JDK8是使用者最多的版本。

JDK14將在4月和7月收到安全更新,然後由9月到期的非LTS版本的JDK 15取代。JDK14包括16項新功能,例如JDK Flight Recorder事件流,模式匹配和開關表示式等特徵。

從JDK9之後,Oracle採用了新的釋出週期:每6個月釋出一個版本,每3年釋出一個LTS版本。JDK14是繼JDK9之後釋出的第四個版本, 該版本為非LTS版本,最新的LTS版本為JDK11。

下面是JDK版本情況

這個混個眼熟就行,隨時關注JDK版本更新和新特性。

官網地址:
https://www.oracle.com/java/

關於JDK安裝這裡就省略。

JDK、JRE、JVM的關係

上面已經說過JDK和JVM的相關概念,

JRE全程Java Runtime Environment,是執行基於Java語言編寫的程式所不可缺少的執行環境。也是通過它,Java的開發者才得以將自己開發的程式釋出到使用者手中,讓使用者使用。

三者到底是什麼關係呢?

關於三者關係請看官網

https://docs.oracle.com/javase/8/docs/index.html

JDK中包含JRE,也包括JDK,而JRE也包括JDK。範圍關係:JDK>JRE>JVM

".java"檔案到".class"檔案

`javac`命令

編寫一個HelloWorld.java檔案

內容就是一個Java入門

publicclassHelloWorld{
publicstaticvoidmain(String[]args){
System.out.println("Helloworld");
}
}

開啟CMD,進入當前目錄,使用命令

javacHelloWorld.java

就編譯出HelloWorld.class

編譯過程

這個javac命令過程到底幹了些什麼呢?

javac背後大致做了這些操作

這個流程

1、詞法分析

讀取原始碼,一個位元組一個位元組的讀取,找出其中我們定義好的關鍵字(如Java中的if、else、for、while等關鍵詞,識別哪些if是合法的關鍵詞,哪些不是),這就是詞法分析器進行詞法分析的過程,其結果是從原始碼中找出規範化的Token流。

2、語法分析

通過語法分析器對詞法分析後Token流進行語法分析,這一步檢查這些關鍵字組合再一次是否符合Java語言規範(如在if後面是不是緊跟著一個布林判斷表示式),詞法分析的結果是形成一個符合Java語言規範的抽象語法樹。

3、語義分析

通過語義分析器進行語義分析。語音分析主要是將一些難懂的、複雜的語法轉化成更加簡單的語法,結果形成最簡單的語法(如將foreach轉換成for迴圈 ,好有註解等),最後形成一個註解過後的抽象語法樹,這個語法樹更為接近目標語言的語法規則。

4、生成位元組碼

通過位元組碼生產器生成位元組碼,根據經過註解的語法抽象樹生成位元組碼,也就是將一個數據結構轉化為另一個數據結構。最後生成我們想要的.class檔案。

使用十六進位制檢視class檔案內容

我只用的是Notepad++,選中文字→外掛→Converter→ASCII->HEX

class檔案的開頭就是

CAFEBABE

想要學習這裡的十六進位制的位元組碼的含義可以參考

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html

javap檢視class檔案內容

javap是 Java class檔案分解器,可以反編譯(即對javac編譯的檔案進行反編譯),也可以檢視java編譯器生成的位元組碼。

新建一個User.java原始檔,經過javac編譯後,生成User.classs。

packagecom.tian.demo.test;

publicclassUser{
privateintage=22;
privateStringname="tian";

publicintaddAge(){
returnage=age+1;
}

publicstaticvoidmain(String[]args){

}
}

使用javap命令

javap-vUser.class>log.txt

開啟log.txt

Classfile/D:/workspace/new/demo/src/main/java/com/tian/demo/test/User.class
Lastmodified2020-11-5;size441bytes
MD5checksum2fa72d3f53bd9f138e0bfae82aba67e3
Compiledfrom"User.java"
publicclasscom.tian.demo.test.User
minorversion:0
majorversion:52
flags:ACC_PUBLIC,ACC_SUPER
Constantpool:
#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=Utf8age
#8=Utf8I
#9=Utf8name
#10=Utf8Ljava/lang/String;
#11=Utf8<init>
#12=Utf8()V
#13=Utf8Code
#14=Utf8LineNumberTable
#15=Utf8addAge
#16=Utf8()I
#17=Utf8main
#18=Utf8([Ljava/lang/String;)V
#19=Utf8SourceFile
#20=Utf8User.java
#21=NameAndType#11:#12//"<init>":()V
#22=NameAndType#7:#8//age:I
#23=Utf8tian
#24=NameAndType#9:#10//name:Ljava/lang/String;
#25=Utf8com/tian/demo/test/User
#26=Utf8java/lang/Object
{
publiccom.tian.demo.test.User();
descriptor:()V
flags:ACC_PUBLIC
Code:
stack=2,locals=1,args_size=1
0:aload_0
1:invokespecial#1//Methodjava/lang/Object."<init>":()V
4:aload_0
5:bipush22
7:putfield#2//Fieldage:I
10:aload_0
11:ldc#3//Stringtian
13:putfield#4//Fieldname:Ljava/lang/String;
16:return
LineNumberTable:
line3:0
line4:4
line5:10

publicintaddAge();
descriptor:()I
flags:ACC_PUBLIC
Code:
stack=3,locals=1,args_size=1
0:aload_0
1:aload_0
2:getfield#2//Fieldage:I
5:iconst_1
6:iadd
7:dup_x1
8:putfield#2//Fieldage:I
11:ireturn
LineNumberTable:
line8:0

publicstaticvoidmain(java.lang.String[]);
descriptor:([Ljava/lang/String;)V
flags:ACC_PUBLIC,ACC_STATIC
Code:
stack=0,locals=1,args_size=1
0:return
LineNumberTable:
line13:0
}
SourceFile:"User.java"

魔數與class檔案版本
常量池
訪問標誌
類索引、父類索引、介面索引
欄位表集合
方法表集合
屬性表集合

然後JVM就可以讀取這個User.class檔案進行解析等一系列的操作。

以上就是我們的Java檔案到class檔案。