垃圾回收機制的意義
垃圾回收可以有效的防止內存泄露,有效的使用空閑的內存;
內存泄露:指該內存空間使用完畢后未回收,在不涉及復雜數據結構的一般情況下,java的內存泄露表現為一個內存對象的生命周期超出了程序需要它的時間長度,我們有是也將其稱為“對象游離”;
整體了解 JDK & JVM
首先要對官方的 SDK 有點認識,同時要明白下面的概念:
- Java SE(Java Platform, Standard Edition):它是 Java 的標準版,主要用于桌面應用開發(fā),同時也是 Java 的基礎,它包含 Java 語言基礎、JDBC(Java 數據庫連接性)操作、I/O(輸出輸出)操作、網絡通信、多線程等技術。
- Java EE(Java Platform, Enterprise Edition):它是 Java 的企業(yè)版本(javax..*),包含了 Servlet、JSP、JMS、JNDI 等的擴展。
- Java ME(Java Platform, Micro Edition):一般是指 Java ME Embedded,Java 微型版本,一般做嵌入式開發(fā)用。
- JRE(Java Runtime Environment),Java 運行環(huán)境。
- JDK(Java Development Kit),Java 開發(fā)者工具集,是用來編譯和執(zhí)行 Java 程序必備的 Java 開發(fā)環(huán)境,現在我們一般說 JDK 就是指的 Oracle 的 Java SE。因為 Sun JDK 和 Open JDK、JRockit 都被 Orcale 收購了,一統了江湖。
我們看下 Oracle 官方的圖,如下:
- JDK全視角
這樣我們對 JDK、JRE,有了概念上的認識之后,我們來看下我們的 JVM(Java Virtual Machine)——Java 虛擬機,也就是 HotSport 了。
Java 內存模型
Java 內存模型是理解 Java 多線程和 Java GC 必須要了解的抽象知識點,我們可以通過工具來更好的掌握 Java 內存模型。我給大家一個點:“我們通過不同的視角來理解內存模型”。我們可以通過不同的視角來理解內存模型。因為 JVM 類里面實際的內存操作遠比我們想象得要復雜,因為這部分代碼是 Oracle 官方的核心機密,沒有對外公開,我們也只能通過官方文檔及其 Jdk/bin 目錄下面的工具來做到整體認識。
站在理解線程的視角看內存模型
我們可以把 JVM 內存結構直接分成線程私有內存和共享主內存。這樣我們就可以很好地理解多線程的很多問題如同步鎖、lock、validate 關鍵字,及其 ThreadLocal.
我們從內存設置的角度出發(fā)
我們可以將內存直接分位堆內存、非堆內存(JDK8 以后叫 Metaspace,元空間)和其它,三個大的類別。 我們來看一下 JConsole 和 JVisualVM。
java/bin/jconsole 打開以后界面如下:
java/bin/jvisualvm 打開以后界面如下:
利用 tools,也可以看到 Java 工具也是簡單的將其分成堆和非堆(Metaspace)。
而其它是什么呢?
Other 指的是“直接內存”,如一些(IO/NIO),這些 JVM 控制不了(如果線程變多線程棧吃的內存也會變的非常大,不可設置)。
對應的 JVM 設置的參數是:
- Xmx4g:JVM 最大允許分配的堆內存,按需分配;
- Xms4g:JVM 初始分配的堆內存,一般和 Xmx 配置成一樣以避免每次 gc 后 JVM 重新分配內存;
- XX:MetaspaceSize=64m 初始化元空間大?。?/li>
- XX:MaxMetaspaceSize=128m 最大化元空間大小。
Metaspace 建議大家不要設置,一般讓 JVM 自己啟動的時候動態(tài)擴容就好了,沒必要自己去設置。如果不動態(tài)加載 class ,當啟動起來的時候,一般是很少有變化的。
從這個角度我們可以認為我們的 JVM 內存的大小是堆+metaspace+io(運行時產生的大小)。
我們從 JVM 的運行期的視角來看
可以分為五大部分:方法區(qū)、堆、本地方法棧區(qū)、PC 計數器、線程棧。我們也可以看下面的圖,PC 計數器和棧、本地方法棧,是隨著當前的線程開始而開始,銷毀而銷毀的。
我們再通過下面這個圖理解一下這五個區(qū)和線程的關系:
對應的 JVM 的參數為 Xss512k,用來設置每個線程的堆棧大小。
從垃圾回收機制的視角來看
垃圾回收算法種類
標記-清除算法
標記-清除算法分兩個步驟,分別為“標記”和“清除”,字如其人。它是一個最基礎的垃圾回收算法,更高級的垃圾回收算法都是基于它改進的。
它的運行過程是這樣的:首先標記出所有需要回收的對象,標記完成后,再統一回收掉所有被標記的對象。

