虛擬機器(Virtual Machine,VM)由來已久,早在1972年,IBM就推出所謂的VM/370產品;1973年Pascal語言就有所謂的P-code Machine(P-code的意思是Pseudo-code或Pascal-code)。但真正打響虛擬機器名號,讓虛擬機器廣為人知者,就要歸功於1990年代中期開始興起的Java了。

總而言之,虛擬機器就是「虛假」的機器,我們為這個想像中的機器定義好機器語言,寫一個「特殊程式」來解讀並執行此機器語言,這個特殊程式就是「虛擬機器」。最陽春的虛擬機器本質上就是「解譯器」。

一般人不會想直接用虛擬機器的機器語言寫程式,而是會用更高階的語言撰寫程式,再利用編譯器將高階語言編譯成虛擬機器語言。因此虛擬機器通常伴隨著編譯器而來,例如JVM的javac編譯器以及.NET CLR的csc編譯器。

高階語言如Java或C#,要能在作業系統和硬體上執行,其編譯器必須先將高階語言「編譯」成虛擬機器語言,然後虛擬機器再將此虛擬機器語言「解譯」成實際的機器語言。由於需要同時用到編譯器與解譯器,所以我們稱虛擬機器運作的方式是混合式(hybrid),虛擬機器語言則為「中間語言」或bytecode。

中間語言的最大好處是:它和底下實體機器(平臺)無關,所以可以跨平臺。只要平臺上有對應的虛擬機器,就能執行此中間語言。這是編譯式語言無法做到的。解譯式語言其實也有這個好處,但最大的缺點是源碼的暴露。

一般來說,中間語言的邏輯相當高階,可以很容易「反編譯」(decompile)回高階語言。所以Java程式和.NET程式,如果沒有經過混淆器(obfuscator)處理過,就直接發布給客戶,那麼其實就和把源碼交給客戶沒有兩樣,中間語言和解譯式語言一樣會有源碼暴露的問題。早年大家還對此有一些警覺,現在卻好像都不在乎了。

純編譯的語言(例如C)與純解譯的語言(例如Python),都是「一口氣」將高階語言「轉換」成實際的機器語言。兩者的差別是,編譯器提前做這樣的轉換,而解譯器則是執行時才做這樣的轉換。不管如何,這種「一口氣」的作法會比虛擬機器兩階段式作法更複雜,更難實作,除此之外,也需要寫更多程式。

例如:有10個高階語言和10個平臺。如果用編譯式的作法,必須寫出100(10 x 10)個程式(編譯器)。但是用虛擬機器的作法,只需要20(10+10)個程式(10個編譯器與10個虛擬機器)。而這裡所謂的20個程式,每個實際上只作了一半的事,所以整體而言相當於只用了10(20 x 0.5)個程式。100遠大於10,利用虛擬機器來實作語言,顯然可以省下不少工夫。

由於虛擬機器做了一半編譯器工作,也做了一半解譯器的工作,所以效率介於編譯式與解譯式之間。編譯的工作分成兩階段,分別是分析與合成,其中最耗時間的部分是分析,這部分剛好在虛擬機器的「編譯器」中處理掉了,所以虛擬機器的「解譯器」執行時只需要處理合成的部分,效率相當高。因此虛擬機器的執行效能,可以相當逼近編譯式語言。

廠商發行純編譯式語言寫出來的軟體時,為了省卻麻煩,通常只會編譯出一個版本,而沒有針對各種硬體環境做最佳化。例如,廠商只會編譯出i586一般環境的版本,不會使用到特殊的處理器指令,也不會針對多核心做最佳化,只把64位元CPU當32位元在用……。因此,如果虛擬機器夠厲害,能夠充分運用硬體環境,它執行bytecode的效率甚至還可能比純編譯式語言更快。但目前混合式的程式執行速度還是比編譯式慢。

為了要提升效率,虛擬機器會將解譯器的部分,用其他技術取代,尤其是JIT編譯器和AOT編譯器。

JIT(Just-in-Time,及時)編譯器是最普遍的作法,每次執行到一小段中間語言,就將它解譯成實際的機器語言,然後「保存在記憶體中」,下次需要執行時就不用重新解譯。但JIT編譯器還是需要在執行期進行解譯,而且不會將保存結果存在磁碟中,所以下次重新執行程式時,還是得重頭解譯一次。

AOT(Ahead-Of-Time,提前)編譯器針對JIT效率上的缺失,讓你提前編譯(而不是在執行時才做),且會將編譯結果儲存在磁碟內。利用AOT編譯器處理過的程式,本質上已經不算是混合式語言,而算是編譯式語言,如同C一樣。

虛擬機器的好處相當明顯,技術瓶頸也一一被克服,所以未來將越來越蓬勃發展,許多原本是編譯式或解譯式的語言,都開始採用虛擬機器(例如Erlang)。我敢說現在你的PC上,已經同時安裝好幾個虛擬機器,至少有Flash的AVM、Java的JVM、.NET的CLR,它們正隨時在默默地幫你工作呢!

作者簡介:
蔡學鏞-技術顧問
清華大學資訊工程碩士,曾任華碩集團軟體工程師、元智大學資訊系講師、美商歐萊禮出版社技術編輯、臺灣微軟特約專欄作家。

熱門新聞

Advertisement