Android 優(yōu)化二 Java內(nèi)存分配機(jī)制及內(nèi)存泄漏

本篇文章已授權(quán)微信公眾號(hào)郭霖獨(dú)家發(fā)布

Java內(nèi)存分配機(jī)制及內(nèi)存泄漏
目錄介紹
1.JVM內(nèi)存管理
1.1 JVM內(nèi)存管理圖
1.2 Java采用GC進(jìn)行內(nèi)存管理膏孟。
2.JVM內(nèi)存分配的幾種策略
2.1 靜態(tài)的
2.2 棧式的
2.3 堆式的
2.4 堆和棧的區(qū)別
2.5 得出結(jié)論
2.6 舉個(gè)例子
2.7 調(diào)用 System.gc();進(jìn)行內(nèi)存回收
3.GC簡(jiǎn)單介紹
3.1 內(nèi)存垃圾回收機(jī)制
3.2 關(guān)于GC介紹
3.3 如何監(jiān)聽GC過程
3.4 GC過程與對(duì)象的引用類型關(guān)系
4.內(nèi)存泄漏簡(jiǎn)單介紹
4.1 內(nèi)存泄漏的定義
4.2 內(nèi)存泄漏與內(nèi)存溢出的區(qū)別
4.3 內(nèi)存泄漏帶來的影響
4.4 典型內(nèi)存泄漏案例

好消息

  • 博客筆記大匯總【16年3月到至今】喇完,包括Java基礎(chǔ)及深入知識(shí)點(diǎn)销钝,Android技術(shù)博客们豌,Python學(xué)習(xí)筆記等等对碌,還包括平時(shí)開發(fā)中遇到的bug匯總靶病,當(dāng)然也在工作之余收集了大量的面試題震肮,長期更新維護(hù)并且修正,持續(xù)完善……開源的文件是markdown格式的胰坟!同時(shí)也開源了生活博客因篇,從12年起,積累共計(jì)47篇[近20萬字]笔横,轉(zhuǎn)載請(qǐng)注明出處竞滓,謝謝!
  • 鏈接地址:https://github.com/yangchong211/YCBlogs
  • 如果覺得好吹缔,可以star一下商佑,謝謝!當(dāng)然也歡迎提出建議厢塘,萬事起于忽微茶没,量變引起質(zhì)變!

思維導(dǎo)圖

Java內(nèi)存分配機(jī)制.png

1.JVM內(nèi)存管理

1.1 JVM內(nèi)存管理

Image.png

1.2 Java采用GC進(jìn)行內(nèi)存管理晚碾。

Android虛擬機(jī)的垃圾回收采用的是根搜索算法抓半。GC會(huì)從根節(jié)點(diǎn)(GC Roots)開始對(duì)heap進(jìn)行遍歷。到最后格嘁,部分沒有直接或者間接引用到GC Roots的就是需要回收的垃圾笛求,會(huì)被GC回收掉。而內(nèi)存泄漏出現(xiàn)的原因就是存在了無效的引用讥蔽,導(dǎo)致本來需要被GC的對(duì)象沒有被回收掉涣易。

深入的JVM內(nèi)存管理知識(shí),推薦《深入理解Java虛擬機(jī)》冶伞。

2.JVM內(nèi)存分配的幾種策略新症。

2.1 靜態(tài)的

靜態(tài)的存儲(chǔ)區(qū),內(nèi)存在程序編譯的時(shí)候就已經(jīng)分配好了响禽,這塊內(nèi)存在程序整個(gè)運(yùn)行期間都一直存在
它主要存放靜態(tài)數(shù)據(jù)徒爹、全局的static數(shù)據(jù)和一些常量。

2.2 棧式的

