溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

JVM對象創(chuàng)建與內(nèi)存分配機制的原理是什么

發(fā)布時間:2021-06-21 11:02:38 來源:億速云 閱讀:138 作者:chen 欄目:開發(fā)技術

本篇內(nèi)容主要講解“JVM對象創(chuàng)建與內(nèi)存分配機制的原理是什么”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“JVM對象創(chuàng)建與內(nèi)存分配機制的原理是什么”吧!

1. 對象的創(chuàng)建

對象創(chuàng)建的主要流程:

JVM對象創(chuàng)建與內(nèi)存分配機制的原理是什么

1.類加載檢查

虛擬機遇到一條new指令時,首先將去檢查這個指令的參數(shù)是否能在常量池中定位到一個類的符號引用,并且檢查這個符號引用代表的類是否已被加載、解析和初始化過。如果沒有,那必須先執(zhí)行相應的類加載過程。 new指令對應到語言層面上講是,new關鍵詞、對象克隆、對象序列化等。

2.分配內(nèi)存

在類加載檢查通過后,接下來虛擬機將為新生對象分配內(nèi)存。對象所需內(nèi)存的大小在類 加載完成后便可完全確定,為對象分配空間的任務等同于把 一塊確定大小的內(nèi)存從Java堆中劃分出來。

這個步驟有兩個問題:

  • 如何劃分內(nèi)存。

  • 在并發(fā)情況下, 可能出現(xiàn)正在給對象A分配內(nèi)存,指針還沒來得及修改, 對象B又同時使用了原來的指針來分配內(nèi)存的情況。

  • 劃分內(nèi)存的方法:

    • “指針碰撞”(Bump the Pointer)(默認用指針碰撞)

如果Java堆中內(nèi)存是絕對規(guī)整的,所有用過的內(nèi)存都放在一邊,空閑的內(nèi)存放在另一邊,中間放著一個指針作為分界點的指示器,那所分配內(nèi)存就僅僅是把那個指針向空閑空間那邊挪動一段與對象大小相等的距離。

- “空閑列表”(Free List)

如果Java堆中的內(nèi)存并不是規(guī)整的,已使用的內(nèi)存和空 閑的內(nèi)存相互交錯,那就沒有辦法簡單地進行指針碰撞了,虛擬機就必須維護一個列表,記 錄上哪些內(nèi)存塊是可用的,在分配的時候從列表中找到一塊足夠大的空間劃分給對象實例, 并更新列表上的記錄

  • 解決并發(fā)問題的方法:

    • CAS(compare and swap)

虛擬機采用CAS配上失敗重試的方式保證更新操作的原子性來對分配內(nèi)存空間的動作進行同步處理。

- 本地線程分配緩沖(Thread Local Allocation Buffer,TLAB)

把內(nèi)存分配的動作按照線程劃分在不同的空間之中進行,即每個線程在Java堆中預先分配一小塊內(nèi)存。通過-XX:+/-UseTLAB參數(shù)來設定虛擬機是否使用TLAB(JVM會默認開啟-XX:+UseTLAB),-XX:TLABSize 指定TLAB大小。

3.初始化零值

內(nèi)存分配完成后,虛擬機需要將分配到的內(nèi)存空間都初始化為零值(不包括對象頭), 如果使用TLAB,這一工作過程也可以提前至TLAB分配時進行。這一步操作保證了對象的實例字段在Java代碼中可以不賦初始值就直接使用,程序能訪問到這些字段的數(shù)據(jù)類型所對應的零值。

4.設置對象頭

初始化零值之后,虛擬機要對對象進行必要的設置,例如這個對象是哪個類的實例、如何才能找到類的元數(shù)據(jù)信息、對象的哈希碼、對象的GC分代年齡等信息。這些信息存放在對象的對象頭Object Header之中。

在HotSpot虛擬機中,對象在內(nèi)存中存儲的布局可以分為3塊區(qū)域:對象頭(Header)、 實例數(shù)據(jù)(Instance Data)和對齊填充(Padding)。 HotSpot虛擬機的對象頭包括兩部分信息,第一部分用于存儲對象自身的運行時數(shù)據(jù), 如哈希碼(HashCode)、GC分代年齡、鎖狀態(tài)標志、線程持有的鎖、偏向線程ID、偏向時 間戳等。對象頭的另外一部分是類型指針,即對象指向它的類元數(shù)據(jù)的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例。

5.執(zhí)行方法

執(zhí)行方法,即對象按照程序員的意愿進行初始化。對應到語言層面上講,就是為屬性賦值(注意,這與上面的賦零值不同,這是由程序員賦的值),和執(zhí)行構(gòu)造方法。

2. 對象內(nèi)存分配

對象內(nèi)存分配流程圖

JVM對象創(chuàng)建與內(nèi)存分配機制的原理是什么

2.1 對象棧上分配

我們通過JVM內(nèi)存分配可以知道JAVA中的對象都是在堆上進行分配,當對象沒有被引用的時候,需要依靠GC進行回收內(nèi)存,如果對象數(shù)量較多的時候,會給GC帶來較大壓力,也間接影響了應用的性能。為了減少臨時對象在堆內(nèi)分配的數(shù)量,JVM通過逃逸分析確定該對象不會被外部訪問。如果不會逃逸可以將該對象在棧上分配內(nèi)存,這樣該對象所占用的內(nèi)存空間就可以隨棧幀出棧而銷毀,就減輕了垃圾回收的壓力。

  • 對象逃逸分析:就是分析對象動態(tài)作用域,當一個對象在方法中被定義后,它可能被外部方法所引用,例如作為調(diào)用參數(shù)傳遞到其他地方中。

