浅谈JVM的实现与垃圾回收机制
Java被稱為是一個人類可讀的編程語言,其主要特點是基于類和面向?qū)ο?#xff0c;Java的開源版本被稱為OpenJDK。Java編程環(huán)境由兩個部分組成:Java語言和運行環(huán)境,運行環(huán)境也稱為Java虛擬機(jī)(JVM),JVM是一個為執(zhí)行Java程序提供運行時環(huán)境的程序。本文主要探討JVM的實現(xiàn)機(jī)制。
什么是JVM
解釋之前,先上一張圖嚇一下大家:
這張圖中我們需要注意的是,JVM的核心組件包括三個部分:Heap, JIT Compiler, GC, 當(dāng)我們要優(yōu)化JVM的性能的時候主要調(diào)優(yōu)的目標(biāo)是這三個組件。
JVM負(fù)責(zé)解釋執(zhí)行字節(jié)碼文件,所有平臺上的JVM向編譯器提供相同的編程接口,而編譯器只需要面向虛擬機(jī),生成虛擬機(jī)能理解的代碼,然后由虛擬機(jī)來解釋執(zhí)行。JVM是一個為執(zhí)行Java程序提供運行時環(huán)境的程序。沒有JVM,Java程序也就不能執(zhí)行。一個Java程序通常通過以下的方式執(zhí)行:
java <arguments> <program name>首先操作系統(tǒng)會啟動JVM進(jìn)程為Java程序提供運行時環(huán)境,然后會在剛啟動的虛擬機(jī)中執(zhí)行我們的Java程序。需要注意的是,Java程序首先要被轉(zhuǎn)換(編譯)成Java字節(jié)碼(bytecode),JVM上運行的是Java字節(jié)碼程序。JVM可以看成是一個JAVA字節(jié)碼程序解釋器。
Write Once, Run anywhere
當(dāng)使用Java編譯器編譯Java程序時,生成的是與平臺無關(guān)的字節(jié)碼,這些字節(jié)碼不面向任何具體平臺,它只面向JVM。不同平臺的JVM都是不同的,但它們都提供了相同的接口。JVM是Java程序跨平臺的關(guān)鍵部分,只要為不同平臺實現(xiàn)了相應(yīng)的虛擬機(jī),編譯后的Java字節(jié)碼就可以在該平臺上運行。
JVM收集運行時信息以為如何執(zhí)行代碼做出更好的決策。這意味著JVM能夠自動模擬和優(yōu)化運行在它上面的程序。JVM在Java程序和操作系統(tǒng)間形成了一個中間層,使得開發(fā)者不用過多的處理和操作系統(tǒng)相關(guān)的具體細(xì)節(jié)問題。
JVM的另一個好處是,任何能夠編譯成字節(jié)碼的語言都可以在它上面運行,不僅僅是Java,比如Groovy, Scala和Clojure這些基于JVM的語言。這也意味著,這些語言可以輕松的使用其他語言寫的函數(shù)庫。例如一個Scala開發(fā)者可以調(diào)用Java庫,因為他們運行在相同的平臺上。
從實際硬件上隔離出來,意味著Java代碼就像一個沙盒,這可以避免一運行有害代碼對機(jī)器硬件造成的傷害。安全性是JVM的主要好處之一。需要注意的是,并不是所有的JVM都是相同的,在Oracle的標(biāo)準(zhǔn)JVM實現(xiàn)標(biāo)準(zhǔn)之上還存在很多其他的實現(xiàn)方式。而我們主要討論的是HotSport JVM,這也是OpenJDK和Oracle JVM的實現(xiàn)基礎(chǔ)。
JIT - Just In Time
正如我們之前討論的,JVM運行的是字節(jié)碼。但是,如果有一段代碼會被頻繁的執(zhí)行,那么JVM可以決定將這段代碼編譯為機(jī)器碼(native code)以加快這段代碼的執(zhí)行速度。JIT可編譯的最小執(zhí)行塊是一個方法。默認(rèn)情況下,一個代碼塊需要被執(zhí)行1500次才會被JIT編譯,這個次數(shù)是可配置的。利用JIT機(jī)制可以有效的提升系統(tǒng)的性能,但是,JIT編譯并不是無代價的,其需要耗費額外的系統(tǒng)資源和時間。
Java內(nèi)存管理和GC
在Java中,對象所占用的內(nèi)存在對象不再使用后會自動被回收。這些工作是由一個叫垃圾回收器Garbage Collector的進(jìn)程完成的。相比較C/C++程序而言,你必須手動調(diào)用free()函數(shù)或delete操作符來回收內(nèi)存。在這里我們主要討論的是HotSpot JVM(這也是OpenJDK和Oracle Java的實現(xiàn)基礎(chǔ)),事實上,還有很多其他的JVM實現(xiàn)方式。
GC的優(yōu)缺點
好處是
壞處是
Generational GC
在了解更多關(guān)于GC的問題之前有必要理解Java內(nèi)存中的堆區(qū)(Heap)事如何工作的。所有的對象都存在于堆中(與此對應(yīng)的是棧,這里初訪者變量和方法,以及堆中對象的引用)。垃圾回收的過程也就是將堆區(qū)中不再需要對象清除的過程。幾乎所有的GCs都是“分代的(generational)”,也就是說堆區(qū)會劃分成很多的區(qū)塊,也稱為代。Hotspot的堆結(jié)構(gòu)如下圖所示:
New/Young Generation
大多數(shù)的應(yīng)用中持有的對象很大部分是短生命周期的,這被稱為“Weak generational hypothesis”。在垃圾回收期間分析應(yīng)用中所有的對象是一件緩慢而耗時的工作,因此可以將短生命周期的對象在其被創(chuàng)建時就分隔出來。因此New Generation可以進(jìn)一步劃分為:
- Eden Space (Eden空間):所有的新創(chuàng)建的對象都存在與此。當(dāng)其變滿時,minor GC便會出現(xiàn)。然后所有仍然被引用的對象被移動到幸存者空間中。
- Survivor Spaces (幸存者空間):對不同的JVM而言,幸存者空間的實現(xiàn)方式也不盡相同,但基本原理都是相同的。New Generation中的每一個GC都會增加幸存者空間中的對象年齡。如果對象的年齡超過某個特定值(默認(rèn)情況下是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可以執(zhí)行“Stop The World”機(jī)制,并且通常會占用更長的時間,因此Full GC也稱為絕大不多的JVM可以進(jìn)行優(yōu)化的地方。
Permanent Generation
Permanent Generation用于存放類的元信息。在Java 8中其被metaspace所取代。通常Permanent Generation無需為了確保其有足夠空間而被優(yōu)化,但是當(dāng)類沒有被正確上傳時,其仍有可能發(fā)生內(nèi)存泄露的情況。
Java中的內(nèi)存泄露
Java所支持的垃圾回收機(jī)制有效的減少了內(nèi)存溢出的發(fā)生。內(nèi)存溢出意味著當(dāng)分配出去的內(nèi)存卻永遠(yuǎn)都沒有被回收。雖然JVM能夠自動回收那些沒有被使用的對象所占用的內(nèi)存,但事實上,Java中還是回發(fā)生內(nèi)存溢出現(xiàn)象。假設(shè)存在一個有效的(但是沒有被使用)對象引用指向一個沒有被使用的對象,這會導(dǎo)致該對象一直占用著內(nèi)存。
舉例來說,當(dāng)一個方法需要運行很長的時間(或者永遠(yuǎn)都在運行),方法中的局部變量可以在長時間的持有對象引用,而這遠(yuǎn)超出自己實際需要的時間。代碼如下:
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 inputfor(;;) handle_input(result); }內(nèi)存泄露也會出現(xiàn)在你使用類似于HashMap這類數(shù)據(jù)結(jié)構(gòu)時將一個對象與另一個對象相關(guān)聯(lián)的情況。即使兩個對象都不再被需要了,這種關(guān)聯(lián)仍會存在于hash表中,除非hash表本身被垃圾回收器回收了,否則其中的關(guān)聯(lián)對象會一直占用內(nèi)存。如果hash表會運行相當(dāng)長的時間的話,那么內(nèi)存泄露便會發(fā)生。
System.gc() and finalize()
可以手動執(zhí)行垃圾回收嘛?
這應(yīng)該是個有意思的問題。答案是可以,也不可以。我們可以調(diào)用System.gc()方法建議JVM執(zhí)行垃圾回收。然后,并沒有任何保證說JVM一定會執(zhí)行該操作。作為開發(fā)者,我們無法知曉JVM是否執(zhí)行了我們的代碼。并且,通常認(rèn)為使用System.gc()是個很不明智的做法。
finalize()
finalize()方法存在于java.lang.Object類中,可以被所有對象所使用。默認(rèn)情況下其不執(zhí)行任何動作。當(dāng)垃圾回收器確定了一個對象沒有任何引用時,其會調(diào)用finalize()方法。但是,finalize方法并不一定會被執(zhí)行,因此也不建議覆寫finalize()該方法。
References
總結(jié)
以上是生活随笔為你收集整理的浅谈JVM的实现与垃圾回收机制的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java 理论与实践: 垃圾收集简史
- 下一篇: docker 入门教程指南