首先針對垃圾收集提出的兩個問題肘迎?
- 什么時候回收?
- 怎么回收?
針對問題1妓布,為了回答什么時候回收這個問題窿侈,就需要清楚處于怎樣狀態(tài)下的對象才需要回收。
處于怎樣狀態(tài)下的對象才需要回收秋茫?在強引用下史简,只有當(dāng)對象失去所有引用的時候,才要對其進(jìn)行回收肛著。
那么如何判斷對象處于是否引用的狀態(tài)呢圆兵?
目前有兩種主流辦法:
- 引用計數(shù)法
引用計數(shù)法是指每個對象都有一個引用計數(shù)器,每當(dāng)該對象被引用枢贿,計數(shù)器加1殉农,失去一個引用,計數(shù)器減1局荚。如果該對象的計數(shù)器值為0時超凳,則說明該對象無任何引用。
該方法的優(yōu)勢:判定效率高
劣勢:解決不了"循環(huán)引用"問題
那么耀态,何為循環(huán)引用呢轮傍?
舉個例子:
//jack所引用的對象引用計數(shù)加1,reference = 1
Student jack = new Student();
//lucy所引用的對象引用計數(shù)加1,reference = 1
Student lucy = new Student();
//jack.goodFriend所引用的對象(即為jack所引用的對象)引用計數(shù)加1,reference = 2
jack.goodFriend = lucy;
//jack.goodFriend所引用的對象(即為lucy所引用的對象)引用計數(shù)加1,reference = 2
lucy.goodFriend = jack;
//jack.goodFriend所引用的對象(即為jack所引用的對象)引用計數(shù)減1,reference = 1
jack = null;
//jack.goodFriend所引用的對象(即為lucy所引用的對象)引用計數(shù)減1,reference = 1
lucy = null;
** 在該例子中,雖然兩個對象還都有引用計數(shù)首装,但是經(jīng)過 jack = null
和 lucy = null
之后创夜,都無法再訪問這兩個對象了。所以由于這個劣勢仙逻,在java中并沒有使用到它驰吓,相反,使用的下面這種垃圾收集搜索算法 **
- 根搜索算法
根搜索算法是指:通過一系列名為"GC Roots"的對象作為起始點系奉,從這些節(jié)點起開始向下搜索檬贰,搜索所走過的路徑成為引用鏈(Reference Chain),當(dāng)一個對象到GC Roots沒有任何引用鏈相連缺亮,即該對象到GC Roots不可達(dá)時翁涤,則此對象已經(jīng)失去所有引用,已不可用了瞬内。
那么迷雪,哪些對象可以充當(dāng)GC Roots對象呢?(為什么呢虫蝶?)
- 虛擬機(jī)棧(棧幀中的本地變量表)中的引用的對象
- 方法區(qū)中的類靜態(tài)屬性引用的對象
- 方法區(qū)中的常量引用的對象
- 本地方法棧中JNI(Native方法)的引用的對象
擴(kuò)展章咧,"引用"這個詞語,在Java里面的意思可有很多呢能真,光我知道就至少有四個赁严,它們分別是:
- 強引用(Strong References)
對于強引用扰柠,則是我們經(jīng)常在程序里面new一個對象,即 Boy boy = new Boy()
,只要boy不置為null,則我們new的這個對象就會一直存在,垃圾收集器這家伙就不敢拿它怎么辦疼约。
- 軟引用(Soft References)
對于軟引用的聲明 SoftReference<T> softReference = new SoftReference<T>(Object obj);
具體例子: SoftReference<T> softReference = new SoftReference<T>(new Boy());
這個時候該Boy()實例就持有一個強引用boy和一個軟引用softReference卤档。
那么該軟引用softReference有什么用呢?
當(dāng)boy = null
的時候程剥,此時可以通過softReference.get()方法來重新獲得一個Boy的強引用劝枣。那么我就在想了,為什么要存在這個軟引用呢织鲸?我再用一個強引用指向Boy不也可以嗎舔腾?
先來看一下軟引用的特點:
該軟引用的確可以重新獲得一個該對象的強引用,而且該軟引用指向的對象也不會被垃圾收集器給收集搂擦,但是一旦JVM發(fā)現(xiàn)內(nèi)存不夠稳诚,那接下來就要對這些軟引用開刀了---對它們所引用的對象進(jìn)行收集,以獲得所需要的內(nèi)存瀑踢。而一旦垃圾收集結(jié)束扳还,該softReference.get()方法返回的便是null了。所以橱夭,軟引用這種引用可以幫助我們再次獲得強引用氨距,但是它也有可能會被清理掉。
那么這種特點的意義何在徘钥?
軟引用指向一個對象衔蹲,一塊內(nèi)存,但是該對象我們有可能會使用到它呈础,所以我們需要一個get()方法來立即獲得該對象的一個強引用,但是也可能不會使用到它橱健,可這樣的話而钞,這個對象又占著一塊內(nèi)存資源,所以我認(rèn)為在這里JVM非常巧妙地采取了一種折中辦法拘荡,在內(nèi)存不夠,OutOfMemory的時候臼节,就要把這個對象給回收掉,空出多余的內(nèi)存供系統(tǒng)正常使用珊皿。實在是妙呀网缝!那么在什么應(yīng)用場景下會使用到軟引用呢?通過對軟引用的了解蟋定,我認(rèn)為在對數(shù)據(jù)粉臊、資源進(jìn)行緩存的時候需要用到,有些非必須資源我們可以用一個軟引用持有驶兜,當(dāng)還沒被回收掉的時候扼仲,可以提升應(yīng)用程序的性能远寸,而當(dāng)內(nèi)存不夠,需要回收的時候屠凶,那就給回收掉驰后,也沒什么太大的損失。
- 弱引用(Weak References)
弱引用是什么矗愧?
聲明一個弱引用WeakReference<T> weakWidget = new WeakReference<T>(Object obj)
舉個例子:WeakReference<T> weakReference = new WeakReference<T>(new Boy());
這個時候weakReference就是作為一個指向Boy對象的弱引用灶芝。
那么這個弱引用有什么特點呢?
在JVM中唉韭,如果一個對象被一個弱引用所指向夜涕,那么該對象首先會在第一次垃圾收集周期被標(biāo)記(沒有任何條件,直接就會被標(biāo)記)纽哥,然后在第二次垃圾收集周期被回收掉钠乏。
不過在JAVA中提供了一個WeakHashMap()類,根據(jù)名字就可以得知這個類的一些基本用法了春塌。WeakHashMap()類中的key為弱引用類型晓避,value則為實例對象。當(dāng)key所引用的對象被清理掉之后只壳,該WeakHashMap()則會自動調(diào)用remove()方法來將對應(yīng)的一組key-value給刪除掉俏拱。
那么弱引用的這種特點有什么作用嗎?
在學(xué)習(xí)這個弱引用的時候吼句,查閱了許多的英文資料锅必,不同的資料描述不一樣,但是基本上說的還都是同一個東西惕艳。于此同時又對比了一下軟引用搞隐,個人認(rèn)為弱引用和它的功能比較類似,也是作為數(shù)據(jù)远搪、資源緩存的一個很好的API劣纲,但之所以弱引用中有一個 "弱"字,就是因為該類型的引用不需要任何條件谁鳍,直接就會被標(biāo)記為垃圾癞季,然后接下來一步就會被清理掉。所以在清理之前倘潜,我們可以通過 weakReference.get()來再次獲得一個該對象的引用绷柒,等到清理掉之后,返回的就是null涮因。
4.虛引用(Phantom References)
虛引用可以理解為該引用指向了一個已經(jīng)調(diào)用過一次finalize()方法的對象废睦,那么再下一次垃圾收集的時候,就果斷將該對象給回收掉蕊退。虛引用是引用中最弱最弱的一種郊楣,以至于調(diào)用get()方法返回值始終都是null
憔恳。
虛引用的清除過程大概是怎樣的呢?
Java提供了一個ReferenceQueue類净蚤,即為引用隊列類钥组,JVM會將該虛引用入隊到該ReferenceQueue,等到出隊的時候今瀑,也就是對象回收的時候程梦,與此同時,也會給系統(tǒng)發(fā)送一個信號橘荠,表示該對象已被回收屿附。
所以根據(jù)"對象被回收要接受到信號"這個特性,我們便先人一步知道了該對象被回收的時間哥童,這個時候我們可以做一些后續(xù)的操作挺份。
好了,總結(jié)完了對象何時會被回收之后贮懈,接下來要看看對象是如何被回收的匀泊。提到"如何"二字,如果用編程的思想來考慮的話朵你,就是設(shè)計算法的問題了各聘。
所以在"如何回收對象"這個問題上,JVM給我們提供了四種方法來解決抡医。
- 標(biāo)記-清除(Mark-Sweep)算法
簡述一下該算法:該算法分為兩個階段:標(biāo)記
和 清除
階段躲因。
在標(biāo)記階段,JVM所要做的事情有忌傻,給待回收的對象做上標(biāo)記大脉。
在清除階段,JVM則會命令垃圾收集器在一次垃圾回收的時候?qū)σ呀?jīng)被標(biāo)記的對象進(jìn)行大清理水孩。
但是箱靴,該算法存在怎樣的問題呢?
會有內(nèi)存碎片的產(chǎn)生
那么產(chǎn)生內(nèi)存碎片有什么危害嗎荷愕?
內(nèi)存碎片一旦產(chǎn)生,就意味著我們的一部分內(nèi)存就被分割成一塊一塊較小的內(nèi)存了棍矛,這樣每當(dāng)有占有內(nèi)存較大的對象要來分配的話安疗,我們沒有足夠的內(nèi)存來提供,但是這個對象又不可能不給人家分配內(nèi)分對不對够委?所以JVM就不得不再次把垃圾收集器給叫過來荐类,說:"看吧,都說了不建議你用這種 標(biāo)記-清除方式 方法來干活茁帽,你就是不聽玉罐,看看現(xiàn)在麻煩來了吧屈嗤?你趕緊再去收集一次垃圾吧,抓緊騰出一塊地方給剛剛那個新來的客人吊输,人家是客饶号,我們可惹不起"。垃圾收集器受到老大的這般訓(xùn)斥后季蚂,就趕緊屁顛屁顛地跑過去干活了茫船。
- 標(biāo)記-整理(Mark-Compact)算法
簡述一下該算法:該算法與上一次算法的不同之處就在于,當(dāng)每個待回收的對象被做上標(biāo)記之后扭屁,垃圾收集器先不著急把它們一個個地給回收掉算谈,而且先粗中有細(xì)地先把每個對象進(jìn)行一個整理,怎么整理呢料滥?將被標(biāo)記的對象從第一個到最后一個依次有序地重新排列在內(nèi)存的一端然眼,然后再給一鍋端了,這樣做的好處與第一個算法相比葵腹,好處自然是大大的高每,為什么呢?因為不會產(chǎn)生內(nèi)存碎片呀礁蔗!
注意:該算法一般用在老年代內(nèi)存區(qū)
- 復(fù)制(Copying)算法
簡述一下該算法:"復(fù)制"二字觉义,我們可以大概猜測這種算法可能是要復(fù)制一塊內(nèi)存吧?沒錯浴井,準(zhǔn)確地說晒骇,這種算法它會將內(nèi)存分為均等的兩份cake1和cake2,每份一模一樣磺浙,不多也不少洪囤。然后在為對象分配內(nèi)存的時候,會先在cake1上分配撕氧。最后當(dāng)cake1上的內(nèi)存被用光瘤缩,要用到cake2內(nèi)存的時候,就會先去cake1上檢測哪些對象是可回收的伦泥,哪些是不可回收的剥啤。對于暫時還不可回收的對象,我們就直接將其依次有序地復(fù)制到cake2上不脯,對于那些可回收的對象府怯,就果斷讓垃圾收集器過來把它們統(tǒng)統(tǒng)給趕走。這樣一來防楷,我們的cake1就又完全變成一塊嶄新等待開發(fā)的內(nèi)存了牺丙。這樣每當(dāng)再次需要為對象分配內(nèi)存的時候,就在cake2上進(jìn)行复局,接下來的過程就像第一次一樣冲簿,循環(huán)交互粟判,協(xié)同工作。
這種算法的優(yōu)點:很明顯峦剔,這種算法也不會產(chǎn)生內(nèi)存碎片(其實只要不是隨意地對對象進(jìn)行回收档礁,回收之前或者之后稍微做一些處理,都不會產(chǎn)生內(nèi)存碎片的)羊异,而且實現(xiàn)簡單事秀,運行高效。
不過上面的那一種算法只是剛誕生時候的設(shè)計野舶,它將內(nèi)存按照1:1的比例來分配易迹,這樣有時候會造成50%的內(nèi)存浪費,這對于程序員來說真得很讓人痛心平道,那么這種算法有沒有什么改進(jìn)呢睹欲?
引用一段來自周志明先生所著的《深入理解Java虛擬機(jī)——JVM高級特性與最佳實踐(第2版)》的原話:
IBM的專門研究表明,新生代中的對象98%是朝生夕死的一屋,所以并不需要按照1:1的比例來劃分內(nèi)存空間窘疮。而是將內(nèi)存分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次需要為對象分配內(nèi)存的時候就現(xiàn)在Eden和其中的一塊Survivor冀墨。當(dāng)回收時闸衫,將Eden和Survivor中還存活著的對象一次性拷貝到另外一個Survivor空間上,最后清理掉Eden和剛才用過的Survivor的空間诽嘉。HotSpot虛擬機(jī)默認(rèn)Eden和Survivor的大小比例是8:1蔚出,也就是每次新生代中可用內(nèi)存空間為整個新生代容量的90%,只有10%的內(nèi)存是會被"浪費"的虫腋。
從這段話中我知道了原來該算法將內(nèi)存劃分比例從1:1調(diào)整到了8:1骄酗,其中劃分了三個區(qū)域:Eden和兩個Survivor,然后內(nèi)存分配首先在Eden和一塊Surivor上(也就是我的那個cake1)悦冀,然后
當(dāng)一次垃圾收集到來的時候趋翻,會根據(jù)上邊復(fù)制算法描述的那樣,該轉(zhuǎn)移的轉(zhuǎn)移盒蟆,該清除的清除踏烙。不過轉(zhuǎn)移的內(nèi)存是第二塊Survivor區(qū)域。
看完這本書里面的這段描述之后历等,覺得豁然開朗宙帝。但是隨著思維的慣性又思考下去,發(fā)現(xiàn)遇到了一個問題:如果第二塊Survivor的內(nèi)存不夠存儲轉(zhuǎn)移的對象了該怎么辦募闲?就不存儲內(nèi)存了嗎?或者會發(fā)生內(nèi)存溢出嗎愿待?又接著看下去發(fā)現(xiàn)原來書中對我這個疑惑給予了一定的解釋浩螺,他是這樣說的
當(dāng)然靴患,98%的對象可回收只是一般場景下的數(shù)據(jù),我們沒有辦法保證每次回收都只有不多余10%的對象存活要出,當(dāng)Survivor空間不夠用時鸳君,需要依賴其他內(nèi)存(這里指老年代)進(jìn)行分配擔(dān)保(Handle Promotion)。
有了這段話的解釋患蹂,就多多少少解決了一些我的疑惑或颊。不過又發(fā)現(xiàn)這兩段引用中,有兩個詞不是太理解传于,一個叫"新生代"囱挑,一個叫"老年代"。這兩個**代指的又是什么呢沼溜?
4.分代收集(Generational Collection)算法
簡述一下分代收集算法:"分代"是指根據(jù)對象的存活周期的不同把內(nèi)存劃分為幾塊平挑,一般是把java堆分為新生代和老年代。哈哈系草,這里終于提到了"新生代"和"老年代"啦通熄!那么,它倆具體指什么呢找都?
看一下這本書對其的簡單介紹:
在新生代中唇辨,每次垃圾收集時都會有大批對象死去,只有少量存活能耻。那就選用復(fù)制算法赏枚,只需要付出少量存活對象的復(fù)制成本就可以完成收集。而老年代中因為對象存活率高嚎京,沒有額外空間對它進(jìn)行分配擔(dān)保嗡贺,就必須使用"標(biāo)記-清理"或"標(biāo)記-整理"算法來進(jìn)行回收。
真好鞍帝,不光介紹了這兩個代诫睬,而且把我剛剛學(xué)過的那些算法也用在相應(yīng)了代上了。根據(jù)復(fù)制算法的特點我知道了何時選擇它帕涌,當(dāng)大部分對象處于"朝生夕死"摄凡,使用次數(shù)不多的時候,這種算法就突顯出了它的優(yōu)勢:內(nèi)存浪費少蚓曼,不會產(chǎn)生內(nèi)存碎片亲澡,并且實現(xiàn)簡單,運行高效纫版。而當(dāng)對象存活率高的時候床绪,就用那兩個標(biāo)記算法。
垃圾收集器
前邊學(xué)習(xí)了垃圾收集都有哪些算法,那么接下來就要來了解一下運行這些垃圾收集算法的東西癞己,那就是垃圾收集器膀斋。
垃圾收集器的分類
Serial收集器
Serial收集器是最基本、歷史最悠久的收集器痹雅,在JDK1.3.1之前是虛擬機(jī)新生代收集的唯一選擇仰担。
特點:單線程,簡單高效(因為沒有線程交互所帶來的系統(tǒng)開銷)绩社,進(jìn)行垃圾收集時需要暫停其他所有線程(stop the world)摔蓝,所以就會有一定的卡頓現(xiàn)象。
應(yīng)用:虛擬機(jī)在Clinet模式下的默認(rèn)新生代收集器愉耙。
ParNew收集器
ParNew收集器是Serial收集器的多線程版本贮尉。
特點:多線程,速度相對較慢(因為有線程交互所帶來的系統(tǒng)開銷)劲阎,進(jìn)行垃圾收集時需要暫停其他所有進(jìn)程
應(yīng)用:虛擬機(jī)在Server模式下首選的新生代收集器绘盟,不過為什么呢?目前除了Serial收集器外悯仙,目前只有它能與CMS收集器配合工作龄毡。
Parallel Scavenge收集器
Parallel Scavenge收集器是一個多線程的新生代收集器,使用復(fù)制算法锡垄。
特點:多線程沦零,吞吐量優(yōu)先
所謂吞吐量是指:CPU運行用戶代碼的時間與CPU總消耗時間的比值,即吞吐量 = 運行用戶代碼時間 / (運行用戶代碼時間 + 垃圾收集時間)
Serial Old收集器
Serial Old收集器是Serial收集器的單線程老年代版本货岭,使用"標(biāo)記-整理"算法
特點:適用于老年代路操,單線程
應(yīng)用:
虛擬機(jī)在Client模式下使用該收集器;
在Server模式下千贯,在JDK1.5及之前的版本中與Parallel Scavenge收集器搭配使用作為CMD收集器的后備預(yù)案屯仗,在并發(fā)收集發(fā)生Concurrent Mode Failure的時候使用。
Parallel Old收集器
Parallel Old收集器是Parallel Scavenge收集器的老年代版本搔谴,使用多線程和"標(biāo)記-整理"算法紊遵。
特點:適用于老年代夯秃,多線程
應(yīng)用:在注重吞吐量及CPU資源敏感的場合难咕,優(yōu)先考慮Parallel Scavenge收集器和Parallel Old收集器
CMS收集器
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標(biāo)的收集器回怜,基于"標(biāo)記-清除"算法。
收集過程:
1.初始標(biāo)記(CMS initial mark)
特點:單線程芜果,stop the world
作用:僅僅是標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對象鞠呈,速度很快
2.并發(fā)標(biāo)記(CMS concurrent mark)
特點:單線程,與其他線程并發(fā)運行
3.重新標(biāo)記(CMS remark)
特點:多線程右钾,stop the world
作用:修正并發(fā)標(biāo)記期間蚁吝,因用戶程序繼續(xù)運行而導(dǎo)致標(biāo)記產(chǎn)生變動的那一部分對象的標(biāo)記記錄旱爆。
4.并發(fā)清除(CMS concurrent sweep)
特點:單線程,與其他線程并發(fā)運行
應(yīng)用:服務(wù)端
很明顯的缺點:
1.對CPU資源敏感灭将。CMS默認(rèn)啟動的回收線程(CPU數(shù)量 + 3) / 4 疼鸟,當(dāng)CPU >= 4的時候,并發(fā)回收時垃圾收集線程最多占用不超過25%的CPU資源庙曙,但是當(dāng)CPU < 4 的時候,CMS對用戶程序的影響就可能變得很大浩淘。
2.CMS無法處理"浮動垃圾"(Floating Garbage)捌朴,可能出現(xiàn)"Concurrent Mode Failure"失敗而導(dǎo)致另一次Full GC的產(chǎn)生。 什么是"浮動垃圾"呢张抄?在CMS結(jié)束標(biāo)記之后砂蔽,有一部分對象也成可以被清理的垃圾了,可CMS無法在本次的垃圾處理過程中回收掉它們署惯,所以又動態(tài)產(chǎn)生的這部分垃圾叫做"浮動垃圾"左驾。在CMS運行的同時,也有用戶線程在運行极谊,所以就需要預(yù)留夠足夠的內(nèi)存空間給用戶線程诡右,而當(dāng)CMS不能保證這一點的時候,就會出現(xiàn)"Concurrent Mode Failure"這種錯誤轻猖。
3.CMS基于的"標(biāo)記-清除"算法會產(chǎn)生內(nèi)存碎片帆吻。(不過CMS較好地解決了這種問題,解決的辦法便是在經(jīng)過一次的CMS垃圾處理過程服務(wù)之后咙边,還會再送一個碎片整理服務(wù))
G1收集器
G1收集器是當(dāng)前收集器技術(shù)發(fā)展的最前沿成果猜煮,基于"標(biāo)記-整理"算法,
特點:能夠精確地控制停頓败许,可以實現(xiàn)在基本不犧牲吞吐量的前提下完成低停頓的內(nèi)存回收王带。
那么,為什么有以上優(yōu)點呢市殷?引用一段來自《深入理解Java虛擬機(jī)-JVM高級特性與最佳實踐》
G1收集器極力地避免全區(qū)域的垃圾收集愕撰,之前的收集器進(jìn)行收集的范圍都是整個新生代或老年代,而G1將整個JAVA堆(包括新生代被丧、老年代)劃分為多個大小固定的獨立區(qū)域(Region)盟戏,并且跟蹤這些區(qū)域里面的垃圾堆積程度,在后臺維護(hù)一個優(yōu)先列表甥桂,每次根據(jù)允許的收集時間柿究,優(yōu)先回收垃圾最多的區(qū)域(Garbage First名字的由來)。這樣一來黄选,區(qū)域劃分以及有優(yōu)先級的區(qū)域回收蝇摸,保證了G1收集器在有限的時間內(nèi)可以獲得最高的收集效率
內(nèi)存分配與回收策略
1.對象優(yōu)先在Eden分配
大多數(shù)情況下婶肩,對象在新生代Eden區(qū)中分配。當(dāng)Eden區(qū)沒有足夠的空間進(jìn)行分配時貌夕,虛擬機(jī)將發(fā)起一次Minor Gc(新生代垃圾收集律歼,復(fù)制算法)。
2.大對象直接進(jìn)入老年代
1.什么是大對象啡专?
需要大量連續(xù)內(nèi)存空間的Java對象(很長的字符串和數(shù)組)
那么险毁,這樣的大對象為什么要直接進(jìn)入老年代呢?
因為經(jīng)常出現(xiàn)大對象容易導(dǎo)致內(nèi)存還有不少空間時就提前觸發(fā)垃圾收集以獲取足夠的連續(xù)空間來"安置"它們们童。所以畔况,虛擬機(jī)提供了一個-XX:PretenureSizeThreshold參數(shù),如果所需內(nèi)存值超過該參數(shù)值慧库,就直接在老年代中分配跷跪,這樣就直接避免了新生代區(qū)頻繁地進(jìn)行GC操作了。
3.長期存活的對象將進(jìn)入老年代
如何衡量一個對象的存活時間呢齐板?
JVM為每個對象設(shè)置了一個對象年齡計數(shù)器吵瞻,每一次進(jìn)行分代收集之后,如果位于新生代的對象還沒有被收集的話甘磨,該年齡計數(shù)器加1橡羞,如果該值超過一個閥值(默認(rèn)為15歲),則該對象會被調(diào)入到老年代中去享缚淼担咯尉姨!
4.動態(tài)對象年齡判定
這是另一種可以進(jìn)入老年代的途徑:如果在Survivor空間中相同年齡所有對象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對象就可以直接進(jìn)入老年代吗冤,而無須等待到當(dāng)初設(shè)定的那個閥值又厉。
5.空間分配擔(dān)保
JVM將內(nèi)存分為新生代和老年代,在新生代又分為一個Eden區(qū)和兩個Survivor區(qū)域椎瘟,在進(jìn)行垃圾收集的時候覆致,第二塊Survivor區(qū)域用于存儲還存活的對象,但是有可能會存在所有存活對象所占內(nèi)存過多肺蔚,導(dǎo)致Survivor區(qū)域不夠用煌妈,這個時候就要向老年代區(qū)域申請擔(dān)保,把多余的對象放在老年區(qū)宣羊。不過此時需要有一個對老年區(qū)是否也能存放得下這些對象的一個評估璧诵,那就是根據(jù)之前每次晉升到老年代的平均大小是否大于老年代的剩余空間大小,
如果大于的話仇冯,就意味著此次的對象有很大的可能性是晉升不到老年代區(qū)的之宿,意思就是老年代內(nèi)存有很大可能是不夠用的,那么該怎么做呢苛坚?只能把老年代區(qū)進(jìn)行一次Full GC 來騰出一些空間了比被。
但是如果平均大小小于剩余空間的話色难,那就意味著有很大可能性是能夠晉升的,那么就趕緊把這些對象給放進(jìn)老年代區(qū)嗎等缀?等等枷莉!這里還有一個HandlePromotionFailure設(shè)置選項,該選項的意思是是否允許擔(dān)保失敗(這里是有可能失敗的)尺迂。如果允許笤妙,一旦老年代區(qū)放不下,那就立馬在新生代區(qū)執(zhí)行MinorGC垃圾收集過程噪裕。如果不允許的話危喉,一旦老年代放不下,那就要在老年代立馬進(jìn)行一次Full GC垃圾收集了州疾。這樣,無論哪種情況發(fā)生皇拣,我們要么在新生代進(jìn)行MinorGC或者在老年代進(jìn)行FullGC严蓖,這樣總能盡最大可能來為對象分配內(nèi)存空間。
參考資料
書籍:《深入理解Java虛擬機(jī)-JVM高級特性與最佳實踐》周志強