在執(zhí)行方法時(shí),方法一些內(nèi)部變量的存儲(chǔ)都可以放在棧上面創(chuàng)建,方法執(zhí)行結(jié)束的時(shí)候這些存儲(chǔ)單元就會(huì)自動(dòng)被注釋掉果港。棧 內(nèi)存包括分配的運(yùn)算速度很快辛臊,因?yàn)閮?nèi)在在處理器里面鱼鼓。當(dāng)然容量有限,并且棧式一塊連續(xù)的內(nèi)存區(qū)域,大小是由操作系統(tǒng)決定的崎逃,他先進(jìn)后出丽焊,進(jìn)出完成不會(huì)產(chǎn)生碎片较剃,運(yùn)行效率高且穩(wěn)定

2.3 堆式的

也叫動(dòng)態(tài)內(nèi)存 。我們通常使用new 來申請(qǐng)分配一個(gè)內(nèi)存技健。這里也是我們討論內(nèi)存泄漏優(yōu)化的關(guān)鍵存儲(chǔ)區(qū)写穴。GC會(huì)根據(jù)內(nèi)存的使用情況,對(duì)堆內(nèi)存里的垃圾內(nèi)存進(jìn)行回收雌贱。堆內(nèi)存是一塊不連續(xù)的內(nèi)存區(qū)域啊送,如果頻繁地new/remove會(huì)造成大量的內(nèi)存碎片,GC頻繁的回收欣孤,導(dǎo)致內(nèi)存抖動(dòng)馋没,這也會(huì)消耗我們應(yīng)用的性能

2.4 堆和棧的區(qū)別

在函數(shù)中(說明是局部變量)定義的一些基本類型的變量和對(duì)象的引用變量都是在函數(shù)的棧內(nèi)存中分配。

當(dāng)在一段代碼塊中定義一個(gè)變量時(shí)降传,java就在棧中為這個(gè)變量分配內(nèi)存空間披泪,當(dāng)超過變量的作用域后,java會(huì)自動(dòng)釋放掉為該變量分配的內(nèi)存空間搬瑰,該內(nèi)存空間可以立刻被另作他用。

堆內(nèi)存用于存放所有由new創(chuàng)建的對(duì)象(內(nèi)容包括該對(duì)象其中的所有成員變量)和數(shù)組控硼。在堆中分配的內(nèi)存泽论,由java虛擬機(jī)自動(dòng)垃圾回收器來管理。

在堆中產(chǎn)生了一個(gè)數(shù)組或者對(duì)象后卡乾,還可以在棧中定義一個(gè)特殊的變量翼悴,這個(gè)變量的取值等于數(shù)組或者對(duì)象在堆內(nèi)存中的首地址,在棧中的這個(gè)特殊的變量就變成了數(shù)組或者對(duì)象的引用變量幔妨,以后就可以在程序中使用棧內(nèi)存中的引用變量來訪問堆中的數(shù)組或者對(duì)象鹦赎,引用變量相當(dāng)于為數(shù)組或者對(duì)象起的一個(gè)別名,或者代號(hào)误堡。

2.5 得出結(jié)論

1.局部變量的基本數(shù)據(jù)類型和引用古话,存儲(chǔ)于棧中,引用的對(duì)象實(shí)體存儲(chǔ)于堆中锁施。因?yàn)樗鼈儗儆诜椒ㄖ械淖兞颗悴龋芷陔S方法而結(jié)束。
2.成員變量全部存儲(chǔ)與堆中(包括基本數(shù)據(jù)類型悉抵,引用和引用的對(duì)象實(shí)體)肩狂,因?yàn)樗鼈儗儆陬悾悓?duì)象終究是要被new出來使用的姥饰。
3.我們所說的內(nèi)存泄露傻谁,只針對(duì)堆內(nèi)存,他們存放的就是引用指向的對(duì)象實(shí)體列粪。

2.6 舉個(gè)例子

public class Sample() {
    int s1 = 0;
    Sample mSample1 = new Sample();
    public void method() {
        int s2 = 1;
        Sample mSample2 = new Sample();
    }
}
Sample mSample3 = new Sample();
Sample 類的局部變量 s2 和引用變量 mSample2 都是存在于棧中审磁,但 mSample2 指向的對(duì)象是存在于堆上的谈飒。
mSample3 指向的對(duì)象實(shí)體存放在堆上,包括這個(gè)對(duì)象的所有成員變量 s1 和 mSample1力图,而它自己存在于棧中步绸。

