Java垃圾回收

這篇博客是對Java垃圾回收的總結,主要是對Java Garbage Collection Introduction以及後續的三篇博客的翻譯。我把這四篇博客翻譯到這一篇博客裏,把參考的其餘博客的連接附在文章末尾。java

Java Garbage Collection Introduction

在Java中,分配和回收對象的內存空間是在JVM中、由垃圾回收機制自動完成的。不像C語言,使用Java的開發人員不須要寫代碼來進行垃圾回收。Java之因此如此流行以及可以幫助程序員更好的開發,這是其中一個緣由。程序員

這個系列一共有四篇博客來介紹Java的垃圾回收,分別是:算法

  1. Java Garbage Collection Introduction
  2. How Java Garbage Collection Works?
  3. Types of Java Garbage Collectors
  4. Java Garbage Collection Monitoring and Analysis

這一節是這篇博客的第一部分。主要內容是解釋一些基礎的術語,好比JDK, JVM, JRE, HotSpot VM,而後理解JVM的總體架構和Java堆內存結構。理解這些概念對學習Java垃圾回收頗有幫助。數組

Key Java Terminologies

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。架構

Java HotSpot Virtual Machine

不一樣的JVM在垃圾回收的實現上可能有點不同。 在SUN被收購以前,Oracle有JRockit JVM,而且在SUN收購以後,Oracle得到了HotSpot JVM。目前Oracle維護了兩個JVM實現,而且已經聲明,在一段時間內這兩個JVM實現將被合併到一個。併發

HotSpot JVM是標準Oracle SE平臺的核心組件。在這個垃圾回收教程中,咱們將看到基於HotSpot虛擬機的垃圾回收原理。app

JVM Architecture

下圖總結了JVM的關鍵組件。在JVM架構中,兩個主要涉及到垃圾回收的組件是堆內存(heap memory)和垃圾回收器(garbage collector)。堆內存是運行時數據所在的區域,這個區域存着對象實例,垃圾回收器也在這個區域內操做。咱們如今看看這些東西如何適應到更大的方案。

Runtime Data Area

正如虛擬機規範所說的那樣,JVM中的內存分爲5個虛擬的區域:

  • Method Area:也被稱爲非堆區域(在HotSpot JVM的實現當中)。它被分爲兩個主要的子區域:持久代和代碼緩存。前者會存儲包括類定義,結構,字段,方法(數據及代碼)以及常量在內的類相關數據。後者是用來存儲編譯後的代碼。編譯後的代碼就是本地代碼,它是由JIT編譯器生成的,這個編譯器是Oracle HotSpot JVM所特有的。
  • Heap Memory:後續會詳細介紹。
  • Java Stacks:和Java類中的方法密切相關,它會存儲局部變量以及方法調用的中間結果及返回值,Java中的每一個線程都有本身專屬的棧,這個棧是別的線程沒法訪問的。
  • PC Registers:特定線程的程序計數器,包含JVM正在執行的指令的地址(若是是本地方法的話它的值則未定義)
  • Native Method Stack:用於本地方法(非Java代碼),按線程分配

Java Heap Memory

理解Java Heap Memory在JVM模型中的角色很重要。在運行期間,Java實例保存在heap memory區域。當一個對象沒有引用指向它時,它就須要從內存裏被清除。在垃圾回收的過程當中,這些對象在heap memory裏被清除,而後這些空間可以再被利用。heap memory主要有三個區域:

  1. 年輕代。年輕代分三個區:Eden區(任何進去內存區域的對象實例都要通過eden區)、Survivor0區(從eden區過來S0區的、稍微老一點的對象)、Survivor1區(從S0區過來、稍微老一點的對象)
  2. 年老代。這個區主要是從S1過來的對象。
  3. 持久代。這個區包含一些元數據信息好比,類、方法等。

更新:持久代區域已經從Java SE 8移除。取代它的是另外一個內存區域也被稱爲元空間(本地堆內存中的一部分)。

How Java Garbage Collection Works?

這個系列博客主要是幫助理解Java回收的基本概念以及其如何工做。這部分是這個系列的第二部分,但願你有閱讀過第一部分Java Garbage Collection Introduction

Java垃圾回收是一個自動處理的過程,目的是管理程序運行時的內存問題。經過這種方式,JVM可以自動減輕程序員在分配和釋放內存資源的問題。

Java Garbage Collection GC Initiation

垃圾回收做爲一個自動的過程,程序員不用在代碼裏顯式地啓動垃圾回收機制。System.gc()和Runtime.gc()可以顯式地讓JVM執行垃圾回收過程。

