JVM學習--對象內(nèi)存的分配策略
Java所承諾的自動內(nèi)存管理主要是針對對象內(nèi)存的回收和對象內(nèi)存的分配。
在Java虛擬機的五塊內(nèi)存空間中,程序計數(shù)器、Java虛擬機棧、本地方法棧內(nèi)存的分配和回收都具有確定性,一般在編譯階段就能確定需要分配的內(nèi)存大小,并且由于都是線程私有,因此它們的內(nèi)存空間都隨著線程的創(chuàng)建而創(chuàng)建,線程的結(jié)束而回收。也就是這三個區(qū)域的內(nèi)存分配和回收都具有確定性,垃圾回收器不需要在這里花費太大的精力。
而Java虛擬機中的方法區(qū)因為是用來存儲類信息、常量、靜態(tài)變量,這些數(shù)據(jù)的變動性較小,因此不是Java內(nèi)存管理重點需要關(guān)注的區(qū)域。
而對于堆,所有線程共享,所有的對象都需要在堆中創(chuàng)建和回收。雖然每個對象的大小在類加載的時候就能確定,但對象的數(shù)量只有在程序運行期間才能確定,因此堆中內(nèi)存的分配具有較大的不確定性。此外,對象的生命周期長短不一,因此需要針對不同生命周期的對象采用不同的內(nèi)存回收算法,增加了內(nèi)存回收的復雜性。
綜上所述:Java自動內(nèi)存管理最核心的功能是堆內(nèi)存中對象的分配與回收。
對象優(yōu)先在Eden區(qū)中分配
目前主流的垃圾收集器都會采用分代回收算法,因此需要將堆內(nèi)存分為新生代和老年代。
在新生代中為了防止內(nèi)存碎片問題,因此垃圾收集器一般都選用“復制”算法。因此,堆內(nèi)存的新生代被進一步分為:Eden區(qū)+Survior1區(qū)+Survior2區(qū)。
每次創(chuàng)建對象時,首先會在Eden區(qū)中分配。
若Eden區(qū)已滿,則在Survior1區(qū)中分配。
若Eden區(qū)+Survior1區(qū)剩余內(nèi)存太少,導致對象無法放入該區(qū)域時,就會啟用“分配擔保”,將當前Eden區(qū)+Survior1區(qū)中的對象轉(zhuǎn)移到老年代中,然后再將新對象存入Eden區(qū)。
大對象直接進入老年代
所謂“大對象”就是指一個占用大量連續(xù)存儲空間的對象,如數(shù)組。
當發(fā)現(xiàn)一個大對象在Eden區(qū)+Survior1區(qū)中存不下的時候就需要分配擔保機制把當前Eden區(qū)+Survior1區(qū)的所有對象都復制到老年代中去。
我們知道,一個大對象能夠存入Eden區(qū)+Survior1區(qū)的概率比較小,發(fā)生分配擔保的概率比較大,而分配擔保需要涉及到大量的復制,就會造成效率低下。
因此,對于大對象我們直接把他放到老年代中去,從而就能避免大量的復制操作。
那么,什么樣的對象才是“大對象”呢?
通過-XX:PretrnureSizeThreshold參數(shù)設(shè)置大對象
該參數(shù)用于設(shè)置大小超過該參數(shù)的對象被認為是“大對象”,直接進入老年代。
注意:該參數(shù)只對Serial和ParNew收集器有效。
生命周期較長的對象進入老年代
老年代用于存儲生命周期較長的對象,那么我們?nèi)绾闻袛嘁粋€對象的年齡呢?
新生代中的每個對象都有一個年齡計數(shù)器,當新生代發(fā)生一次MinorGC后,存活下來的對象的年齡就加一,當年齡超過一定值時,就將超過該值的所有對象轉(zhuǎn)移到老年代中去。
使用-XXMaxTenuringThreshold設(shè)置新生代的***年齡
設(shè)置該參數(shù)后,只要超過該參數(shù)的新生代對象都會被轉(zhuǎn)移到老年代中去。
相同年齡的對象內(nèi)存超過Survior內(nèi)存一半的對象進入老年代
如果當前新生代的Survior中,年齡相同的對象的內(nèi)存空間總和超過了Survior內(nèi)存空間的一半,那么所有年齡相同的對象和超過該年齡的對象都被轉(zhuǎn)移到老年代中去。無需等到對象的年齡超過MaxTenuringThreshold才被轉(zhuǎn)移到老年代中去。
“分配擔保”策略詳解
當垃圾收集器準備要在新生代發(fā)起一次MinorGC時,首先會檢查“老年代中***的連續(xù)空閑區(qū)域的大小 是否大于 新生代中所有對象的大?。?rdquo;,也就是老年代中目前能夠?qū)⑿律兴袑ο笕垦b下?
若老年代能夠裝下新生代中所有的對象,那么此時進行MinorGC沒有任何風險,然后就進行MinorGC。
若老年代無法裝下新生代中所有的對象,那么此時進行MinorGC是有風險的,垃圾收集器會進行一次預測:根據(jù)以往MinorGC過后存活對象的平均數(shù)來預測這次MinorGC后存活對象的平均數(shù)。
如果以往存活對象的平均數(shù)小于當前老年代***的連續(xù)空閑空間,那么就進行MinorGC,雖然此次MinorGC是有風險的。
如果以往存活對象的平均數(shù)大于當前老年代***的連續(xù)空閑空間,那么就對老年代進行一次Full GC,通過清除老年代中廢棄數(shù)據(jù)來擴大老年代空閑空間,以便給新生代作擔保。
這個過程就是分配擔保。
注意:
1. 分配擔保是老年代為新生代作擔保;
2. 新生代中使用“復制”算法實現(xiàn)垃圾回收,老年代中使用“標記-清除”或“標記-整理”算法實現(xiàn)垃圾回收,只有使用“復制”算法的區(qū)域才需要分配擔保,因此新生代需要分配擔保,而老年代不需要分配擔保。
名詞解釋:
GC:垃圾收集器
Minor GC:新生代GC,指發(fā)生在新生代的垃圾收集動作,所有的Minor GC都會觸發(fā)全世界的暫停(stop-the-world),停止應用程序的線程,不過這個過程非常短暫。
Major GC/Full GC:老年代GC,指發(fā)生在老年代的GC。
JVM:Java Virtual Machine(Java虛擬機)的縮寫。