2.7 調(diào)用 System.gc();進(jìn)行內(nèi)存回收

我們知道可以調(diào)用 System.gc();進(jìn)行內(nèi)存回收,但是GC不一定會(huì)執(zhí)行吃媒。面對(duì)GC的機(jī)制瓤介,我們是否無能為力?其實(shí)我們可以通過聲明一些引用標(biāo)記來讓GC更好對(duì)內(nèi)存進(jìn)行回收赘那。

Image.png

小技巧

成員變量全部存儲(chǔ)在堆中(包括基本數(shù)據(jù)類型刑桑,引用及引用的對(duì)象實(shí)體),因?yàn)樗麄儗儆陬惸贾郏悓?duì)象最終還是要被new出來的
局部變量的基本數(shù)據(jù)類型和引用存在棧中祠斧,應(yīng)用的對(duì)象實(shí)體存儲(chǔ)在堆中。因?yàn)樗鼈儗儆诜椒ó?dāng)中的變量拱礁,生命周期會(huì)隨著方法一起結(jié)束

3.GC工作原理

3.1 內(nèi)存垃圾回收機(jī)制
是從程序的主要運(yùn)行對(duì)象(如靜態(tài)對(duì)象/寄存器/棧上指向的堆內(nèi)存對(duì)象等)開始檢查引用鏈琢锋,當(dāng)遍歷一遍后得到上述這些無法回收的對(duì)象和他們所引用的對(duì)象鏈,組成無法回收的對(duì)象集合呢灶,而其他孤立對(duì)象(集)就作為垃圾回收
GC為了能夠正確釋放對(duì)象吴超,必須監(jiān)控每一個(gè)對(duì)象的運(yùn)行狀態(tài),包括對(duì)象的申請(qǐng)鸯乃、引用鲸阻、被引用、賦值等缨睡,GC都需要進(jìn)行監(jiān)控鸟悴。監(jiān)視對(duì)象狀態(tài)是為了更加準(zhǔn)確地、及時(shí)地釋放對(duì)象奖年,而釋放對(duì)象的根本原則就是該對(duì)象不再被引用细诸。

3.2 關(guān)于GC介紹
有幾個(gè)函數(shù)可以訪問GC,例如運(yùn)行GC的函數(shù)System.gc()陋守,但是根據(jù)Java語言規(guī)范定義揍堰,該函數(shù)不保證JVM的垃圾收集器一定會(huì)執(zhí)行。因?yàn)椴煌腏VM實(shí)現(xiàn)者可能使用不同的算法管理GC
通常GC的線程的優(yōu)先級(jí)別較低嗅义。JVM調(diào)用GC的策略也有很多種屏歹,有的是內(nèi)存使用到達(dá)一定程度時(shí),GC才開始工作之碗,也有定時(shí)執(zhí)行的蝙眶,有的是平緩執(zhí)行GC,有的是中斷式執(zhí)行GC。但通常來說幽纷,我們不需要關(guān)心這些式塌。
通過關(guān)鍵字 new 為每個(gè)對(duì)象申請(qǐng)內(nèi)存空間 (基本類型除外),所有的對(duì)象都在堆 (Heap)中分配空間

3.3 如何監(jiān)聽GC過程
系統(tǒng)每進(jìn)行一次GC操作時(shí)友浸,都會(huì)在LogCat中打印一條日志峰尝,我們只要去分析這條日志就可以了,日志的基本格式如下
D/dalvikvm: <GC_Reason> <Amount_freed>, <Heap_stats>, <Pause_time>
第一部分GC_Reason收恢,這個(gè)是觸發(fā)這次GC操作的

