本文詳細描述了 Java 堆內存模型,垃圾回收算法以及處理內存泄露的最佳方案,並輔之以圖表,但願能對理解 Java 內存結構有所幫助。原文做者 Sumith Puri,本文系 OneAPM 工程師編譯整理。html
下圖展現了 Java 堆內存模型,以及運行在 Java 虛擬機中任意 Java 應用的 PermGen (內存永久保存區域),下面的比率展現了 JVM 各代類型容許的內存大小分配狀況,全部的數據均適用於 Java 1.7 及如下版本。該圖也被稱爲 Java 內存模型的「管理區(Managed Area)」。java
除此以外,還有一塊堆棧區(Stack Area),可經過 -Xss 選項進行配置。該區域存儲了全部線程的堆引用、本地引用、程序計數器寄存器、代碼緩存以及本地變量。該區域也稱爲內存模型的本地區(Native Area)。程序員
全部新對象都首先在 Eden Space 建立。一旦該區達到由 JVM 設定的任意閾值,新生代垃圾回收機制(Minor GC)就會啓動。它會首先清除全部的非引用對象,並將引用對象從 'eden' 與 'from' 區移至 'to' 倖存者區。垃圾回收一結束,'from' 與 'to' 的角色(名字)就會對換。算法
這是倖存者區的一部分。或者視爲倖存者區中的一個角色。這兒就是以前垃圾回收中的 'to' 角色。編程
這也是倖存者區的一部分。也能夠視爲倖存者區中的一個角色。垃圾回收過程當中的全部引用對象都會從 'from' 與 'eden' 區移至此處。數組
根據閾值限定的不一樣,對象們會從 '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,前者指定伊甸園區相對倖存者區的比率,後者指定年老代區相對新生代區的比率。
永久代區(Permgen)用於存儲如下信息:常量池 (內存池),字段與方法數據及代碼。
該算法使用簡單的「標記-清掃-壓縮(mark-sweep-compact)」循環清理年輕代與年老代,適合內存佔用較低、CPU 使用量較少的客戶端系統。
該算法使用 N 個線程(N 的值能夠經過 ** -XX:ParallelGCThreads=N** 設定,N 同時表明垃圾回收佔用的 CPU 內核數)。其中,年輕代垃圾回收會使用 N 個線程,而年老代只用一個線程。
該算法對年輕代與年老代均使用 N 個線程,其餘方面與並行 GC 徹底一致。
顧名思義,CMS GC 會最小化垃圾回收所需的停頓時間。該算法最適於建立高響應度的應用,且只做用於年老代。它會建立多條垃圾回收的線程,與應用線程同時工做。垃圾回收的線程數量可使用 -XX:ParallelCMSThreads=n 標記指定。
這是一種並行、併發、不斷壓縮的低停頓垃圾回收器。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:
request <s> bytes for <r>
,交換空間溢出?這是內存泄露最多見的根源。一般,當操做系統沒有足夠的交換空間,或另外一個進程佔用了系統中的全部可用內存,就會致使內存泄露。簡而言之,因爲空間用盡,堆內存沒法提供所請求的空間大小。該信息中的 's' 表明失敗的請求所需的內存大小(以字節爲單位),而 'r' 表明內存請求的緣由。在大多數狀況下,此處的 'r' 是報告分配失敗的源模塊,有時也會是具體的緣由。
Exception in thread "main": java.lang.OutOfMemoryError:
你能夠將內存泄露看作一種疾病,而內存溢出錯誤爲一種徵兆。但不是全部的內存溢出錯誤都意味着內存泄露,不是全部的內存泄露都之內存溢出爲徵兆。
維基百科的定義:在計算機科學中,內存泄露是一種以以下方式發生的資源泄露——計算機程序錯誤地分配內存,致使再也不須要的內存得不到釋放。在面向對象的編程語言中,一個對象若存儲在內存中,卻沒法由運行的代碼獲取,即爲內存泄露。
當再也不須要的對象引用仍舊多餘地予以保存,即爲內存泄露。
在 Java 中,內存泄露是指對象已再也不使用,但垃圾回收未能將他們視作不使用對象予以回收。
當程序再也不使用某個對象,但在一些沒法觸及的位置該對象仍舊被引用,即爲內存泄露。也所以,垃圾回收器沒法刪除它。該對象佔用的內存空間沒法釋放,程序所需的總內存就會增長。長此以往,應用的性能就會降低,JVM 可能會耗盡全部內存。
在某種程度上,當沒法再給年老區分配內存時,內存泄露就會發生。
內存泄露最多見的一些狀況:
我建議結合使用 Visual VM 與 JDK,對內存泄露問題進行調試。
調試內存泄露問題的經常使用策略或步驟:
原文地址:https://dzone.com/articles/java-memory-architecture-model-garbage-collection
OneAPM for Java 可以深刻到全部 Java 應用內部完成應用性能管理和監控,包括代碼級別性能問題的可見性、性能瓶頸的快速識別與追溯、真實用戶體驗監控、服務器監控和端到端的應用性能管理。想閱讀更多技術文章,請訪問 OneAPM 官方博客。