Android垃圾回收:
分配內(nèi)存-GC不回收軟引用(GC_FOR_MALLOC)-增長到最大堆-(GC回收軟引用(GC_BEFORE_OOM)+增長到最大堆)
虛擬機(jī)棧:Java虛擬機(jī)棧描述的是Java方法執(zhí)行的內(nèi)存模型:Java棧中存放的是一個(gè)個(gè)的棧幀,每個(gè)棧幀對應(yīng)一個(gè)被調(diào)用的方法。
1.java內(nèi)存

程序計(jì)數(shù)器
雖然JVM中的程序計(jì)數(shù)器并不像匯編語言中的程序計(jì)數(shù)器一樣是物理概念上的CPU寄存器,但是JVM中的程序計(jì)數(shù)器的功能跟匯編語言中的程序計(jì)數(shù)器的功能在邏輯上是等同的,也就是說是用來指示 執(zhí)行哪條指令的。
由于在JVM中,多線程是通過線程輪流切換來獲得CPU執(zhí)行時(shí)間的,因此,在任一具體時(shí)刻,一個(gè)CPU的內(nèi)核只會執(zhí)行一條線程中的指令,因此,為了能夠使得每個(gè)線程都在線程切換后能夠恢復(fù)在切換之前的程序執(zhí)行位置,每個(gè)線程都需要有自己獨(dú)立的程序計(jì)數(shù)器,并且不能互相被干擾,否則就會影響到程序的正常執(zhí)行次序。因此,可以這么說,程序計(jì)數(shù)器是每個(gè)線程所私有的。
在JVM規(guī)范中規(guī)定,如果線程執(zhí)行的是非native方法,則程序計(jì)數(shù)器中保存的是當(dāng)前需要執(zhí)行的指令的地址;如果線程執(zhí)行的是native方法,則程序計(jì)數(shù)器中的值是undefined。
由于程序計(jì)數(shù)器中存儲的數(shù)據(jù)所占空間的大小不會隨程序的執(zhí)行而發(fā)生改變,因此,對于程序計(jì)數(shù)器是不會發(fā)生內(nèi)存溢出現(xiàn)象(OutOfMemory)的。
Java虛擬機(jī)棧
- Java虛擬機(jī)棧描述的是Java方法執(zhí)行的內(nèi)存模型:Java棧中存放的是一個(gè)個(gè)的棧幀,每個(gè)棧幀對應(yīng)一個(gè)被調(diào)用的方法,在棧幀中包括局部變量表(Local Variables)、操作數(shù)棧(Operand Stack)、指向當(dāng)前方法所屬的類的運(yùn)行時(shí)常量池的引用(Reference to runtime constant pool)、方法返回地址(Return Address)和一些額外的附加信息。當(dāng)線程執(zhí)行一個(gè)方法時(shí),就會隨之創(chuàng)建一個(gè)對應(yīng)的棧幀,并將建立的棧幀壓棧。當(dāng)方法執(zhí)行完畢之后,便會將棧幀出棧。因此可知,線程當(dāng)前執(zhí)行的方法所對應(yīng)的棧幀必定位于Java棧的頂部。講到這里,大家就應(yīng)該會明白為什么 在 使用 遞歸方法的時(shí)候容易導(dǎo)致棧內(nèi)存溢出的現(xiàn)象了以及為什么棧區(qū)的空間不用程序員去管理了(當(dāng)然在Java中,程序員基本不用關(guān)系到內(nèi)存分配和釋放的事情,因?yàn)镴ava有自己的垃圾回收機(jī)制),這部分空間的分配和釋放都是由系統(tǒng)自動實(shí)施的。對于所有的程序設(shè)計(jì)語言來說,棧這部分空間對程序員來說是不透明的。
- Java虛擬機(jī)棧是線程私有的,它的生命周期與線程相同。
- 程序員主要關(guān)注的stack棧內(nèi)存,就是虛擬機(jī)棧中局部變量表部分。
局部變量表存放了編譯時(shí)期可知的各種基本數(shù)據(jù)類型和對象引用。
局部變量表所需的內(nèi)存空間在編譯時(shí)期完成分配,當(dāng)進(jìn)入一個(gè)方法時(shí),這個(gè)方法需要在棧幀中分配多大的局部變量空間是完全確定的,在方法運(yùn)行期間不會改變局部變量表的大小。 - Java虛擬機(jī)規(guī)范對這個(gè)區(qū)域規(guī)定了兩種異常情況:
- 如果線程請求的棧深度大于虛擬機(jī)所允許的深度,將拋出
StackOverflowError異常; - 如果虛擬機(jī)??梢詣討B(tài)擴(kuò)展,如果擴(kuò)展時(shí)無法申請到足夠的內(nèi)存,就會拋出
OutOfMemoryError異常;
本地方法棧
本地方法棧與虛擬機(jī)棧所發(fā)揮的作用是非常相似的,它們之間的區(qū)別不過是虛擬機(jī)棧為虛擬機(jī)執(zhí)行Java方法服務(wù)(也就是字節(jié)碼),而本地方法棧為虛擬機(jī)使用到的Native方法服務(wù)。
Java堆
Java堆是被所有的線程共享的一塊內(nèi)存區(qū)域,在虛擬機(jī)啟動時(shí)創(chuàng)建。
Java堆的唯一目的就是存放對象實(shí)例,幾乎所有的對象實(shí)例都在這里分配內(nèi)存。
Java堆是垃圾回收器管理的主要區(qū)域,因此也被稱為"GC堆"。
從內(nèi)存回收的角度看,由于現(xiàn)在收集器基本都采用分代收集算法,所以Java堆可以細(xì)分為:新生代、老生代、持久帶;
方法區(qū)
方法區(qū)也是被所有的線程共享的一塊內(nèi)存區(qū)域。它用于存儲已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼和運(yùn)行時(shí)常量池等數(shù)據(jù)。
Java虛擬機(jī)規(guī)范對方法區(qū)的限制非常寬松,可以選擇不實(shí)現(xiàn)垃圾回收。
這區(qū)域的內(nèi)存回收目標(biāo)主要是針對常量池的回收和類型的卸載。
Java虛擬機(jī)規(guī)范規(guī)定,當(dāng)方法區(qū)無法滿足內(nèi)存分配的需求時(shí),將拋出OutOfMemoryError異常。
運(yùn)行時(shí)常量池:
運(yùn)行時(shí)常量池是方法區(qū)的一部分。CLass文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項(xiàng)信息是常量池,用于存放編譯期生成的各種字面量和符號引用,這部分內(nèi)容將在類加載后進(jìn)入方法區(qū)的運(yùn)行時(shí)常量池中存放,如:String類的intern()方法。
方法區(qū)1.8之前算在持久代,后來換成了元數(shù)據(jù)區(qū)MetaspaceJava 虛擬機(jī)16:Metaspace
Metaspace是方法區(qū)在HotSpot中的實(shí)現(xiàn),它與持久代最大的區(qū)別在于:Metaspace并不在虛擬機(jī)內(nèi)存中而是使用本地內(nèi)存。因此Metaspace具體大小理論上取決于32位/64位系統(tǒng)可用內(nèi)存的大小,可見也不是無限制的,需要配置參數(shù)。
三個(gè)問題:參考
- 那些內(nèi)存需要回收?(對象是否可以被回收的兩種經(jīng)典算法: 引用計(jì)數(shù)法 和 可達(dá)性分析算法)
- 什么時(shí)候回收? (堆的新生代、老年代、永久代的垃圾回收時(shí)機(jī),MinorGC 和 FullGC)
- 如何回收?(三種經(jīng)典垃圾回收算法(標(biāo)記清除算法、復(fù)制算法、標(biāo)記整理算法)及分代收集算法 和 七種垃圾收集器)
一. 如何確定一個(gè)對象是否可以被回收?
1.引用計(jì)數(shù)法(Reference Counting Collector)
優(yōu)點(diǎn):引用計(jì)數(shù)收集器可以很快的執(zhí)行,交織在程序運(yùn)行中。對程序需要不被長時(shí)間打斷的實(shí)時(shí)環(huán)境比較有利。
缺點(diǎn): 無法檢測出循環(huán)引用。
2. 可達(dá)性分析算法:判斷對象的引用鏈?zhǔn)欠窨蛇_(dá)
程序把所有的引用關(guān)系看作一張圖,從一個(gè)節(jié)點(diǎn)GC ROOT開始,尋找對應(yīng)的引用節(jié)點(diǎn),找到這個(gè)節(jié)點(diǎn)以后,繼續(xù)尋找這個(gè)節(jié)點(diǎn)的引用節(jié)點(diǎn),當(dāng)所有的引用節(jié)點(diǎn)尋找完畢之后,剩余的節(jié)點(diǎn)則被認(rèn)為是沒有被引用到的節(jié)點(diǎn),即無用的節(jié)點(diǎn)。
java中可作為GC Root的對象有
1.虛擬機(jī)棧中引用的對象(局部變量表)
2.方法區(qū)中靜態(tài)屬性引用的對象
3. 方法區(qū)中常量引用的對象
4.本地方法棧中引用的對象(Native對象)
二. 垃圾收集算法
- 標(biāo)記-清除算法分析
標(biāo)記-清除算法采用從根集合進(jìn)行掃描,對存活的對象對象標(biāo)記,標(biāo)記完畢后,再掃描整個(gè)空間中未被標(biāo)記的對象,進(jìn)行回收,如上圖所示。標(biāo)記-清除算法不需要進(jìn)行對象的移動,并且僅對不存活的對象進(jìn)行處理,在存活對象比較多的情況下極為高效,但由于標(biāo)記-清除算法直接回收不存活的對象,因此會造成內(nèi)存碎片。 - 標(biāo)記-整理算法
標(biāo)記整理算法的標(biāo)記過程類似標(biāo)記清除算法,但后續(xù)步驟不是直接對可回收對象進(jìn)行清理,而是讓所有存活的對象都向一端移動,然后直接清理掉端邊界以外的內(nèi)存,類似于磁盤整理的過程,標(biāo)記-整理算法是在標(biāo)記-清除算法的基礎(chǔ)上,又進(jìn)行了對象的移動,因此成本更高,但是卻解決了內(nèi)存碎片的問題。該垃圾回收算法適用于對象存活率高的場景(老年代) - 復(fù)制算法
復(fù)制算法將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當(dāng)這一塊的內(nèi)存用完了,就將還存活著的對象復(fù)制到另外一塊上面,然后再把已使用過的內(nèi)存空間一次清理掉。這種算法適用于對象存活率低的場景,比如新生代。這樣使得每次都是對整個(gè)半?yún)^(qū)進(jìn)行內(nèi)存回收,內(nèi)存分配時(shí)也就不用考慮內(nèi)存碎片等復(fù)雜情況,只要移動堆頂指針,按順序分配內(nèi)存即可,實(shí)現(xiàn)簡單,運(yùn)行高效。
事實(shí)上,現(xiàn)在商用的虛擬機(jī)都采用這種算法來回收新生代。 - 分代算法:
不同的對象的生命周期(存活情況)是不一樣的,而不同生命周期的對象位于堆中不同的區(qū)域,因此對堆內(nèi)存不同區(qū)域采用不同的策略進(jìn)行回收可以提高 JVM 的執(zhí)行效率。當(dāng)代商用虛擬機(jī)使用的都是分代收集算法:新生代對象存活率低,就采用復(fù)制算法;老年代存活率高,就用標(biāo)記清除算法或者標(biāo)記整理算法。Java堆內(nèi)存一般可以分為新生代、老年代和永久代三個(gè)模塊
堆空間分代
分代:
年輕代(Young Generation)
1.所有新生成的對象首先都是放在年輕代的。年輕代的目標(biāo)就是盡可能快速的收集掉那些生命周期短的對象。
2.新生代內(nèi)存按照8:1:1的比例分為一個(gè)Eden區(qū),兩個(gè) Survivor區(qū)0、1(一般而言)。大部分對象在Eden區(qū)中生成?;厥諘r(shí)先將eden區(qū)存活對象復(fù)制到一個(gè)survivor0區(qū),然后清空eden區(qū),當(dāng)這個(gè)survivor0區(qū)也存放滿了時(shí),則將eden區(qū)和survivor0區(qū)存活對象復(fù)制到另一個(gè)survivor1區(qū),然后清空eden和這個(gè)survivor0區(qū),此時(shí)survivor0區(qū)是空的,然后將survivor0區(qū)和survivor1區(qū)交換,即保持survivor1區(qū)為空, 如此往復(fù)。
3.當(dāng)survivor1區(qū)不足以存放 eden和survivor0的存活對象時(shí),就將存活對象直接存放到老年代。若是老年代也滿了就會觸發(fā)一次Full GC,也就是新生代、老年代都進(jìn)行回收
4.新生代發(fā)生的GC也叫做Minor GC(未成年GC),MinorGC發(fā)生頻率比較高(不一定等Eden區(qū)滿了才觸發(fā))
年老代(Old Generation)
1.在年輕代中經(jīng)歷了N次垃圾回收后仍然存活的對象,就會被放到年老代中。因此,可以認(rèn)為年老代中存放的都是一些生命周期較長的對象。
2.內(nèi)存比新生代也大很多(大概比例是1:2),當(dāng)老年代內(nèi)存滿時(shí)觸發(fā)Major GC(成年GC)即Full GC,F(xiàn)ull GC發(fā)生頻率比較低,老年代對象存活時(shí)間比較長,存活率標(biāo)記高。
持久代(Permanent Generation)
用于存放靜態(tài)文件,如Java類、方法等。持久代對垃圾回收沒有顯著影響,但是有些應(yīng)用可能動態(tài)生成或者調(diào)用一些class,例如Hibernate 等,在這種時(shí)候需要設(shè)置一個(gè)比較大的持久代空間來存放這些運(yùn)行過程中新增的類。
三.垃圾收集器
如果說垃圾收集算法是內(nèi)存回收的方法論,那么垃圾收集器就是內(nèi)存回收的具體實(shí)現(xiàn)。
新生代收集器使用的收集器:Serial、PraNew、Parallel Scavenge
老年代收集器使用的收集器:Serial Old、Parallel Old、CMS
略。。。
四. 內(nèi)存分配與回收策略
-
對象優(yōu)先在Eden分配,當(dāng)Eden區(qū)沒有足夠空間進(jìn)行分配時(shí),虛擬機(jī)將發(fā)起一次MinorGC?,F(xiàn)在的商業(yè)虛擬機(jī)一般都采用復(fù)制算法來回收新生代,將內(nèi)存分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor。 當(dāng)進(jìn)行垃圾回收時(shí),將Eden和Survivor中還存活的對象一次性地復(fù)制到另外一塊Survivor空間上,最后處理掉Eden和剛才的Survivor空間。(HotSpot虛擬機(jī)默認(rèn)Eden和Survivor的大小比例是8:1)當(dāng)Survivor空間不夠用時(shí),需要依賴?yán)夏甏M(jìn)行分配擔(dān)保。 -
大對象直接進(jìn)入老年代。所謂的大對象是指,需要大量連續(xù)內(nèi)存空間的Java對象,最典型的大對象就是那種很長的字符串以及數(shù)組。 -
多次Minor GC后存活的對象將進(jìn)入老年代。當(dāng)對象在新生代中經(jīng)歷過一定次數(shù)(默認(rèn)MaxTenuringThreshold為15)的Minor GC后,就會被晉升到老年代中。 -
動態(tài)對象年齡判定。為了更好地適應(yīng)不同程序的內(nèi)存狀況,虛擬機(jī)并不是永遠(yuǎn)地要求對象年齡必須達(dá)到了MaxTenuringThreshold才能晉升老年代,如果在Survivor空間中相同年齡所有對象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對象就可以直接進(jìn)入老年代,無須等到MaxTenuringThreshold中要求的年齡。