雖然這個機制可以讓程序員啓動垃圾回收,可是這個責任仍是在JVM上。JVM可以選擇拒絕你的請求,因此咱們無法保證這兩個命令確定會執行垃圾回收。JVM會經過eden區的可用內存來判斷是否執行垃圾回收。JVM規範把這個選擇留給了JVM的具體實現,因此這些實現的細節對不一樣的JVM來講也不同。

毫無疑問的是,垃圾回收機制不能強制執行。我剛剛發現了一個調用System.gc()的應用場景,看這篇文章就知道何時調用System.gc()是合適的

Java Garbage Collection Process

垃圾回收是這樣一個過程:回收再也不使用的內存空間,讓這些空間可以被未來的實例對象所用。

Eden Space

當一個實例被建立的時候,它首先存在堆內存區域的年輕代的eden區。

注意:假如你不懂這些術語,建議你去讀Java Garbage Collection Introduction,這篇博客把內存模型、JVM架構和一些專業術語解釋的很詳細。

Survivor Space (S0 and S1)

做爲次要垃圾回收(minor GC)週期的一部分,那些仍然被引用的對象會從eden區被搬到survivor的S0區。一樣的過程,垃圾回收器會掃描S0區,而後把實例搬到S1區。

那些沒有被引用的對象會被標記。經過不一樣的垃圾回收器(一共有四類垃圾回收器,後續的教程會講這個)來決定這些被標記的對象在內存中被移除仍是在單獨的進程中來完成。

Old Generation

年老代或者持久代是堆內存中的第二個邏輯部分。當垃圾回收器執行次要垃圾回收週期的時候,那些仍在S1區存活的實例將被移到年老代,S1區域中沒有被引用的實例將會被標記以便清除。

Major GC

在垃圾回收的過程當中,年老代是實例對象生命週期的最後一個環節。Major GC是掃描年老代的堆內存的垃圾回收過程。若是有對象沒有被引用,那它們將被標記以便清除,若是有被引用,那它們將繼續留在年老代。

Memory Fragmentation

一旦實例對象在堆內存中被刪除,那它們所佔的區域就又能夠被未來的實例對象使用。這些空的區域在內存中將會造成不少碎片。爲了更快的爲實例分配內存,咱們應該解決這個碎片問題。根據不一樣垃圾回收器的選擇,被回收的內存將在回收的過程同時或者在GC另外獨立的過程當中壓縮整合。

Finalization of Instances in Garbage Collection

就在清除一個對象並回收它的內存空間以前,垃圾回收器將會調用各個實例的finalize()方法,這樣實例對象就有機會能夠釋放掉它佔用的資源。儘管finalize()方法是保證在回收內存空間以前執行的,可是對具體的執行時間和執行順序是沒有任何保證的。多個實例之間的finalize()執行順序是不能提早預知的,甚至有可能它們是並行執行的。程序不該該預先假設實例執行finalize()的順序,也不該該使用finalize()方法來回收資源。

  • 在finalize過程當中拋出的任何異常都默認被忽略掉了,同時該對象的銷燬過程被取消
  • JVM規範並無討論關於弱引用的垃圾回收,而且是明確聲明的。具體的細節留給實現者決定。
  • 垃圾回收是由守護進程執行的

When an object becomes eligible for garbage collection?

  • 任何不能被活着的線程用到的實例
  • 不能被其餘對象用到的循環引用對象

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的具體實現以及運行時的內存有關。可是咱們能知道的一點是:編譯器發現一個實例在以後的程序中再也不被引用時能夠選擇提早釋放實例的內存。

  • 這裏有個實例什麼時候變成可回收更好的例子。實例全部的屬性能夠被存儲在寄存器中,以後能夠從寄存器中讀取這些屬性值。在任何狀況下,這些值未來都不會被寫回到實例對象中。儘管這些值在將來仍是被使用到了,可是實例對象依然能夠被標記爲可回收的。(注:這個例子我沒看懂)
  • 咱們能夠簡單地認爲,當一個實例被賦值爲null時就能夠被回收。咱們也能夠像上面提到的、複雜地考慮這個問題。這些都是由JVM的具體實現決定的。其目標都是但願留下最少的痕跡、提升響應性能和增大吞吐量。爲了可以達到這些目的,JVM實現者能夠在垃圾回收中選擇更好的模式或算法來回收內存。
  • 當finalize()被調用的時候,JVM釋放掉當前線程的全部同步塊。

Example Program for GC Scope

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++;
    }

Example Program for GC OutOfMemoryError

垃圾回收不保證內存的安全問題,粗心的代碼會形成內存溢出。

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)

Types of Java Garbage Collectors

在這個教程中,咱們將學習各類各樣的垃圾回收器。Java垃圾回收是一個自動處理的過程,目的是管理程序運行時的內存問題。這是這個系列教程的第三部分,在前面兩個部分,咱們學習了How Java Garbage Collection Works,這部分頗有意思,我建議你完整讀一遍。而第一部分Java Garbage Collection Introduction中,咱們學習了JVM的架構、堆內存模型和Java相關的術語。

