歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

淺談JVM的實現與垃圾回收機制

Java被稱為是一個人類可讀的編程語言,其主要特點是基於類和面向對象,Java的開源版本被稱為OpenJDK。Java編程環境由兩個部分組成:Java語言和運行環境,運行環境也稱為Java虛擬機(JVM),JVM是一個為執行Java程序提供運行時環境的程序。本文主要探討JVM的實現機制。

什麼是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的實現基礎。

JIT - Just In Time

正如我們之前討論的,JVM運行的是字節碼。但是,如果有一段代碼會被頻繁的執行,那麼JVM可以決定將這段代碼編譯為機器碼(native code)以加快這段代碼的執行速度。JIT可編譯的最小執行塊是一個方法。默認情況下,一個代碼塊需要被執行1500次才會被JIT編譯,這個次數是可配置的。利用JIT機制可以有效的提升系統的性能,但是,JIT編譯並不是無代價的,其需要耗費額外的系統資源和時間。

Java內存管理和GC

在Java中,對象所占用的內存在對象不再使用後會自動被回收。這些工作是由一個叫垃圾回收器Garbage Collector的進程完成的。相比較C/C++程序而言,你必須手動調用free()函數或delete操作符來回收內存。在這裡我們主要討論的是HotSpot JVM(這也是OpenJDK和Oracle Java的實現基礎),事實上,還有很多其他的JVM實現方式。

GC的優缺點

好處是

  1. 開發者無需過問內存管理,可以專注於解決實際問題。雖然內存洩露仍有可能會發生,但發生的概率比較小。
  2. GC的智能算法可以在後台自動的進行內存管理,且這種管理在大多數時候是最佳的。

壞處是

  1. 當垃圾回收發生時,它會影響程序的性能,甚至會暫停程序的執行。這個被稱為“Stop the world”垃圾回收機制,整個程序進程會被暫停以等待垃圾回收執行完。對某些應用而言,這可能是無法接受的。
  2. 開發者並不能指定何時或使用何種方法執行GC。

Generational GC

在了解更多關於GC的問題之前有必要理解Java內存中的堆區(Heap)事如何工作的。所有的對象都存在於堆中(與此對應的是棧,這裡初訪者變量和方法,以及堆中對象的引用)。垃圾回收的過程也就是將堆區中不再需要對象清除的過程。幾乎所有的GCs都是“分代的(generational)”,也就是說堆區會劃分成很多的區塊,也稱為代。Hotspot的堆結構如下圖所示:

New/Young Generation

大多數的應用中持有的對象很大部分是短生命周期的,這被稱為“Weak generational hypothesis”。在垃圾回收期間分析應用中所有的對象是一件緩慢而耗時的工作,因此可以將短生命周期的對象在其被創建時就分隔出來。因此New Generation可以進一步劃分為:

  • Eden Space (Eden空間):所有的新創建的對象都存在與此。當其變滿時,minor GC便會出現。然後所有仍然被引用的對象被移動到幸存者空間中。
  • Survivor Spaces (幸存者空間):對不同的JVM而言,幸存者空間的實現方式也不盡相同,但基本原理都是相同的。New Generation中的每一個GC都會增加幸存者空間中的對象年齡。如果對象的年齡超過某個特定值(默認情況下是15),該對象會被移往Old Generation。

New Generation中的GC也被稱為minor GC。使用New Generation的好處是可以減少分片帶來的影響。

Old Generation

任何從New Generation中的幸存者空間中幸存下來的對象會被送往Old Generation。Old Generation通常比New Generation大很多。存在於Old Generation中的GC也被稱為Full GC。Full GC可以執行“Stop The World”機制,並且通常會占用更長的時間,因此Full GC也稱為絕大不多的JVM可以進行優化的地方。

Permanent Generation

Permanent Generation用於存放類的元信息。在Java 8中其被metaspace所取代。通常Permanent Generation無需為了確保其有足夠空間而被優化,但是當類沒有被正確上傳時,其仍有可能發生內存洩露的情況。

Java中的內存洩露

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() and finalize()

可以手動執行垃圾回收嘛?

這應該是個有意思的問題。答案是可以,也不可以。我們可以調用System.gc()方法建議JVM執行垃圾回收。然後,並沒有任何保證說JVM一定會執行該操作。作為開發者,我們無法知曉JVM是否執行了我們的代碼。並且,通常認為使用System.gc()是個很不明智的做法。

finalize()

finalize()方法存在於java.lang.Object類中,可以被所有對象所使用。默認情況下其不執行任何動作。當垃圾回收器確定了一個對象沒有任何引用時,其會調用finalize()方法。但是,finalize方法並不一定會被執行,因此也不建議覆寫finalize()該方法。

References

  1. Java In a Nutshell, 6th Edition
  2. http://java.dzone.com/articles/jvm-and-garbage-collection
  3. http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html

Copyright © Linux教程網 All Rights Reserved