6 堆 Heap??????????
6.1 核心概述
- 一個JVM實例只存在一個堆內存,堆也是Java內存管理的核心區域。
- Java 堆區在JVM啟動的時候即被創建,其空間大小也就確定了。是JVM管理的最大一塊內存空間。
- 堆是GC(Garbage Collection,垃圾收集器)執行垃圾回收的重點區域。
堆內存細分
Java 7及之前堆內存邏輯上分為三部分:新生區 + 養老區 + 永久區
- Young Generation Space 新生區 Young/New
- 又被劃分為Eden區和Survivor區
- Tenure generation space 養老區 Old/Tenure
- Permanent Space 永久區 Perm
Java 8及之后堆內存邏輯上分為三部分:新生區 + 養老區 + 元空間
- Young Generation Space 新生區 Young/New
- 又被劃分為Eden區和Survivor區
- Tenure generation space 養老區 Old/Tenure
- Meta Space 元空間 Meta
約定:新生區(代)<=>年輕代 、 養老區<=>老年區(代)、 永久區<=>永久代
6.2 設置堆內存大小與OOM
堆空間大小設置
Java堆區用于存儲Java對象實例,堆的大小在JVM啟動時就已經設定好了,大家可以通過選項"-Xmx"和"-Xms"來進行設置。
通常會將-Xms和-Xmx兩個參數配置相同的值,其目的是為了能夠在 Java垃圾回收機制清理完堆區后不需要重新分隔計算堆區的大小,從而提高性能。
默認情況下
- 初始內存大小:物理電腦內存大小 / 64
- 最大內存大小:物理電腦內存大小 / 4
public class HeapTest {
? ?public static void main(String[] args) {
? ? ? ?// Java虛擬機中的堆內存容量
? ? ? ?long initialMemory = Runtime.getRuntime().totalMemory() / 1024 / 1024;
? ? ? ?// Java虛擬機中的最大堆內存容量
? ? ? ?long maxMemory = Runtime.getRuntime().maxMemory() / 1024 / 1024;
?
? ? ? ?System.out.println("-Xms: " + initialMemory + "M");
? ? ? ?System.out.println("-Xmx: " + maxMemory + "M");
? }
}
查看設置的參數
- 方式1:jps / jstat -gc 進程id
- 方式2:-XX:+PrintFCDetails
OutOfMemory舉例
public class OOMTest {
? ?public static void main(String[]args){
? ? ? ?ArrayList<Picture> list = new ArrayList<>();
? ? ? ?while(true){
? ? ? ? ? ?try {
? ? ? ? ? ? ? ?Thread.sleep(20);
? ? ? ? ? } catch (InterruptedException e){
? ? ? ? ? ? ? ?e.printStackTrace();
? ? ? ? ? }
? ? ? ? ? ?list.add(new Picture(new Random().nextInt(1024*1024)));
? ? ? }
? }
}
打印結果
Exception in thread "main" java.lang.OutofMemoryError: Java heap space
? ?at com.atguigu. java.Picture.<init>(OOMTest. java:25)
? ?at com.atguigu.java.O0MTest.main(OOMTest.java:16)
6.3 年輕代與老年代
存儲在JVM中的Java對象可以被劃分為兩類:
- 一類是生命周期較短的瞬時對象,這類對象的創建和消亡都非常迅速
- 另外一類對象的生命周期卻非常長,在某些極端的情況下還能夠與JVM的生命周期保持一致
- 默認
-XX:NewRatio=2
,表示新生代占1,老年代占2,新生代占整個堆的1/3
幾乎所有的 Java對象都是在Eden區被new出來的。絕大部分的Java對象的銷毀都在新生代進行了。
6.4 圖解對象分配過程
1. new的對象先放伊甸園區。此區有大小限制。
2. 當伊甸園的空間填滿時,程序又需要創建對象,JVM的垃圾回收器將對伊甸園區進行垃圾回收(MinorGC),將伊甸園區中的不再被其他對象所引用的對象進行銷毀。再加載新的對象放到伊甸園區。
3. 然后將伊甸園中的剩余對象移動到幸存者0區。
4. 如果再次觸發垃圾回收,此時上次幸存下來的放到幸存者0區的,如果沒有回收,就會放到幸存者1區。
5. 如果再次經歷垃圾回收,此時會重新放回幸存者0區,接著再去幸存者1區。
?
6. 啥時候能去養老區呢?可以設置次數。默認是15次。
○ 可以設置參數:進行設置-Xx:MaxTenuringThreshold= N
7. 在養老區,相對悠閑。當養老區內存不足時,再次觸發GC:Major GC,進行養老區的內存清理
8. 若養老區執行了Major GC之后,發現依然無法進行對象的保存,就會產生OOM異常。
-
伊甸園區的對象先往to區放(空的)
-
年齡計數器達到15晉升老年代
-
總結
- 針對幸存者s0,s1區的總結:復制之后有交換,誰空誰是to
- 關于垃圾回收:頻繁在新生區收集,很少在老年代收集,幾乎不再永久代和元空間進行收集
流程圖
常用調優工具(在JVM下篇:性能監控與調優篇會詳細介紹)
- JDK命令行
- Eclipse:Memory Analyzer Tool
- Jconsole
- VisualVM
- Jprofiler
- Java Flight Recorder
- GCViewer
- GC Easy
6.5 Minor GC、MajorGC、Full GC
JVM在進行GC時,并非每次都對上面三個內存區域一起回收的,大部分時候回收的都是指新生代。
針對Hotspot VM的實現,它里面的GC按照回收區域又分為兩大種類型:一種是部分收集(Partial GC),一種是整堆收集(FullGC)
- 部分收集:不是完整收集整個Java堆的垃圾收集。其中又分為:
-
- 新生代收集(Minor GC / Young GC):只是新生代的垃圾收集
-
- 老年代收集(Major GC / Old GC):只是老年代的圾收集。
-
-
- 目前,只有CMSGC會有單獨收集老年代的行為。
-
-
-
- 注意,很多時候Major GC會和Full GC混淆使用,需要具體分辨是老年代回收還是整堆回收。
-
-
- 混合收集(MixedGC):收集整個新生代以及部分老年代的垃圾收集。
-
-
- 目前,只有G1 GC會有這種行為
-
- 整堆收集(Full GC):收集整個java堆和方法區的垃圾收集。
6.6 堆空間分代思想
分代的唯一理由就是優化GC性能。
如果沒有分代,GC的時候要找到哪些對象沒用,就會對堆的所有區域進行掃描。而很多對象都是朝生夕死的,如果分代的話,把新創建的對象放到某一地方,當GC的時候先把這塊存儲“朝生夕死”對象的區域進行回收,這樣就會騰出很大的空間出來。
6.7 內存分配策略
針對不同年齡段的對象分配原則如下所示:
- 優先分配到Eden
- 大對象直接分配到老年代(盡量避免程序中出現過多的大對象)
- 長期存活的對象分配到老年代
- 動態對象年齡判斷:如果survivor區中相同年齡的所有對象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對象可以直接進入老年代,無須等到
MaxTenuringThreshold
中要求的年齡。 - 空間分配擔保:
-XX:HandlePromotionFailure
6.8 為對象分配內存:TLAB
6.9 小結:堆空間的參數設置
// 詳細的參數內容會在JVM下篇:性能監控與調優篇中進行詳細介紹,這里先熟悉下
-XX:+PrintFlagsInitial ?//查看所有的參數的默認初始值
-XX:+PrintFlagsFinal ?//查看所有的參數的最終值(可能會存在修改,不再是初始值)
-Xms ?//初始堆空間內存(默認為物理內存的1/64)
-Xmx ?//最大堆空間內存(默認為物理內存的1/4)
-Xmn ?//設置新生代的大小。(初始值及最大值)
-XX:NewRatio ?//配置新生代與老年代在堆結構的占比
-XX:SurvivorRatio ?//設置新生代中Eden和S0/S1空間的比例
-XX:MaxTenuringThreshold ?//設置新生代垃圾的最大年齡
-XX:+PrintGCDetails //輸出詳細的GC處理日志
//打印gc簡要信息:①-Xx:+PrintGC ② - verbose:gc
-XX:HandlePromotionFalilure://是否設置空間分配擔保
堆是分配對象的唯一選擇么?
在Java虛擬機中,對象是在Java堆中分配內存的,這是一個普遍的常識。
但是,有一種特殊情況,那就是如果經過逃逸分析(Escape Analysis)后發現,一個對象并沒有逃逸出方法的話,那么就可能被優化成棧上分配。這樣就無需在堆上分配內存,也無須進行垃圾回收了。這也是最常見的堆外存儲技術。