引言:
C/C++ 自己去分配內(nèi)存和釋放內(nèi)存--手動(dòng)管理
malloc free
什么是內(nèi)存泄露:內(nèi)存不在GC掌控之內(nèi)了趟卸。
當(dāng)一個(gè)對(duì)象已經(jīng)不需要再使用了蹄葱,本該被回收時(shí),而有另外一個(gè)正在使用的對(duì)象持有它的引用從而就導(dǎo)致
對(duì)象不能被回收锄列。這種導(dǎo)致了本該被回收的對(duì)象不能被回收而停留在堆內(nèi)存中图云,就產(chǎn)生了內(nèi)存泄漏
了解java的GC內(nèi)存回收機(jī)制:某對(duì)象不再有任何的引用的時(shí)候才會(huì)進(jìn)行回收。
ArrayList<String> list = new Arraylist<String>();
了解內(nèi)存分配的幾種策略:
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.棧式的
在執(zhí)行函數(shù)(方法)時(shí)帕翻,函數(shù)一些內(nèi)部變量的存儲(chǔ)都可以放在棧上面創(chuàng)建,函數(shù)執(zhí)行結(jié)束的時(shí)候這些存儲(chǔ)單元就會(huì)自動(dòng)被釋放掉萝风。
棧內(nèi)存包括分配的運(yùn)算速度很快嘀掸,因?yàn)閮?nèi)置在處理器的里面的。當(dāng)然容量有限规惰。
3.堆式的
也叫做動(dòng)態(tài)內(nèi)存分配睬塌。有時(shí)候可以用malloc或者new來(lái)申請(qǐng)分配一個(gè)內(nèi)存。在C/C++可能需要自己負(fù)責(zé)釋放(java里面直接依賴GC機(jī)制)。
在C/C++這里是可以自己掌控內(nèi)存的揩晴,需要有很高的素養(yǎng)來(lái)解決內(nèi)存的問(wèn)題勋陪。java在這一塊貌似程序員沒(méi)有很好的方法自己去解決垃圾內(nèi)存,需要的是編程的時(shí)候就要注意自己良好的編程習(xí)慣硫兰。
區(qū)別:堆是不連續(xù)的內(nèi)存區(qū)域诅愚,堆空間比較靈活也特別大。
棧式一塊連續(xù)的內(nèi)存區(qū)域劫映,大小是有操作系統(tǒng)覺(jué)決定的违孝。
堆管理很麻煩,頻繁地new/remove會(huì)造成大量的內(nèi)存碎片泳赋,這樣就會(huì)慢慢導(dǎo)致效率低下雌桑。
對(duì)于棧的話,他先進(jìn)后出祖今,進(jìn)出完全不會(huì)產(chǎn)生碎片校坑,運(yùn)行效率高且穩(wěn)定。
public class Main{
int a = 1;
Student s = new Student();
public void XXX(){
int b = 1;//棧里面
Student s2 = new Student();
}
}
1.成員變量全部存儲(chǔ)在堆中(包括基本數(shù)據(jù)類型千诬,引用及引用的對(duì)象實(shí)體)---因?yàn)樗麄儗儆陬愃D浚悓?duì)象最終還是要被new出來(lái)的。
2.局部變量的基本數(shù)據(jù)類型和引用存儲(chǔ)于棧當(dāng)中大渤,引用的對(duì)象實(shí)體存儲(chǔ)在堆中制妄。-----因?yàn)樗麄儗儆诜椒ó?dāng)中的變量,生命周期會(huì)隨著方法一起結(jié)束泵三。
我們所討論內(nèi)存泄露耕捞,主要討論堆內(nèi)存,他存放的就是引用指向的對(duì)象實(shí)體烫幕。
有時(shí)候確實(shí)會(huì)有一種情況:當(dāng)需要的時(shí)候可以訪問(wèn)俺抽,當(dāng)不需要的時(shí)候可以被回收也可以被暫時(shí)保存以備重復(fù)使用。
比如:ListView或者GridView较曼、REcyclerView加載大量數(shù)據(jù)或者圖片的時(shí)候磷斧,
圖片非常占用內(nèi)存,一定要管理好內(nèi)存捷犹,不然很容易內(nèi)存溢出弛饭。
滑出去的圖片就回收,節(jié)省內(nèi)存萍歉÷滤蹋看ListView的源碼----回收對(duì)象,還會(huì)重用ConvertView枪孩。
如果用戶反復(fù)滑動(dòng)或者下面還有同樣的圖片憔晒,就會(huì)造成多次重復(fù)IO(很耗時(shí))藻肄,
那么需要緩存---平衡好內(nèi)存大小和IO,算法和一些特殊的java類拒担。
算法:lrucache(最近最少使用先回收)
特殊的java類:
利于回收嘹屯,StrongReference,SoftReference从撼,WeakReference州弟,PhatomReference
StrongReference強(qiáng)引用:
回收時(shí)機(jī):從不回收 使用:對(duì)象的一般保存 生命周期:JVM停止的時(shí)候才會(huì)終止
SoftReference,軟引用
回收時(shí)機(jī):當(dāng)內(nèi)存不足的時(shí)候低零;使用:SoftReference<String>結(jié)合ReferenceQueue構(gòu)造有效期短呆馁;生命周期:內(nèi)存不足時(shí)終止
WeakReference,弱引用
回收時(shí)機(jī):在垃圾回收的時(shí)候毁兆;使用:同軟引用; 生命周期:GC后終止
PhatomReference 虛引用
回收時(shí)機(jī):在垃圾回收的時(shí)候阴挣;使用:合ReferenceQueue來(lái)跟蹤對(duì)象唄垃圾回收期回收的活動(dòng)气堕; 生命周期:GC后終止
開(kāi)發(fā)時(shí),為了防止內(nèi)存溢出畔咧,處理一些比較占用內(nèi)存大并且生命周期長(zhǎng)的對(duì)象的時(shí)候茎芭,可以盡量使用軟引用和弱引用。
軟引用比LRU算法更加任性誓沸,回收量是比較大的梅桩,你無(wú)法控制回收哪些對(duì)象。
比如使用場(chǎng)景:默認(rèn)頭像拜隧、默認(rèn)圖標(biāo)宿百。
ListView或者GridView、REcyclerView要使用內(nèi)存緩存+外部緩存(SD卡)
常見(jiàn)內(nèi)存泄露
1.單例模式導(dǎo)致內(nèi)存對(duì)象無(wú)法釋放而導(dǎo)致內(nèi)存泄露
能用Application的context就用Application的
2.設(shè)置監(jiān)聽(tīng)很容易出現(xiàn)內(nèi)存泄露
例如:
handler.post(callback)
onDestroy(){
handler.removeCallback();
}
3.非靜態(tài)內(nèi)部類引起內(nèi)存泄露
public void loadData(){//隱士持有MainActivity實(shí)例洪添。MainActivity.this.a
new Thread(new Runnable() {
@Override
public void run() {
while(true){
try {
//int b=a;
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
解決方案:
將非靜態(tài)內(nèi)部類修改為靜態(tài)內(nèi)部類垦页。
(靜態(tài)內(nèi)部類不會(huì)隱士持有外部類)
4.資源未關(guān)閉引起的內(nèi)存泄露情況
比如:BroadCastReceiver、Cursor干奢、Bitmap痊焊、IO流、自定義屬性attribute
attr.recycle()回收忿峻。
當(dāng)不需要使用的時(shí)候薄啥,要記得及時(shí)釋放資源。否則就會(huì)內(nèi)存泄露逛尚。
5.無(wú)限循環(huán)動(dòng)畫
沒(méi)有在onDestroy中停止動(dòng)畫垄惧,否則Activity就會(huì)變成泄露對(duì)象。
比如:輪播圖效果黑低。
如何找到項(xiàng)目中存在的內(nèi)存泄露的這些地方呢
1.確定是否存在內(nèi)存泄露
1)Android Monitors的內(nèi)存分析
最直觀的看內(nèi)存增長(zhǎng)情況赘艳,知道該動(dòng)作是否發(fā)生內(nèi)存泄露酌毡。
動(dòng)作發(fā)生之前:GC完后內(nèi)存1.4M; 動(dòng)作發(fā)生之后:GC完后內(nèi)存1.6M
2)使用MAT內(nèi)存分析工具
MAT分析heap的總內(nèi)存占用大小來(lái)初步判斷是否存在泄露
Heap視圖中有一個(gè)Type叫做data object,即數(shù)據(jù)對(duì)象蕾管,也就是我們的程序中大量存在的類類型的對(duì)象枷踏。
在data object一行中有一列是“Total Size”,其值就是當(dāng)前進(jìn)程中所有Java數(shù)據(jù)對(duì)象的內(nèi)存總量掰曾,
一般情況下旭蠕,這個(gè)值的大小決定了是否會(huì)有內(nèi)存泄漏。
我們反復(fù)執(zhí)行某一個(gè)操作并同時(shí)執(zhí)行GC排除可以回收掉的內(nèi)存旷坦,注意觀察data object的Total Size值掏熬,
正常情況下Total Size值都會(huì)穩(wěn)定在一個(gè)有限的范圍內(nèi),也就是說(shuō)由于程序中的的代碼良好秒梅,沒(méi)有造成對(duì)象不被垃圾回收的情況旗芬。
反之如果代碼中存在沒(méi)有釋放對(duì)象引用的情況,隨著操作次數(shù)的增多Total Size的值會(huì)越來(lái)越大捆蜀。
那么這里就已經(jīng)初步判斷這個(gè)操作導(dǎo)致了內(nèi)存泄露的情況疮丛。
2.先找懷疑對(duì)象(哪些對(duì)象屬于泄露的)
MAT對(duì)比操作前后的hprof來(lái)定位內(nèi)存泄露是泄露了什么數(shù)據(jù)對(duì)象。(這樣做可以排除一些對(duì)象辆它,不用后面去查看所有被引用的對(duì)象是否是嫌疑)
快速定位到操作前后所持有的對(duì)象哪些是增加了(GC后還是比之前多出來(lái)的對(duì)象就可能是泄露對(duì)象嫌疑犯)
技巧:Histogram中還可以對(duì)對(duì)象進(jìn)行Group誊薄,比如選擇Group By Package更方便查看自己Package中的對(duì)象信息。
- MAT分析hprof來(lái)定位內(nèi)存泄露的原因所在锰茉。(哪個(gè)對(duì)象持有了上面懷疑出來(lái)的發(fā)生泄露的對(duì)象)
1)Dump出內(nèi)存泄露“當(dāng)時(shí)”的內(nèi)存鏡像hprof呢蔫,分析懷疑泄露的類;
2)把上面2得出的這些嫌疑犯一個(gè)一個(gè)排查個(gè)遍飒筑。步驟:
(1)進(jìn)入Histogram片吊,過(guò)濾出某一個(gè)嫌疑對(duì)象類
(2)然后分析持有此類對(duì)象引用的外部對(duì)象(在該類上面點(diǎn)擊右鍵List Objects--->with incoming references)
(3)再過(guò)濾掉一些弱引用、軟引用协屡、虛引用定鸟,因?yàn)樗鼈冞t早可以被GC干掉不屬于內(nèi)存泄露
(在類上面點(diǎn)擊右鍵Merge Shortest Paths to GC Roots--->exclude all phantom/weak/soft etc.references)
(4)逐個(gè)分析每個(gè)對(duì)象的GC路徑是否正常
此時(shí)就要進(jìn)入代碼分析此時(shí)這個(gè)對(duì)象的引用持有是否合理,這就要考經(jīng)驗(yàn)和體力了著瓶!
(比如上課的例子中:旋轉(zhuǎn)屏幕后MainActivity有兩個(gè)联予,肯定MainActivity發(fā)生泄露了,
那誰(shuí)導(dǎo)致他泄露的呢材原?原來(lái)是我們的CommonUtils類持有了旋轉(zhuǎn)之前的那個(gè)MainActivity他沸久,
那是否合理?結(jié)合邏輯判斷當(dāng)然不合理余蟹,由此找到內(nèi)存泄露根源是CommonUtils類持有了該MainActivity實(shí)例造成的卷胯。
怎么解決?罪魁禍?zhǔn)渍业搅送疲趺唇鉀Q應(yīng)該不難了窑睁,不同情況解決辦法不一樣挺峡,要靠你的智慧了。)
context.getapplictioncontext()可以嗎担钮?
可以3髟!只要讓CommonUtils類不直接只有MainActivity的實(shí)例就可以了箫津。
一般我是最笨的方法解決
new出來(lái)對(duì)象狭姨,用完后把它 = null;這樣算不算優(yōu)化
假如:方法里面定義的對(duì)象,要去管嗎苏遥?一般不需要管饼拍。
自己=null,要自己去控制所有對(duì)象的生命周期 判斷各種空指針田炭,有點(diǎn)麻煩师抄。
但是在很多時(shí)候去想到主動(dòng)將對(duì)象置為null是很好的習(xí)慣。
判斷一個(gè)應(yīng)用里面內(nèi)存泄露避免得很好教硫,怎么看司澎?
當(dāng)app退出的時(shí)候,這個(gè)進(jìn)程里面所有的對(duì)象應(yīng)該就都被回收了栋豫,尤其是很容易被泄露的(View,Activity)是否還內(nèi)存當(dāng)中谚殊。
可以讓app退出以后丧鸯,查看系統(tǒng)該進(jìn)程里面的所有的View、Activity對(duì)象是否為0.
工具:使用AndroidStudio--AndroidMonitor--System Information--Memory Usage查看Objects里面的views和Activity的數(shù)量是否為0.
常用優(yōu)化工具
1.Profiler
2.LeakCanary