當 JVM 收到一個 new 指令時,會檢查指令中的參數在常量池是否有這個符号的引用,還會檢查該類是否已經被加載過了,如果沒有的話則要進行一次類加載。
當 <code>JVM</code> 收到一個 <code>new</code> 指令時,會檢查指令中的參數在常量池是否有這個符号的引用,還會檢查該類是否已經被加載過了,如果沒有的話則要進行一次類加載。
接着就是配置設定記憶體了,通常有兩種方式:
指針碰撞
空閑清單
使用指針碰撞的前提是堆記憶體是完全工整的,用過的記憶體和沒用的記憶體各在一邊每次配置設定的時候隻需要将指針向空閑記憶體一方移動一段和記憶體大小相等區域即可。
當堆中已經使用的記憶體和未使用的記憶體互相交錯時,指針碰撞的方式就行不通了,這時就需要采用空閑清單的方式。虛拟機會維護一個空閑的清單,用于記錄哪些記憶體是可以進行配置設定的,配置設定時直接從可用記憶體中直接配置設定即可。
堆中的記憶體是否工整是有垃圾收集器來決定的,如果帶有壓縮功能的垃圾收集器就是采用指針碰撞的方式來進行記憶體配置設定的。
配置設定記憶體時也會出現并發問題:
這樣可以在建立對象的時候使用 <code>CAS</code> 這樣的樂觀鎖來保證。
也可以将記憶體配置設定安排在每個線程獨有的空間進行,每個線程首先在堆記憶體中配置設定一小塊記憶體,稱為本地配置設定緩存(<code>TLAB : Thread Local Allocation Buffer</code>)。
配置設定記憶體時,隻需要在自己的配置設定緩存中配置設定即可,由于這個記憶體區域是線程私有的,是以不會出現并發問題。
可以使用 <code>-XX:+/-UseTLAB</code> 參數來設定 JVM 是否開啟 <code>TLAB</code> 。
記憶體配置設定之後需要對該對象進行設定,如對象頭。對象頭的一些應用可以檢視 Synchronize 關鍵字原理。
一個對象被建立之後自然是為了使用,在 Java 中是通過棧來引用堆記憶體中的對象來進行操作的。
對于我們常用的 <code>HotSpot</code> 虛拟機來說,這樣引用關系是通過直接指針來關聯的。
如圖:
這樣的好處就是:在 Java 裡進行頻繁的對象通路可以提升通路速度(相對于使用句柄池來說)。
簡單的來說對象都是在堆記憶體中配置設定的,往細一點看則是優先在 <code>Eden</code> 區配置設定。
這裡就涉及到堆記憶體的劃分了,為了友善垃圾回收,JVM 将對記憶體分為新生代和老年代。
而新生代中又會劃分為 <code>Eden</code> 區,<code>from Survivor、to Survivor</code> 區。
其中 <code>Eden</code> 和 <code>Survivor</code> 區的比例預設是 <code>8:1:1</code>,當然也支援參數調整 <code>-XX:SurvivorRatio=8</code>。
當在 <code>Eden</code> 區配置設定記憶體不足時,則會發生 <code>minorGC</code> ,由于 <code>Java</code> 對象多數是朝生夕滅的特性,是以 <code>minorGC</code> 通常會比較頻繁,效率也比較高。
當發生 <code>minorGC</code> 時,JVM 會根據複制算法将存活的對象拷貝到另一個未使用的 <code>Survivor</code> 區,如果 <code>Survivor</code> 區記憶體不足時,則會使用配置設定擔保政策将對象移動到老年代中。
談到 <code>minorGC</code> 時,就不得不提到 <code>fullGC(majorGC)</code> ,這是指發生在老年代的 <code>GC</code> ,不論是效率還是速度都比 <code>minorGC</code> 慢的多,回收時還會發生 <code>stop the world</code> 使程式發生停頓,是以應當盡量避免發生 <code>fullGC</code> 。
也有一些情況會導緻對象直接在老年代配置設定,比如當配置設定一個大對象時(大的數組,很長的字元串),由于 <code>Eden</code> 區沒有足夠大的連續空間來配置設定時,會導緻提前觸發一次 <code>GC</code>,是以盡量别頻繁的建立大對象。
是以 <code>JVM</code> 會根據一個門檻值來判斷大于該門檻值對象直接配置設定到老年代,這樣可以避免在新生代頻繁的發生 <code>GC</code>。
對于一些在新生代的老對象 <code>JVM</code> 也會根據某種機制移動到老年代中。
JVM 是根據記錄對象年齡的方式來判斷該對象是否應該移動到老年代,根據新生代的複制算法,當一個對象被移動到 <code>Survivor</code> 區之後 JVM 就給該對象的年齡記為1,每當熬過一次 <code>minorGC</code> 後對象的年齡就 +1 ,直到達到門檻值(預設為15)就移動到老年代中。
可以使用 <code>-XX:MaxTenuringThreshold=15</code> 來配置這個門檻值。
雖說這些内容略顯枯燥,但當應用發生不正常的 <code>GC</code> 時,可以友善更快的定位問題。
最近在總結一些 Java 相關的知識點,感興趣的朋友可以一起維護。
位址: https://github.com/crossoverJie/Java-Interview
作者:
crossoverJie
出處:
https://crossoverjie.top

歡迎關注部落客公衆号與我交流。
本文版權歸作者所有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出,
如有問題, 可郵件(crossoverJie#gmail.com)咨詢。