Java被稱為是一個人類可讀的編程語言,其主要特點是基於類和面向對象,Java的開源版本被稱為OpenJDK。Java編程環境由兩個部分組成:Java語言和運行環境,運行環境也稱為Java虛擬機(JVM),JVM是一個為執行Java程序提供運行時環境的程序。本文主要探討JVM的實現機制。
解釋之前,先上一張圖嚇一下大家:
這張圖中我們需要注意的是,JVM的核心組件包括三個部分:Heap, JIT Compiler, GC, 當我們要優化JVM的性能的時候主要調優的目標是這三個組件。
JVM負責解釋執行字節碼文件,所有平台上的JVM向編譯器提供相同的編程接口,而編譯器只需要面向虛擬機,生成虛擬機能理解的代碼,然後由虛擬機來解釋執行。JVM是一個為執行Java程序提供運行時環境的程序。沒有JVM,Java程序也就不能執行。一個Java程序通常通過以下的方式執行:
java <arguments> <program name>
首先操作系統會啟動JVM進程為Java程序提供運行時環境,然後會在剛啟動的虛擬機中執行我們的Java程序。需要注意的是,Java程序首先要被轉換(編譯)成Java字節碼(bytecode),JVM上運行的是Java字節碼程序。JVM可以看成是一個JAVA字節碼程序解釋器。
Write Once, Run anywhere
當使用Java編譯器編譯Java程序時,生成的是與平台無關的字節碼,這些字節碼不面向任何具體平台,它只面向JVM。不同平台的JVM都是不同的,但它們都提供了相同的接口。JVM是Java程序跨平台的關鍵部分,只要為不同平台實現了相應的虛擬機,編譯後的Java字節碼就可以在該平台上運行。
JVM收集運行時信息以為如何執行代碼做出更好的決策。這意味著JVM能夠自動模擬和優化運行在它上面的程序。JVM在Java程序和操作系統間形成了一個中間層,使得開發者不用過多的處理和操作系統相關的具體細節問題。
JVM的另一個好處是,任何能夠編譯成字節碼的語言都可以在它上面運行,不僅僅是Java,比如Groovy, Scala和Clojure這些基於JVM的語言。這也意味著,這些語言可以輕松的使用其他語言寫的函數庫。例如一個Scala開發者可以調用Java庫,因為他們運行在相同的平台上。
從實際硬件上隔離出來,意味著Java代碼就像一個沙盒,這可以避免一運行有害代碼對機器硬件造成的傷害。安全性是JVM的主要好處之一。需要注意的是,並不是所有的JVM都是相同的,在Oracle的標准JVM實現標准之上還存在很多其他的實現方式。而我們主要討論的是HotSport JVM,這也是OpenJDK和Oracle JVM的實現基礎。
正如我們之前討論的,JVM運行的是字節碼。但是,如果有一段代碼會被頻繁的執行,那麼JVM可以決定將這段代碼編譯為機器碼(native code)以加快這段代碼的執行速度。JIT可編譯的最小執行塊是一個方法。默認情況下,一個代碼塊需要被執行1500次才會被JIT編譯,這個次數是可配置的。利用JIT機制可以有效的提升系統的性能,但是,JIT編譯並不是無代價的,其需要耗費額外的系統資源和時間。
在Java中,對象所占用的內存在對象不再使用後會自動被回收。這些工作是由一個叫垃圾回收器Garbage Collector
的進程完成的。相比較C/C++程序而言,你必須手動調用free()
函數或delete
操作符來回收內存。在這裡我們主要討論的是HotSpot JVM(這也是OpenJDK和Oracle Java的實現基礎),事實上,還有很多其他的JVM實現方式。
好處是
壞處是
在了解更多關於GC的問題之前有必要理解Java內存中的堆區(Heap)事如何工作的。所有的對象都存在於堆中(與此對應的是棧,這裡初訪者變量和方法,以及堆中對象的引用)。垃圾回收的過程也就是將堆區中不再需要對象清除的過程。幾乎所有的GCs都是“分代的(generational)”,也就是說堆區會劃分成很多的區塊,也稱為代。Hotspot的堆結構如下圖所示:
大多數的應用中持有的對象很大部分是短生命周期的,這被稱為“Weak generational hypothesis”。在垃圾回收期間分析應用中所有的對象是一件緩慢而耗時的工作,因此可以將短生命周期的對象在其被創建時就分隔出來。因此New Generation
可以進一步劃分為:
minor GC
便會出現。然後所有仍然被引用的對象被移動到幸存者空間中。New Generation中的GC也被稱為minor GC
。使用New Generation
的好處是可以減少分片帶來的影響。
任何從New Generation中的幸存者空間中幸存下來的對象會被送往Old Generation。Old Generation通常比New Generation大很多。存在於Old Generation中的GC也被稱為Full GC
。Full GC可以執行“Stop The World”機制,並且通常會占用更長的時間,因此Full GC也稱為絕大不多的JVM可以進行優化的地方。
Permanent Generation用於存放類的元信息。在Java 8中其被metaspace所取代。通常Permanent Generation無需為了確保其有足夠空間而被優化,但是當類沒有被正確上傳時,其仍有可能發生內存洩露的情況。
Java所支持的垃圾回收機制有效的減少了內存溢出的發生。內存溢出意味著當分配出去的內存卻永遠都沒有被回收。雖然JVM能夠自動回收那些沒有被使用的對象所占用的內存,但事實上,Java中還是回發生內存溢出現象。假設存在一個有效的(但是沒有被使用)對象引用指向一個沒有被使用的對象,這會導致該對象一直占用著內存。
舉例來說,當一個方法需要運行很長的時間(或者永遠都在運行),方法中的局部變量可以在長時間的持有對象引用,而這遠超出自己實際需要的時間。代碼如下:
public static void main(String args[]) {
int bigArray[] = new int[1000];
int result = compute(bigArray);
// We no longer need bigArray. It will get garbage collected when
// there are no more references to it. Because bigArray is a local
// variable, it refers to the array until this method returns. But
// this method doesn't return. So we've got to explicitly get rid
// of the referenceourselves, so the garbage collector knows it can
// reclaim the array.
bigArray = null;
// Loop forever, handling the user's input
for(;;) handle_input(result);
}
內存洩露也會出現在你使用類似於HashMap這類數據結構時將一個對象與另一個對象相關聯的情況。即使兩個對象都不再被需要了,這種關聯仍會存在於hash表中,除非hash表本身被垃圾回收器回收了,否則其中的關聯對象會一直占用內存。如果hash表會運行相當長的時間的話,那麼內存洩露便會發生。
這應該是個有意思的問題。答案是可以,也不可以。我們可以調用System.gc()
方法建議JVM執行垃圾回收。然後,並沒有任何保證說JVM一定會執行該操作。作為開發者,我們無法知曉JVM是否執行了我們的代碼。並且,通常認為使用System.gc()
是個很不明智的做法。
finalize()
方法存在於java.lang.Object
類中,可以被所有對象所使用。默認情況下其不執行任何動作。當垃圾回收器確定了一個對象沒有任何引用時,其會調用finalize()
方法。但是,finalize方法並不一定會被執行,因此也不建議覆寫finalize()
該方法。