1鬼店、JVM內(nèi)存區(qū)域
- 方法區(qū)
- 堆
- 虛擬機(jī)棧
- 本地方法棧
- 程序計(jì)數(shù)器
方法區(qū)
堆
??所有線程共享
所有對(duì)象實(shí)例及數(shù)組都需要在堆上分配(隨著棧上分配、標(biāo)量替換等师骗,也不那么絕對(duì)了)
??可以處于物理上不連續(xù)的內(nèi)存空間历等,當(dāng)堆中沒(méi)有內(nèi)存用于實(shí)例分配,堆也無(wú)法再擴(kuò)展時(shí)辟癌,拋出OutOfMemoryError異常寒屯。
虛擬機(jī)棧
??為虛擬機(jī)執(zhí)行Java方法(也就是字節(jié)碼)服務(wù)
??線程私有
- 如果線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的深度,將拋出StackOverflowError異常
- 如果虛擬機(jī)検蛏伲可以動(dòng)態(tài)擴(kuò)展寡夹,如果擴(kuò)展時(shí)無(wú)法申請(qǐng)到足夠的內(nèi)存处面,就會(huì)拋出OutOfMemoryError異常
本地方法棧
??為虛擬機(jī)執(zhí)行Native方法服務(wù)
??線程私有
- 如果線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的深度,將拋出StackOverflowError異常
- 如果虛擬機(jī)椧觯可以動(dòng)態(tài)擴(kuò)展鸳君,如果擴(kuò)展時(shí)無(wú)法申請(qǐng)到足夠的內(nèi)存,就會(huì)拋出OutOfMemoryError異常
程序計(jì)數(shù)器
??當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器
- 若線程正在執(zhí)行的是Java方法患蹂,則為虛擬機(jī)字節(jié)碼指令地址
- 若線程正在執(zhí)行的是Native方法,則為空
是唯一一個(gè)在Java虛擬機(jī)規(guī)范中沒(méi)有規(guī)定任何OutOfMemoryError情況的區(qū)域
2砸紊、對(duì)象的創(chuàng)建
- 指針碰撞
所有用過(guò)的內(nèi)存放在一邊传于,沒(méi)有用過(guò)的內(nèi)存放在另一邊。僅需要移動(dòng)中間的臨界點(diǎn)指針 - 空閑列表
通過(guò)列表記錄哪些內(nèi)存塊是可用的醉顽,在分配時(shí)從列表中找到足夠大的內(nèi)存
采用哪種方式由Java堆是否規(guī)整決定
Java堆是否規(guī)整由垃圾收集器是否帶壓縮整理功能決定
對(duì)象創(chuàng)建非常頻繁沼溜,存在并發(fā)問(wèn)題:
- 方案一:對(duì)分配內(nèi)存空間的動(dòng)作進(jìn)行同步處理:虛擬機(jī)采用了CAS配上失敗重試的方式保證更新操作的原子性
- 方案二:按照線程劃分不同的塊,每個(gè)線程在Java堆中預(yù)先分配一小塊內(nèi)存游添,成為本地線程分配緩沖(TLAB)
2系草、對(duì)象的訪問(wèn)
- 句柄
- 直接指針
內(nèi)存溢出:(out of memory)通俗理解就是內(nèi)存不夠,通常在運(yùn)行大型軟件或游戲時(shí)唆涝,軟件或游戲所需要的內(nèi)存遠(yuǎn)遠(yuǎn)超出了你主機(jī)內(nèi)安裝的內(nèi)存所承受大小找都,就叫內(nèi)存溢出。
內(nèi)存泄漏:(Memory Leak)是指程序中己動(dòng)態(tài)分配的堆內(nèi)存由于某種原因程序未釋放或無(wú)法釋放廊酣,造成系統(tǒng)內(nèi)存的浪費(fèi)能耻,導(dǎo)致程序運(yùn)行速度減慢甚至系統(tǒng)崩潰等嚴(yán)重后果
如果是建立過(guò)多線程導(dǎo)致的內(nèi)存溢出,在不能減少線程數(shù)或更換64位虛擬機(jī)的情況下亡驰,就只能通過(guò)減少最大堆和減少棧容量來(lái)?yè)Q取更多的線程晓猛。
3、 GC算法
程序計(jì)數(shù)器凡辱、虛擬機(jī)棧戒职、本地方法棧不需要過(guò)多考慮回收的問(wèn)題
僅需關(guān)注Java堆和方法區(qū)
3.1對(duì)象存活還是死亡
- 引用計(jì)數(shù)法
很難解決對(duì)象間相互循環(huán)引用的問(wèn)題 - 可達(dá)性分析算法
使用“GCRoots對(duì)象”作為起點(diǎn)。
可作為GC Roots對(duì)象:- 虛擬機(jī)棧(棧幀中的本地變量表)中引用的對(duì)象
- 方法區(qū)中類靜態(tài)屬性引用的對(duì)象
- 方法區(qū)中常量引用的對(duì)象
- 本地方法棧中JNI(即一般說(shuō)的Native方法)引用的變量
3.1.1引用類型
http://www.reibang.com/p/825cca41d962
- 強(qiáng)引用
只要強(qiáng)引用存在透乾,被引用對(duì)象永遠(yuǎn)不會(huì)被回收 - 軟引用 SoftReference
系統(tǒng)在發(fā)生OOM之前洪燥,將這些對(duì)象列進(jìn)回收范圍,如果回收后依然沒(méi)有足夠內(nèi)存续徽,才拋出OOM - 弱引用 WeakReference
對(duì)象僅會(huì)存活到下一次垃圾收集發(fā)生之前蚓曼。get方法可以訪問(wèn)被引用實(shí)例。 - 虛引用
對(duì)象僅會(huì)存活到下一次垃圾收集發(fā)生之前钦扭。get方法返回null纫版。為一個(gè)對(duì)象設(shè)置虛引用的唯一目的就是能在對(duì)象被回收時(shí)受到通知。
3.1.2對(duì)象死亡
對(duì)象真正死亡至少要經(jīng)歷兩次標(biāo)記過(guò)程
- 對(duì)象在可達(dá)性分析后沒(méi)有與GC Roots存在引用鏈客情,會(huì)被第一次標(biāo)記其弊。
- 若對(duì)象沒(méi)有覆蓋finalize()方法或finalize()方法已被虛擬機(jī)調(diào)用過(guò)癞己,被認(rèn)為沒(méi)有必要執(zhí)行finalize()方法。
- 其余均被認(rèn)為有必要執(zhí)行finalize()方法
2.有必要執(zhí)行finalize()方法的對(duì)象會(huì)被放入F-Queue隊(duì)列中梭伐,稍后被虛擬機(jī)建立的低優(yōu)先級(jí)的Finalize線程執(zhí)行痹雅。(虛擬機(jī)僅保證調(diào)用finalize方法,但不保證等待它執(zhí)行結(jié)束糊识,避免執(zhí)行緩慢绩社、死循環(huán)等)。
虛擬機(jī)會(huì)對(duì)F-Queue隊(duì)列中的對(duì)象進(jìn)行第二次標(biāo)記赂苗,如果對(duì)象在finalize方法中與引用鏈上的任何一個(gè)對(duì)象建立了關(guān)聯(lián)愉耙,則被移出待回收集合。
如果這之后對(duì)象仍然在待回收集合拌滋,則對(duì)象已死亡朴沿,等待垃圾回收。
任何對(duì)象的finalize方法僅會(huì)被調(diào)用一次败砂,之后再也不會(huì)被調(diào)用
方法區(qū)(永久代)的垃圾回收主要回收:廢棄常量+無(wú)用的類
廢棄常量與堆中的對(duì)象類似赌渣,沒(méi)有被引用則清出常量池
無(wú)用的類必須滿足:
- 該類所有的實(shí)例都已被回收,java堆中不存在該類的任何實(shí)例
- 加載該類的ClassLoader已經(jīng)被回收
- 該類對(duì)應(yīng)的java.lang.Class對(duì)象沒(méi)有在任何地方被引用昌犹,無(wú)法在任何地方通過(guò)反射訪問(wèn)該類的方法
滿足以上3個(gè)條件僅可以被回收坚芜,而不是必然被回收
4、 GC算法
4.1標(biāo)記-清除算法
標(biāo)記全部需回收對(duì)象祭隔,在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對(duì)象货岭。
- 效率問(wèn)題:
標(biāo)記和清除效率都不高 - 空間問(wèn)題:
產(chǎn)生大量不連續(xù)內(nèi)存碎片,導(dǎo)致分配大對(duì)象時(shí)不得不重新觸發(fā)GC
4.2復(fù)制算法(用于新生代)
將可用內(nèi)存分為大小相等的兩塊疾渴,每次只使用一塊千贯,GC時(shí)將存活的對(duì)象復(fù)制到另一塊。全部清除原來(lái)那半搞坝。
*空間問(wèn)題:
內(nèi)存浪費(fèi)搔谴,代價(jià)太高
由于新生代中98%的對(duì)象都是“朝生夕死”,并不需要1:1劃分內(nèi)存空間桩撮。
采用1塊較大的Eden空間和2塊較小的Survivor空間敦第。
每次使用1塊Eden和1塊Survivor空間,清理時(shí)將存活對(duì)象復(fù)制到1塊Survivor空間店量。全部清除原來(lái)的1塊Eden和1塊Survivor空間芜果。
當(dāng)Survivor空間不夠用時(shí),需要老年代進(jìn)行分配擔(dān)保融师。
4.3標(biāo)記-整理算法(用于老年代)
標(biāo)記全部需回收對(duì)象右钾,將所有存活對(duì)象向一端移動(dòng)。然后直接清理掉端邊界以外的內(nèi)存。
4.4分代收集算法
新生代采用復(fù)制算法舀射,老年代采用標(biāo)記-清除或標(biāo)記-整理算法窘茁。
5、 垃圾收集器
5.1Serial收集器
- 僅用單線程完成垃圾收集工作
- 進(jìn)行收集時(shí)脆烟,必須暫停其他所有工作線程
新生代單線程復(fù)制算法山林,暫停所有工作線程
老年代單線程標(biāo)記-整理算法,暫停所有工作線程
單CPU環(huán)境下邢羔,簡(jiǎn)單而高效
5.2 ParNew收集器
- Serial收集器的多線程版本
新生代多線程復(fù)制算法驼抹,暫停所有工作線程
老年代單線程標(biāo)記-整理算法,暫停所有工作線程
僅Serial和ParNew收集器可以和CMS配合工作
因?yàn)镻arallel Scavenge和G1都沒(méi)有采用傳統(tǒng)的GC收集器代碼框架
5.3 Parallel Scavenge收集器(新生代收集器)
新生代多線程復(fù)制算法
Parallel Scavenge收集器的目的是達(dá)到一個(gè)可控制的吞吐量拜鹤。(CPU運(yùn)行用戶代碼時(shí)間/總時(shí)間)
- 停頓時(shí)間短適合與用戶交互的程序
- 高吞吐量適合高效利用CPU砂蔽,適合后臺(tái)運(yùn)算而不需要太多交互的任務(wù)
可設(shè)置兩個(gè)參數(shù)控制吞吐量:
- 最大垃圾收集停頓時(shí)間
- 直接設(shè)置吞吐量大小
需注意,GC停頓時(shí)間犧牲了吞吐量和新生代空間署惯。
收集300M的新生代肯定比500M要快,但之前10秒收集一次每次100ms镣隶,現(xiàn)在5秒一次极谊,一次70ms,吞吐量也相應(yīng)下降安岂。
5.4 Serial Old收集器(老年代收集器)
Serial 收集器的老年代版本
老年代采用單線程標(biāo)記-整理算法轻猖,暫停所有工作線程給Client模式下的虛擬機(jī)使用
-
給Server模式下的虛擬機(jī)使用
- 與Parallel Scavenge收集器配合使用
- 作為CMS收集器的后備預(yù)案,在并發(fā)發(fā)生Concurrent Mode Failure時(shí)使用域那。
5.5 Parallel Old收集器(老年代收集器)
- Parallel Scavenge收集器的老年代版本
老年代采用多線程標(biāo)記-整理算法咙边,暫停所有工作線程
在注重吞吐量和CPU資源敏感的場(chǎng)合,都可以優(yōu)先考慮Parallel Scavenge+ Parallel Old的組合次员。
5.6 CMS收集器
*是一種以獲取最短回收停頓時(shí)間為目標(biāo)的收集器
1.初始標(biāo)記败许,stop the world
2.并發(fā)標(biāo)記
3.重新標(biāo)記,stop the world
4.并發(fā)清除
- 對(duì)CPU資源非常敏感淑蔚。
并發(fā)時(shí)導(dǎo)致用戶進(jìn)程變慢市殷。CMS默認(rèn)啟動(dòng)線程數(shù)是(CPU數(shù)量+3)/4 - 無(wú)法處理浮動(dòng)垃圾,可能出現(xiàn)Concurrent Mode Failure失敗而導(dǎo)致另一次Full GC的產(chǎn)生刹衫。
因?yàn)槔占A段用戶線程還是繼續(xù)運(yùn)行醋寝。
CMS不能等到老年代幾乎完全被填滿再開(kāi)始收集,若收集過(guò)程中預(yù)留的內(nèi)存無(wú)法滿足程序需要带迟,則會(huì)出現(xiàn)Current Mode Failure失敗音羞。虛擬機(jī)臨時(shí)啟動(dòng)后備預(yù)案Serial Old - CMS基于標(biāo)記-清除算法,會(huì)有大量空間碎片產(chǎn)生仓犬。
5.7 G1收集器
1.初始標(biāo)記
2.并發(fā)標(biāo)記
3.最終標(biāo)記
4.篩選回收
G1將Java堆劃分為多個(gè)大小相等的區(qū)域嗅绰。新生代老年代不再物理隔離。
在后臺(tái)維護(hù)一個(gè)優(yōu)先列表,每次根據(jù)允許的收集時(shí)間办陷,優(yōu)先回收價(jià)值最大的Region貌夕。
- 并行與并發(fā)
- 分代收集
- 空間整合
- 可預(yù)測(cè)的停頓
6、 內(nèi)存分配
6.1 對(duì)象優(yōu)先在Eden區(qū)分配
Eden區(qū)沒(méi)有足夠空間時(shí)民镜,出發(fā)Minor GC
(新生代Minor GC
老年代Major GC / Full GC啡专,一般比Minor GC慢10倍以上)
6.2 大對(duì)象直接進(jìn)入老年代
常見(jiàn)的很長(zhǎng)的字符串和數(shù)組
6.3 長(zhǎng)期存活的對(duì)象進(jìn)入老年代
對(duì)象在Eden區(qū)出生并經(jīng)歷一次Minor GC后仍能被Survivor容納的話,記為1歲制圈,移動(dòng)代Survivor中们童。
每經(jīng)歷一次Minor GC就增加1歲。
當(dāng)年齡增加到一定程度(默認(rèn)15歲)鲸鹦,就會(huì)被晉升到老年代中慧库。
6.4 動(dòng)態(tài)對(duì)象年齡判斷
如果在Survivor空間中相同年齡所有對(duì)象大小總和大于Survivor空間的一半,則年齡大于或等于該年齡的對(duì)象就直接進(jìn)入老年代馋嗜,無(wú)需等到要求的年齡齐板。
6.5 空間分配擔(dān)保
在Minor GC前,檢查老年代最大可用連續(xù)空間是否大于新生代空間總和葛菇。若大于則Minor GC可以安全進(jìn)行甘磨。否則,則查看是否允許擔(dān)保失敗眯停。若允許擔(dān)保失敗济舆,則查看老年代最大可用連續(xù)空間是否大于歷次新生代晉升到老年代的平均大小。如果大于則嘗試進(jìn)行Minor GC(雖然有風(fēng)險(xiǎn))莺债,否則進(jìn)行Full GC滋觉。
SafePoint
由于 Full GC(或Minor GC) 會(huì)影響性能,所以我們要在一個(gè)合適的時(shí)間點(diǎn)發(fā)起 GC齐邦,這個(gè)時(shí)間點(diǎn)被稱為 Safe Point椎侠,這個(gè)時(shí)間點(diǎn)的選定既不能太少以讓 GC 時(shí)間太長(zhǎng)導(dǎo)致程序過(guò)長(zhǎng)時(shí)間卡頓,也不能過(guò)于頻繁以至于過(guò)分增大運(yùn)行時(shí)的負(fù)荷侄旬。一般當(dāng)線程在這個(gè)時(shí)間點(diǎn)上狀態(tài)是可以確定的肺蔚,如確定 GC Root 的信息等,可以使 JVM 開(kāi)始安全地 GC儡羔。Safe Point 主要指的是以下特定位置:
循環(huán)的末尾
方法返回前
調(diào)用方法的 call 之后
拋出異常的位置 另外需要注意的是由于新生代的特點(diǎn)(大部分對(duì)象經(jīng)過(guò) Minor GC后會(huì)消亡)宣羊, Minor GC 用的是復(fù)制算法,而在老生代由于對(duì)象比較多汰蜘,占用的空間較大仇冯,使用復(fù)制算法會(huì)有較大開(kāi)銷(復(fù)制算法在對(duì)象存活率較高時(shí)要進(jìn)行多次復(fù)制操作,同時(shí)浪費(fèi)一半空間)所以根據(jù)老生代特點(diǎn)族操,在老年代進(jìn)行的 GC 一般采用的是標(biāo)記整理法來(lái)進(jìn)行回收苛坚。