話很少說,肝就完了。算法
今天聊聊對象內存分配和回收策略。數組
在以前的筆記中,已經講到過內存結構,對象在內存中的分配,以及垃圾回收算法及垃圾收集器的相關知識。本篇筆記記錄一下對象的如何在內存中進行分配以及如何從內存中回收。安全
在Java中,其相對於C語言來講,它不用人爲的去操做對象內存的釋放。直接由虛擬機自動解決了。而歸根結底,虛擬機解決的是兩個問題:給對象分配內存、回收分配給對象的內存。學習
在聊到虛擬機內存結構的時候,與對象關係最爲密切的就是堆空間,任何對象的建立都是在堆空間去分配內存的。並且對於新建立的對象,主要分配在新生代的Eden區上。若是啓動本地線程分配緩衝(TLAB),那麼將會按照線程優先在TLAB上分配。對於大對象,也可能直接被分配在老年代中。編碼
具體如何分配,其規則並非百分百固定的,主要取決於垃圾收集器的組合以及虛擬機與內存相關的參數配置。線程
在學習堆內存的時候,有說到整個對內存空間會按照對象的年齡進行分代,分爲新生代和老年代。這是主要關注的區域(固然還有元數據區)。日誌
對於新生對象,會優先分配在Eden區。能夠這麼理解,新生代的Eden區是大部分對象生命週期的開始。回顧一下在堆內存章節所學習到的對象在新生代的流傳過程。新生代分爲Eden區和Survivor區(Survivor From和Survivor To)。對象
須要注意的是,在新生代中,存在一種叫作分配擔保機制。什麼叫作分配擔保機制呢。官方點的描述就是,當觸發MinorGC時,存活的對象會轉移到另一個Survivor區,當這個區域沒法保存還存活的對象,那麼就會觸發分配擔保機制。直接將對象複製到老年代中。通俗點說,銀行放貸,你做爲擔保人(老年代),當實際還款人(新生代)換不起貸款的時候(對象所需的內存空間),將直接由擔保人來進行還款(老年代的空間用來存對象)。對,大概就是這麼個意思。生命週期
雖然前面說到,新生的對象都會分配在Eden區,可是實際上新生代和老年代的內存比大體爲1:2,能夠模糊點理解,就是說老年代的空間大小是新生代空間大小的兩倍。內存
什麼樣的對象又會被定義爲大對象呢,一般是指須要大量連續內存來存儲的對象。例如超長的字符串和數組。可是有一點要明確的是,老年代若是被大量大對象所佔據,會很容易觸發Full GC,因此,在實際編碼過程當中,應當避免大對象的出現。從而下降垃圾收集的概率。
在虛擬機中,存在一個**-XX:PretenureSizeThreshold**參數,讓大於這個設置值的對象直接分配至老年代,這樣就可以避免新生代中頻繁的發生在Eden區和Survivor區進行對象的內存複製。
在說到新生代時,MinorGC會觸發對象的複製(在Eden區和Survivor區)。每一次複製都會將對象的年齡加1。當對象的年齡到達必定的閥值時。就會被移動到老年代中。在虛擬機中,能夠經過**-XX:MaxTenuringThreshold**來設置這個閥值。
以上就是關於虛擬機在內存分配方面的舉措:新生對象分配在Eden區、大對象直接進入老年代、長期存活的對象進入老年代。
在虛擬機中,並非永遠的要求對象的年齡必須達到了MaxTenuringThreshold指定的年齡閥值才進入到老年代。當Survivor空間中,年齡相同的對象總內存大小超過了Survivor空間的一半的時候,就會被複制轉移到老年代,而無需達到MaxTenuringThreshold所指定的年齡。怎麼理解呢。假設當前Survivor空間爲100K,MaxTenuringThreshold設置的值爲15,存在兩個相同年齡的對象A、B,兩個對象的總大小達到了60K,年齡均爲5,那麼這兩個對象的總大小已經超過了Survivor區的一半,則均會被直接送入到老年代中。
因此,動態判斷對象年齡並非說對象非獲得了指定的年齡值纔會被移動到老年代,而是會根據內存實際狀況,動態的去判斷對象是否應該移動到老年代。
在前面說到新對象建立在Eden區時,提到過度配擔保機制(在發生MinorGC時,若Survivor區不足以來保存對象,則會將對象分配到老年代)。虛擬機在發生MinorGC以前,會先檢查老年代的最大可用連續內存空間是否大於新生代全部對象的總空間大小。若是成立,那麼MinorGC是能夠保證安全的。若是不成立,則會校驗HandlePromotionFailure參數設置的值是否容許擔失敗。若是容許,則會繼續檢查老年代最大可用連續內存是否大於以前晉升到老年代對象的平均大小,若大於,則嘗試進行一次MinorGC,若小於,或者說HandlePromotionFailure設置不容許擔保失敗,此時則進行一次FullGC。
在新生代中,採用的是複製算法,而複製算法每次都會空置其中一個Survivor區用來MinorGC時進行輪換。當大量的對象在MinorGC以後還繼續存活,則會被送入到老年代。而老年代要容納新生代中轉移過來的對象,前提是必須老年代有足夠的連續內存來容納這些對象。可是在內存回收以前,會有多少對象存活下來是個未知數,因此,每一次都會取上一次重新生代中晉升到老年代中的對象大小的平均值做爲經驗值來與老年代剩餘的空間進行比較,從而決定是否進行FullGC來進行垃圾回收來騰出空間。
後面會單獨記錄經過實際代碼和GC日誌分析來更好的理解上面的內容。
不怕路歹行不怕大雨淋,心上一字敢 面對個人夢,甘願來做憨人。 --<憨人>