摘要:本文詳細(xì)描述了堆內(nèi)存模型,垃圾回收算法以及處理內(nèi)存泄露的最佳方案,并輔之以圖表,希望能對理解內(nèi)存結(jié)構(gòu)有所幫助。該區(qū)域也稱為內(nèi)存模型的本地區(qū)。在中,內(nèi)存泄露是指對象已不再使用,但垃圾回收未能將他們視做不使用對象予以回收。
本文詳細(xì)描述了 Java 堆內(nèi)存模型,垃圾回收算法以及處理內(nèi)存泄露的最佳方案,并輔之以圖表,希望能對理解 Java 內(nèi)存結(jié)構(gòu)有所幫助。原文作者 Sumith Puri,本文系 OneAPM 工程師編譯整理。
下圖展示了 Java 堆內(nèi)存模型,以及運行在 Java 虛擬機(jī)中任意 Java 應(yīng)用的 PermGen (內(nèi)存永久保存區(qū)域),下面的比率展示了 JVM 各代類型允許的內(nèi)存大小分配情況,所有的數(shù)據(jù)均適用于 Java 1.7 及以下版本。該圖也被稱為 Java 內(nèi)存模型的“管理區(qū)(Managed Area)”。
Java 內(nèi)存結(jié)構(gòu)(Java 內(nèi)存模型)除此之外,還有一塊堆棧區(qū)(Stack Area),可通過 -Xss 選項進(jìn)行配置。該區(qū)域存儲了所有線程的堆引用、本地引用、程序計數(shù)器寄存器、代碼緩存以及本地變量。該區(qū)域也稱為內(nèi)存模型的本地區(qū)(Native Area)。
Java 內(nèi)存模型(結(jié)構(gòu))的管理區(qū) [Young Generation/Nursery] 伊甸園區(qū)(Eden Space)所有新對象都首先在 Eden Space 創(chuàng)建。一旦該區(qū)達(dá)到由 JVM 設(shè)定的任意閾值,新生代垃圾回收機(jī)制(Minor GC)就會啟動。它會首先清除所有的非引用對象,并將引用對象從 "eden" 與 "from" 區(qū)移至 "to" 幸存者區(qū)。垃圾回收一結(jié)束,"from" 與 "to" 的角色(名字)就會對換。
[Young Generation/Nursery] 幸存者 1 區(qū) (From)這是幸存者區(qū)的一部分?;蛘咭暈樾掖嬲邊^(qū)中的一個角色。這兒就是之前垃圾回收中的 "to" 角色。
[Young Generation/Nursery] 幸存者 2 區(qū) (To)這也是幸存者區(qū)的一部分。也可以視為幸存者區(qū)中的一個角色。垃圾回收過程中的所有引用對象都會從 "from" 與 "eden" 區(qū)移至此處。
[Old Generation] 年老代區(qū)(Tenured)根據(jù)閾值限定的不同,對象們會從 "to" 幸存者區(qū)移至年老代區(qū)。你可以使用 -XX:+PrintTenuringDistribution 檢查閾值,該指令會按照年齡顯示對象(占用的字節(jié)空間)。年齡是指對象在幸存者區(qū)內(nèi)移動的次數(shù)。
其他重要的標(biāo)記還有 -XX:InitialTenuringThreshold、-XX:MaxTenuringThreshold 與 -XX:TargetSurvivorRatio ,這些標(biāo)記能幫你實現(xiàn)最佳的年老代區(qū)與幸存者區(qū)使用方案。
通過設(shè)置 -XX:InitialTenuringThreshold 與 -XX:MaxTenuringThreshold,可以指定年齡的最初值與最大值,而幸存者區(qū) (To) 的使用率則由 -XX:+NeverTenure 與 -XX:+AlwaysTenure 決定。前者是指永遠(yuǎn)不將對象存儲到年老代區(qū),而后者恰恰相反,總是將對象存儲到年老代區(qū)。
此處進(jìn)行的垃圾回收是年老代垃圾回收(Major GC)。當(dāng)堆空間已滿或者年老代區(qū)占滿時,就會觸發(fā) Major GC。此時,通常會由一個“停止一切(Stop-the-World)”事件或線程執(zhí)行垃圾回收。此外,還有另一種稱為全垃圾回收(Full GC)的垃圾回收機(jī)制,會涉及諸如永久內(nèi)存區(qū)域。
與整體堆內(nèi)存相關(guān)的另兩個重要且有趣的標(biāo)記是 -XX:SurvivorRatio 與 -XX:NewRatio,前者指定伊甸園區(qū)相對幸存者區(qū)的比率,后者指定年老代區(qū)相對新生代區(qū)的比率。
[Permanent Generation] 永久代區(qū)(Permgen space)永久代區(qū)(Permgen)用于存儲以下信息:常量池 (內(nèi)存池),字段與方法數(shù)據(jù)及代碼。
垃圾回收算法 串行 GC(Serial GC) (-XX:UseSerialGC): 針對年輕代與年老代的垃圾回收該算法使用簡單的“標(biāo)記-清掃-壓縮(mark-sweep-compact)”循環(huán)清理年輕代與年老代,適合內(nèi)存占用較低、CPU 使用量較少的客戶端系統(tǒng)。
并行 GC(Parallel GC) (-XX:UseParallelGC): 針對年輕代與年老代的垃圾回收該算法使用 N 個線程(N 的值可以通過 -XX:ParallelGCThreads=N 設(shè)定,N 同時代表垃圾回收占用的 CPU 內(nèi)核數(shù))。其中,年輕代垃圾回收會使用 N 個線程,而年老代只用一個線程。
并行 Old GC (-XX:UseParallelOldGC): 針對年輕代與年老代的垃圾回收該算法對年輕代與年老代均使用 N 個線程,其他方面與并行 GC 完全一致。
并發(fā) Mark and Sweep GC (-XX:ConcMarkSweepGC): 針對年老代的垃圾回收顧名思義,CMS GC 會最小化垃圾回收所需的停頓時間。該算法最適于創(chuàng)建高響應(yīng)度的應(yīng)用,且只作用于年老代。它會創(chuàng)建多條垃圾回收的線程,與應(yīng)用線程同時工作。垃圾回收的線程數(shù)量可以使用 -XX:ParallelCMSThreads=n 標(biāo)記指定。
G1 GC (-XX:UseG1GC): 針對年輕代與年老代的垃圾回收 (將堆內(nèi)存等分為大小相同的區(qū)塊)這是一種并行、并發(fā)、不斷壓縮的低停頓垃圾回收器。G1 是在 Java 7 中引入以取代 CMS GC 的,它會先將堆內(nèi)存分為多個大小相等的區(qū)塊,繼而執(zhí)行垃圾回收。通常,從活動數(shù)據(jù)最少的區(qū)塊開始,因此以垃圾為先。
最常見的內(nèi)存溢出問題所有 Java 程序員都應(yīng)該知道的最常見的內(nèi)存溢出問題:
Exception in thread "main": java.lang.OutOfMemoryError: Java heap space( Java 堆內(nèi)存)。這并不一定意味著內(nèi)存泄露,也可能是分配的堆內(nèi)存空間太小。此外,在運行時間較長的應(yīng)用中,也可能是因為一個無意識的引用被指向堆對象(內(nèi)存泄露)。即便是應(yīng)用本身調(diào)用的 APIs,也可能保存著指向無依據(jù)對象的引用。而且,在大量使用終結(jié)器的應(yīng)用中,對象們有時可能正排在終結(jié)隊列中。當(dāng)這樣的應(yīng)用創(chuàng)建高優(yōu)先級的線程時,會導(dǎo)致越來越多的對象排在終結(jié)隊列中,最終導(dǎo)致內(nèi)存溢出。
Exception in thread "main": java.lang.OutOfMemoryError: PermGen space(永久存儲空間)。如果加載了很多類與方法,或者創(chuàng)建了很多字符串常量,特別是使用 intern() 方法進(jìn)行創(chuàng)建(從 JDK 7 開始,interned 字符串就不再存儲在 PermGen 中),這類錯誤就會出現(xiàn)。當(dāng)出現(xiàn)這類錯誤時,打印的堆棧跟蹤附近可能會出現(xiàn)如下文本:ClassLoader.defineClass。
Exception in thread "main": java.lang.OutOfMemoryError: Requested array size exceeds VM limit (請求的數(shù)組大小超出 VM 限制)。當(dāng)請求的數(shù)組大小超過可用的堆空間時,這類報錯就會出現(xiàn)。這類錯誤通常歸咎于編程錯誤,在運行時請求了極大的數(shù)組大小。
Exception in thread "main": java.lang.OutOfMemoryError:
request bytes for ,交換空間溢出?這是內(nèi)存泄露最常見的根源。通常,當(dāng)操作系統(tǒng)沒有足夠的交換空間,或另一個進(jìn)程占用了系統(tǒng)中的所有可用內(nèi)存,就會導(dǎo)致內(nèi)存泄露。簡而言之,由于空間用盡,堆內(nèi)存無法提供所請求的空間大小。該信息中的 "s" 代表失敗的請求所需的內(nèi)存大小(以字節(jié)為單位),而 "r" 代表內(nèi)存請求的原因。在大多數(shù)情況下,此處的 "r" 是報告分配失敗的源模塊,有時也會是具體的原因。
Exception in thread "main": java.lang.OutOfMemoryError:
你可以將內(nèi)存泄露看做一種疾病,而內(nèi)存溢出錯誤為一種征兆。但不是所有的內(nèi)存溢出錯誤都意味著內(nèi)存泄露,不是所有的內(nèi)存泄露都以內(nèi)存溢出為征兆。
維基百科的定義:在計算機(jī)科學(xué)中,內(nèi)存泄露是一種以如下方式發(fā)生的資源泄露——計算機(jī)程序錯誤地分配內(nèi)存,導(dǎo)致不再需要的內(nèi)存得不到釋放。在面向?qū)ο蟮木幊陶Z言中,一個對象若存儲在內(nèi)存中,卻無法由運行的代碼獲取,即為內(nèi)存泄露。
Java 中常用的內(nèi)存泄露定義當(dāng)不再需要的對象引用仍舊多余地予以保存,即為內(nèi)存泄露。
在 Java 中,內(nèi)存泄露是指對象已不再使用,但垃圾回收未能將他們視做不使用對象予以回收。
當(dāng)程序不再使用某個對象,但在一些無法觸及的位置該對象仍舊被引用,即為內(nèi)存泄露。也因此,垃圾回收器無法刪除它。該對象占用的內(nèi)存空間無法釋放,程序所需的總內(nèi)存就會增加。久而久之,應(yīng)用的性能就會下降,JVM 可能會耗盡所有內(nèi)存。
在某種程度上,當(dāng)無法再給年老區(qū)分配內(nèi)存時,內(nèi)存泄露就會發(fā)生。
內(nèi)存泄露最常見的一些情況:
ThreadLocal 變量
循環(huán)與復(fù)雜的雙向引用
JNI 內(nèi)存泄露
可變的靜態(tài)域(最為常見)
我建議結(jié)合使用 Visual VM 與 JDK,對內(nèi)存泄露問題進(jìn)行調(diào)試。
常見的內(nèi)存泄露調(diào)試方法NetBeans 分析器
使用 jhat Utility
創(chuàng)建 Heap Dump
獲取當(dāng)下運行進(jìn)程的堆內(nèi)存柱狀圖
獲取內(nèi)存溢出錯誤的堆內(nèi)存柱狀圖
監(jiān)控在等待終結(jié)的對象數(shù)量
第三方內(nèi)存調(diào)試器
調(diào)試內(nèi)存泄露問題的常用策略或步驟:
確認(rèn)征兆
啟用詳細(xì)的垃圾回收機(jī)制(verbose GC)
啟用性能分析
分析堆棧跟蹤
原文地址:https://dzone.com/articles/java-memory-architecture-model-garbage-collection
OneAPM for Java 能夠深入到所有 Java 應(yīng)用內(nèi)部完成應(yīng)用性能管理和監(jiān)控,包括代碼級別性能問題的可見性、性能瓶頸的快速識別與追溯、真實用戶體驗監(jiān)控、服務(wù)器監(jiān)控和端到端的應(yīng)用性能管理。想閱讀更多技術(shù)文章,請訪問 OneAPM 官方博客。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/64728.html
摘要:遵循特定規(guī)則,利用操作符,終止節(jié)點和其他非終止節(jié)點,構(gòu)造新的字符串非終結(jié)符是表示字符串的樹的內(nèi)部節(jié)點。語法中的生產(chǎn)具有這種形式非終結(jié)符終結(jié),非終結(jié)符和運算符的表達(dá)式語法的非終結(jié)點之一被指定為根。 大綱 基于狀態(tài)的構(gòu)建 基于自動機(jī)的編程 設(shè)計模式:Memento提供了將對象恢復(fù)到之前狀態(tài)的功能(撤消)。 設(shè)計模式:狀態(tài)允許對象在其內(nèi)部狀態(tài)改變時改變其行為。 表驅(qū)動結(jié)構(gòu)* 基于語法的構(gòu)...
摘要:結(jié)構(gòu)型模式適配器模式橋接模式裝飾模式組合模式外觀模式享元模式代理模式。行為型模式模版方法模式命令模式迭代器模式觀察者模式中介者模式備忘錄模式解釋器模式模式狀態(tài)模式策略模式職責(zé)鏈模式責(zé)任鏈模式訪問者模式。 主要版本 更新時間 備注 v1.0 2015-08-01 首次發(fā)布 v1.1 2018-03-12 增加新技術(shù)知識、完善知識體系 v2.0 2019-02-19 結(jié)構(gòu)...
摘要:哪吒社區(qū)技能樹打卡打卡貼函數(shù)式接口簡介領(lǐng)域優(yōu)質(zhì)創(chuàng)作者哪吒公眾號作者架構(gòu)師奮斗者掃描主頁左側(cè)二維碼,加入群聊,一起學(xué)習(xí)一起進(jìn)步歡迎點贊收藏留言前情提要無意間聽到領(lǐng)導(dǎo)們的談話,現(xiàn)在公司的現(xiàn)狀是碼農(nóng)太多,但能獨立帶隊的人太少,簡而言之,不缺干 ? 哪吒社區(qū)Java技能樹打卡?【打卡貼 day2...
摘要:在設(shè)計模式中,所有的設(shè)計模式都遵循這一原則。其實就是說在應(yīng)用程序中,所有的類如果使用或依賴于其他的類,則應(yīng)該依賴這些其他類的抽象類,而不是這些其他類的具體類。使用設(shè)計模式是為了可重用代碼讓代碼更容易被他人理解保證代碼可靠性。 這是劉意老師的JAVA基礎(chǔ)教程的筆記講的賊好,附上傳送門 傳智風(fēng)清揚-超全面的Java基礎(chǔ) 一、面向?qū)ο笏枷朐O(shè)計原則 1.單一職責(zé)原則 其實就是開發(fā)人員經(jīng)常說的高...
摘要:設(shè)計模式無論是對于最底層的的編碼實現(xiàn)還是較高層的架構(gòu)設(shè)計都有著重要的指導(dǎo)作用。所謂光說不練假把式,今天我就把項目中常見的應(yīng)用場景涉及到的主要設(shè)計模式及其相關(guān)設(shè)計模式總結(jié)一下,用實例分析和對比的方式在一片文章中就把最常見的種設(shè)計模式梳理清楚。 設(shè)計模式無論是對于最底層的的編碼實現(xiàn)還是較高層的架構(gòu)設(shè)計都有著重要的指導(dǎo)作用。所謂光說不練假把式,今天我就把項目中常見的應(yīng)用場景涉及到的主要設(shè)計模...
閱讀 2041·2023-04-25 15:11
閱讀 3461·2021-09-23 11:57
閱讀 1372·2021-07-26 23:38
閱讀 1319·2019-08-30 15:54
閱讀 635·2019-08-30 15:53
閱讀 3245·2019-08-26 13:36
閱讀 986·2019-08-26 12:01
閱讀 2863·2019-08-23 16:21