您好,登錄后才能下訂單哦!
本篇內容主要講解“JVM對象創建與內存分配機制的原理是什么”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“JVM對象創建與內存分配機制的原理是什么”吧!
對象創建的主要流程:
虛擬機遇到一條new指令時,首先將去檢查這個指令的參數是否能在常量池中定位到一個類的符號引用,并且檢查這個符號引用代表的類是否已被加載、解析和初始化過。如果沒有,那必須先執行相應的類加載過程。 new指令對應到語言層面上講是,new關鍵詞、對象克隆、對象序列化等。
在類加載檢查通過后,接下來虛擬機將為新生對象分配內存。對象所需內存的大小在類 加載完成后便可完全確定,為對象分配空間的任務等同于把 一塊確定大小的內存從Java堆中劃分出來。
這個步驟有兩個問題:
如何劃分內存。
在并發情況下, 可能出現正在給對象A分配內存,指針還沒來得及修改, 對象B又同時使用了原來的指針來分配內存的情況。
劃分內存的方法:
“指針碰撞”(Bump the Pointer)(默認用指針碰撞)
如果Java堆中內存是絕對規整的,所有用過的內存都放在一邊,空閑的內存放在另一邊,中間放著一個指針作為分界點的指示器,那所分配內存就僅僅是把那個指針向空閑空間那邊挪動一段與對象大小相等的距離。
- “空閑列表”(Free List)
如果Java堆中的內存并不是規整的,已使用的內存和空 閑的內存相互交錯,那就沒有辦法簡單地進行指針碰撞了,虛擬機就必須維護一個列表,記 錄上哪些內存塊是可用的,在分配的時候從列表中找到一塊足夠大的空間劃分給對象實例, 并更新列表上的記錄
解決并發問題的方法:
CAS(compare and swap)
虛擬機采用CAS配上失敗重試的方式保證更新操作的原子性來對分配內存空間的動作進行同步處理。
- 本地線程分配緩沖(Thread Local Allocation Buffer,TLAB)
把內存分配的動作按照線程劃分在不同的空間之中進行,即每個線程在Java堆中預先分配一小塊內存。通過-XX:+/-UseTLAB參數來設定虛擬機是否使用TLAB(JVM會默認開啟-XX:+UseTLAB),-XX:TLABSize 指定TLAB大小。
內存分配完成后,虛擬機需要將分配到的內存空間都初始化為零值(不包括對象頭), 如果使用TLAB,這一工作過程也可以提前至TLAB分配時進行。這一步操作保證了對象的實例字段在Java代碼中可以不賦初始值就直接使用,程序能訪問到這些字段的數據類型所對應的零值。
初始化零值之后,虛擬機要對對象進行必要的設置,例如這個對象是哪個類的實例、如何才能找到類的元數據信息、對象的哈希碼、對象的GC分代年齡等信息。這些信息存放在對象的對象頭Object Header之中。
在HotSpot虛擬機中,對象在內存中存儲的布局可以分為3塊區域:對象頭(Header)、 實例數據(Instance Data)和對齊填充(Padding)。 HotSpot虛擬機的對象頭包括兩部分信息,第一部分用于存儲對象自身的運行時數據, 如哈希碼(HashCode)、GC分代年齡、鎖狀態標志、線程持有的鎖、偏向線程ID、偏向時 間戳等。對象頭的另外一部分是類型指針,即對象指向它的類元數據的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例。
執行方法,即對象按照程序員的意愿進行初始化。對應到語言層面上講,就是為屬性賦值(注意,這與上面的賦零值不同,這是由程序員賦的值),和執行構造方法。
對象內存分配流程圖
我們通過JVM內存分配可以知道JAVA中的對象都是在堆上進行分配,當對象沒有被引用的時候,需要依靠GC進行回收內存,如果對象數量較多的時候,會給GC帶來較大壓力,也間接影響了應用的性能。為了減少臨時對象在堆內分配的數量,JVM通過逃逸分析確定該對象不會被外部訪問。如果不會逃逸可以將該對象在棧上分配內存,這樣該對象所占用的內存空間就可以隨棧幀出棧而銷毀,就減輕了垃圾回收的壓力。
對象逃逸分析:就是分析對象動態作用域,當一個對象在方法中被定義后,它可能被外部方法所引用,例如作為調用參數傳遞到其他地方中。
大多數情況下,對象在新生代中 Eden 區分配。當 Eden 區沒有足夠空間進行分配時,虛擬機將發起一次Minor GC。
Minor GC/Young GC:指發生新生代的的垃圾收集動作,Minor GC非常頻繁,回收速度一般也比較快。
Major GC/Full GC:一般會回收老年代 ,年輕代,方法區的垃圾,Major GC的速度一般會比Minor GC的慢10倍以上。
Eden與Survivor區默認8:1:1
大量的對象被分配在eden區,eden區滿了后會觸發minor gc,可能會有99%以上的對象成為垃圾被回收掉,剩余存活的對象會被挪到為空的那塊survivor區,下一次eden區滿了后又會觸發minor gc,把eden區和survivor區垃圾對象回收,把剩余存活的對象一次性挪動到另外一塊為空的survivor區,因為新生代的對象都是朝生夕死的,存活時間很短,所以JVM默認的8:1:1的比例是很合適的,讓eden區盡量的大,survivor區夠用即可,
== JVM默認有這個參數-XX:+UseAdaptiveSizePolicy(默認開啟),會導致這個8:1:1比例自動變化,如果不想這個比例有變化可以設置參數-XX:-UseAdaptiveSizePolicy ==
大對象就是需要大量連續內存空間的對象(比如:字符串、數組)。JVM參數 -XX:PretenureSizeThreshold 可以設置大對象的大小,如果對象超過設置大小會直接進入老年代,不會進入年輕代,這個參數只在 Serial 和ParNew兩個收集器下有效。比如設置JVM參數:-XX:PretenureSizeThreshold=1000000 (單位是字節) -XX:+UseSerialGC ,再執行下上面的第一個程序會發現大對象直接進了老年代
為了避免為大對象分配內存時的復制操作而降低效率。
既然虛擬機采用了分代收集的思想來管理內存,那么內存回收時就必須能識別哪些對象應放在新生代,哪些對象應放在老年代中。為了做到這一點,虛擬機給每個對象一個對象年齡(Age)計數器。如果對象在 Eden 出生并經過第一次 Minor GC 后仍然能夠存活,并且能被 Survivor 容納的話,將被移動到 Survivor 空間中,并將對象年齡設為1。對象在 Survivor 中每熬過一次 MinorGC,年齡就增加1歲,當它的年齡增加到一定程度(默認為15歲,CMS收集器默認6歲,不同的垃圾收集器會略微有點不同),就會被晉升到老年代中。對象晉升到老年代的年齡閾值,可以通過參數 -XX:MaxTenuringThreshold 來設置。
當前放對象的Survivor區域里(其中一塊區域,放對象的那塊s區),一批對象的總大小大于這塊Survivor區域內存大小的50%(-XX:TargetSurvivorRatio可以指定),那么此時大于等于這批對象年齡最大值的對象,就可以直接進入老年代了,例如Survivor區域里現在有一批對象,年齡1+年齡2+年齡n的多個年齡對象總和超過了Survivor區域的50%,此時就會把年齡n(含)以上的對象都放入老年代。這個規則其實是希望那些可能是長期存活的對象,盡早進入老年代。對象動態年齡判斷機制一般是在minor gc之后觸發的。
年輕代每次minor gc之前JVM都會計算下老年代剩余可用空間
如果這個可用空間小于年輕代里現有的所有對象大小之和(包括垃圾對象) 就會看一個“-XX:-HandlePromotionFailure”(jdk1.8默認就設置了)的參數是否設置了
如果有這個參數,就會看看老年代的可用內存大小,是否大于之前每一次minor gc后進入老年代的對象的平均大小。
如果上一步結果是小于或者之前說的參數沒有設置,那么就會觸發一次Full gc,對老年代和年輕代一起回收一次垃圾,如果回收完還是沒有足夠空間存放新的對象就會發生"OOM"
當然,如果minor gc之后剩余存活的需要挪動到老年代的對象大小還是大于老年代可用空間,那么也會觸發full gc,full gc完之后如果還是沒有空間放minor gc之后的存活對象,則也會發生“OOM”
到此,相信大家對“JVM對象創建與內存分配機制的原理是什么”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。