歡迎關(guān)注公眾號“Tim在路上”
hello world 作為我們學(xué)習(xí)的第一個個程序,看起來很簡單,但是要理解其執(zhí)行的具體流程還是需要很深的功底,包括對組成原理,操作系統(tǒng)的理解,今天將java hello world 進(jìn)行整理一下吧??!
廢話不多說,先上hello world
public class Main {
public static void main(String[] args) {
String s = "helloWorld";
String s2 = new String("helloWorld");
System.out.println(s);
System.out.println(s2);
}
}
很簡單的程序,具體執(zhí)行是怎么樣的?
相信大家都知道 java 代碼的可移植性,是由于java解釋器和虛擬機,所以處理java原代碼的過程,就是java代碼執(zhí)行的過程:
Java 代碼的運行過程?
Java 源代碼 -> 編輯器 -> 字節(jié)碼文件
字節(jié)碼 -> JVM -> 機器碼文件
每一種平臺的解釋器是不同的,但是實現(xiàn)的虛擬機是相同的,這也就是 Java 為什么能夠 跨平臺的原因
java 原文件通過編譯器編譯成.class字節(jié)碼文件,字節(jié)碼文件通過 JVM 虛擬機,生成機器碼文件
1. 代碼編譯 ,詞法語法語義分析,將java 原代碼編譯成字節(jié)碼文件;
我們的.java 文件最終會轉(zhuǎn)換為.class文件
2. 類加載機制,采用雙親委派避免重復(fù)加載(類名+類加載器),加載,驗證,準(zhǔn)備(準(zhǔn)備會對一些常量進(jìn)行初始化,遍歷初始化為0或null),解析,初始化
我們知道 JVM 虛擬機的入口就是類加載器,在加載java中的類時采用雙親委托機制,可以防止用戶新寫的類,替代jre中的類。
當(dāng)一個類收到了類加載請求,他首先不會嘗試自己去加載這個類,而是把這個請求委派給父 類去完 成,每一個層次類加載器都是如此,因此所有的加載請求都應(yīng)該傳送到啟動類加載其中, 只有當(dāng)父類 加載器反饋自己無法完成這個請求的時候(在它的加載路徑下沒有找到所需加載的 Class), 子類加 載器才會嘗試自己去加載。
類加載器有,啟動(Bootstrap)類加載器,擴展(Extension)類加載器,系統(tǒng)(System)類加載器,自定義類加載器
我們的Main類由系統(tǒng)類加載器進(jìn)行加載,委托給父類加載器
在JVM中表示兩個class對象是否為同一個類對象存在兩個必要條件
類的完整類名必須一致,包括包名。
加載這個類的ClassLoader(指ClassLoader實例對象)必須相同
詳細(xì)的類加載過程是:
JVM 類加載機制分為五個部分:加載,驗證,準(zhǔn)備,解析,初始化。
- 加載, 這個階段會在內(nèi)存中生成一個代表這個類的 java.lang.Class 作為方法區(qū)這個類的各種數(shù) 據(jù)的入口。
- 驗證,確保 Class文件的字節(jié)流中包含的信息是否符合當(dāng)前虛擬機的要求
- 準(zhǔn)備,是正式為類變量分配內(nèi)存并設(shè)置類變量的初始值階段,public static int v = 8080,實際 上變量 v 在準(zhǔn)備階段過后的初始值為 0 而不是 8080,但是如果聲明的是常量就是8080,例如 public static final int v = 8080。
4.解析,解析階段是指虛擬機將常量池中的符號引用替換為直接引用的過程。
3. 在jvm內(nèi)存中棧和本地線程棧是線程私有的,會在線程中創(chuàng)建一個main方法的棧幀
在main方法棧幀中,里面S,S2存放在其中的字符變量表中,然后“helloword”是一個字符串常量,在jvm中會存放在方法區(qū)中的字符常量池中的,然后將其從常量池中彈到操作數(shù)??臻g,然后賦值到s的空間中。這個 new String("helloword"), 先指向堆空間,堆空間再去指向 方法區(qū)常量池中。復(fù)制一份常量數(shù)據(jù)再放到s2 對應(yīng)的寄存器空間中。然后字面變量在字符變量表中指向這些空間。
具體可以使用 javap -c Main.class 解析.class 文件
Compiled from "Main.java"
public class com.bupt.learn.Main {
public com.bupt.learn.Main();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
// 將 helloword 從常量池推送到棧頂
0: ldc #2 // String helloWorld
// 將棧頂 引用 型數(shù)值存入第二個局部變量
2: astore_1
// 堆中創(chuàng)建一個對象,并將其引用值壓入棧頂
3: new #3 // class java/lang/String
// 復(fù)制棧頂數(shù)值并將復(fù)制值壓入棧頂
6: dup
// 將 helloword 從常量池推送到棧頂
7: ldc #2 // String helloWorld
// 調(diào)用超類構(gòu)造方法,實例初始化方法,私有方法
9: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V
// 將棧頂 引用 型數(shù)值存入第三個局部變量
12: astore_2
// 獲取指定類的靜態(tài)字段,并將其壓入棧頂
13: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
// 將第二個 引用 型局部變量推送至棧頂
16: aload_1
// 調(diào)用實例方法
17: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
20: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
23: aload_2
24: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
27: return
}
4. System.out.println(s):系統(tǒng)加載System.class字節(jié)碼文件到方法區(qū),并且系統(tǒng)會默認(rèn)在堆區(qū)創(chuàng)建System.out、System.in、System.err三個對象。
字符串在被輸出時會自動調(diào)用toString()方法。
println 是 java 原始的IO包,是使用 BIO 進(jìn)行輸出
一次I/O的完成的步驟
當(dāng)進(jìn)程發(fā)起系統(tǒng)調(diào)用時,這個系統(tǒng)調(diào)用就進(jìn)入內(nèi)核模式,然后開始I/O操作
I/O操作分為兩個步驟;
1、磁盤把數(shù)據(jù)裝載到內(nèi)核的內(nèi)存空間,
2、內(nèi)核的內(nèi)存空間的數(shù)據(jù)copy到用戶的內(nèi)存空間中(此過程是I/O發(fā)生的地方)
阻塞:進(jìn)程發(fā)起I/O調(diào)用,進(jìn)程又不得不等待I/O的完成,此時CPU把進(jìn)程切換出去,進(jìn)程處于睡眠狀態(tài)則此過程為阻塞I/O
阻塞I/O系統(tǒng)怎么通知進(jìn)程?
I/O完成,系統(tǒng)直接通知進(jìn)程,則進(jìn)程被喚醒