1.堆內存簡介
1.1什么是堆內存?
堆內存是java內存中的一種,它的作用是用於存儲java中的實例對象和數組,當我們new一個對象或者創建一個數組的時候,就會在堆內存中開辟一段空間給它,用於存放。類加載器讀取了類文件后,保存所有引用類型的真實信息,以方便執行器執行
1.2堆內存的特點
1.堆這塊區域是JVM中最大的,應用的對象和數據都是存在這個區域.
2.堆這塊區域也是線程共享的,也是 gc 主要的回收區.
3.一個JVM實例只存在一個堆內存,堆內存的大小是可以調節的。
4.堆內存的存儲特點----先進先出,后進后出
5.堆可以動態地分配內存大小,生存期也不必事先告訴編譯器,因為它是在運行時動態分配內存的,但缺點是,由於要在運行時動態分配內存,存取速度較慢
1.3堆內存不夠的原因:
1. Java虛擬機的堆內存設置不夠,可能存在內存泄漏,內存溢出問題或者堆空間的大小不合理,在處理比較可觀的數據量,沒有顯式指定JVM堆大小或者指定數值偏小,通過參數一Xms、一Xmx來調整。
2.代碼中創建了大量大對象,並且長時間不能被垃圾收集器回收(存在被引用)
2.堆內存的組成部分
1.堆內存邏輯上由新生代 ( Young ),老年代 ( Old )和永久代(Perm)組成,其中新生代 ( Young )又被划分為:Eden、From Survivor(From區)和To Survivor(T區)三個區域
2.從JDK8開始,Metaspace(元空間)替代了永久代
3.注意事項(重要)
(1) 堆內存邏輯上由新生代 ( Young ),老年代 ( Old )和永久代(Perm)或者Metaspace(元空間)組成. (邏輯上:堆=新生+老年+永久或者元空間)
(2)堆內存物理上由新生代 ( Young )和老年代 ( Old )組成,也就是堆內存的大小等於新生代的大小+老年代的大小(物理上:堆=新生+老年)
3.堆內存的空間比例分配
1.堆內存的空間分別分給了新生代和老年代,在默認情況下新生代 ( Young ) 占 1/3 的堆空間,老年代 ( Old ) 占2/3 的堆空間
2.新生代 又被划分為:Eden、From Survivor(From區)和To Survivor(To區)三個區域,默認情況下其中 Eden 區占8/10 的新生代空間,from 和to 占 1/10 的新生代空間.(Eden: from : to = 8 : 1 : 1)
3.JVM默認有這個參數-XX:+UseAdaptiveSizePolicy(默認開啟),會導致這個8:1:1比例自動變化,如果不想這個比例有變化,也可以設置參數-XX:-UseAdaptiveSizePolicy
4.新生代
新生代是類的誕生、成長、消亡的區域,一個類在這里產生,應用,最后被垃圾回收器收集,結束生命;新生代又分為兩部分:伊甸區(Eden)和兩個幸存者區(From區,To區)
4.1新生代的垃圾回收(MinorGC)
(1)所有的類都是在伊甸區被new出來的,當伊甸園的空間用完時,程序又需要創建對象,JVM的垃圾回收器將對伊甸園區進行垃圾回收(MinorGC),將伊甸園區中的不再被其他對象所引用的對象進行銷毀。然后將伊甸園中的剩余對象移動到幸存0區。若幸存0區也滿了,再對該區進行垃圾回收,然后移動到1 區。那如果1 區也滿了,再移動到養老區。若養老區也滿了,那么這個時候將產生MajorGC(FullGC),進行養老區的內存清理。
(2)Minor GC和Full GC 有什么不同呢?
Minor GC/Young GC:指發生新生代的的垃圾收集動作,Minor GC非常頻繁,回收速度一般也比較快。
Major GC/Full GC:一般會回收老年代 ,年輕代,方法區的垃圾,Major GC的速度一般會比Minor GC的慢10倍以上。
4.1.1新生的垃圾回收過程(復制->清空->互換)
因為年輕代中的對象基本都是朝生夕死的(80%以上),所以在年輕代的垃圾回收算法使用的是復制算法
1.eden,From區復制到To區,年齡+1
首先,當Eden區滿的時候會觸發第一次GC,把還活着的對象拷貝到From區,當Eden區再次觸發GC的時候會掃描Eden區和From區域,對這兩個區域進行垃圾回收,經過這次回收后還存活的對象,則直接復制到To區域(如果有對象的年齡已經達到了老年的標准,則賦值到老年代區),同時把這些對象的年齡+1
2.清空,eden區,From區
然后,清空Eden和From區中的對象,也即復制之后有交換,誰空誰是to
3.To區和From區互換
最后,To區和From區互換,原To區成為下一次GC時的From區。部分對象會在From區和To區域中復制來復制去,如此交換15次(次數可以調節)最終如果還是存活,就存入到老年代
4.2新生代(年輕代)的參數調節
5.年老代
主要接收由年輕代發送過來的對象,一般情況下,經過了數次Minor GC 之后還會保存下來的對象才會進入到老年代,當老年代內存不足時,將引發 "major GC”,即"Full GC
5.1注意事項
1.若養老區執行了FullGC之后發現依然無法進行對象的保存,就會產生OOM異常“OutOfMemoryError”。
2.如果出現java.lang.OutOfMemoryError: Javaheap space異常,說明Java虛擬機的堆內存不夠,原因有二
(1)Java虛擬機的堆內存設置不夠,可以通過參數-Xms、-Xmx來調整。
(2)代碼中創建了大量大對象,並且長時間不能被垃圾收集器收集(存在被引用),也就所謂內存泄漏
5.2Full GC
Full GC是發生在老年代的垃圾收集動作,老年代一般是由標記清除算法或者是標記清除算法與標記整理算法的混合實現,因為老年代里面的對象幾乎個個都是在 Survivor 區域中存活過來的,所以,Full GC 發生的次數不會有 Young GC那么頻繁,並且做一次 Full GC 要比進行一次 Young GC 的時間更長
1.盡可能讓對象都在新生代里分配和回收,盡量別讓太多對象頻繁進入老年代,避免頻繁對老年代進行垃圾回收,同時給系統充足的內存大小,避免新生代頻繁的進行垃圾回收
5.3什么樣的對象可以直接進入老年代
5.3.1大對象直接進入老年代 (根據對象大小)
1.大對象就是需要大量連續內存空間的對象,如果對象超過設置大小會直接進入老年代,不會進入年輕代。最典型的大對象就是那種很長的字符串以及數組
2.好處:為了避免為大對象分配內存時的復制操作而降低效率
3.JVM參數 -XX:PretenureSizeThreshold 可以設置大對象的大小,然后再執行下上面的第一個程序會發現大對象直接進了老年代 ,不過這個參數只在 Serial 和ParNew兩個收集器下
5.4設置堆內存的大小
5.4.1堆內存的默認大小
1. 初始內存大小:物理電腦內存大小 / 64 最大內存大小:物理電腦內存大小 / 4
2.其實堆內存的空間大小是可以手動設置的
3.開發中建議將初始堆內存和最大的堆內存設置成相同的值,防止GC后頻繁擴容或者回收操作造成的系統資源浪費.
5.4.2堆內存的參數設置
1.參數解釋:-X:是jvm的運行參數,ms:是memory start(初始化大小),mx:是最大內存大小
2.具體查看某個參數的指令(命令行):第一步:jps:查看當前運行中的進程,第二步:jinfo -flag SurvivorRatio 進程id
3.具體參數設置如下:
-Xms: 用來設置堆空間(年輕代+老年代)的初始內存大小,例如-Xms700m-XX:+PrintFlagsFinal :查看所有的參數的最終值(可能會存在修改,不再是初始值)
-Xmx: 用來設置堆空間(年輕代+老年代)的最大內存大小,例如-Xmx700m
-XX:+PrintFlagsInitial:查看所有的參數的默認初始值
-XX:NewRatio:配置新生代與老年代在堆結構的占比
-XX:SurvivorRatio:設置新生代中Eden和S0/S1空間的比例
-XX:MaxTenuringThreshold:設置新生代垃圾的最大年齡
-XX:+PrintGCDetails:輸出詳細的GC處理日志
6.永久代(1.8之前)
永久代存儲區是一個常駐內存區域,用於存放JDK自身所攜帶的Class,Interface的元數據,也就是說它存儲的是運行環境必須的類信息,被裝載進此區域的數據是不會被垃圾回收器回收掉的,關閉JVM 才會釋放此區域所占用的內存
XX:PermSize //代表永久代的初始容量
備注:由於調整元空間的大小需要Full GC,這是非常昂貴的操作,如果應用在啟動的時候發生大量Full GC,通常都是由於永久代或元空間發生
了大小調整,基於這種情況,一般建議在JVM參數中將MetaspaceSize和MaxMetaspaceSize設置成一樣的值,並設置得比初始值要大,
對於8G物理內存的機器來說,一般我會將這兩個值都設置為256M
7.元空間(1.8)
在Java8中,永久代已經被移除,被一個稱為元空間的區域所取代。元空間的本質和永久代類似。默認情況下,元空間的大小僅受本地內存限制。類的元數據放入nativememory, 字符串池和類的靜態變量放入java 堆中,這樣可以加載多少類的元數據就不再由MaxPermSize控制, 而由系統的實際可用空間來控制。
元空間與永久代之間最大的區別在於:永久代帶使用的JVM的堆內存,但是java8以后的元空間並不在虛擬機中而是使用本機物理內存。
當元空間溢出時會得到如下錯誤: java.lang.OutOfMemoryError: MetaSpace
7.1元空間的JVM參數設置
-XX:MaxMetaspaceSize://設置元空間最大值, 默認是-1, 即不限制, 或者說只受限於本地內存大小。
-XX:MetaspaceSize: //指定元空間觸發Fullgc的初始閾值(元空間無固定初始大小), 以字節為單位,默認是21M
備注:達到該值就會觸發full gc 同時收集器會對該值進行調整: 如果釋放了大量的空間, 就適當降低該值;
如果釋放了很少的空間, 那么在不超過-XX:MaxMetaspaceSize(如果設置了的話) 的情況下, 適當提高該值。
9.元空間、永久代與方法區的關系
1.方法區是Java虛擬機規范中的定義,是一種規范
,而永久代和元空間是一種實現,一個是標准的一個是實現。
2.永久代物理是堆的一部分,和新生代,老年代地址是連續的,而元空間屬於本地內存;存儲內容不同,元空間存儲類的元信息,靜態變量和常量池等並入堆中。相當於永久代的數據被分到了堆和元空間中