設置堆內存大小和 OOM
Java 堆用于存儲 Java 對象實例,那么堆的大小在 JVM 啟動的時候就確定了,我們可以通過 -Xmx
和 -Xms
來設定
-Xms
用來表示堆的起始內存,等價于-XX:InitialHeapSize
-Xmx
用來表示堆的最大內存,等價于-XX:MaxHeapSize
如果堆的內存大小超過 -Xmx
設定的最大內存, 就會拋出 OutOfMemoryError
異常。
我們通常會將 -Xmx
和 -Xms
兩個參數配置為相同的值,其目的是為了能夠在垃圾回收機制清理完堆區后不再需要重新分隔計算堆的大小,從而提高性能。
-
默認情況下,初始堆內存大小為:電腦內存大小/64
-
默認情況下,最大堆內存大小為:電腦內存大小/4
可以通過代碼獲取到我們的設置值,當然也可以模擬 OOM:
public static void main(String[] args) {//返回 JVM 堆大小long initalMemory = Runtime.getRuntime().totalMemory() / 1024 /1024;//返回 JVM 堆的最大內存long maxMemory = Runtime.getRuntime().maxMemory() / 1024 /1024;System.out.println("-Xms : "+initalMemory + "M");System.out.println("-Xmx : "+maxMemory + "M");System.out.println("系統內存大小:" + initalMemory * 64 / 1024 + "G");System.out.println("系統內存大小:" + maxMemory * 4 / 1024 + "G");
}
?
查看 JVM 堆內存分配
-
在默認不配置 JVM 堆內存大小的情況下,JVM 根據默認值來配置當前內存大小
-
默認情況下新生代和老年代的比例是 1:2,可以通過
新生代中的 Eden:From Survivor:To Survivor 的比例是 8:1:1,可以通過–XX:NewRatio
來配置-XX:SurvivorRatio
來配置 -
若在 JDK 7 中開啟了
-XX:+UseAdaptiveSizePolicy
,JVM 會動態調整 JVM 堆中各個區域的大小以及進入老年代的年齡此時
–XX:NewRatio
和-XX:SurvivorRatio
將會失效,而 JDK 8 是默認開啟-XX:+UseAdaptiveSizePolicy
在 JDK 8中,不要隨意關閉
-XX:+UseAdaptiveSizePolicy
,除非對堆內存的劃分有明確的規劃
每次 GC 后都會重新計算 Eden、From Survivor、To Survivor 的大小,計算依據是GC過程中統計的GC時間、吞吐量、內存占用量。
java -XX:+PrintFlagsFinal -version | grep HeapSizeuintx ErgoHeapSizeLimit = 0 {product}uintx HeapSizePerGCThread = 87241520 {product}uintx InitialHeapSize := 134217728 {product}uintx LargePageHeapSizeThreshold = 134217728 {product}uintx MaxHeapSize := 2147483648 {product}
java version "1.8.0_211"
Java(TM) SE Runtime Environment (build 1.8.0_211-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.211-b12, mixed mode)$ jmap -heap 進程號
?
?
對象在堆中的生命周期
- 在 JVM 內存模型的堆中,堆被劃分為新生代和老年代
- 新生代又被進一步劃分為 Eden區 和 Survivor區,Survivor 區由 From Survivor 和 To Survivor 組成
- 當創建一個對象時,對象會被優先分配到新生代的 Eden 區
- 此時 JVM 會給對象定義一個對象年輕計數器(
-XX:MaxTenuringThreshold
)
- 此時 JVM 會給對象定義一個對象年輕計數器(
- 當 Eden 空間不足時,JVM 將執行新生代的垃圾回收(Minor GC)
- JVM 會把存活的對象轉移到 Survivor 中,并且對象年齡 +1
- 對象在 Survivor 中同樣也會經歷 Minor GC,每經歷一次 Minor GC,對象年齡都會+1
- 如果分配的對象超過了
-XX:PetenureSizeThreshold
,對象會直接被分配到老年代
?
對象的分配過程
為對象分配內存是一件非常嚴謹和復雜的任務,JVM 的設計者們不僅需要考慮內存如何分配、在哪里分配等問題,并且由于內存分配算法和內存回收算法密切相關,所以還需要考慮 GC 執行完內存回收后是否會在內存空間中產生內存碎片。
- new 的對象先放在伊甸園區,此區有大小限制
- 當伊甸園的空間填滿時,程序又需要創建對象,JVM 的垃圾回收器將對伊甸園區進行垃圾回收(Minor GC),將伊甸園區中的不再被其他對象所引用的對象進行銷毀。再加載新的對象放到伊甸園區,然后將伊甸園中的剩余對象移動到幸存者 0 區
- 如果再次觸發垃圾回收,此時上次幸存下來的放到幸存者 0 區,如果沒有回收,就會放到幸存者 1 區
- 如果再次經歷垃圾回收,此時會重新放回幸存者 0 區,接著再去幸存者 1 區
- 什么時候才會去養老區呢? 默認是 15 次回收標記
- 在養老區,相對悠閑。當養老區內存不足時,再次觸發 Major GC,進行養老區的內存清理
- 若養老區執行了 Major GC 之后發現依然無法進行對象的保存,就會產生 OOM 異常
?
?
GC 垃圾回收簡介? Minor GC、Major GC、Full GC
JVM 在進行 GC 時,并非每次都對堆內存(新生代、老年代;方法區)區域一起回收的,大部分時候回收的都是指新生代。
針對 HotSpot VM 的實現,它里面的 GC 按照回收區域又分為兩大類:部分收集(Partial GC),整堆收集(Full GC)
- 部分收集:不是完整收集整個 Java 堆的垃圾收集。其中又分為:
- 新生代收集(Minor GC/Young GC):只是新生代的垃圾收集
- 老年代收集(Major GC/Old GC):只是老年代的垃圾收集
- 目前,只有 CMS GC 會有單獨收集老年代的行為
- 很多時候 Major GC 會和 Full GC 混合使用,需要具體分辨是老年代回收還是整堆回收
- 混合收集(Mixed GC):收集整個新生代以及部分老年代的垃圾收集
- 目前只有 G1 GC 會有這種行為
- 整堆收集(Full GC):收集整個 Java 堆和方法區的垃圾