<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          醒酒菜:動(dòng)畫圖解核心內(nèi)存區(qū)--堆

          共 6348字,需瀏覽 13分鐘

           ·

          2021-06-18 10:08

          端午佳節(jié)一下子就過完了,大家是不是還沉迷在假期的歡樂氣氛中無法自拔?今天阿Q為大家準(zhǔn)備了上好的“醒酒菜”——JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)的核心內(nèi)存區(qū)——堆。

          堆的概述

          一般來說:

          • 一個(gè)Java程序的運(yùn)行對應(yīng)一個(gè)進(jìn)程;
          • 一個(gè)進(jìn)程對應(yīng)著一個(gè)JVM實(shí)例(JVM的啟動(dòng)由引導(dǎo)類加載器加載啟動(dòng)),同時(shí)也對應(yīng)著多個(gè)線程;
          • 一個(gè)JVM實(shí)例擁有一個(gè)運(yùn)行時(shí)數(shù)據(jù)區(qū)(Runtime類,為餓漢式單例類);
          • 一個(gè)運(yùn)行時(shí)數(shù)據(jù)區(qū)中的堆和方法區(qū)是多線程共享的,而本地方法棧、虛擬機(jī)棧、程序計(jì)數(shù)器是線程私有的。

          堆空間差不多是最大的內(nèi)存空間,也是運(yùn)行時(shí)數(shù)據(jù)區(qū)最重要的內(nèi)存空間。堆可以處于物理上不連續(xù)的內(nèi)存空間,但在邏輯上它應(yīng)該被視為連續(xù)的。

          在方法結(jié)束后,堆中的對象不會(huì)馬上被移除,僅僅在垃圾收集的時(shí)候才會(huì)被移除。堆,是GC(Garbage Collection,垃圾收集器)執(zhí)行垃圾回收的重點(diǎn)區(qū)域。

          堆內(nèi)存大小設(shè)置

          堆一旦被創(chuàng)建,它的大小也就確定了,初始內(nèi)存默認(rèn)為電腦物理內(nèi)存大小的1/64,最大內(nèi)存默認(rèn)為電腦物理內(nèi)存的1/4,但是堆空間的大小是可以調(diào)節(jié),接下來我們來演示一下。

          準(zhǔn)備工具

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

          安裝完重啟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...");
              }
          }

          IDEA設(shè)置

          • -Xms10m用于表示堆區(qū)的起始內(nèi)存為10m,等價(jià)于-XX:InitialHeapSize
          • -Xmx10m用于表示堆區(qū)的最大內(nèi)存為10m,等價(jià)于-XX:MaxHeapSize;
          • 其中-XJVM的運(yùn)行參數(shù),msmemory start
          ?

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

          ?

          啟動(dòng)程序

          啟動(dòng)程序之后去jvisualvm查看一旦堆區(qū)中的內(nèi)存大小超過-Xmx所指定的最大內(nèi)存時(shí),將會(huì)拋出OOM(Out Of MemoryError)異常。

          堆的分代

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

          • 一類是生命周期較短的瞬時(shí)對象,這類對象的創(chuàng)建和消亡都非常迅速;
          • 另一類是生命周期非常長,在某些情況下還能與JVM的生命周期保持一致;

          堆區(qū)分代

          經(jīng)研究表明70%-99%的對象屬于臨時(shí)對象,為了提高GC的性能,Hotspot虛擬機(jī)又將堆區(qū)進(jìn)行了進(jìn)一步劃分。如圖所示,堆區(qū)又分為年輕代(YoungGen)和老年代(OldGen);其中年輕代又分為伊甸園區(qū)(Eden)和幸存者區(qū)(Survivor);幸存者區(qū)分為幸存者0區(qū)(Survivor0,S0)和幸存者1區(qū)(Survivor1,S1),有時(shí)也叫from區(qū)和to區(qū)。

          ?

          分代完成之后,GC時(shí)主要檢測新生代Eden區(qū)。

          ?

          「統(tǒng)一概念:」
          新生區(qū)<=>新生代<=>年輕代
          養(yǎng)老區(qū)<=>老年區(qū)<=>老年代

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

          新生代與老年代在堆結(jié)構(gòu)的占比

          • 默認(rèn)參數(shù)-XX:NewRatio=2,表示新生代占1,老年代占2,新生代占整個(gè)堆的1/3;
          • 可以修改-XX:NewRatio=4,表示新生代占1,老年代占4,新生代占整個(gè)堆的1/5;
          ?

          該參數(shù)在開發(fā)中一般不會(huì)調(diào)整,如果生命周期長的對象偏多時(shí)可以選擇調(diào)整。

          ?

          Eden與Survivor在堆結(jié)構(gòu)的占比

          HotSpot中,Eden空間和另外兩個(gè)Survivor空間所占的比例是8:1:1(測試的時(shí)候是6:1:1),開發(fā)人員可以通過選項(xiàng)-XX:SurvivorRatio調(diào)整空間比例,如-XX:SurvivorRatio=8

          ?

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

          ?

          -Xmn設(shè)置新生代最大內(nèi)存大?。J(rèn)就好),如果既設(shè)置了該參數(shù),又設(shè)置了NewRatio的值,則以該參數(shù)設(shè)置為準(zhǔn)。

          查看設(shè)置的參數(shù)

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

          圖解對象分配過程

          對象分配過程

          1. new的對象先放伊甸園區(qū),此區(qū)有大小限制;
          2. 當(dāng)伊甸園的空間填滿時(shí),程序繼續(xù)創(chuàng)建對象,JVM的垃圾回收器將對伊甸園區(qū)進(jìn)行垃圾回收(Minor GC,也叫YGC):將伊甸園區(qū)中的不再被其他對象所引用的對象進(jìn)行銷毀,將未被銷毀的對象移動(dòng)到幸存者0區(qū)并分配age;
          3. 然后再加載新的對象放到伊甸園區(qū);
          4. 如果再次觸發(fā)垃圾回收,將此次未被銷毀的對象和上一次放在幸存者0區(qū)且此次也未被銷毀的對象一齊移動(dòng)到幸存者一區(qū),此時(shí)新對象的age為1,上次的對象的age加1變?yōu)?;
          5. 如果再次經(jīng)歷垃圾回收,此時(shí)會(huì)重新放回幸存者0區(qū),接著再去幸存者1區(qū),age也隨之增加;
          6. 默認(rèn)當(dāng)age為15時(shí),未被回收的對象將移動(dòng)到老年區(qū)??梢酝ㄟ^設(shè)置參數(shù)來更改默認(rèn)配置:-XX:MaxTenuringThreshold=<N>;該過程稱為晉升(promotion);
          7. 在養(yǎng)老區(qū),相對悠閑,當(dāng)老年區(qū)內(nèi)存不足時(shí),再次觸發(fā)GC(Major GC),進(jìn)行養(yǎng)老區(qū)的內(nèi)存清理;
          8. 若養(yǎng)老區(qū)執(zhí)行了Major GC之后發(fā)現(xiàn)依然無法進(jìn)行對象的保存,就會(huì)產(chǎn)生OOM異常。
          ?

          S0,S1滿時(shí)不會(huì)觸發(fā)YGC,但是YGC會(huì)回收S0,S1的對象。

          ?

          「總結(jié)」

          • 針對幸存者s0,s1區(qū):復(fù)制之后有交換,誰空誰是to;
          • 關(guān)于垃圾回收:頻繁在新生區(qū)收集,很少在養(yǎng)老區(qū)收集,幾乎不再永久區(qū)/元空間收集。

          對象特殊情況分配過程

          1. 新對象申請內(nèi)存,如果Eden放的下,則直接存入Eden;如果存不下則進(jìn)行YGC;
          2. YGC之后如果能存下則放入Eden,如果還存不下(為超大對象),則嘗試存入Old區(qū);
          3. 如果Old區(qū)可以存放,則存入;如果不能存入,則進(jìn)行Full GC;
          4. Full GC之后如果可以存入Old區(qū),則存入;如果內(nèi)存空間還不夠,則OOM;
          5. 圖右側(cè)為YGC的流程圖:當(dāng)YGC之后未銷毀的對象放入幸存者區(qū),此時(shí)如果幸存者區(qū)的空間可以裝下該對象,則存入幸存者區(qū),否則,直接存入老年代;
          6. 當(dāng)在幸存者區(qū)的對象超過閾值時(shí),可以晉升為老年代,未達(dá)到閾值的依舊在幸存者區(qū)復(fù)制交換。

          內(nèi)存分配策略

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

          1. 優(yōu)先分配到Eden;
          2. 大對象直接分配到老年代:盡量避免程序中出現(xiàn)過多的大對象;
          3. 長期存活的對象分配到老年代;
          4. 動(dòng)態(tài)對象年齡判斷:如果Survivor區(qū)中相同年齡的所有對象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對象可以直接進(jìn)入到老年代。無需等到MaxTenuringThreshold中要求的年齡;

          數(shù)值變小原理

          代碼樣例,設(shè)置參數(shù):-Xms600m,-Xmx600m

          public class HeapSpaceInitial {
              public static void main(String[] args) {

                  //返回Java虛擬機(jī)中的堆內(nèi)存總量
                  long initialMemory = Runtime.getRuntime().totalMemory() / 1024 / 1024;
                  //返回Java虛擬機(jī)試圖使用的最大堆內(nèi)存量
                  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();
                  }
              }
          }
          //執(zhí)行結(jié)果
          -Xms : 575M
          -Xmx : 575M

          明明設(shè)置的600M,怎么變成575M了呢?這是因?yàn)樵诙褍?nèi)存存取數(shù)據(jù)時(shí),新生代里邊只有伊甸園和幸存者1區(qū)或者是幸存者2區(qū)存儲對象,所以會(huì)少一個(gè)幸存者區(qū)的內(nèi)存空間。

          GC

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

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

          Partial GC

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

          • 新生代收集(Minor GC/Young GC):只是新生代的垃圾收集;
          • 老年代收集(Major GC/Old GC):只是老年代的垃圾收集;
          • 混合收集(Mixed GC):收集整個(gè)新生代以及部分老年代的垃圾收集,只有G1 GC (按照region劃分新生代和老年代的數(shù)據(jù))會(huì)有這種行為。

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

          Full GC

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

          觸發(fā)機(jī)制

          年輕代GC(Minor GC)觸發(fā)機(jī)制
          1. 當(dāng)年輕代空間不足時(shí),就會(huì)觸發(fā)Minor GC,這里的年輕代滿指的是Eden代滿,Survivor滿不會(huì)引發(fā)GC。(每次Minor GC會(huì)清理年輕代的內(nèi)存,Survivor是被動(dòng)GC,不會(huì)主動(dòng)GC)
          2. 因?yàn)?code style="overflow-wrap: break-word;margin-right: 2px;margin-left: 2px;font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(53, 148, 247);background: rgba(59, 170, 250, 0.1);padding-right: 2px;padding-left: 2px;border-radius: 2px;height: 21px;line-height: 22px;">Java對象大多都具備“朝生夕滅”的特性,所以Minor GC非常頻繁,一般回收速度也比較快。
          3. Minor GC會(huì)引發(fā)STWStop The World),暫停其他用戶的線程,等垃圾回收結(jié)束,用戶線程才恢復(fù)運(yùn)行。
          老年代GC(Major GC/Full GC)觸發(fā)機(jī)制
          1. 指發(fā)生在老年代的GC,對象從老年代消失時(shí),Major GC或者Full GC發(fā)生了;
          2. 出現(xiàn)了Major GC,經(jīng)常會(huì)伴隨至少一次的Minor GC(不是絕對的,在Parallel Scavenge收集器的收集策略里就有直接進(jìn)行Major GC的策略選擇過程),也就是老年代空間不足時(shí),會(huì)先嘗試觸發(fā)Minor GC。如果之后空間還不足,則觸發(fā)Major GC;
          3. Major GC速度一般會(huì)比Minor GC慢10倍以上,STW時(shí)間更長;
          4. 如果Major GC后,內(nèi)存還不足,就報(bào)OOM了。
          Full GC觸發(fā)機(jī)制

          觸發(fā)Full GC執(zhí)行的情況有以下五種:

          1. 調(diào)用System.gc()時(shí),系統(tǒng)建議執(zhí)行Full GC,但是不必然執(zhí)行;
          2. 老年代空間不足;
          3. 方法區(qū)空間不足;
          4. 通過Minor GC后進(jìn)入老年代的平均大小小于老年代的可用內(nèi)存;
          5. Eden區(qū),Survivor S0from)區(qū)向S1to)區(qū)復(fù)制時(shí),對象大小大于To Space可用內(nèi)存,則把該對象轉(zhuǎn)存到老年代,且老年代的可用內(nèi)存小于該對象大小。
          ?

          Full GC是開發(fā)或調(diào)優(yōu)中盡量要避免的,這樣暫停時(shí)間會(huì)短一些。

          ?
          推薦閱讀:
          為什么MySQL選擇RR作為默認(rèn)隔離級別?
          故事篇:數(shù)據(jù)庫架構(gòu)演變之路
          35 張圖帶你 MySQL 調(diào)優(yōu)

          關(guān)互聯(lián)網(wǎng)全棧架構(gòu)價(jià)。

          瀏覽 50
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久久久大学生毛片 | 怕怕怕视频大全 | 伊人伊人伊人伊人伊人 | 北条麻妃被躁57分钟视频在线 | 操逼视频在线观看视频 |