上圖是Java內(nèi)存模型運行時區(qū)域圖。
運行時數(shù)據(jù)區(qū)分:
1.程序計數(shù)器:
- 由于java是多線程的疟丙,為了能夠線程上下切換后能夠恢復(fù)到正確的折行位置,每條線程都要有一個獨立的程序計數(shù)器舆绎,記錄當(dāng)前線程正在折行的方法的虛擬機指令星立,該區(qū)域沒有OOM發(fā)生胞锰。
2.JAVA虛擬機棧(本地方法棧) - 線程私有的:
- 生命周期跟線程一樣的灾锯,描述的是JAVA方法執(zhí)行的內(nèi)存模型兢榨,每個方法創(chuàng)建調(diào)用的時候會創(chuàng)建一個棧幀嗅榕,由于存儲局部變量表等信息。當(dāng)進入一個方法吵聪,棧幀需要的內(nèi)存是已經(jīng)確定的凌那。
會發(fā)生Stackoverflow和oom(因為申請棧幀需要內(nèi)存)
3.JAVA堆:所有線程共享 :
- 所有對象的實例以及數(shù)組都要在堆上分配內(nèi)存。
由于收集器基本都是分代收集算法吟逝,所以java堆還可以細分為:新生代帽蝶,老生代,可以處于物理不連續(xù)的存儲空間。
4.方法區(qū):所有線程共享:
- 存放虛擬機加載的類的信息块攒,常量励稳,靜態(tài)變量等佃乘。
5.運行時常量池:方法區(qū)的一部分,
什么是對象:
- 對象包括:對象頭驹尼,實例數(shù)據(jù)趣避,對其填充。
- 對象創(chuàng)建:當(dāng)new時新翎,虛擬機回去檢查常量池中是否有對應(yīng)的類的引用并且是否其被加載程帕,解析和初始化掖桦,對象需要的大小在類被加載后就可以確定了
- java虛擬機維護一個空閑的列表瘸恼,記錄堆上面那個區(qū)域是可以用的,
對象分配好內(nèi)存后匣摘,要將對象對應(yīng)的類的實例亏吝,元數(shù)據(jù)岭埠,對象的哈希碼等存放在對象頭。
引用
- 強引用:用 new 出來的
- 軟引用:如果第一次發(fā)現(xiàn)內(nèi)存不夠蔚鸥,會將軟引用對象放入二次回收中枫攀,如果還是內(nèi)存不足,那就會拋出異常株茶。
- 弱引用来涨,下一次GC發(fā)生的時候,就會回收启盛。
- 虛引用
回收
- 當(dāng)一個對象不可達時蹦掐,要被回收還要經(jīng)過2次標(biāo)記,第一次發(fā)現(xiàn)沒有對應(yīng)的GcRoots鏈后僵闯,標(biāo)記一次卧抗,如果發(fā)現(xiàn)該對象重寫了對應(yīng)的finalize()方法,那虛擬機會將該對象放在一個F-queue隊列里面標(biāo)記鳖粟,記住社裆,finalize方法是不能做耗時操作。
當(dāng)F-Queue隊列被finalize線程執(zhí)行時向图,會進行第二次標(biāo)記泳秀,如果這個是finalize方法將該對象重新引用,那么該對象就會被標(biāo)記為不是回收對象榄攀。
備注:finalize方法只會被虛擬機調(diào)用一次嗜傅。
- 回收方法區(qū)(永久代):
方法區(qū)回收分為:廢棄的常量和無用的類。
- 判斷一個類是無用的:
1.內(nèi)存中已經(jīng)沒有該類的任何實例檩赢,
2.加載該類的ClassLoader已經(jīng)被回收吕嘀。
3.該類對應(yīng)的Class對象沒有任何引用。
(永久代也會溢出的,大量使用反射偶房,動態(tài)代理的時候趁曼,要適時卸載類)
2.回收算法
- 標(biāo)記-清除
- 重新復(fù)制
- 標(biāo)記-整理。
目前商用的虛擬機將java堆分為新生代和老生代棕洋,然后再根據(jù)不同的區(qū)域采用不同的回收算法彰阴。
一般對象都是分配在eden(新生代),當(dāng)新生代內(nèi)存不夠時拍冠,會發(fā)生一次minorGC
- 需要大對象時尿这,會在老生代分配內(nèi)存,大對象庆杜,比如byte[]數(shù)組等
大對象對于內(nèi)存分配不友好射众。更要避免分配臨時的大對象,因為有可能在內(nèi)存還很多的時候晃财,還要觸發(fā)GC來給大對象分配連續(xù)的內(nèi)存叨橱,
當(dāng)一個對象經(jīng)過一段時間的回收后還存活,會將其移動到老生代断盛。
以下況會觸發(fā)GC罗洗,使用的算法是mark-sweep。
- 其中钢猛,Mark階段從根集(Root Set)開始伙菜,遞歸地標(biāo)記出當(dāng)前所有被引用的對象,而Sweep階段負責(zé)回收那些沒有被引用的對象
- gc是虛擬機啟動的時候創(chuàng)建的一個線程命迈,每次在堆上面成功創(chuàng)建一個對象的時候贩绕,都會檢測當(dāng)前的閑堆大小是否小于等于128k,如果是的話壶愤,就會調(diào)用有關(guān)觸發(fā)GC_CONCURRENT類型的GC淑倾。閑時會等待一定的時間,等待后如果發(fā)現(xiàn)沒有被調(diào)用gc那么它就調(diào)用函數(shù)trimHeaps對Java堆進行裁剪征椒,以便可以將堆上的一些沒有使用到的內(nèi)存交還給內(nèi)核娇哆。。否則的話勃救,就會調(diào)用函數(shù)dvmCollectGarbageInternal進行類型為GC_CONCURRENT的GC碍讨。
- 當(dāng)應(yīng)用程序調(diào)用System.gc、VMRuntime.gc接口剪芥,或者接收到SIGUSR1信號時垄开,最終會調(diào)用到函數(shù)dvmCollectGarbage琴许。