JVM內存分配與溢出分析

一、內存分配

1、內存區域

年輕代(Young generation)

所有新生成的對象首先都是放在年輕代的。年輕代的目標就是儘可能快速的收集掉那些生命週期短的對象。年輕代分三個區。一個Eden區,兩個Survivor區(一般而言)。大部分對象在Eden區中生成。當Eden區滿時,還存活的對象將被複制到Survivor區(兩個中的一個),當這個Survivor區滿時,此區的存活對象將被複制到另外一個Survivor區,當這個Survivor去也滿了的時候,從第一個Survivor區複製過來的並且此時還存活的對象,將被複制“年老區(Tenured)”。需要注意,Survivor的兩個區是對稱的,沒先後關係,所以同一個區中可能同時存在從Eden複製過來 對象,和從前一個Survivor複製過來的對象,而複製到年老區的只有從第一個Survivor去過來的對象。而且,Survivor區總有一個是空的。同時,根據程序需要,Survivor區是可以配置爲多個的(多於兩個),這樣可以增加對象在年輕代中的存在時間,減少被放到年老代的可能。

年老代(Tenured / Old Generation)

在年輕代中經歷了N次垃圾回收後仍然存活的對象,就會被放到年老代中。因此,可以認爲年老代中存放的都是一些生命週期較長的對象。

持久代(Perm Area)

用於存放靜態文件,如今Java類、方法等。持久代對垃圾回收沒有顯著影響,但是有些應用可能動態生成或者調用一些class,例如Hibernate等,在這種時候需要設置一個比較大的持久代空間來存放這些運行過程中新增的類。持久代大小通過-XX:MaxPermSize=進行設置。

持久代主要存放的是Java類的類信息,與垃圾收集要收集的Java對象關係不大。
年輕代和年老代的劃分是對垃圾收集影響比較大的。

這裏寫圖片描述

2、內存分配策略
1)有限分配Eden
-verbose:gc -XX:+PrintGCDetails -XX:+UserSerialGC -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRadio=8
2)大對象直接分配到老年代
-XX:PretenureSizeThreshold=6M
說明當對象爲6M以上時,進入老年代
3)長期存活對象分配到老年代
-XX:MaxTenuringThreshold
默認值爲15
4)空間分配擔保
在MinorGC之前,會先檢查老年代最大可用空間是否可以容納新生代所有對象(防止新生代全部晉升時放不下),如果可以容納,則MinorGC可以安全執行。否則,檢查是否允許擔保失敗,是則檢查老年代最大可用空間是否大於歷次晉升到老年代的對象的平均大小,是則嘗試進行MinorGC;小於或者MinorGC失敗,則會發起一次FullGC清理老年代。
-XX:+HandlePromotionFailure開啓空間分配擔保
-XX:-HandlePromotionFailure關閉空間分配擔保
5)動態對象年齡判斷
爲了能更好地適應不同程序的內存狀況,虛擬機並不是永遠地要求對象的年齡必須達到了MaxTenuringThreshold才能晉升老年代,如果在Survivor空間中相同年齡所有對象大小的總和大於Survivor空間的一半,年齡大於或者等於該年齡的對象直接可以進入老年代,無須等到MaxTenuringThreshold中要求的年齡。下面通過測試代碼對allocation2進行註釋前和註釋後的收集情況進行對比。

二、JVM參數

java啓動參數共分爲三類
1、標準參數(-),所有的JVM實現都必須實現這些參數的功能,而且向後兼容。
-verbose:class
輸出jvm載入類的相關信息,當jvm報告說找不到類或者類衝突時可此進行診斷。
-verbose:gc
輸出每次GC的相關情況。
-verbose:jni