標記-清除算法的缺點有兩個,一個是空間問題,標記清除之后會產生大量的不連續(xù)內存碎片。內存碎片太多,程序在之后的運行過程中就有可能找不到足夠的連續(xù)內存來分配較大的對象,進而不得不提前觸發(fā)另一次垃圾回收,導致程序效率降低。標記-清除算法的另一個缺點是效率問題,標記和清除的效率都不高,兩次掃描耗時嚴重。
復制算法
復制算法把內存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。如果正在用的這塊沒有足夠的可使用空間了,那么就將還活著的對象復制到另一塊去,再把使用過的內存一次性清掉。

這樣就實現了簡單高效的做法,每一次進行內存回收時,就不用再去考慮內存碎片這些復雜的情況,只需要移動堆頂指針就可以。但是缺點也很明顯,可使用內存只有原來的一半了,而且持續(xù)復制生命力很旺盛的對象也會讓效率降低哇。復制算法適用于存活對象少、垃圾對象多的情況,這種情況在新生代比較常見。
標記-壓縮算法
在老年代,大部分對象都是存活的對象,復制算法在這里就不靠譜了,所以有人提出了標記壓縮算法,標記過程和標記清除算法一樣,但是清理時不是簡單的清理,而是讓所有存活的對象都向一端移動,然后直接清理掉邊界以外的內存,需要移動對象的成本。

分代垃圾收集算法
之前說過,逐一標記和壓縮 Java 虛擬機里的所有對象非常低效:分配的對象越多,垃圾回收需時就越久。不過,根據統計,大部分的對象,其實用沒多久就不用了。
來看個例子吧。(下圖中,豎軸代表已分配的字節(jié),而橫軸代表程序運行時間)

上圖可見,存活(沒被釋放)的對象隨運行時間越來越少。而圖中左側的那些峰值,也表明了大部分對象其實都挺短命的。
JVM 分代
根據之前的規(guī)律,就可以用來提升 JVM 的效率了。方法是,把堆分成幾個部分(就是所謂的分代),分別是新生代、老年代,以及永生代。
- image
新對象會被分配在新生代內存。一旦新生代內存滿了,就會開始對死掉的對象,進行所謂的小型垃圾回收過程。一片新生代內存里,死掉的越多,回收過程就越快;至于那些還活著的對象,此時就會老化,并最終老到進入老年代內存。
Stop the World 事件 —— 小型垃圾回收屬于一種叫 "Stop the World" 的事件。在這種事件發(fā)生時,所有的程序線程都要暫停,直到事件完成(比如這里就是完成了所有回收工作)為止。
老年代用來保存長時間存活的對象。通常,設置一個閾值,當達到該年齡時,年輕代對象會被移動到老年代。最終老年代也會被回收。這個事件成為 Major GC。
Major GC 也會觸發(fā)STW(Stop the World)。通常,Major GC會慢很多,因為它涉及到所有存活對象。所以,對于響應性的應用程序,應該盡量避免Major GC。還要注意,Major GC的STW的時長受年老代垃圾回收器類型的影響。
永久代包含JVM用于描述應用程序中類和方法的元數據。永久代是由JVM在運行時根據應用程序使用的類來填充的。此外,Java SE類庫和方法也存儲在這里。
如果JVM發(fā)現某些類不再需要,并且其他類可能需要空間,則這些類可能會被回收。
分代垃圾收集過程
現在你已經理解了為什么堆被分成不同的代,現在是時候看看這些空間是如何相互作用的。 后面的圖片將介紹JVM中的對象分配和老化過程。
首先,將任何新對象分配給 eden 空間。 兩個 survivor 空間都是空的。
- b0ca011987f9aa3f7e574ef982933e19224.jpg
當 eden 空間填滿時,會觸發(fā)輕微的垃圾收集。
- image
引用的對象被移動到第一個 survivor 空間。 清除 eden 空間時,將刪除未引用的對象。
- image
在下一次Minor GC中,Eden區(qū)也會做同樣的操作。刪除未被引用的對象,并將被引用的對象移動到Survivor區(qū)。然而,這里,他們被移動到了第二個Survivor區(qū)(S1)。此外,第一個Survivor區(qū)(S0)中,在上一次Minor GC幸存的對象,會增加年齡,并被移動到S1中。待所有幸存對象都被移動到S1后,S0和Eden區(qū)都會被清空。注意,Survivor區(qū)中有了不同年齡的對象。
- image
在下一次Minor GC中,會重復同樣的操作。不過,這一次Survivor區(qū)會交換。被引用的對象移動到S0,。幸存的對象增加年齡。Eden區(qū)和S1被清空。
- image
此幻燈片演示了 promotion。 在較小的GC之后,當老化的物體達到一定的年齡閾值(在該示例中為8)時,它們從年輕一代晉升到老一代。
- image
隨著較小的GC持續(xù)發(fā)生,物體將繼續(xù)被推廣到老一代空間。
- image
所以這幾乎涵蓋了年輕一代的整個過程。 最終,將主要對老一代進行GC,清理并最終壓縮該空間。
- image








