Java 內存結構備忘錄

本文詳細描述了 Java 堆內存模型,垃圾回收算法以及處理內存泄露的最佳方案,並輔之以圖表,但願能對理解 Java 內存結構有所幫助。原文做者 Sumith Puri,本文系 OneAPM 工程師編譯整理。html

下圖展現了 Java 堆內存模型,以及運行在 Java 虛擬機中任意 Java 應用的 PermGen (內存永久保存區域),下面的比率展現了 JVM 各代類型容許的內存大小分配狀況,全部的數據均適用於 Java 1.7 及如下版本。該圖也被稱爲 Java 內存模型的「管理區(Managed Area)」。java

Java 內存結構(Java 內存模型)

Java 內存結構備忘錄

除此以外,還有一塊堆棧區(Stack Area),可經過 -Xss 選項進行配置。該區域存儲了全部線程的堆引用、本地引用、程序計數器寄存器、代碼緩存以及本地變量。該區域也稱爲內存模型的本地區(Native Area)。程序員

Java 內存模型(結構)的管理區

[Young Generation/Nursery] 伊甸園區(Eden Space)

全部新對象都首先在 Eden Space 建立。一旦該區達到由 JVM 設定的任意閾值,新生代垃圾回收機制(Minor GC)就會啓動。它會首先清除全部的非引用對象,並將引用對象從 'eden' 與 'from' 區移至 'to' 倖存者區。垃圾回收一結束,'from' 與 'to' 的角色(名字)就會對換。算法

[Young Generation/Nursery] 倖存者 1 區 (From)

這是倖存者區的一部分。或者視爲倖存者區中的一個角色。這兒就是以前垃圾回收中的 'to' 角色。編程

[Young Generation/Nursery] 倖存者 2 區 (To)

這也是倖存者區的一部分。也能夠視爲倖存者區中的一個角色。垃圾回收過程當中的全部引用對象都會從 'from' 與 'eden' 區移至此處。數組

[Old Generation] 年老代區(Tenured)

根據閾值限定的不一樣,對象們會從 'to' 倖存者區移至年老代區。你可使用 -XX:+PrintTenuringDistribution 檢查閾值,該指令會按照年齡顯示對象(佔用的字節空間)。年齡是指對象在倖存者區內移動的次數。緩存

其餘重要的標記還有 -XX:InitialTenuringThreshold-XX:MaxTenuringThreshold-XX:TargetSurvivorRatio ,這些標記能幫你實現最佳的年老代區與倖存者區使用方案。服務器

經過設置 -XX:InitialTenuringThreshold-XX:MaxTenuringThreshold,能夠指定年齡的最初值與最大值,而倖存者區 (To) 的使用率則由 -XX:+NeverTenure-XX:+AlwaysTenure 決定。前者是指永遠不將對象存儲到年老代區,然後者偏偏相反,老是將對象存儲到年老代區。併發

此處進行的垃圾回收是年老代垃圾回收(Major GC)。當堆空間已滿或者年老代區佔滿時,就會觸發 Major GC。此時,一般會由一個「中止一切(Stop-the-World)」事件或線程執行垃圾回收。此外,還有另外一種稱爲全垃圾回收(Full GC)的垃圾回收機制,會涉及諸如永久內存區域。編程語言

與總體堆內存相關的另兩個重要且有趣的標記是 -XX:SurvivorRatio-XX:NewRatio,前者指定伊甸園區相對倖存者區的比率,後者指定年老代區相對新生代區的比率。

[Permanent Generation] 永久代區(Permgen space)

永久代區(Permgen)用於存儲如下信息:常量池 (內存池),字段與方法數據及代碼。

垃圾回收算法

串行 GC(Serial GC) (-XX:UseSerialGC): 針對年輕代與年老代的垃圾回收

該算法使用簡單的「標記-清掃-壓縮(mark-sweep-compact)」循環清理年輕代與年老代,適合內存佔用較低、CPU 使用量較少的客戶端系統。

並行 GC(Parallel GC) (-XX:UseParallelGC): 針對年輕代與年老代的垃圾回收

該算法使用 N 個線程(N 的值能夠經過 ** -XX:ParallelGCThreads=N** 設定,N 同時表明垃圾回收佔用的 CPU 內核數)。其中,年輕代垃圾回收會使用 N 個線程,而年老代只用一個線程。

並行 Old GC (-XX:UseParallelOldGC): 針對年輕代與年老代的垃圾回收

該算法對年輕代與年老代均使用 N 個線程,其餘方面與並行 GC 徹底一致。

併發 Mark and Sweep GC (-XX:ConcMarkSweepGC): 針對年老代的垃圾回收

顧名思義,CMS GC 會最小化垃圾回收所需的停頓時間。該算法最適於建立高響應度的應用,且只做用於年老代。它會建立多條垃圾回收的線程,與應用線程同時工做。垃圾回收的線程數量可使用 -XX:ParallelCMSThreads=n 標記指定。

G1 GC (-XX:UseG1GC): 針對年輕代與年老代的垃圾回收 (將堆內存等分爲大小相同的區塊)