2.2 對象在Eden區(qū)分配

大多數(shù)情況下,對象在新生代中 Eden 區(qū)分配。當 Eden 區(qū)沒有足夠空間進行分配時,虛擬機將發(fā)起一次Minor GC。

  • Minor GC/Young GC:指發(fā)生新生代的的垃圾收集動作,Minor GC非常頻繁,回收速度一般也比較快。

  • Major GC/Full GC:一般會回收老年代 ,年輕代,方法區(qū)的垃圾,Major GC的速度一般會比Minor GC的慢10倍以上。

  • Eden與Survivor區(qū)默認8:1:1

大量的對象被分配在eden區(qū),eden區(qū)滿了后會觸發(fā)minor gc,可能會有99%以上的對象成為垃圾被回收掉,剩余存活的對象會被挪到為空的那塊survivor區(qū),下一次eden區(qū)滿了后又會觸發(fā)minor gc,把eden區(qū)和survivor區(qū)垃圾對象回收,把剩余存活的對象一次性挪動到另外一塊為空的survivor區(qū),因為新生代的對象都是朝生夕死的,存活時間很短,所以JVM默認的8:1:1的比例是很合適的,讓eden區(qū)盡量的大,survivor區(qū)夠用即可,

== JVM默認有這個參數(shù)-XX:+UseAdaptiveSizePolicy(默認開啟),會導致這個8:1:1比例自動變化,如果不想這個比例有變化可以設置參數(shù)-XX:-UseAdaptiveSizePolicy ==

2.3 大對象直接進入老年代

大對象就是需要大量連續(xù)內(nèi)存空間的對象(比如:字符串、數(shù)組)。JVM參數(shù) -XX:PretenureSizeThreshold 可以設置大對象的大小,如果對象超過設置大小會直接進入老年代,不會進入年輕代,這個參數(shù)只在 Serial 和ParNew兩個收集器下有效。比如設置JVM參數(shù):-XX:PretenureSizeThreshold=1000000 (單位是字節(jié)) -XX:+UseSerialGC ,再執(zhí)行下上面的第一個程序會發(fā)現(xiàn)大對象直接進了老年代

2.3.1 為什么要這樣呢?

為了避免為大對象分配內(nèi)存時的復制操作而降低效率。

2.4 長期存活的對象將進入老年代

既然虛擬機采用了分代收集的思想來管理內(nèi)存,那么內(nèi)存回收時就必須能識別哪些對象應放在新生代,哪些對象應放在老年代中。為了做到這一點,虛擬機給每個對象一個對象年齡(Age)計數(shù)器。如果對象在 Eden 出生并經(jīng)過第一次 Minor GC 后仍然能夠存活,并且能被 Survivor 容納的話,將被移動到 Survivor 空間中,并將對象年齡設為1。對象在 Survivor 中每熬過一次 MinorGC,年齡就增加1歲,當它的年齡增加到一定程度(默認為15歲,CMS收集器默認6歲,不同的垃圾收集器會略微有點不同),就會被晉升到老年代中。對象晉升到老年代的年齡閾值,可以通過參數(shù) -XX:MaxTenuringThreshold 來設置。

2.5 對象動態(tài)年齡判斷

當前放對象的Survivor區(qū)域里(其中一塊區(qū)域,放對象的那塊s區(qū)),一批對象的總大小大于這塊Survivor區(qū)域內(nèi)存大小的50%(-XX:TargetSurvivorRatio可以指定),那么此時大于等于這批對象年齡最大值的對象,就可以直接進入老年代了,例如Survivor區(qū)域里現(xiàn)在有一批對象,年齡1+年齡2+年齡n的多個年齡對象總和超過了Survivor區(qū)域的50%,此時就會把年齡n(含)以上的對象都放入老年代。這個規(guī)則其實是希望那些可能是長期存活的對象,盡早進入老年代。對象動態(tài)年齡判斷機制一般是在minor gc之后觸發(fā)的。

2.6 老年代空間分配擔保機制

年輕代每次minor gc之前JVM都會計算下老年代剩余可用空間

如果這個可用空間小于年輕代里現(xiàn)有的所有對象大小之和(包括垃圾對象) 就會看一個“-XX:-HandlePromotionFailure”(jdk1.8默認就設置了)的參數(shù)是否設置了

如果有這個參數(shù),就會看看老年代的可用內(nèi)存大小,是否大于之前每一次minor gc后進入老年代的對象的平均大小。

如果上一步結(jié)果是小于或者之前說的參數(shù)沒有設置,那么就會觸發(fā)一次Full gc,對老年代和年輕代一起回收一次垃圾,如果回收完還是沒有足夠空間存放新的對象就會發(fā)生"OOM"

當然,如果minor gc之后剩余存活的需要挪動到老年代的對象大小還是大于老年代可用空間,那么也會觸發(fā)full gc,full gc完之后如果還是沒有空間放minor gc之后的存活對象,則也會發(fā)生“OOM”

JVM對象創(chuàng)建與內(nèi)存分配機制的原理是什么

到此,相信大家對“JVM對象創(chuàng)建與內(nèi)存分配機制的原理是什么”有了更深的了解,不妨來實際操作一番吧!這里是億速云網(wǎng)站,更多相關內(nèi)容可以進入相關頻道進行查詢,關注我們,繼續(xù)學習!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。

jvm
AI