Java運行時數(shù)據(jù)區(qū)域
在這之前先了解JVM運行時的數(shù)據(jù)區(qū)域,分五塊:
程序計數(shù)器
用于表示當前代碼執(zhí)行到第幾行
虛擬機棧
虛擬機棧描述的是 Java 方法執(zhí)行的內存模型:每個方法在執(zhí)行的同時都會創(chuàng)建一個棧幀(Stack Frame,是方法運行時的基礎數(shù)據(jù)結構)用于存儲局部變量表、操作數(shù)棧吠昭、動態(tài)鏈接作瞄、方法出口等信息丑勤。每一個方法從調用直至執(zhí)行完成的過程贤姆,就對應著一個棧幀在虛擬機棧中入棧到出棧的過程沐飘。
本地方法棧
本地方法棧與虛擬機棧所發(fā)揮的作用是非常相似,虛擬機棧對應描述的是Java方法,而本地方法棧則描述的是Native(C++)方法合敦。
方法區(qū)
方法區(qū)與 Java 堆一樣,是各個線程共享的內存區(qū)域豌汇,它用于存儲已被虛擬機加載的類信息幢炸、常量、靜態(tài)變量拒贱、即時編譯器編譯后的代碼等數(shù)據(jù)宛徊。雖然 Java 虛擬機規(guī)范把方法區(qū)描述為堆的一個邏輯部分,但是它卻有一個別名叫做 Non-Heap(非堆)逻澳,目的應該是與 Java 堆區(qū)分開來闸天。
JDK8 之前,Hotspot(虛擬機) 中方法區(qū)的實現(xiàn)是永久代(Perm)斜做,JDK8 開始使用元空間(Metaspace)苞氮,以前永久代所有內容的字符串常量移至堆內存,其他內容移至元空間瓤逼,元空間直接在本地內存分配笼吟。
堆
對于大多數(shù)應用來說,Java 堆是 Java 虛擬機所管理的內存中最大的一塊霸旗。Java 堆是被所有線程共享的一塊內存區(qū)域贷帮,在虛擬機啟動時創(chuàng)建。此內存區(qū)域的唯一目的就是存放對象實例定硝,幾乎所有的對象實例都在這里分配內存皿桑。堆是垃圾收集器管理的主要區(qū)域毫目,因此很多時候也被稱做“GC堆”蔬啡。從內存回收的角度來看,由于現(xiàn)在收集器基本都采用分代收集算法镀虐,所以 Java 堆中還可以細分為:新生代和老年代箱蟆;再細致一點的有 Eden 空間、From Survivor 空間刮便、To Survivor 空間空猜。
很明顯Java垃圾回收主要作用于堆區(qū)域,而堆區(qū)域又分為新生代與老年代,而網(wǎng)上說的持久代(永久代)其實是存于方法區(qū)中,并且于JDK1.8后以轉移到元空間中(以前永久代所有內容的字符串常量移至堆內存,其他內容移至元空間恨旱,元空間直接在本地內存分配)
再介紹下堆里又劃分2塊區(qū)域,分別是新生代和老年代(對應比例1:2)
新生代
主要存放新生成的對象,新生代的對象一般是朝生夕滅(存活期短),而新生代的區(qū)域又劃分為三個區(qū)域,分別叫Eden, From Survivor,To Survivor(比例對應為8:1:1)
在新生代區(qū)域觸發(fā)的GC叫做Minor GC
老年代
當新生代的對象經(jīng)過一定次數(shù)的回收仍存活(這個次數(shù)為一個規(guī)定的閾值),那么則會由新生代移動到老年代.老年代則會觸發(fā)Full GC(同時清除新生代,老年代,永久代)
如何判斷對象是否為垃圾
1.引用計數(shù)法
給對象加上計數(shù)器,引用+1,失效-1辈毯。當引用數(shù)為0則為內存垃圾
但是引用計數(shù)法存在一個漏洞,那就是無法解決循環(huán)引用的bug(A持有B的引用,B持有A的引用,運用這個方法則導致兩者都無法被回收)因此出現(xiàn)第二種方法
2.可達性分析法
通過GC Roots作為起始點,從這些節(jié)點開始向下搜索搜贤,搜索走過的路徑叫做引用鏈谆沃。當一個對象與任何一個GC Roots沒有引用鏈相連時,就認為該對象是內存垃圾仪芒。
同樣以上面的案例分析(A,B雖然相互持有引用,但沒有被任何一個GC Roots引用,因此仍然被認為是內存垃圾)
而到底什么對象可以作為GC Roots呢,以下對象都可以 :
- 虛擬機棧(棧幀中的本地變量表)中引用的對象唁影。
- 方法區(qū)中類靜態(tài)屬性引用的對象耕陷。
- 方法區(qū)中常量引用的對象。
- 本地方法棧中JNI(即一般說的Native方法)引用的對象据沈。
Java垃圾回收算法
知道如何判定對象是否為垃圾后就可以制定垃圾回收的策略(算法)了,分為4種
1.標記-清除算法
標記-清除算法采用從根集合(GC Roots)進行掃描哟沫,對存活的對象進行標記,標記完畢后锌介,再掃描整個空間中未被標記的對象嗜诀,進行回收
優(yōu)點: 并沒有移動仍存活對象的內存,對于正在運行的應用進程影響較少
缺點:1 每個對象找一遍導致效率不高
? ? ? ? 2 容易產(chǎn)生大量不連續(xù)內存碎片無法利用
2.復制算法
把內存分成兩半,對象分配內存時只分配到其中一半的區(qū)域,當這一半?yún)^(qū)域內存滿了則把這部分內存整理復制到另一半?yún)^(qū)域,同時把另一半的區(qū)域的內存清空
優(yōu)點:簡單高效(相對于上面的方法只需遍歷內存的一半),不會出現(xiàn)內存碎片
缺點:一半的內存空間無法使用,代價太大
3.標記-整理算法
先標記(標記方法跟第一種方法一樣),然后把存活的對象移向一端,然后再把其余區(qū)域的內存清掉
優(yōu)點:1 相比標記-清除算法不會產(chǎn)生內存碎片
? ? ? ? 2.相比復制算法不會產(chǎn)生空間浪費
4.分代收集算法
其實嚴格來說分代收集算法只是前面3種算法在Java虛擬機中實際的應用:對于新生代內存,采用復制算法,對于老年代內存,采用標記-整理算法
Dalvik與Art虛擬機在垃圾回收算法上的區(qū)別
Dalvik:只能固定一種垃圾回收算法,出廠就固定了
Art:能夠根據(jù)實際情況選擇垃圾回收算法(例如對于正在前臺運行的app采用標記-清除算法,對于轉到后臺運行的app則可以采用標記-整理算法)
觸發(fā)GC的時機
Minor GC觸發(fā)機制:
當年輕代滿時就會觸發(fā)Minor GC,這里的年輕代滿指的是Eden代滿孔祸,Survivor滿不會引發(fā)GC裹虫。
Full GC觸發(fā)機制:
(1)調用System.gc時,系統(tǒng)建議執(zhí)行Full GC融击,但是不必然執(zhí)行
(2)老年代空間不足
(3)方法區(qū)空間不足
(4)通過Minor GC后進入老年代的大小大于老年代的可用內存
(5)由Eden區(qū)筑公、survivor space1(From Space)區(qū)向survivor space2(To Space)區(qū)復制時,對象大小大于To Space可用內存尊浪,則把該對象轉存到老年代匣屡,且老年代的可用內存小于該對象大小
堆的垃圾回收工作流程
新生成的對象會在Eden區(qū)中存在,當Eden內存已滿把Eden無效的數(shù)據(jù)清除,存活的數(shù)據(jù)(沒能釋放內存的數(shù)據(jù))移到From Survivor,假如From Survivor內存滿了則把Eden 和From Survivor的內存整理移到To Survivor中,如此反復(假如其中一個Survivor并不足以承載Eden+另一個Survivor的內存時直接轉移到老年代區(qū)域),在Survivor中只要一次GC沒把對象內存清掉給內存計數(shù)就會+1,當這個計數(shù)達到一定閾值會從新生代區(qū)域轉移到老年代區(qū)域,當老年代區(qū)域內存已滿或出現(xiàn)其他情況又會觸發(fā)的的Full GC