原因武学,一般情況下一共有以下幾種觸發(fā)GC操作的原因:
- GC_CONCURRENT: 當(dāng)我們應(yīng)用程序的堆內(nèi)存快要滿的時(shí)候,系統(tǒng)會(huì)自動(dòng)觸發(fā)GC操作來釋放內(nèi)存伦意。
- GC_FOR_MALLOC: 當(dāng)我們的應(yīng)用程序需要分配更多內(nèi)存火窒,可是現(xiàn)有內(nèi)存已經(jīng)不足的時(shí)候,系統(tǒng)會(huì)進(jìn)行GC操作來釋放內(nèi)存驮肉。
- GC_HPROF_DUMP_HEAP: 當(dāng)生成HPROF文件的時(shí)候熏矿,系統(tǒng)會(huì)進(jìn)行GC操作,關(guān)于HPROF文件我們下面會(huì)講到离钝。
- GC_EXPLICIT: 這種情況就是我們剛才提到過的票编,主動(dòng)通知系統(tǒng)去進(jìn)行GC操作,比如調(diào)用System.gc()方法來通知系統(tǒng)卵渴±秆或者在DDMS中,通過工具按鈕也是可以顯式地告訴系統(tǒng)進(jìn)行GC操作的奖恰。

第二部分Amount_freed,表示系統(tǒng)通過這次GC操作釋放了多少內(nèi)存
第三部分Heap_stats中會(huì)顯示當(dāng)前內(nèi)存的空閑比例以及使用情況(活動(dòng)對(duì)象所占內(nèi)存 / 當(dāng)前程序總內(nèi)存)
第四部分Pause_time表示這次GC操作導(dǎo)致應(yīng)用程序暫停的時(shí)間宛裕。關(guān)于這個(gè)暫停的時(shí)間瑟啃,Android在2.3的版本當(dāng)中進(jìn)行過一次優(yōu)化,在2.3之前GC操作是不能并發(fā)進(jìn)行的揩尸,也就是系統(tǒng)正在進(jìn)行GC蛹屿,那么應(yīng)用程序就只能阻塞住等待GC結(jié)束。雖說這個(gè)阻塞的過程并不會(huì)很長岩榆,也就是幾百毫秒错负,但是用戶在使用我們的程序時(shí)還是有可能會(huì)感覺到略微的卡頓。而自2.3之后勇边,GC操作改成了并發(fā)的方式進(jìn)行犹撒,就是說GC的過程中不會(huì)影響到應(yīng)用程序的正常運(yùn)行,但是在GC操作的開始和結(jié)束的時(shí)候會(huì)短暫阻塞一段時(shí)間粒褒,不過優(yōu)化到這種程度识颊,用戶已經(jīng)是完全無法察覺到了

3.4 GC過程與對(duì)象的引用類型關(guān)系
Java對(duì)引用的分類Strong reference, SoftReference, WeakReference, PhatomReference

Image.png

軟引用和弱引用
在Android應(yīng)用的開發(fā)中,為了防止內(nèi)存溢出奕坟,在處理一些占用內(nèi)存大而且聲明周期較長的對(duì)象時(shí)候祥款,可以盡量應(yīng)用軟引用和弱引用技術(shù)
軟/弱引用可以和一個(gè)引用隊(duì)列(ReferenceQueue)聯(lián)合使用清笨,如果軟引用所引用的對(duì)象被垃圾回收器回收,Java虛擬機(jī)就會(huì)把這個(gè)軟引用加入到與之關(guān)聯(lián)的引用隊(duì)列中刃跛。
利用這個(gè)隊(duì)列可以得知被回收的軟/弱引用的對(duì)象列表抠艾,從而為緩沖器清除已失效的軟/弱引用。

內(nèi)存泄漏的原因:堆內(nèi)存中的長生命周期的對(duì)象持有短生命周期對(duì)象的強(qiáng)/軟引用桨昙,盡管短生命周期對(duì)象已經(jīng)不再需要检号,但是因?yàn)殚L生命周期對(duì)象持有它的引用而導(dǎo)致不能被回收,這就是Java中內(nèi)存泄露的根本原因

4.內(nèi)存泄漏簡(jiǎn)單介紹

