計算機的概念模型
計算機實際上就是實現了一個圖靈機模型。即,輸入參數,根據程序計算,輸出結果。圖靈機模型如圖。
Tape是輸入數據,Program是針對這些數據進行計算的程序,中間橫著的方塊表示的是機器的狀態。
目前使用的電子計算機都是實現了這樣一個抽象模型的產物,只不過物理實現上不一樣。
比如,典型的加法運算。
c = a + b
a和b是輸入參數,c是加法的輸出。
如何實現運算?
設計一臺電腦。CPU里面有一個加法器。現在如何實現加法運算,并將結果輸出呢?實際上可以采用如下的方式。
- 有一根內存條
- 有一個CPU
- CPU含有3條指令(加法,讀取內存,寫入內存)
那么如何實現上述的加法運算呢?我們可以采用下面的模式
第一步,將a放入內存
第二步,將b壓棧
第三步,CPU從內存讀取a和b,并進行加法運算
第四步,將內存中的a和b清空,并將計算后的結果c放入內存
這種計算機被稱為棧式計算機。我們不僅可以在CPU中執行加法,還可以加入減法,乘法,除法,等等。
這種計算機的好處是指令集緊湊精簡,所有操作都以棧頂元素為對象。
但是,它也存在一些固有缺陷,如執行效率較低(因為對于計算機而言,訪問內存操作是一種時間開銷極大的行為)、尋址能力受限等。
改進
為了讓棧式計算機能夠快速地進行運算。CPU可以在內部加入一個寄存器(register),用于總是保存棧頂數據。其計算過程如圖。
由于寄存器處于CPU內部,其訪問速度遠遠大于對內存的直接訪問,
后來隨著技術的發展,CPU內部的寄存器越來越多。不同的廠商針對各自的架構設計特點,發展出了屬于各自架構的寄存器。以x86
架構為例(也就是我們常見的AMD
或Intel CPU),整數寄存器有32個,并且針對每一個寄存器都標記了一個編號以及別名。 下面以0-2號寄存器為例進行說明。
編號 | 別名 |
---|---|
0 | rax |
1 | rcx |
2 | rdx |
于是,CPU發展出下面的形式。
架構類型
由于不同廠商實現寄存器和訪問內存(簡稱訪存)方式的不同,發展出了復雜指令集架構(CISC
)和精簡指令集架構(RISC)。比如,在x86
(CISC
指令集)上實現加法的指令為
ADD EAX, EBX
該指令將EAX
寄存器中的值與EBX
寄存器中的值相加后,將結果放入EBX
。
而典型的ARM架構(RISC指令集)實現加法則為
ADD X0, X1, X2
其含義為將x1
和x2
寄存器中的值相加,將結果放入x0
寄存器中。
實際上,不僅二者匯編指令的編寫不一致,而且由此翻譯成的機器碼也不一直。對于x86
的加法例子,CPU執行上述加法的機器碼為
0x01C3
而對于ARM架構的例子機器碼為
0x8B000000
平臺相關性
對于同樣的加法,在x86
和ARM上實現的指令機器碼是不一樣的。所以,如果有程序要實現一個加法,那么在計算機底層執行時,其執行的內容是不一樣的。
對于C語言這樣的高級語言而言,當實現一個加法運算。例如
int a,b,c;
a = b + c;
在經過編譯器(如gcc
)編譯后,其源文件被編譯生成了可被指定平臺識別的二進制可執行文件。該文件中關于實現加法的指令是不同的。因此,盡管高級語言編寫的內容一致,但可執行程序在最后一步執行時是平臺相關的。
Java程序的平臺無關性
所謂的Java程序平臺無關性是指由Java語言編寫的源程序經過Java虛擬機(JVM
)編譯后,生成的二進制文件(.class)是一致的。即不管是在ARM上編譯生成的.class文件還是x86
上生成的.class文件,其內容是一樣的。不管什么平臺的Java虛擬機都可根據這些.class文件執行。這就與gcc
編譯器完全不同,gcc
生成的二進制文件必須符合平臺的要求,否則不能執行。
原因
Java虛擬機(JVM
)在運行Java程序的時候首先會讀取這些.class文件,將其內容加載到虛擬機內部。至于為什么JVM
稱為虛擬機呢?這是因為JVM
內部實際上是一個由純軟件方式實現的棧式計算機。該棧式計算機被稱為hotspot
,幾乎全部由C++實現。,在hotspot
之外,包裹了一層Java語言編寫的外殼(jdk)供開發者調用。
本質上說,在軟件層面,所有的Java程序運行都通過純軟件的棧式計算機實現計算。但是棧式計算機的具體計算過程,則由平臺的具體指令實現。
這也是為什么openjdk
的源碼目錄結構中會出現不同架構的文件夾。
不僅如此,為了能夠實現**“一次編譯,處處執行”**,Java虛擬機還能根據不同的操作系統進行適配,對于有些與操作系統和CPU結合的部分,也提供了不同的實現。
結論
Java虛擬機即是平臺無關,也是平臺相關。平臺無關是因為其執行過程是由純軟件實現的棧式計算機實現,而平臺相關是因為Java虛擬機的具體操作跟平臺指令和操作系統相關。