這篇博客是對Java垃圾回收的總結,主要是對Java Garbage Collection Introduction以及後續的三篇博客的翻譯。我把這四篇博客翻譯到這一篇博客裏,把參考的其餘博客的連接附在文章末尾。java
在Java中,分配和回收對象的內存空間是在JVM中、由垃圾回收機制自動完成的。不像C語言,使用Java的開發人員不須要寫代碼來進行垃圾回收。Java之因此如此流行以及可以幫助程序員更好的開發,這是其中一個緣由。程序員
這個系列一共有四篇博客來介紹Java的垃圾回收,分別是:算法
這一節是這篇博客的第一部分。主要內容是解釋一些基礎的術語,好比JDK, JVM, JRE, HotSpot VM,而後理解JVM的總體架構和Java堆內存結構。理解這些概念對學習Java垃圾回收頗有幫助。數組
Java API:Java API是幫助程序員進行Java開發的一系列封裝庫的集合。緩存
Java Development Kit (JDK):JDK是一套幫助程序員進行Java開發的工具,這些工具主要是進行編譯、運行、打包、分配和監視Java程序。安全
Java Virtual Machine (JVM):JVM是一個抽象的計算機。Java程序是針對JVM規範來寫的。對不一樣的操做系統來講,JVM有所不一樣,它主要做用是把Java指令翻譯爲操做系統的指令,而且執行它們。JVM可以保證Java代碼獨立於平臺。服務器
Java Runtime Environment (JRE):JRE包括JVM實現和Java API。架構
不一樣的JVM在垃圾回收的實現上可能有點不同。 在SUN被收購以前,Oracle有JRockit JVM,而且在SUN收購以後,Oracle得到了HotSpot JVM。目前Oracle維護了兩個JVM實現,而且已經聲明,在一段時間內這兩個JVM實現將被合併到一個。併發
HotSpot JVM是標準Oracle SE平臺的核心組件。在這個垃圾回收教程中,咱們將看到基於HotSpot虛擬機的垃圾回收原理。app
下圖總結了JVM的關鍵組件。在JVM架構中,兩個主要涉及到垃圾回收的組件是堆內存(heap memory)和垃圾回收器(garbage collector)。堆內存是運行時數據所在的區域,這個區域存着對象實例,垃圾回收器也在這個區域內操做。咱們如今看看這些東西如何適應到更大的方案。
正如虛擬機規範所說的那樣,JVM中的內存分爲5個虛擬的區域:
理解Java Heap Memory在JVM模型中的角色很重要。在運行期間,Java實例保存在heap memory區域。當一個對象沒有引用指向它時,它就須要從內存裏被清除。在垃圾回收的過程當中,這些對象在heap memory裏被清除,而後這些空間可以再被利用。heap memory主要有三個區域:
更新:持久代區域已經從Java SE 8移除。取代它的是另外一個內存區域也被稱爲元空間(本地堆內存中的一部分)。
這個系列博客主要是幫助理解Java回收的基本概念以及其如何工做。這部分是這個系列的第二部分,但願你有閱讀過第一部分Java Garbage Collection Introduction。
Java垃圾回收是一個自動處理的過程,目的是管理程序運行時的內存問題。經過這種方式,JVM可以自動減輕程序員在分配和釋放內存資源的問題。
垃圾回收做爲一個自動的過程,程序員不用在代碼裏顯式地啓動垃圾回收機制。System.gc()和Runtime.gc()可以顯式地讓JVM執行垃圾回收過程。
雖然這個機制可以讓程序員啓動垃圾回收,可是這個責任仍是在JVM上。JVM可以選擇拒絕你的請求,因此咱們無法保證這兩個命令確定會執行垃圾回收。JVM會經過eden區的可用內存來判斷是否執行垃圾回收。JVM規範把這個選擇留給了JVM的具體實現,因此這些實現的細節對不一樣的JVM來講也不同。
毫無疑問的是,垃圾回收機制不能強制執行。我剛剛發現了一個調用System.gc()的應用場景,看這篇文章就知道何時調用System.gc()是合適的。
垃圾回收是這樣一個過程:回收再也不使用的內存空間,讓這些空間可以被未來的實例對象所用。
當一個實例被建立的時候,它首先存在堆內存區域的年輕代的eden區。
注意:假如你不懂這些術語,建議你去讀Java Garbage Collection Introduction,這篇博客把內存模型、JVM架構和一些專業術語解釋的很詳細。
做爲次要垃圾回收(minor GC)週期的一部分,那些仍然被引用的對象會從eden區被搬到survivor的S0區。一樣的過程,垃圾回收器會掃描S0區,而後把實例搬到S1區。
那些沒有被引用的對象會被標記。經過不一樣的垃圾回收器(一共有四類垃圾回收器,後續的教程會講這個)來決定這些被標記的對象在內存中被移除仍是在單獨的進程中來完成。
年老代或者持久代是堆內存中的第二個邏輯部分。當垃圾回收器執行次要垃圾回收週期的時候,那些仍在S1區存活的實例將被移到年老代,S1區域中沒有被引用的實例將會被標記以便清除。
在垃圾回收的過程當中,年老代是實例對象生命週期的最後一個環節。Major GC是掃描年老代的堆內存的垃圾回收過程。若是有對象沒有被引用,那它們將被標記以便清除,若是有被引用,那它們將繼續留在年老代。
一旦實例對象在堆內存中被刪除,那它們所佔的區域就又能夠被未來的實例對象使用。這些空的區域在內存中將會造成不少碎片。爲了更快的爲實例分配內存,咱們應該解決這個碎片問題。根據不一樣垃圾回收器的選擇,被回收的內存將在回收的過程同時或者在GC另外獨立的過程當中壓縮整合。
就在清除一個對象並回收它的內存空間以前,垃圾回收器將會調用各個實例的finalize()方法,這樣實例對象就有機會能夠釋放掉它佔用的資源。儘管finalize()方法是保證在回收內存空間以前執行的,可是對具體的執行時間和執行順序是沒有任何保證的。多個實例之間的finalize()執行順序是不能提早預知的,甚至有可能它們是並行執行的。程序不該該預先假設實例執行finalize()的順序,也不該該使用finalize()方法來回收資源。
Java中有多種不一樣的引用類型。實例的可回收性取決於它的引用類型。
引用 | 垃圾回收 |
---|---|
強引用 | 不適合被垃圾回收 |
軟引用 | 可能會被回收,可是是在最後被回收 |
弱引用 | 適合被回收 |
虛引用 | 適合被回收 |
在編譯期間,Java編譯器有優化技術來選擇把null值賦給一個實例,從而把這個實例標記爲可回收。
class Animal { public static void main(String[] args) { Animal lion = new Animal(); System.out.println("Main is completed."); } protected void finalize() { System.out.println("Rest in Peace!"); } }
在上面的類中,lion實例在初始化後就沒被用過了,因此Java編譯器在lion實例化後把lion賦值爲null而做爲一個優化的手段。這樣finlizer方法可能會在Main方法以前打印結果。咱們不能肯定地保證結果,由於這和JVM的具體實現以及運行時的內存有關。可是咱們能知道的一點是:編譯器發現一個實例在以後的程序中再也不被引用時能夠選擇提早釋放實例的內存。
Class GCScope { GCScope t; static int i = 1; public static void main(String args[]) { GCScope t1 = new GCScope(); GCScope t2 = new GCScope(); GCScope t3 = new GCScope(); // No Object Is Eligible for GC t1.t = t2; // No Object Is Eligible for GC t2.t = t3; // No Object Is Eligible for GC t3.t = t1; // No Object Is Eligible for GC t1 = null; // No Object Is Eligible for GC (t3.t still has a reference to t1) t2 = null; // No Object Is Eligible for GC (t3.t.t still has a reference to t2) t3 = null; // All the 3 Object Is Eligible for GC (None of them have a reference. // only the variable t of the objects are referring each other in a // rounded fashion forming the Island of objects with out any external // reference) } protected void finalize() { System.out.println("Garbage collected from object" + i); i++; }
垃圾回收不保證內存的安全問題,粗心的代碼會形成內存溢出。
import java.util.LinkedList; import java.util.List; public class GC { public static void main(String[] main) { List l = new LinkedList(); // Enter infinite loop which will add a String to the list: l on each // iteration. do { l.add(new String("Hello, World")); } while (true); } }
輸出爲
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.LinkedList.linkLast(LinkedList.java:142) at java.util.LinkedList.add(LinkedList.java:338) at com.javapapers.java.GCScope.main(GCScope.java:12)
在這個教程中,咱們將學習各類各樣的垃圾回收器。Java垃圾回收是一個自動處理的過程,目的是管理程序運行時的內存問題。這是這個系列教程的第三部分,在前面兩個部分,咱們學習了How Java Garbage Collection Works,這部分頗有意思,我建議你完整讀一遍。而第一部分Java Garbage Collection Introduction中,咱們學習了JVM的架構、堆內存模型和Java相關的術語。
Java有四種垃圾回收器:
這四種垃圾回收器各有本身的利弊。更重要的是,咱們程序員能夠選擇JVM用哪一種垃圾回收器。咱們通常是經過設置JVM參數來選擇垃圾回收器。各個垃圾回收器在不一樣應用場景下的效率會有很大的差別。所以瞭解各類不一樣類型的垃圾回收器以及它們的應用場景是很是重要的。
串行垃圾回收器控制全部的應用線程。它是爲單線程環境設計的,只使用一個線程來執行垃圾回收工做。它的工做方式是暫停全部應用線程來執行垃圾回收工做,這種方式不適用於服務器的應用環境。它最適用的是簡單的命令行程序。
使用-XX:+UseSerialGC JVM參數來開啓使用串行垃圾回收器。
並行垃圾回收器也稱做基於吞吐量的回收器。它是JVM的默認垃圾回收器。與Serial垃圾回收器不一樣的是,它使用多個線程來執行垃圾回收工做。和Serial回收器同樣,它在執行垃圾回收工做是也須要暫停全部應用線程。
併發標記清除(Concurrent Mark Sweep,CMS)垃圾回收器,使用多個線程來掃描堆內存並標記可被清除的對象,而後清除標記的對象。CMS垃圾回收器只在下面這兩種情形下暫停工做線程:
對比與並行垃圾回收器,CMS回收器使用更多的CPU來保證更高的吞吐量。若是咱們能夠有更多的CPU用來提高性能,那麼CMS垃圾回收器是比並行回收器更好的選擇。
使用-XX:+UseParNewGC JVM參數來開啓使用CMS垃圾回收器。
G1垃圾回收器應用於大的堆內存空間。它將堆內存空間劃分爲不一樣的區域,對各個區域並行地作回收工做。G1在回收內存空間後還當即堆空閒空間作整合工做以減小碎片。CMS倒是在所有中止(stop the world,STW)時執行內存整合工做。G1會根據各個區域的垃圾數量來對區域評判優先級。
使用-XX:UseG1GC JVM參數來開啓使用G1垃圾回收器。
在使用G1垃圾回收器時,開啓使用-XX:+UseStringDeduplacaton JVM參數。它會經過把重複的String值移動到同一個char[]數組來優化堆內存佔用。這是Java 8 u 20引入的選項。
以上給出的四個Java垃圾回收器,在何時使用哪個取決於應用場景,硬件配置和吞吐量要求。
下面是與Java垃圾回收相關、比較重要的JVM選項。
選項 | 描述 |
---|---|
-XX:+UseSerialGC | Serial Garbage Collector |
-XX:+UseParallelGC | Parallel Garbage Collector |
-XX:+UseConcMarkSweepGC | CMS Garbage Collector |
-XX:ParallelCMSThreads= | CMS Collector – number of threads to use |
-XX:+UseG1GC | G1 Gargbage Collector |
選項 | 描述 |
---|---|
-Xms | Initial heap memory size |
-Xmx | Maximum heap memory size |
-Xmn | Size of Young Generation |
-XX:PermSize | Initial Permanent Generation size |
-XX:MaxPermSize | Maximum Permanent Generation size |
java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC -jar java-application.jar
這篇教程主要介紹垃圾回收機制的監控和分析,咱們會用到一個工具來監控一個Java應用的垃圾回收過程。若是你是一個新手,你最好把這系列博客的前幾篇都看一下,你能夠從Java Garbage Collection Introduction開始。
下面說的工具各自都有利弊,咱們能夠經過選擇合適的工具來進行分析以此提升Java應用的性能。咱們這篇教程主要用Java VisualVM。
Java VisualVM能夠經過Java SE SDK來免費安裝使用。看看你的Java JDK安裝路徑,就在\Java\jdk1.8.0\bin路徑下。固然還有不少其餘的工具,jvisualvm只是其中之一。
Java VisualVM提供了一個包含Java程序信息的可視化接口。它包含不少其餘的工具且集成到這一個。如今像JConsole, jstat, jinfo, jstack, and jmap都被集成到Java VisualVM中了。
Java VisualVM主要用來:
JDK目錄下就有Java VisualVM。
咱們須要安裝一個可視化插件,以便觀察Java GC過程。
啓動你的Java應用,Java VisualVM會自動開始監控,並把結果展現在Java VisualVM可視化接口中。你能夠選擇不一樣的組件來觀察你感興趣的指標。