Java 內(nèi)存分配策略
Java 程序運(yùn)行時(shí)的內(nèi)存分配策略有三種,分別是靜態(tài)分配,棧式分配,和堆式分配项戴,對應(yīng)的都哭,三種存儲策略使用的內(nèi)存空間主要分別是靜態(tài)存儲區(qū)(也稱方法區(qū))掰盘、棧區(qū)和堆區(qū)鼠证。
- 靜態(tài)存儲區(qū)(方法區(qū)):主要存放靜態(tài)數(shù)據(jù)涛目、全局 static 數(shù)據(jù)和常量秸谢。這塊內(nèi)存在程序編譯時(shí)就已經(jīng)分配好,并且在程序整個(gè)運(yùn)行期間都存在霹肝。
- 棧區(qū) :當(dāng)方法被執(zhí)行時(shí)估蹄,方法體內(nèi)的局部變量(其中包括基礎(chǔ)數(shù)據(jù)類型、對象的引用)都在棧上創(chuàng)建沫换,并在方法執(zhí)行結(jié)束時(shí)這些局部變量所持有的內(nèi)存將會自動(dòng)被釋放臭蚁。因?yàn)闂?nèi)存分配運(yùn)算內(nèi)置于處理器的指令集中,效率很高,但是分配的內(nèi)存容量有限垮兑。
- 堆區(qū) : 又稱動(dòng)態(tài)內(nèi)存分配冷尉,通常就是指在程序運(yùn)行時(shí)直接 new 出來的內(nèi)存,也就是對象的實(shí)例系枪。這部分內(nèi)存在不使用時(shí)將會由 Java 垃圾回收器來負(fù)責(zé)回收雀哨。
Java是如何管理內(nèi)存
Java的內(nèi)存管理就是對象的分配和釋放問題。在 Java 中嗤无,程序員需要通過關(guān)鍵字 new 為每個(gè)對象申請內(nèi)存空間 (基本類型除外)震束,所有的對象都在堆 (Heap)中分配空間。另外当犯,對象的釋放是由 GC 決定和執(zhí)行的垢村。在 Java 中,內(nèi)存的分配是由程序完成的嚎卫,而內(nèi)存的釋放是由 GC 完成的嘉栓,這種收支兩條線的方法確實(shí)簡化了程序員的工作。但同時(shí)拓诸,它也加重了JVM的工作侵佃。這也是 Java 程序運(yùn)行速度較慢的原因之一。因?yàn)榈熘В珿C 為了能夠正確釋放對象馋辈,GC 必須監(jiān)控每一個(gè)對象的運(yùn)行狀態(tài),包括對象的申請倍谜、引用迈螟、被引用、賦值等尔崔,GC 都需要進(jìn)行監(jiān)控答毫。
監(jiān)視對象狀態(tài)是為了更加準(zhǔn)確地、及時(shí)地釋放對象季春,而釋放對象的根本原則就是該對象不再被引用洗搂。
為了更好理解 GC 的工作原理,我們可以將對象考慮為有向圖的頂點(diǎn)载弄,將引用關(guān)系考慮為圖的有向邊耘拇,有向邊從引用者指向被引對象。另外宇攻,每個(gè)線程對象可以作為一個(gè)圖的起始頂點(diǎn)驼鞭,例如大多程序從 main 進(jìn)程開始執(zhí)行,那么該圖就是以 main 進(jìn)程頂點(diǎn)開始的一棵根樹尺碰。在這個(gè)有向圖中挣棕,根頂點(diǎn)可達(dá)的對象都是有效對象译隘,GC將不回收這些對象。如果某個(gè)對象 (連通子圖)與這個(gè)根頂點(diǎn)不可達(dá)(注意洛心,該圖為有向圖)固耘,那么我們認(rèn)為這個(gè)(這些)對象不再被引用,可以被 GC 回收词身。
哪些是JVM的GC Roots
- Class 由System Class Loader/Boot Class Loader加載的類對象厅目,這些對象不會被回收。需要注意的是其它的Class Loader實(shí)例加載的類對象不一定是GC root法严,除非這個(gè)類對象恰好是其它形式的GC root损敷;
- 如單例模式的java類;
- 非靜態(tài)內(nèi)部類/匿名類將持有Context深啤;
- Thread 線程拗馒,激活狀態(tài)的線程;
- Runnable/AsyncTask
- TimerTask
- Stack Local 棧中的對象溯街。每個(gè)線程都會分配一個(gè)棧诱桂,棧中的局部變量或者參數(shù)都是GC root,因?yàn)樗鼈兊囊秒S時(shí)可能被用到呈昔;
- JNI Local JNI中的局部變量和參數(shù)引用的對象挥等;可能在JNI中定義的,也可能在虛擬機(jī)中定義
- JNI Global JNI中的全局變量引用的對象堤尾;同上
- Monitor Used 用于保證同步的對象肝劲,例如wait(),notify()中使用的對象郭宝、鎖等辞槐。
- Held by JVM JVM持有的對象。JVM為了特殊用途保留的對象剩蟀,它與JVM的具體實(shí)現(xiàn)有關(guān)。比如有System Class Loader, 一些Exceptions對象切威,和一些其它的Class Loader育特。對于這些類,JVM也沒有過多的信息先朦。
可能導(dǎo)致的內(nèi)存泄露
參考 Android開發(fā)編碼規(guī)范導(dǎo)致的內(nèi)存泄露問題:
- 非靜態(tài)內(nèi)部類持有外部類的引用
- 單例持有了 context
- 非靜態(tài)匿名類持有外部類的引用
- handler 內(nèi)存泄露
對以上幾點(diǎn)做一個(gè)總結(jié)
- 不要讓生命周期長于Activity的對象持有到Activity的引用
- 盡量使用Application的Context而不是Activity的Context
- 盡量不要在Activity中使用非靜態(tài)內(nèi)部類缰冤,因?yàn)榉庆o態(tài)內(nèi)部類會隱式持有外部類實(shí)例的引用。使用靜態(tài)內(nèi)部類喳魏,將外部實(shí)例引用作為弱引用持有 android handler 弱引用棉浸。
- 垃圾回收不能解決內(nèi)存泄露,了解Android中垃圾回收機(jī)制
防止Android內(nèi)存泄露的總結(jié)
- 對 Activity 等組件的引用應(yīng)該控制在 Activity 的生命周期之內(nèi)刺彩; 如果不能就考慮使用 getApplicationContext 或者 getApplication迷郑,以避免 Activity 被外部長生命周期的對象引用而泄露枝恋。
- 盡量不要在靜態(tài)變量或者靜態(tài)內(nèi)部類中使用非靜態(tài)外部成員變量(包括context ),即使要使用嗡害,也要考慮適時(shí)把外部成員變量置空焚碌;也可以在內(nèi)部類中使用弱引用來引用外部類的變量。
- 對于生命周期比Activity長的內(nèi)部類對象霸妹,并且內(nèi)部類中使用了外部類的成員變量十电,可以這樣做避免內(nèi)存泄漏:
- 將內(nèi)部類改為靜態(tài)內(nèi)部類靜態(tài)內(nèi)部類中使用弱引用來引用外部類的成員變量
- Handler 的持有的引用對象最好使用弱引用,資源釋放時(shí)也可以清空 Handler 里面的消息叹螟。比如在 Activity onStop 或者 onDestroy 的時(shí)候鹃骂,取消掉該 Handler 對象的 Message和 Runnable.
- 在 Java 的實(shí)現(xiàn)過程中,也要考慮其對象釋放罢绽,最好的方法是在不使用某對象時(shí)畏线,顯式地將此對象賦值為 null,比如使用完Bitmap 后先調(diào)用 recycle()有缆,再賦為null,清空對圖片等資源有直接引用或者間接引用的數(shù)組(使用 array.clear() ; array = null)等象踊,最好遵循誰創(chuàng)建誰釋放的原則。
- 正確關(guān)閉資源棚壁,對于使用了BraodcastReceiver杯矩,ContentObserver,F(xiàn)ile袖外,游標(biāo) Cursor史隆,Stream,Bitmap等資源的使用曼验,應(yīng)該在Activity銷毀時(shí)及時(shí)關(guān)閉或者注銷泌射。
- 保持對對象生命周期的敏感,特別注意單例鬓照、靜態(tài)對象熔酷、全局性集合等的生命周期。
參考
GC的兩種判定方法:引用計(jì)數(shù)與引用鏈
Android開發(fā)編碼規(guī)范導(dǎo)致的內(nèi)存泄露問題
android handler 弱引用
Android開發(fā)從GC root分析內(nèi)存泄漏