這是一種並行、併發、不斷壓縮的低停頓垃圾回收器。G1 是在 Java 7 中引入以取代 CMS GC 的,它會先將堆內存分爲多個大小相等的區塊,繼而執行垃圾回收。一般,從活動數據最少的區塊開始,所以以垃圾爲先。

最多見的內存溢出問題

全部 Java 程序員都應該知道的最多見的內存溢出問題:

  • Exception in thread "main": java.lang.OutOfMemoryError: Java heap space( Java 堆內存)。這並不必定意味着內存泄露,也多是分配的堆內存空間過小。此外,在運行時間較長的應用中,也多是由於一個無心識的引用被指向堆對象(內存泄露)。即使是應用自己調用的 APIs,也可能保存着指向無依據對象的引用。並且,在大量使用終結器的應用中,對象們有時可能正排在終結隊列中。當這樣的應用建立高優先級的線程時,會致使愈來愈多的對象排在終結隊列中,最終致使內存溢出。

  • Exception in thread "main": java.lang.OutOfMemoryError: PermGen space(永久存儲空間)。若是加載了不少類與方法,或者建立了不少字符串常量,特別是使用 intern() 方法進行建立(從 JDK 7 開始,interned 字符串就再也不存儲在 PermGen 中),這類錯誤就會出現。當出現這類錯誤時,打印的堆棧跟蹤附近可能會出現以下文本:ClassLoader.defineClass。
  • Exception in thread "main": java.lang.OutOfMemoryError: Requested array size exceeds VM limit (請求的數組大小超出 VM 限制)。當請求的數組大小超過可用的堆空間時,這類報錯就會出現。這類錯誤一般歸咎於編程錯誤,在運行時請求了極大的數組大小。
  • Exception in thread "main": java.lang.OutOfMemoryError:
    request <s> bytes for <r>,交換空間溢出?這是內存泄露最多見的根源。一般,當操做系統沒有足夠的交換空間,或另外一個進程佔用了系統中的全部可用內存,就會致使內存泄露。簡而言之,因爲空間用盡,堆內存沒法提供所請求的空間大小。該信息中的 's' 表明失敗的請求所需的內存大小(以字節爲單位),而 'r' 表明內存請求的緣由。在大多數狀況下,此處的 'r' 是報告分配失敗的源模塊,有時也會是具體的緣由。

  • Exception in thread "main": java.lang.OutOfMemoryError: (Native method)( <緣由> <堆棧跟蹤> <本地方法> )。該報錯意味着一個本地方法遇到內存分配失敗。問題的根源在於 Java Native Interface(Java 本地接口) 中存在的錯誤,而非 JVM 中運行的代碼錯誤。如果本地代碼不檢查內存分配錯誤,應由會直接崩潰,而不會出現內存溢出。

內存泄露的定義

你能夠將內存泄露看作一種疾病,而內存溢出錯誤爲一種徵兆。但不是全部的內存溢出錯誤都意味着內存泄露,不是全部的內存泄露都之內存溢出爲徵兆。

維基百科的定義:在計算機科學中,內存泄露是一種以以下方式發生的資源泄露——計算機程序錯誤地分配內存,致使再也不須要的內存得不到釋放。在面向對象的編程語言中,一個對象若存儲在內存中,卻沒法由運行的代碼獲取,即爲內存泄露。

Java 中經常使用的內存泄露定義

當再也不須要的對象引用仍舊多餘地予以保存,即爲內存泄露。

在 Java 中,內存泄露是指對象已再也不使用,但垃圾回收未能將他們視作不使用對象予以回收。

當程序再也不使用某個對象,但在一些沒法觸及的位置該對象仍舊被引用,即爲內存泄露。也所以,垃圾回收器沒法刪除它。該對象佔用的內存空間沒法釋放,程序所需的總內存就會增長。長此以往,應用的性能就會降低,JVM 可能會耗盡全部內存。

在某種程度上,當沒法再給年老區分配內存時,內存泄露就會發生。

內存泄露最多見的一些狀況:

  • ThreadLocal 變量
  • 循環與複雜的雙向引用
  • JNI 內存泄露
  • 可變的靜態域(最爲常見)

我建議結合使用 Visual VM 與 JDK,對內存泄露問題進行調試。

常見的內存泄露調試方法

  1. NetBeans 分析器
  2. 使用 jhat Utility
  3. 建立 Heap Dump
  4. 獲取當下運行進程的堆內存柱狀圖
  5. 獲取內存溢出錯誤的堆內存柱狀圖
  6. 監控在等待終結的對象數量
  7. 第三方內存調試器

調試內存泄露問題的經常使用策略或步驟:

  • 確認徵兆
  • 啓用詳細的垃圾回收機制(verbose GC)
  • 啓用性能分析
  • 分析堆棧跟蹤

原文地址:https://dzone.com/articles/java-memory-architecture-model-garbage-collection

OneAPM for Java 可以深刻到全部 Java 應用內部完成應用性能管理和監控,包括代碼級別性能問題的可見性、性能瓶頸的快速識別與追溯、真實用戶體驗監控、服務器監控和端到端的應用性能管理。想閱讀更多技術文章,請訪問 OneAPM 官方博客

相關文章
相關標籤/搜索