Java有四種垃圾回收器:

  1. Serial Garbage Collector
  2. Parallel Garbage Collector
  3. CMS Garbage Collector
  4. G1 Garbage Collector

這四種垃圾回收器各有本身的利弊。更重要的是,咱們程序員能夠選擇JVM用哪一種垃圾回收器。咱們通常是經過設置JVM參數來選擇垃圾回收器。各個垃圾回收器在不一樣應用場景下的效率會有很大的差別。所以瞭解各類不一樣類型的垃圾回收器以及它們的應用場景是很是重要的。

Serial Garbage Collector

串行垃圾回收器控制全部的應用線程。它是爲單線程環境設計的,只使用一個線程來執行垃圾回收工做。它的工做方式是暫停全部應用線程來執行垃圾回收工做,這種方式不適用於服務器的應用環境。它最適用的是簡單的命令行程序。

使用-XX:+UseSerialGC JVM參數來開啓使用串行垃圾回收器。

Parallel Garbage Collector

並行垃圾回收器也稱做基於吞吐量的回收器。它是JVM的默認垃圾回收器。與Serial垃圾回收器不一樣的是,它使用多個線程來執行垃圾回收工做。和Serial回收器同樣,它在執行垃圾回收工做是也須要暫停全部應用線程。

CMS Garbage Collector

併發標記清除(Concurrent Mark Sweep,CMS)垃圾回收器,使用多個線程來掃描堆內存並標記可被清除的對象,而後清除標記的對象。CMS垃圾回收器只在下面這兩種情形下暫停工做線程:

  1. 在老年代中標記引用對象的時候
  2. 在並行垃圾回收的過程當中堆內存中有變化發生

對比與並行垃圾回收器,CMS回收器使用更多的CPU來保證更高的吞吐量。若是咱們能夠有更多的CPU用來提高性能,那麼CMS垃圾回收器是比並行回收器更好的選擇。

使用-XX:+UseParNewGC JVM參數來開啓使用CMS垃圾回收器。

G1 Garbage Collector

G1垃圾回收器應用於大的堆內存空間。它將堆內存空間劃分爲不一樣的區域,對各個區域並行地作回收工做。G1在回收內存空間後還當即堆空閒空間作整合工做以減小碎片。CMS倒是在所有中止(stop the world,STW)時執行內存整合工做。G1會根據各個區域的垃圾數量來對區域評判優先級。

使用-XX:UseG1GC JVM參數來開啓使用G1垃圾回收器。

Java 8 Improvement

在使用G1垃圾回收器時,開啓使用-XX:+UseStringDeduplacaton JVM參數。它會經過把重複的String值移動到同一個char[]數組來優化堆內存佔用。這是Java 8 u 20引入的選項。

以上給出的四個Java垃圾回收器,在何時使用哪個取決於應用場景,硬件配置和吞吐量要求。

Garbage Collection JVM Options

下面是與Java垃圾回收相關、比較重要的JVM選項。

Type of Garbage Collector to run
選項 描述
-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
GC Optimization Options
選項 描述
-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
Example Usage of JVM GC Options
java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC -jar java-application.jar

Java Garbage Collection Monitoring and Analysis

這篇教程主要介紹垃圾回收機制的監控和分析,咱們會用到一個工具來監控一個Java應用的垃圾回收過程。若是你是一個新手,你最好把這系列博客的前幾篇都看一下,你能夠從Java Garbage Collection Introduction開始。

Java Garbage collection monitoring and analysis tools

下面說的工具各自都有利弊,咱們能夠經過選擇合適的工具來進行分析以此提升Java應用的性能。咱們這篇教程主要用Java VisualVM。

  • Java VisualVM
  • Naarad
  • GCViewer
  • IBM Pattern Modeling and Analysis Tool for Java Garbage Collector
  • HPjmeter
  • IBM Monitoring and Diagnostic Tools for Java – Garbage Collection and Memory
  • Visualizer
  • Verbose GC Analyzer

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主要用來:

  • generate and analyze heap memory dumps
  • view and operate on MBeans
  • monitor garbage collection
  • memory and CPU profiling
Launch VisualVM

JDK目錄下就有Java VisualVM。

Install Visual GC Plugin

咱們須要安裝一個可視化插件,以便觀察Java GC過程。

Monitor GC

啓動你的Java應用,Java VisualVM會自動開始監控,並把結果展現在Java VisualVM可視化接口中。你能夠選擇不一樣的組件來觀察你感興趣的指標。

參考的其餘博客

java8的JVM持久代——何去何從?

相關文章
相關標籤/搜索