前言
性能優(yōu)化目的:
1.如何去優(yōu)化自己的項(xiàng)目睁宰,運(yùn)行更流暢柒傻。
現(xiàn)實(shí)App進(jìn)程分配內(nèi)存空間: 16M 32M 64M
2..以后開(kāi)發(fā)項(xiàng)目的時(shí)候就要從一開(kāi)始把項(xiàng)目做好
內(nèi)存泄露
什么是內(nèi)存泄露红符? 內(nèi)存不在GC管控之內(nèi)
當(dāng)一個(gè)對(duì)象已經(jīng)不需要再使用時(shí),本該被回收時(shí)致开,而有另外一個(gè)正在使用的對(duì)象持有它的引用從而導(dǎo)致對(duì)象不能被回收双戳。這種導(dǎo)致了本該被回收的對(duì)象不能被回收而停留在堆內(nèi)存中糜芳,就產(chǎn)生了內(nèi)存泄露峭竣。
不是所有指令都執(zhí)行得又快又好皆撩,下面介紹內(nèi)存及它如何影響系統(tǒng)運(yùn)行。普遍認(rèn)為沮榜,多數(shù)程序語(yǔ)言接近硬件或高性能喻粹,如C守呜、C++和Fortran,通常程序員會(huì)自己管理內(nèi)存弥喉,高手工程師對(duì)內(nèi)存的分配由境,會(huì)慎重處理虏杰,并在未來(lái)結(jié)束使用時(shí)再次分配勒虾,一旦確認(rèn)何時(shí)及怎樣分配內(nèi)存修然,內(nèi)存管理的品質(zhì)就依賴(lài)于工程師的技能跟效率。實(shí)際情況是工程師們玻靡,不都會(huì)去追蹤那零碎的內(nèi)存碎片中贝。程序開(kāi)發(fā)是個(gè)混亂又瘋狂的過(guò)程啃奴,內(nèi)存通常都沒(méi)辦法完全被釋放,這些被囚禁的內(nèi)存叫內(nèi)存泄露雄妥。
內(nèi)存泄露占用了大量資源最蕾,這些資源其實(shí)可以更好地使用,為減少泄露引起的混亂老厌、負(fù)擔(dān)瘟则、甚至資金損失,便有了內(nèi)存管理語(yǔ)言枝秤。
這些語(yǔ)言在運(yùn)行時(shí)跟蹤內(nèi)存分配,以便當(dāng)程序不再需要時(shí)釋放系統(tǒng)內(nèi)存淀弹,完全不用工程師親自操作丹壕,這些內(nèi)存回收藝術(shù)或科學(xué),在內(nèi)存管理環(huán)節(jié)下叫垃圾清理薇溃。這個(gè)設(shè)計(jì)概念在1959年菌赖,當(dāng)初為了解決lisp語(yǔ)言問(wèn)題,由John McCarthy發(fā)明的沐序。
垃圾清理的基本概念有:
第一琉用,找到未來(lái)無(wú)法存取的數(shù)據(jù)堕绩,例如所有不受指令操控的內(nèi)存。
第二邑时,回收被利用過(guò)的資源奴紧。原理簡(jiǎn)單,但是兩百萬(wàn)行編碼晶丘,跟4gigs的分配黍氮,在實(shí)際操作時(shí)卻非常困難。如果在程序中有20000個(gè)對(duì)象分配浅浮,垃圾清理會(huì)讓人困惑沫浆,哪一個(gè)是沒(méi)用的?或者脑题,何時(shí)啟動(dòng)垃圾清理釋放內(nèi)存件缸?這些問(wèn)題其實(shí)很復(fù)雜。好在50年來(lái)叔遂,我們找到了解決問(wèn)題的方法他炊,就是Android Runtime中的垃圾清理。比McCarthy最初的方法更高級(jí)已艰,速度快且是非侵入性的痊末。經(jīng)由分配類(lèi)型,及系統(tǒng)如何有效地組織分配以利GC的運(yùn)行哩掺,并作為新的配置凿叠。所有影響android runtime的內(nèi)存堆都被分割到空間中,根據(jù)這些特點(diǎn)嚼吞,哪些數(shù)據(jù)適合放到什么空間盒件,取決于哪個(gè)Android版本。
了解內(nèi)存分配的幾種策略:
1.靜態(tài)的
在編譯時(shí)就能確定每個(gè)數(shù)據(jù)目標(biāo)在運(yùn)行時(shí)刻的存儲(chǔ)空間需求舱禽,因而在編譯時(shí)就可以給他們分配固定的內(nèi)存空間.這種分配策略要求程序代碼中不允許有可變代碼結(jié)構(gòu)(比如可變數(shù)組的存在)炒刁,也不允許有嵌套或者遞歸結(jié)構(gòu)的出現(xiàn),因?yàn)樗鼈兌紩?huì)導(dǎo)致編譯程序無(wú)法計(jì)算準(zhǔn)確的存儲(chǔ)空間需求誊稚。
靜態(tài)的存儲(chǔ)區(qū)翔始,內(nèi)存在分配的時(shí)候就已經(jīng)分配好了,這塊的內(nèi)存在程序在整個(gè)運(yùn)行期間內(nèi)一直存在里伯。
2.棧式的
棧式存儲(chǔ)分配也可稱(chēng)為動(dòng)態(tài)存儲(chǔ)分配,是由一個(gè)類(lèi)似于堆棧的運(yùn)行棧來(lái)實(shí)現(xiàn)的.和靜態(tài)存儲(chǔ)分配相反,在棧式存儲(chǔ)方案中,程序?qū)?shù)據(jù)區(qū)的需求在編譯時(shí)是完全未知的,只有到運(yùn)行的時(shí)候才能夠知道,但是規(guī)定在運(yùn)行中進(jìn)入一個(gè)程序模塊時(shí),必須知道該程序模塊所需的數(shù)據(jù)區(qū)大小才能夠?yàn)槠浞峙鋬?nèi)存.和我們?cè)跀?shù)據(jù)結(jié)構(gòu)所熟知的棧一樣,棧式存儲(chǔ)分配按照先進(jìn)后出的原則進(jìn)行分配城瞎。
在執(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.堆式的
堆式存儲(chǔ)分配則專(zhuān)門(mén)負(fù)責(zé)在編譯時(shí)或運(yùn)行時(shí)模塊入口處都無(wú)法確定存儲(chǔ)要求的數(shù)據(jù)結(jié)構(gòu)的內(nèi)存分配,比如可變長(zhǎng)度串和對(duì)象實(shí)例.堆由大片的可利用塊或空閑塊組成,堆中的內(nèi)存可以按照任意順序分配和釋放.
在C/C++可能需要自己負(fù)責(zé)釋放(java里面直接額依賴(lài)GC機(jī)制)
棧式和堆式區(qū)別:
從堆和棧的功能和作用來(lái)通俗的比較,堆主要用來(lái)存放對(duì)象的补憾,棧主要是用來(lái)執(zhí)行程序的.
heap:是由malloc之類(lèi)函數(shù)分配的空間所在地漫萄。地址是由低向高增長(zhǎng)的卷员。
stack:是自動(dòng)分配變量,以及函數(shù)調(diào)用的時(shí)候所使用的一些空間腾务。地址是由高向低減少的毕骡。
使用棧就象我們?nèi)ワ堭^里吃飯,只管點(diǎn)菜(發(fā)出申請(qǐng))岩瘦、付錢(qián)未巫、和吃(使用),吃飽了就走启昧,不必理會(huì)切菜叙凡、洗菜等準(zhǔn)備工作和洗碗、刷鍋等掃尾工作密末,他的好處是快捷握爷,但是自由度小。
使用堆就象是自己動(dòng)手做喜歡吃的菜肴严里,比較麻煩新啼,但是比較符合自己的口味,而且自由度大刹碾。
public class Main{
int a = 1;//堆里面
Student s = new Student();//堆里面
public void XXX(){//堆里面
int b = 1;//棧里面
Student s2 = new Student();
}
}
1.成員變量全部存儲(chǔ)在堆中(包括基本數(shù)據(jù)類(lèi)型燥撞,引用和引用的對(duì)象實(shí)體) --- 因?yàn)樗鼈儗儆陬?lèi),類(lèi)最終還是要被new出來(lái)迷帜。
2.局部變量的基本數(shù)據(jù)類(lèi)型和引用存儲(chǔ)于棧當(dāng)中物舒,引用的對(duì)象實(shí)體存儲(chǔ)于在堆中。-----因?yàn)樗麄儗儆诜椒ó?dāng)中的變量戏锹,生命周期會(huì)隨著方法一起結(jié)束冠胯。
我們所討論的內(nèi)存泄漏,主要是討論堆存儲(chǔ)景用,它存放的是引用指向的對(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堰怨。
如果用戶(hù)反復(fù)滑動(dòng)或者下面還有同樣的圖片芥玉,就會(huì)造成多次重復(fù)IO(很耗時(shí)),
那么需要緩存---平衡好內(nèi)存大小和IO备图,算法和一些特殊的java類(lèi)灿巧。
算法:lrucache(最近最少使用先回收)
特殊的java類(lèi):利于回收,StrongReference揽涮,SoftReference抠藕,WeakReference,PhatomReference
StrongReference --- 強(qiáng)引用:
StrongReference 是 Java的默認(rèn)引用實(shí)現(xiàn), 它會(huì)盡可能長(zhǎng)時(shí)間的存活于 JVM 內(nèi)蒋困, 當(dāng)沒(méi)有任何對(duì)象指向它時(shí) GC 執(zhí)行后將會(huì)被回收
回收時(shí)機(jī):從不回收 使用:對(duì)象的一般保存 生命周期:JVM停止的時(shí)候才會(huì)終止
SoftReference --- 軟引用
SoftReference 于 WeakReference 的特性基本一致盾似, 最大的區(qū)別在于 SoftReference 會(huì)盡可能長(zhǎng)的保留引用直到 JVM 內(nèi)存不足時(shí)才會(huì)被回收(虛擬機(jī)保證), 這一特性使得 SoftReference 非常適合緩存應(yīng)用
回收時(shí)機(jī):當(dāng)內(nèi)存不足的時(shí)候;使用:SoftReference<String>結(jié)合ReferenceQueue構(gòu)造有效期短雪标;生命周期:內(nèi)存不足時(shí)終止
WeakReference --- 弱引用
WeakReference 是一個(gè)弱引用, 當(dāng)所引用的對(duì)象在 JVM 內(nèi)不再有強(qiáng)引用時(shí), GC 后 weak reference 將會(huì)被自動(dòng)回
回收時(shí)機(jī):在垃圾回收的時(shí)候零院;使用:同軟引用; 生命周期:GC后終止
PhatomReference --- 虛引用
“虛引用”顧名思義汰聋,就是形同虛設(shè)门粪,與其他幾種引用都不同,虛引用并不會(huì)決定對(duì)象的生命周期烹困。如果一個(gè)對(duì)象僅>持有虛引用玄妈,那么它就和沒(méi)有任何引用一樣,在任何時(shí)候都可能被垃圾回收器回收髓梅。
虛引用主要用來(lái)跟蹤對(duì)象被垃圾回收器回收的活動(dòng)拟蜻。虛引用與軟引用和弱引用的一個(gè)區(qū)別在于:虛引用必須和引用>隊(duì)列 (ReferenceQueue)聯(lián)合使用。當(dāng)垃圾回收器準(zhǔn)備回收一個(gè)對(duì)象時(shí)枯饿,如果發(fā)現(xiàn)它還有虛引用酝锅,就會(huì)在回收對(duì)
象的內(nèi)存之前,把這個(gè)虛引用加入到與之 關(guān)聯(lián)的引用隊(duì)列中奢方。
ReferenceQueue queue = new ReferenceQueue ();
PhantomReference pr = new PhantomReference (object, queue);
程序可以通過(guò)判斷引用隊(duì)列中是否已經(jīng)加入了虛引用搔扁,來(lái)了解被引用的對(duì)象是否將要被垃圾回收。如果程序發(fā)現(xiàn)某個(gè)虛引用已經(jīng)被加入到引用隊(duì)列蟋字,那么就可以在所引用的對(duì)象的內(nèi)存被回收之前采取必要的行動(dòng)稿蹲。
回收時(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卡)
-----------------------------內(nèi)存泄漏例子--------------------------
單例模式導(dǎo)致內(nèi)存對(duì)象無(wú)法釋放而導(dǎo)致內(nèi)存泄漏
public class CommonUtils {
private static CommonUtils instance;
private Context context;
private CommonUtils(Context context) {
this.context = context;
}
public static CommonUtils getInstance(Context context) {
if (instance == null) {
instance = new CommonUtils(context);
}
return instance;
}
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
CommonUtils commonUtils = CommonUtils.getInstance(this);
}
}
運(yùn)行這個(gè)簡(jiǎn)單的單例模式程序蛀序,進(jìn)行多次橫豎屏切換欢瞪,發(fā)現(xiàn)可用內(nèi)存越來(lái)越小活烙,存在內(nèi)存泄漏現(xiàn)象,最終導(dǎo)致內(nèi)存溢出遣鼓。
分析原因:
點(diǎn)擊Monitors->Memory中 Dump Java Heap 采集記錄各個(gè)類(lèi)內(nèi)存使用情況啸盏,如下圖:
根據(jù)圖我們發(fā)現(xiàn),進(jìn)行多次橫豎屏切換時(shí)骑祟,生產(chǎn)了9個(gè)MainActivity回懦。多個(gè)MainActivity所占用的內(nèi)存資源沒(méi)有被GC及時(shí)回收,導(dǎo)致內(nèi)存泄漏次企。
總結(jié)
我們能用Application的context就用Application的
CommonUtils 生命周期是跟Application進(jìn)程同生同死怯晕。
特別感謝
動(dòng)腦學(xué)院Ricky