一篇聊聊JVM優化:堆
一、Java 堆概念
1、簡介
對于Java應用程序來說, Java堆(Java Heap) 是虛擬機所管理的內存中最大的一塊。 Java堆是被所 有線程共享 的一塊內存區域, 在虛擬機啟動時創建。 此內存區域的唯一目的就是存放對象實例, Java 世界里“幾乎”所有的對象實例都在這里分配內存。“幾乎”是指從實現角度來看, 隨著Java語 言的發展, 現在已經能看到些許跡象表明日 后可能出現值類型的支持, 即使只考慮現在, 由于即時編譯技術的進步, 尤其是逃逸分析技術的日漸強大, 棧上 分配、 標量替換優化手段已經導致一些微妙的變化悄然發生, 所以說Java對象實例都分配在堆上也漸漸變得不是 那么絕對了。

2、堆的特點
(1)是Java虛擬機所管理的內存中最大的一塊。
(2)堆是jvm所有線程共享的。 堆中也包含私有的線程緩沖區 Thread Local Allocation Buffer (TLAB)
(3)在虛擬機啟動的時候創建。
(4)唯一目的就是存放對象實例,幾乎所有的對象實例以及數組都要在這里分配內存。 (5)Java堆是垃圾收集器管理的主要區域。
(6)因此很多時候java堆也被稱為“GC堆”(Garbage Collected Heap)。從內存回收的角度來看,由于現在收集器 基本都采用分代收集算法,所以Java堆還可以細分為:新生代和老年代;新生代又可以分為:Eden 空間、From Survivor空間、To Survivor空間。
(7)java堆是計算機物理存儲上不連續的、邏輯上是連續的,也是大小可調節的(通過-Xms和-Xmx控制)。
(8)方法結束后,堆中對象不會馬上移出僅僅在垃圾回收的時候時候才移除。
(9)如果在堆中沒有內存完成實例的分配,并且堆也無法再擴展時,將會拋出OutOfMemoryError異常
3、設置堆空間大小
1. 內存大小-Xmx/-Xms
使用示例: -Xmx20m -Xms5m
說明: 當下Java應用最大可用內存為20M, 最小內存為5M
total Memory和最大的內存之間還是存在一定 差異的,就是說JVM一般會盡量保持內存在一個盡可能低的層面,而非貪婪做法按照最大的內存來進行分配。
其實JVM在分配內存過 程中是動態的, 按需來分配的。
4、堆的分類
現在垃圾回收器都使用分代理論,堆空間也分類如下:
在Java7 Hotspot虛擬機中將Java堆內存分為3個部分:
- 青年代Young Generation
- 老年代Old Generation
- 永久代Permanent Generation

在Java8以后,由于方法區的內存不再分配在Java堆上,而是存儲于本地內存元空間Metaspace中,所以永久代就不 存在了,在幾天前(2018年9約25日)Java11正式發布以后,我從官網上找到了關于Java11中垃圾收集器的官方文檔, 文檔中沒有提到“永久代”,而只有青年代和老年代。

二、年輕代和老年代
1.JVM中存儲java對象可以被分為兩類:
1)年輕代(Young Gen):年輕代主要存放新創建的對象,內存大小相對會比較小,垃圾回收會比較頻繁。年輕代分 成1個Eden Space和2個Suvivor Space(from 和to)。
2)年老代(Tenured Gen):年老代主要存放JVM認為生命周期比較長的對象(經過幾次的Young Gen的垃圾回收后仍 然存在),內存大小相對會比較大,垃圾回收也相對沒有那么頻繁。
2.配置新生代和老年代堆結構占比
默認 -XX:NewRatio=2 , 標識新生代占1 , 老年代占2 ,新生代占整個堆的1/3
修改占比 -XX:NewPatio=4 , 標識新生代占1 , 老年代占4 , 新生代占整個堆的1/5
Eden空間和另外兩個Survivor空間占比分別為8:1:1
可以通過操作選項 -XX:SurvivorRatio 調整這個空間比例。 比如 -XX:SurvivorRatio=8 幾乎所有的java對象都在Eden區創建, 但80%的對象生命周期都很短,創建出來就會被銷毀

從圖中可以看出: 堆大小 = 新生代 + 老年代。其中,堆的大小可以通過參數 –Xms、-Xmx 來指定。
默認的,新生代 ( Young ) 與老年代 ( Old ) 的比例的值為 1:2 ( 該值可以通過參數 –XX:NewRatio 來指定 ),即:新生 代 ( Young ) = 1/3 的堆空間大小。老年代 ( Old ) = 2/3 的堆空間大小。其中,新生代 ( Young ) 被細分為 Eden 和 兩個 Survivor 區域,這兩個 Survivor 區域分別被命名為 from 和 to,以示區分。 默認的,Edem : from : to = 8 : 1 : 1 ( 可以 通過參數 –XX:SurvivorRatio 來設定 ),即: Eden = 8/10 的新生代空間大小,from = to = 1/10 的新生代空間大小。 JVM 每次只會使用 Eden 和其中的一塊 Survivor 區域來為對象服務,所以無論什么時候,總是有一塊 Survivor 區域 是空閑著的。因此,新生代實際可用的內存空間為 9/10 ( 即90% )的新生代空間。
三、對象分配過程
JVM設計者不僅需要考慮到內存如何分配,在哪里分配等問題,并且由于內存分配算法與內存回收算法密切相關, 因此還需要考慮GC執行完內存回收后是否存在空間中間產生內存碎片。
分配過程
1.new的對象先放在伊甸園區。該區域有大小限制
2.當伊甸園區域填滿時,程序又需要創建對象,JVM的垃圾回收器將對伊甸園預期進行垃圾回收(Minor GC),將伊 甸園區域中不再被其他對象引用的額對象進行銷毀,再加載新的對象放到伊甸園區
3.然后將伊甸園區中的剩余對象移動到幸存者0區
4.如果再次觸發垃圾回收,此時上次幸存下來的放在幸存者0區的,如果沒有回收,就會放到幸存者1區
5.如果再次經歷垃圾回收,此時會重新返回幸存者0區,接著再去幸存者1區。
6.如果累計次數到達默認的15次,這會進入養老區。 可以通過設置參數,調整閾值 -XX:MaxTenuringThreshold=N
7.養老區內存不足是,會再次出發GC:Major GC 進行養老區的內存清理
8.如果養老區執行了Major GC后仍然沒有辦法進行對象的保存,就會報OOM異常


分配對象流程
































