面試問題
- 什么是內(nèi)存泄露,什么是內(nèi)存溢出
- 什么情況下會造成堆溢出、棧溢出
- 常見造成內(nèi)存泄露的情況
- 常見造成內(nèi)存溢出的情況
- 談一下垃圾回收機制
什么是內(nèi)存泄露,什么是內(nèi)存溢出
內(nèi)存溢出就是Out Of Memory:指程序申請內(nèi)存時,沒有足夠的內(nèi)存供申請者使用,或者說,給了你一塊存儲int類型數(shù)據(jù)的存儲空間,但是你卻存儲long類型的數(shù)據(jù),那么結(jié)果就是內(nèi)存不夠用,此時就會報錯OOM,即所謂的內(nèi)存溢出。
內(nèi)存泄漏Mempry Leak:是指程序在申請內(nèi)存后,無法釋放已申請的內(nèi)存空間,一次內(nèi)存泄漏似乎不會有大的影響,但內(nèi)存泄漏堆積后的后果就是內(nèi)存溢出。內(nèi)存溢出分堆溢出,棧溢出。
什么情況下會造成堆溢出、棧溢出
堆溢出:不停地創(chuàng)建對象。
棧溢出:方法遞歸。
常見造成內(nèi)存泄露的情況
內(nèi)存泄露一般是由長生命周期引用短生命周期導(dǎo)致的。
- 單例引用Activity或Fragment。
- Handler引用Activity,Handler類使用static,使用弱引用持有外部Activity,在使用之前進行判斷。
- 未取消注冊廣播或回調(diào)導(dǎo)致內(nèi)存泄露。
- 集合中的對象未清理造成內(nèi)存泄露,應(yīng)該在不使用對象時從集合中刪除。
- 關(guān)閉Activity時資源未關(guān)閉或釋放導(dǎo)致內(nèi)存泄露,比如WebView下面持有Activity的引用,需要銷毀WebView之前先將WebView從父容器中移除,然后再銷毀銷毀WebView。
常見造成內(nèi)存溢出的情況
- 生產(chǎn)者與消費者速率不一致。沒有控制隊列中最大size大小,導(dǎo)致堆內(nèi)存溢出。
- JSONObject.toJSON(object) 來處理Javabean的,這樣處理簡單的對象是沒有問題的,但是對象如果復(fù)雜的話就會發(fā)生一些問題。object對象過于復(fù)雜和大量時,用toJSOn解析就會出現(xiàn)CPU、內(nèi)存一直飆升,JVM一直執(zhí)行GC操作,但是無法回收內(nèi)存,最后會報
java.lang.OutOfMemoryError: GC overhead limit exceeded錯誤。 - 內(nèi)存抖動,優(yōu)化內(nèi)存抖動,核心就是防止頻繁創(chuàng)建對象。常見的反面教材就是:循環(huán)中創(chuàng)建對象,大量調(diào)用的api中創(chuàng)建對象。而優(yōu)化的主要手段,就是對象復(fù)用,常見的手段是:對象池,像是Handler的Message單鏈表池,Glide的bitmap池等。
談一下垃圾回收機制
對象是否會被回收的兩個經(jīng)典算法:引用計數(shù)法,和可達性分析算法。
引用計數(shù)法:
- 簡單的來說就是判斷對象的引用數(shù)量。實現(xiàn)方式:給對象共添加一個引用計數(shù)器,每當(dāng)有引用對他進行引用時,計數(shù)器的值就加1,當(dāng)引用失效,也就是不在執(zhí)行此對象是,他的計數(shù)器的值就減1,若某一個對象的計數(shù)器的值為0,那么表示這個對象沒有人對他進行引用,也就是意味著是一個失效的垃圾對象,就會被gc進行回收。
- 但是這種簡單的算法在當(dāng)前的jvm中并沒有采用,原因是他并不能解決對象之間循環(huán)引用的問題。
- 假設(shè)有A和B兩個對象之間互相引用,也就是說A對象中的一個屬性是B,B中的一個屬性時A,這種情況下由于他們的相互引用,從而是垃圾回收機制無法識別。
和可達性分析算法:
因為引用計數(shù)法的缺點有引入了可達性分析算法,通過判斷對象的引用鏈?zhǔn)欠窨蛇_來決定對象是否可以被回收。可達性分析算法是從離散數(shù)學(xué)中的圖論引入的,程序把所有的引用關(guān)系看作一張圖,通過一系列的名為GCRoots的對象作為起始點,從這些節(jié)點開始向下搜索,搜索所走過的路徑稱為引用鏈。當(dāng)一個對象到 GCRoots 沒有任何引用鏈相連(就是從 GC Roots 到這個對象不可達)時,則證明此對象是不可用的。
jvm會在什么時候進行回收:
- 會在cpu空閑的時候自動進行回收
- 在堆內(nèi)存存儲滿了之后
- 主動調(diào)用System.gc()后嘗試進行回收
如何回收:
如何回收說的也就是垃圾收集的算法。
算法又有四個:標(biāo)記-清除算法,復(fù)制算法,標(biāo)記-整理算法,分代收集算法。
標(biāo)記-清除算法:
這是最基礎(chǔ)的一種算法,分為兩個步驟,第一個步驟就是標(biāo)記,也就是標(biāo)記處所有需要回收的對象,標(biāo)記完成后就進行統(tǒng)一的回收掉哪些帶有標(biāo)記的對象。這種算法優(yōu)點是簡單,缺點是效率問題,還有一個最大的缺點是空間問題,標(biāo)記清除之后會產(chǎn)生大量不連續(xù)的內(nèi)存碎片,當(dāng)程序在以后的運行過程中需要分配較大對象時無法找到足夠的連續(xù)內(nèi)存而造成內(nèi)存空間浪費。
復(fù)制算法:
復(fù)制將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當(dāng)這一塊的內(nèi)存用完了,就將還存活著的對象復(fù)制到另外一塊上面,然后再把已使用過的內(nèi)存空間一次清理掉。這樣使得每次都是對其中的一塊進行內(nèi)存回收,內(nèi)存分配時也就不用考慮內(nèi)存碎片等復(fù)雜情況。只是這種算法的代價是將內(nèi)存縮小為原來的一半。
標(biāo)記-整理算法:
標(biāo)記整理算法與標(biāo)記清除算法很相似,但最顯著的區(qū)別是:標(biāo)記清除算法僅對不存活的對象進行處理,剩余存活對象不做任何處理,造成內(nèi)存碎片;而標(biāo)記整理算法不僅對不存活對象進行處理清除,還對剩余的存活對象進行整理,重新整理,因此其不會產(chǎn)生內(nèi)存碎片。
分代收集算法:
分代收集算法是一種比較智能的算法,也是現(xiàn)在jvm使用最多的一種算法,他本身其實不是一個新的算法,而是他會在具體的場景自動選擇以上三種算法進行垃圾對象回收。
了解過場景之后再結(jié)合分代收集算法得出結(jié)論:
- 在新生代中,每次垃圾收集時都發(fā)現(xiàn)有大批對象死去,只有少量存活,那就選用復(fù)制算法。只需要付出少量存活對象的復(fù)制成本就可以完成收集。
- 老年代中因為對象存活率高、沒有額外空間對他進行分配擔(dān)保,就必須用標(biāo)記-清除或者標(biāo)記-整理。