2、非標準參數(-X),默認jvm實現這些參數的功能,但是並不保證所有jvm實現都滿足,且不保證向後兼容;
-Xms512m 設置JVM促使內存爲512m。此值可以設置與-Xmx相同,以避免每次垃圾回收完成後JVM重新分配內存。
-Xmx512m ,設置JVM最大可用內存爲512M。
-Xmn200m:設置年輕代大小爲200M。整個堆大小=年輕代大小 + 年老代大小 + 持久代大小。持久代一般固定大小爲64m,所以增大年輕代後,將會減小年老代大小。此值對系統性能影響較大,Sun官方推薦配置爲整個堆的3/8。
-Xss128k:
設置每個線程的堆棧大小。JDK5.0以後每個線程堆棧大小爲1M,以前每個線程堆棧大小爲256K。更具應用的線程所需內存大小進行調整。在相同物理內存下,減小這個值能生成更多的線程。但是操作系統對一個進程內的線程數還是有限制的,不能無限生成,經驗值在3000~5000左右。
-Xloggc:file
與-verbose:gc功能類似,只是將每次GC事件的相關情況記錄到一個文件中,文件的位置最好在本地,以避免網絡的潛在問題。
若與verbose命令同時出現在命令行中,則以-Xloggc爲準。
-Xprof
跟蹤正運行的程序,並將跟蹤數據在標準輸出輸出;適合於開發環境調試。

3、非Stable參數(-XX),此類參數各個jvm實現會有所不同,將來可能會隨時取消,需要慎重使用
性能調優參數:
這裏寫圖片描述
行爲參數:
這裏寫圖片描述
調試參數:
這裏寫圖片描述

例如:

-Xmx200m -Xms50m -XX:HeapDumpOnOutofMemoryError -XX:HeapDumpPath=d:/Memory.dump
分配了200M最大空間 ,啓動 最小空間50M , 發生了 內存溢出錯誤 dump路徑爲Memory.dump
-verbose:gc -XX:+PrintGCDetails
 輸出每次GC的相關情況。每次GC時打印詳細信息

三、內存區域

這裏寫圖片描述
1、程序計數器
一塊較小的內存空間,可以看做是當前線程所執行的字節碼的行號指示器,如果執行java方法,則記錄字節碼指令的地址,如果爲native,則值爲undefined,此區域是唯一在JVM中沒有規定任何OutofMemoryError情況的區域。
2、java虛擬機棧
描述的是方法執行的動態內存模型,棧幀是當每個方法執行時,都會創建,伴隨着方法從創建到執行完成,用於存儲局部變量表,操作數棧,動態鏈接,方法出口等。進棧出棧是以棧幀爲單位的。
局部變量表是存放編譯器可知的各種基本數據類型、引用類型、returnAddress類型。其內存空間在編譯期完成分配,當進入一個方法時,這個方法需要在幀分配多少內存是固定的,在方法運行期間是不會改變局部變量表大小的。
如果只進不出就會出現StackOverFlowError和OutOfMemory的錯誤
3、本地方法棧
虛擬機棧是爲虛擬機執行java方法服務
本地方法棧是爲虛擬機執行native方法服務,其餘與虛擬機棧相同
4、堆內存
存放對象實例、垃圾收集器主要管理區域
存放新生代、老年代、Eden空間
5、方法區
存儲虛擬機加載的類信息(類版本、字段、方法、接口),常量,靜態變量,即時編譯器編譯後的代碼等數據
垃圾回收在方法區的行爲較少,會存在對常量池的回收和類型的卸載
如果方法區異常,則會拋出OutOfMemory

常量池(字符串常量池、class常量池和運行時常量池):
•在JDK6.0及之前版本,字符串常量池是放在Perm Gen區(也就是方法區)中;
•在JDK7.0版本,字符串常量池被移到了堆中了。至於爲什麼移到堆內,大概是由於方法區的內存空間太小了。

運行時常量池存放基本類型包裝類(包裝類不管理浮點型,整形只會管理-128到127)和String(通過String.intern()方法可以強制將String放入常量池)。之所以稱之爲動態,是因爲不單能在編譯期產生常量,運行期間也可以,當然運行時常量池同樣是所有線程共享。

字符串常量池中的字符串只存在一份。當產生2個相同的字符串時,則常量池只存儲一次,堆內存開闢2次空間,使用HashSet的存儲方式。

class常量池用於存放編譯器生成的各種字面量(Literal)和符號引用(Symbolic References)
•字面量包括:1.文本字符串 2.八種基本類型的值 3.被聲明爲final的常量等;
•符號引用包括:1.類和方法的全限定名 2.字段的名稱和描述符 3.方法的名稱和描述符。

運行時常量池存在於內存中,也就是class常量池被加載到內存之後的版本,不同之處是:它的字面量可以動態的添加(String#intern()),符號引用可以被解析爲直接引用

四、分析內存的dump文件

使用Eclipse Memory Analyzer

發佈了93 篇原創文章 · 獲贊 24 · 訪問量 17萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章