當 JVM
收到一個 new
指令時,會檢查指令中的參數在常量池是否有這個符號的引用,還會檢查該類是否已經被加載過了,若是沒有的話則要進行一次類加載。git
接着就是分配內存了,一般有兩種方式:github
使用指針碰撞的前提是堆內存是徹底工整的,用過的內存和沒用的內存各在一邊每次分配的時候只須要將指針向空閒內存一方移動一段和內存大小相等區域便可。算法
當堆中已經使用的內存和未使用的內存互相交錯時,指針碰撞的方式就行不通了,這時就須要採用空閒列表的方式。虛擬機會維護一個空閒的列表,用於記錄哪些內存是能夠進行分配的,分配時直接從可用內存中直接分配便可。數組
堆中的內存是否工整是有垃圾收集器來決定的,若是帶有壓縮功能的垃圾收集器就是採用指針碰撞的方式來進行內存分配的。緩存
分配內存時也會出現併發問題:併發
這樣能夠在建立對象的時候使用 CAS
這樣的樂觀鎖來保證。線程
也能夠將內存分配安排在每一個線程獨有的空間進行,每一個線程首先在堆內存中分配一小塊內存,稱爲本地分配緩存(TLAB : Thread Local Allocation Buffer
)。指針
分配內存時,只須要在本身的分配緩存中分配便可,因爲這個內存區域是線程私有的,因此不會出現併發問題。code
可使用 -XX:+/-UseTLAB
參數來設定 JVM 是否開啓 TLAB
。對象
內存分配以後須要對該對象進行設置,如對象頭。對象頭的一些應用能夠查看 Synchronize 關鍵字原理。
一個對象被建立以後天然是爲了使用,在 Java 中是經過棧來引用堆內存中的對象來進行操做的。
對於咱們經常使用的 HotSpot
虛擬機來講,這樣引用關係是經過直接指針來關聯的。
如圖:
這樣的好處就是:在 Java 裏進行頻繁的對象訪問能夠提高訪問速度(相對於使用句柄池來講)。
簡單的來講對象都是在堆內存中分配的,往細一點看則是優先在 Eden
區分配。
這裏就涉及到堆內存的劃分了,爲了方便垃圾回收,JVM 將對內存分爲新生代和老年代。
而新生代中又會劃分爲 Eden
區,from Survivor、to Survivor
區。
其中 Eden
和 Survivor
區的比例默認是 8:1:1
,固然也支持參數調整 -XX:SurvivorRatio=8
。
當在 Eden
區分配內存不足時,則會發生 minorGC
,因爲 Java
對象多數是朝生夕滅的特性,因此 minorGC
一般會比較頻繁,效率也比較高。
當發生 minorGC
時,JVM 會根據複製算法將存活的對象拷貝到另外一個未使用的 Survivor
區,若是 Survivor
區內存不足時,則會使用分配擔保策略將對象移動到老年代中。
談到 minorGC
時,就不得不提到 fullGC(majorGC)
,這是指發生在老年代的 GC
,不管是效率仍是速度都比 minorGC
慢的多,回收時還會發生 stop the world
使程序發生停頓,因此應當儘可能避免發生 fullGC
。
也有一些狀況會致使對象直接在老年代分配,好比當分配一個大對象時(大的數組,很長的字符串),因爲 Eden
區沒有足夠大的連續空間來分配時,會致使提早觸發一次 GC
,因此儘可能別頻繁的建立大對象。
所以 JVM
會根據一個閾值來判斷大於該閾值對象直接分配到老年代,這樣能夠避免在新生代頻繁的發生 GC
。
對於一些在新生代的老對象 JVM
也會根據某種機制移動到老年代中。
JVM 是根據記錄對象年齡的方式來判斷該對象是否應該移動到老年代,根據新生代的複製算法,當一個對象被移動到 Survivor
區以後 JVM 就給該對象的年齡記爲1,每當熬過一次 minorGC
後對象的年齡就 +1 ,直到達到閾值(默認爲15)就移動到老年代中。
可使用
-XX:MaxTenuringThreshold=15
來配置這個閾值。
雖然說這些內容略顯枯燥,但當應用發生不正常的 GC
時,能夠方便更快的定位問題。
最近在總結一些 Java 相關的知識點,感興趣的朋友能夠一塊兒維護。