4.1 內(nèi)存泄漏的定義
當(dāng)一個(gè)對(duì)象已經(jīng)不需要使用了绊率,本該被回收時(shí)谨敛,而有另外一個(gè)正在使用的對(duì)象持有它的引用,從而導(dǎo)致了對(duì)象不能被GC回收滤否。這種導(dǎo)致了本該被回收的對(duì)象不能被回收而停留在堆內(nèi)存中脸狸,就產(chǎn)生了內(nèi)存泄漏

4.2 內(nèi)存泄漏與內(nèi)存溢出的區(qū)別
內(nèi)存泄漏(Memory Leak)
進(jìn)程中某些對(duì)象已經(jīng)沒有使用的價(jià)值了,但是他們卻還可以直接或間接地被引用到GC Root導(dǎo)致無法回收藐俺。當(dāng)內(nèi)存泄漏過多的時(shí)候炊甲,再加上應(yīng)用本身占用的內(nèi)存,日積月累最終就會(huì)導(dǎo)致內(nèi)存溢出OOM

內(nèi)存溢出(OOM)
當(dāng)應(yīng)用的heap資源超過了Dalvik虛擬機(jī)分配的內(nèi)存就會(huì)內(nèi)存溢出

4.3 內(nèi)存泄漏帶來的影響
應(yīng)用卡頓
泄漏的內(nèi)存影響了GC的內(nèi)存分配欲芹,過多的內(nèi)存泄漏會(huì)影響應(yīng)用的執(zhí)行效率

應(yīng)用異常(OOM)
過多的內(nèi)存泄漏卿啡,最終會(huì)導(dǎo)致 Dalvik分配的內(nèi)存,出現(xiàn)OOM

4.4 典型內(nèi)存泄漏案例
案例代碼

Vector v = new Vector(10);
for (int i = 1; i < 100; i++) {
    Object o = new Object();
    v.add(o);
    o = null; 
}

分析
在這個(gè)例子中菱父,我們循環(huán)申請(qǐng)Object對(duì)象颈娜,并將所申請(qǐng)的對(duì)象放入一個(gè) Vector 中,如果我們僅僅釋放引用本身浙宜,那么 Vector 仍然引用該對(duì)象官辽,所以這個(gè)對(duì)象對(duì) GC 來說是不可回收的。因此粟瞬,如果對(duì)象加入到Vector 后同仆,還必須從 Vector 中刪除,最簡(jiǎn)單的方法就是將 Vector 對(duì)象設(shè)置為 null裙品。

其他說明

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末俗批,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子市怎,更是在濱河造成了極大的恐慌岁忘,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件区匠,死亡現(xiàn)場(chǎng)離奇詭異臭觉,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門蝠筑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來狞膘,“玉大人,你說我怎么就攤上這事什乙⊥旆猓” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵臣镣,是天一觀的道長辅愿。 經(jīng)常有香客問我,道長忆某,這世上最難降的妖魔是什么点待? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮弃舒,結(jié)果婚禮上癞埠,老公的妹妹穿的比我還像新娘。我一直安慰自己聋呢,他們只是感情好苗踪,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著削锰,像睡著了一般通铲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上器贩,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天颅夺,我揣著相機(jī)與錄音,去河邊找鬼蛹稍。 笑死吧黄,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的稳摄。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼饲宿,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼厦酬!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起瘫想,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤仗阅,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后国夜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體减噪,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了筹裕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片醋闭。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖朝卒,靈堂內(nèi)的尸體忽然破棺而出证逻,到底是詐尸還是另有隱情,我是刑警寧澤抗斤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布囚企,位于F島的核電站,受9級(jí)特大地震影響瑞眼,放射性物質(zhì)發(fā)生泄漏龙宏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一伤疙、第九天 我趴在偏房一處隱蔽的房頂上張望银酗。 院中可真熱鬧,春花似錦掩浙、人聲如沸花吟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽衅澈。三九已至,卻和暖如春谬墙,著一層夾襖步出監(jiān)牢的瞬間今布,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來泰國打工拭抬, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留部默,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓造虎,卻偏偏與公主長得像傅蹂,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子算凿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容