讀書會第十二期動畫圖解核心內存區--堆

      網友投稿 855 2022-05-30

      今天阿Q為大家準備了上好的“醒酒菜”——JVM運行時數據區的核心內存區——堆。

      堆的概述

      一般來說:

      一個Java程序的運行對應一個進程;

      一個進程對應著一個JVM實例(JVM的啟動由引導類加載器加載啟動),同時也對應著多個線程;

      一個JVM實例擁有一個運行時數據區(Runtime類,為餓漢式單例類);

      一個運行時數據區中的堆和方法區是多線程共享的,而本地方法棧、虛擬機棧、程序計數器是線程私有的。

      堆空間差不多是最大的內存空間,也是運行時數據區最重要的內存空間。堆可以處于物理上不連續的內存空間,但在邏輯上它應該被視為連續的。

      在方法結束后,堆中的對象不會馬上被移除,僅僅在垃圾收集的時候才會被移除。堆,是GC(Garbage Collection,垃圾收集器)執行垃圾回收的重點區域。

      堆內存大小設置

      堆一旦被創建,它的大小也就確定了,初始內存默認為電腦物理內存大小的1/64,最大內存默認為電腦物理內存的1/4,但是堆空間的大小是可以調節,接下來我們來演示一下。

      JDK自帶內存分析的工具:在已安裝JDK的bin目錄下找到jvisualvm.exe。打開該軟件,下載插件Visual GC,一定要點擊檢查最新版本,否則會導致安裝失敗。

      安裝完重啟jvisualvm

      public class HeapDemo { public static void main(String[] args) { System.out.println("start..."); try { Thread.sleep(1000000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("end..."); } }

      -Xms10m用于表示堆區的起始內存為10m,等價于-XX:InitialHeapSize;

      -Xmx10m用于表示堆區的最大內存為10m,等價于-XX:MaxHeapSize;

      其中-X是JVM的運行參數,ms是memory start

      通常會將-Xms和-Xmx兩個參數配置相同的值,其目的就是為了能夠在java垃圾回收機制清理完堆區后不需要重新分隔計算堆區的大小,從而提高性能。

      啟動程序之后去jvisualvm查看

      一旦堆區中的內存大小超過-Xmx所指定的最大內存時,將會拋出OOM(Out Of MemoryError)異常。

      堆的分代

      存儲在JVM中的java對象可以被劃分為兩類:

      一類是生命周期較短的瞬時對象,這類對象的創建和消亡都非常迅速;

      另一類是生命周期非常長,在某些情況下還能與JVM的生命周期保持一致;

      經研究表明70%-99%的對象屬于臨時對象,為了提高GC的性能,Hotspot虛擬機又將堆區進行了進一步劃分。

      如圖所示,堆區又分為年輕代(YoungGen)和老年代(OldGen);其中年輕代又分為伊甸園區(Eden)和幸存者區(Survivor);幸存者區分為幸存者0區(Survivor0,S0)和幸存者1區(Survivor1,S1),有時也叫from區和to區。

      分代完成之后,GC時主要檢測新生代Eden區。

      統一概念:

      新生區<=>新生代<=>年輕代

      養老區<=>老年區<=>老年代

      幾乎所有的Java對象都是在Eden區被new出來的,有的大對象在該區存不下可直接進入老年代。絕大部分的Java對象都銷毀在新生代了(IBM公司的專門研究表明,新生代80%的對象都是“朝生夕死”的)。

      默認參數-XX:NewRatio=2,表示新生代占1,老年代占2,新生代占整個堆的1/3;

      可以修改-XX:NewRatio=4,表示新生代占1,老年代占4,新生代占整個堆的1/5;

      該參數在開發中一般不會調整,如果生命周期長的對象偏多時可以選擇調整。

      在HotSpot中,Eden空間和另外兩個Survivor空間所占的比例是8:1:1(測試的時候是6:1:1),開發人員可以通過選項-XX:SurvivorRatio調整空間比例,如-XX:SurvivorRatio=8

      可以在cmd中通過jps 查詢進程號-> jinfo -flag NewRatio(SurvivorRatio) + 進程號 查詢配置信息

      -Xmn設置新生代最大內存大小(默認就好),如果既設置了該參數,又設置了NewRatio的值,則以該參數設置為準。

      以上邊的代碼為例:設置啟動參數-XX:+PrintGCDetails;可在cmd窗口中輸入jps查詢進程號,然后通過jstat -gc 進程id指令查看進程的內存使用情況。

      圖解對象分配過程

      new的對象先放伊甸園區,此區有大小限制;

      當伊甸園的空間填滿時,程序繼續創建對象,JVM的垃圾回收器將對伊甸園區進行垃圾回收(Minor GC,也叫YGC):將伊甸園區中的不再被其他對象所引用的對象進行銷毀,將未被銷毀的對象移動到幸存者0區并分配age;

      然后再加載新的對象放到伊甸園區;

      如果再次觸發垃圾回收,將此次未被銷毀的對象和上一次放在幸存者0區且此次也未被銷毀的對象一齊移動到幸存者一區,此時新對象的age為1,上次的對象的age加1變為2;

      如果再次經歷垃圾回收,此時會重新放回幸存者0區,接著再去幸存者1區,age也隨之增加;

      默認當age為15時,未被回收的對象將移動到老年區。可以通過設置參數來更改默認配置:-XX:MaxTenuringThreshold=;該過程稱為晉升(promotion);

      在養老區,相對悠閑,當老年區內存不足時,再次觸發GC(Major GC),進行養老區的內存清理;

      若養老區執行了Major GC之后發現依然無法進行對象的保存,就會產生OOM異常。

      S0,S1滿時不會觸發YGC,但是YGC會回收S0,S1的對象。

      總結

      針對幸存者s0,s1區:復制之后有交換,誰空誰是to;

      關于垃圾回收:頻繁在新生區收集,很少在養老區收集,幾乎不再永久區/元空間收集。

      【讀書會第十二期】動畫圖解核心內存區--堆

      新對象申請內存,如果Eden放的下,則直接存入Eden;如果存不下則進行YGC;

      YGC之后如果能存下則放入Eden,如果還存不下(為超大對象),則嘗試存入Old區;

      如果Old區可以存放,則存入;如果不能存入,則進行Full GC;

      Full GC之后如果可以存入Old區,則存入;如果內存空間還不夠,則OOM;

      圖右側為YGC的流程圖:當YGC之后未銷毀的對象放入幸存者區,此時如果幸存者區的空間可以裝下該對象,則存入幸存者區,否則,直接存入老年代;

      當在幸存者區的對象超過閾值時,可以晉升為老年代,未達到閾值的依舊在幸存者區復制交換。

      針對不同年齡段的對象分配原則如下:

      優先分配到Eden;

      大對象直接分配到老年代:盡量避免程序中出現過多的大對象;

      長期存活的對象分配到老年代;

      動態對象年齡判斷:如果Survivor區中相同年齡的所有對象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對象可以直接進入到老年代。無需等到MaxTenuringThreshold中要求的年齡;

      代碼樣例,設置參數:-Xms600m,-Xmx600m

      public class HeapSpaceInitial { 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"); try { Thread.sleep(1000000); } catch (InterruptedException e) { e.printStackTrace(); } } } //執行結果 -Xms : 575M -Xmx : 575M

      明明設置的600M,怎么變成575M了呢?這是因為在堆內存存取數據時,新生代里邊只有伊甸園和幸存者1區或者是幸存者2區存儲對象,所以會少一個幸存者區的內存空間。

      GC

      JVM進行GC時,并非每次都對新生代、老年代、方法區(永久代、元空間)這三個區域一起回收,大部分回收是指新生代。

      針對HotSpot VM的實現,它里面的GC按照回收區域又分為兩大種類型:一種是部分收集(Partial GC),一種是整堆收集(Full GC)

      部分收集:不是完整收集整個Java堆的垃圾收集。其中又分為:

      新生代收集(Minor GC/Young GC):只是新生代的垃圾收集;

      老年代收集(Major GC/Old GC):只是老年代的垃圾收集;

      混合收集(Mixed GC):收集整個新生代以及部分老年代的垃圾收集,只有G1 GC (按照region劃分新生代和老年代的數據)會有這種行為。

      目前,只有CMS GC會有單獨收集老年代的行為;很多時候Major GC會和Full GC 混淆使用,需要具體分辨是老年代回收還是整堆回收。

      整堆收集(Full GC):整個java堆和方法區的垃圾收集。

      當年輕代空間不足時,就會觸發Minor GC,這里的年輕代滿指的是Eden代滿,Survivor滿不會引發GC。(每次Minor GC會清理年輕代的內存,Survivor是被動GC,不會主動GC)

      因為Java對象大多都具備“朝生夕滅”的特性,所以Minor GC非常頻繁,一般回收速度也比較快。

      Minor GC會引發STW(Stop The World),暫停其他用戶的線程,等垃圾回收結束,用戶線程才恢復運行。

      指發生在老年代的GC,對象從老年代消失時,Major GC或者Full GC發生了;

      出現了Major GC,經常會伴隨至少一次的Minor GC(不是絕對的,在Parallel Scavenge收集器的收集策略里就有直接進行Major GC的策略選擇過程),也就是老年代空間不足時,會先嘗試觸發Minor GC。如果之后空間還不足,則觸發Major GC;

      Major GC速度一般會比Minor GC慢10倍以上,STW時間更長;

      如果Major GC后,內存還不足,就報OOM了。

      觸發Full GC執行的情況有以下五種:

      調用System.gc()時,系統建議執行Full GC,但是不必然執行;

      老年代空間不足;

      方法區空間不足;

      通過Minor GC后進入老年代的平均大小小于老年代的可用內存;

      由Eden區,Survivor S0(from)區向S1(to)區復制時,對象大小大于To Space可用內存,則把該對象轉存到老年代,且老年代的可用內存小于該對象大小。

      Full GC是開發或調優中盡量要避免的,這樣暫停時間會短一些。

      阿Q將持續更新java實戰方面的文章,感興趣的可以關注下,也可以來技術群討論問題呦!

      Java JVM

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:Android Gradle構建腳本
      下一篇:12.4 Linux實時監聽進程運行狀態(top命令)
      相關文章
      亚洲av无码乱码国产精品| 亚洲视频一区网站| 亚洲日韩精品国产3区| 老色鬼久久亚洲AV综合| 亚洲妇熟XXXX妇色黄| 亚洲精品无码永久中文字幕| 亚洲欧洲国产成人综合在线观看| 无码不卡亚洲成?人片| 亚洲AV无码男人的天堂| 丰满亚洲大尺度无码无码专线| 亚洲av无码专区国产不乱码| 亚洲第一综合天堂另类专 | 亚洲Aⅴ在线无码播放毛片一线天| 亚洲色欲色欲www在线播放 | 亚洲日本在线观看视频| 亚洲午夜福利精品久久| 国产成人综合亚洲AV第一页| 国产AV无码专区亚洲AV手机麻豆| 青青草原亚洲视频| 亚洲第一AAAAA片| 亚洲一区二区三区四区在线观看| 久久久久亚洲AV无码专区首JN | 国产成人 亚洲欧洲| 亚洲精品tv久久久久| 激情综合色五月丁香六月亚洲| 国产亚洲午夜高清国产拍精品| 亚洲日韩中文无码久久| 亚洲国产a∨无码中文777| 亚洲午夜视频在线观看| 亚洲噜噜噜噜噜影院在线播放| 精品久久亚洲中文无码| 亚洲国产成人无码AV在线| 国产精品亚洲专区一区| 亚洲一区二区精品视频| 国产精品亚洲成在人线| 亚洲国产美国国产综合一区二区| 亚洲国产成人超福利久久精品| 涩涩色中文综合亚洲| 国产亚洲漂亮白嫩美女在线| 中文字幕亚洲专区| 亚洲国产高清视频|