亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

內(nèi)存泄露從入門(mén)到精通三部曲之基礎(chǔ)知識(shí)篇

nicercode / 3319人閱讀

摘要:騰訊特約作者姚潮生首先以一個(gè)內(nèi)存泄露實(shí)例來(lái)開(kāi)始本節(jié)基礎(chǔ)概念的內(nèi)容。堆內(nèi)存用于存放所有由創(chuàng)建的對(duì)象內(nèi)容包括該對(duì)象其中的所有成員變量和數(shù)組?;氐轿覀兊膯?wèn)題,為什么內(nèi)存會(huì)泄露堆內(nèi)存中的長(zhǎng)生命周期的對(duì)象持有短生命周期對(duì)象的強(qiáng)軟引用,盡管

騰訊Bugly特約作者: 姚潮生

首先以一個(gè)內(nèi)存泄露實(shí)例來(lái)開(kāi)始本節(jié)基礎(chǔ)概念的內(nèi)容。

實(shí)例1:?jiǎn)卫龑?dǎo)致內(nèi)存對(duì)象無(wú)法釋放而泄露

可以看出ImageUtil這個(gè)工具類是一個(gè)單例,并引用了activity的context。

試想這個(gè)場(chǎng)景,應(yīng)用起來(lái)以后,轉(zhuǎn)屏。轉(zhuǎn)屏以后,舊MainActivity會(huì)destroy,新MainActivity會(huì)重建,導(dǎo)致單例ImageUtil重新getInstance。很不幸的是,由于instance已經(jīng)不是空的了,所以ImageUtil不會(huì)重建,還持有之前的Context,也就是之前的那個(gè)MainActivity實(shí)例的context,因此會(huì)造成兩個(gè)問(wèn)題:

功能問(wèn)題:使用ImageUitl訪問(wèn)context相關(guān)內(nèi)容時(shí)可能會(huì)發(fā)生異常(因?yàn)楫?dāng)前context并不是當(dāng)前activity的context);

內(nèi)存泄露:舊context被生命周期更長(zhǎng)的靜態(tài)變量持有而導(dǎo)致activity無(wú)法釋放造成泄漏?。ㄒ虼遂o態(tài)變量是很容易因此內(nèi)存泄露的!)

使用工具可以看到ImageUtil引用了MainActivity導(dǎo)致MainActivity駐留內(nèi)存發(fā)生泄漏。

備注:本系列部分概念和例子引用來(lái)自網(wǎng)絡(luò)。

內(nèi)存泄露,我們要研究的泄露對(duì)象到底是什么?

首先我們來(lái)了解程序運(yùn)行時(shí),所需內(nèi)存的分配策略:

按照編譯原理的觀點(diǎn),程序運(yùn)行時(shí)的內(nèi)存分配有三種策略,分別是靜態(tài)的、棧式的、和堆式的,對(duì)應(yīng)的,三種存儲(chǔ)策略使用的內(nèi)存空間主要分別是靜態(tài)存儲(chǔ)區(qū)(也稱方法區(qū))、堆區(qū)和棧區(qū)。他們的功能不同,對(duì)他們使用方式也就不同。

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

棧區(qū):在執(zhí)行函數(shù)時(shí),函數(shù)內(nèi)局部變量的存儲(chǔ)單元都可以在棧上創(chuàng)建,函數(shù)執(zhí)行結(jié)束時(shí)這些存儲(chǔ)單元自動(dòng)被釋放。棧內(nèi)存分配運(yùn)算內(nèi)置于處理器的指令集中,效率很高,但是分配的內(nèi)存容量有限。

堆區(qū):亦稱動(dòng)態(tài)內(nèi)存分配。程序在運(yùn)行的時(shí)候用malloc或new申請(qǐng)任意大小的內(nèi)存,程序員自己負(fù)責(zé)在適當(dāng)?shù)臅r(shí)候用free或delete釋放內(nèi)存(Java則依賴?yán)厥掌鳎?dòng)態(tài)內(nèi)存的生存期可以由我們決定,如果我們不釋放內(nèi)存,程序?qū)⒃谧詈蟛裴尫诺魟?dòng)態(tài)內(nèi)存。 但是,良好的編程習(xí)慣是:如果某動(dòng)態(tài)內(nèi)存不再使用,需要將其釋放掉。

接下來(lái)我們集中說(shuō)下堆和棧的區(qū)別:

在函數(shù)中(說(shuō)明是局部變量)定義的一些基本類型的變量和對(duì)象的引用變量都是在函數(shù)的棧內(nèi)存中分配。當(dāng)在一段代碼塊中定義一個(gè)變量時(shí),java就在棧中為這個(gè)變量分配內(nèi)存空間,當(dāng)超過(guò)變量的作用域后,java會(huì)自動(dòng)釋放掉為該變量分配的內(nèi)存空間,該內(nèi)存空間可以立刻被另作他用。

堆內(nèi)存用于存放所有由new創(chuàng)建的對(duì)象(內(nèi)容包括該對(duì)象其中的所有成員變量)和數(shù)組。在堆中分配的內(nèi)存,由java虛擬機(jī)自動(dòng)垃圾回收器來(lái)管理。在堆中產(chǎn)生了一個(gè)數(shù)組或者對(duì)象后,還可以在棧中定義一個(gè)特殊的變量,這個(gè)變量的取值等于數(shù)組或者對(duì)象在堆內(nèi)存中的首地址,在棧中的這個(gè)特殊的變量就變成了數(shù)組或者對(duì)象的引用變量,以后就可以在程序中使用棧內(nèi)存中的引用變量來(lái)訪問(wèn)堆中的數(shù)組或者對(duì)象,引用變量相當(dāng)于為數(shù)組或者對(duì)象起的一個(gè)別名,或者代號(hào)。

堆是不連續(xù)的內(nèi)存區(qū)域(因?yàn)橄到y(tǒng)是用鏈表來(lái)存儲(chǔ)空閑內(nèi)存地址,自然不是連續(xù)的),堆大小受限于計(jì)算機(jī)系統(tǒng)中有效的虛擬內(nèi)存(32bit系統(tǒng)理論上是4G),所以堆的空間比較靈活,比較大。棧是一塊連續(xù)的內(nèi)存區(qū)域,大小是操作系統(tǒng)預(yù)定好的,windows下棧大小是2M(也有是1M,在編譯時(shí)確定,VC中可設(shè)置)。

對(duì)于堆,頻繁的new/delete會(huì)造成大量?jī)?nèi)存碎片,使程序效率降低。對(duì)于棧,它是先進(jìn)后出的隊(duì)列,進(jìn)出一一對(duì)應(yīng),不產(chǎn)生碎片,運(yùn)行效率穩(wěn)定高。

舉一個(gè)關(guān)于變量存儲(chǔ)位置的實(shí)例2:

結(jié)論

局部變量的基本數(shù)據(jù)類型和引用存儲(chǔ)于棧中,引用的對(duì)象實(shí)體存儲(chǔ)于堆中?!?yàn)樗鼈儗儆诜椒ㄖ械淖兞?,生命周期隨方法而結(jié)束。

成員變量全部存儲(chǔ)與堆中(包括基本數(shù)據(jù)類型,引用和引用的對(duì)象實(shí)體)——因?yàn)樗鼈儗儆陬悾悓?duì)象終究是要被new出來(lái)使用的。

回到我們的問(wèn)題:內(nèi)存泄露需要關(guān)注的是什么?

我們這里說(shuō)的內(nèi)存泄露,是針對(duì),也只針對(duì)堆內(nèi)存,他們存放的就是引用指向的對(duì)象實(shí)體。

那么第二個(gè)問(wèn)題就是,內(nèi)存為什么會(huì)泄露?

為了判斷Java中是否有內(nèi)存泄露,我們首先必須了解Java是如何管理(堆)內(nèi)存的。Java的內(nèi)存管理就是對(duì)象的分配和釋放問(wèn)題。在Java中,內(nèi)存的分配是由程序完成的,而內(nèi)存的釋放是由垃圾收集器(Garbage Collection,GC)完成的,程序員不需要通過(guò)調(diào)用函數(shù)來(lái)釋放內(nèi)存,但它只能回收無(wú)用并且不再被其它對(duì)象引用的那些對(duì)象所占用的空間。

Java的內(nèi)存垃圾回收機(jī)制是從程序的主要運(yùn)行對(duì)象(如靜態(tài)對(duì)象/寄存器/棧上指向的堆內(nèi)存對(duì)象等)開(kāi)始檢查引用鏈,當(dāng)遍歷一遍后得到上述這些無(wú)法回收的對(duì)象和他們所引用的對(duì)象鏈,組成無(wú)法回收的對(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ì)象不再被引用。

在Java中,這些無(wú)用的對(duì)象都由GC負(fù)責(zé)回收,因此程序員不需要考慮這部分的內(nèi)存泄露。雖然,我們有幾個(gè)函數(shù)可以訪問(wèn)GC,例如運(yùn)行GC的函數(shù)System.gc(),但是根據(jù)Java語(yǔ)言規(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才開(kāi)始工作,也有定時(shí)執(zhí)行的,有的是平緩執(zhí)行GC,有的是中斷式執(zhí)行GC。但通常來(lái)說(shuō),我們不需要關(guān)心這些。

至此,我們來(lái)看看Java中需要被回收的垃圾:

{
Person p1 = new Person();
……
}

引用句柄p1的作用域是從定義到“}”處,執(zhí)行完這對(duì)大括號(hào)中的所有代碼后,產(chǎn)生的Person對(duì)象就會(huì)變成垃圾,因?yàn)橐眠@個(gè)對(duì)象的句柄p1已超過(guò)其作用域,p1失效,在棧中被銷毀,因此堆上的Person對(duì)象不再被任何句柄引用了。 因此person變?yōu)槔?,?huì)被回收。

從上面的例子和解釋,可以看到一個(gè)很關(guān)鍵的詞:引用。

通俗的講,通過(guò)A能調(diào)用并訪問(wèn)到B,那就說(shuō)明A持有 B 的引用,或A就是B的引用,B的引用計(jì)數(shù) +1。

比如 Person p1 = new Person();通過(guò)P1能操作Person對(duì)象,因此P1是Person的引用;

比如類O中有一個(gè)成員變量是I類對(duì)象,因此我們可以使用o.i的方式來(lái)訪問(wèn)I類對(duì)象的成員,因此o持有一個(gè)i對(duì)象的引用。

GC過(guò)程與對(duì)象的引用類型是嚴(yán)重相關(guān)的,我們來(lái)看看Java對(duì)引用的分類Strong reference, SoftReference, WeakReference, PhatomReference

講多一步,這里的軟引用/弱引用一般是做什么的呢?

在Android應(yīng)用的開(kāi)發(fā)中,為了防止內(nèi)存溢出,在處理一些占用內(nèi)存大而且聲明周期較長(zhǎng)的對(duì)象時(shí)候,可以盡量應(yīng)用軟引用和弱引用技術(shù)。

軟/弱引用可以和一個(gè)引用隊(duì)列(ReferenceQueue)聯(lián)合使用,如果軟引用所引用的對(duì)象被垃圾回收器回收,Java虛擬機(jī)就會(huì)把這個(gè)軟引用加入到與之關(guān)聯(lián)的引用隊(duì)列中。利用這個(gè)隊(duì)列可以得知被回收的軟/弱引用的對(duì)象列表,從而為緩沖器清除已失效的軟/弱引用。

假設(shè)我們的應(yīng)用會(huì)用到大量的默認(rèn)圖片,比如應(yīng)用中有默認(rèn)的頭像,默認(rèn)游戲圖標(biāo)等等,這些圖片很多地方會(huì)用到。如果每次都去讀取圖片,由于讀取文件需要硬件操作,速度較慢,會(huì)導(dǎo)致性能較低。所以我們考慮將圖片緩存起來(lái),需要的時(shí)候直接從內(nèi)存中讀取。但是,由于圖片占用內(nèi)存空間比較大,緩存很多圖片需要很多的內(nèi)存,就可能比較容易發(fā)生OutOfMemory異常。這時(shí),我們可以考慮使用軟/弱引用技術(shù)來(lái)避免這個(gè)問(wèn)題發(fā)生。以下就是高速緩沖器的雛形:

首先定義一個(gè)HashMap,保存軟引用對(duì)象。

private Map> imageCache = new HashMap>();

再來(lái)定義一個(gè)方法,保存Bitmap的軟引用到HashMap。

public class CacheBySoftRef {
    // 首先定義一個(gè)HashMap,保存軟引用對(duì)象。
    private Map> imageCache = new HashMap>();
    // 再來(lái)定義一個(gè)方法,保存Bitmap的軟引用到HashMap。
    public void addBitmapToCache(String path) {
        // 強(qiáng)引用的Bitmap對(duì)象
        Bitmap bitmap = BitmapFactory.decodeFile(path);
        // 軟引用的Bitmap對(duì)象
        SoftReference softBitmap = new SoftReference(bitmap);
        // 添加該對(duì)象到Map中使其緩存
        imageCache.put(path, softBitmap);
    }
    // 獲取的時(shí)候,可以通過(guò)SoftReference的get()方法得到Bitmap對(duì)象。
    public Bitmap getBitmapByPath(String path) {
        // 從緩存中取軟引用的Bitmap對(duì)象
        SoftReference softBitmap = imageCache.get(path);
        // 判斷是否存在軟引用
        if (softBitmap == null) {
            return null;
        }
        // 通過(guò)軟引用取出Bitmap對(duì)象,如果由于內(nèi)存不足Bitmap被回收,將取得空 ,如果未被回收,則可重復(fù)使用,提高速度。
        Bitmap bitmap = softBitmap.get();
        return bitmap;
    }
}

使用軟引用以后,在OutOfMemory異常發(fā)生之前,這些緩存的圖片資源的內(nèi)存空間可以被釋放掉的,從而避免內(nèi)存達(dá)到上限,避免Crash發(fā)生。

如果只是想避免OutOfMemory異常的發(fā)生,則可以使用軟引用。如果對(duì)于應(yīng)用的性能更在意,想盡快回收一些占用內(nèi)存比較大的對(duì)象,則可以使用弱引用。

另外可以根據(jù)對(duì)象是否經(jīng)常使用來(lái)判斷選擇軟引用還是弱引用。如果該對(duì)象可能會(huì)經(jīng)常使用的,就盡量用軟引用。如果該對(duì)象不被使用的可能性更大些,就可以用弱引用。

回到我們的問(wèn)題,為什么內(nèi)存會(huì)泄露?

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

Bugly 是騰訊內(nèi)部產(chǎn)品質(zhì)量監(jiān)控平臺(tái)的外發(fā)版本,其主要功能是App發(fā)布以后,對(duì)用戶側(cè)發(fā)生的Crash以及卡頓現(xiàn)象進(jìn)行監(jiān)控并上報(bào),讓開(kāi)發(fā)同學(xué)可以第一時(shí)間了解到App的質(zhì)量情況,及時(shí)機(jī)型修改。目前騰訊內(nèi)部所有的產(chǎn)品,均在使用其進(jìn)行線上產(chǎn)品的崩潰監(jiān)控。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/64733.html

相關(guān)文章

  • 內(nèi)存泄露入門(mén)精通部曲排查方法

    摘要:對(duì)比操作前后的來(lái)定位內(nèi)存泄露的根因所在。手機(jī)管家內(nèi)存泄露每日監(jiān)控方案目前手機(jī)管家的內(nèi)存泄露每日監(jiān)控會(huì)自動(dòng)運(yùn)行并輸出是否存在疑似泄露的報(bào)告郵件,不論泄露對(duì)象的大小。 騰訊Bugly特約作者: 姚潮生 最原始的內(nèi)存泄露測(cè)試 重復(fù)多次操作關(guān)鍵的可疑的路徑,從內(nèi)存監(jiān)控工具中觀察內(nèi)存曲線,是否存在不斷上升的趨勢(shì)且不會(huì)在程序返回時(shí)明顯回落。這種方式可以發(fā)現(xiàn)最基本,也是最明顯的內(nèi)存泄露問(wèn)題,對(duì)用戶價(jià)...

    李世贊 評(píng)論0 收藏0
  • 內(nèi)存泄露入門(mén)精通部曲排查方法

    摘要:對(duì)比操作前后的來(lái)定位內(nèi)存泄露的根因所在。手機(jī)管家內(nèi)存泄露每日監(jiān)控方案目前手機(jī)管家的內(nèi)存泄露每日監(jiān)控會(huì)自動(dòng)運(yùn)行并輸出是否存在疑似泄露的報(bào)告郵件,不論泄露對(duì)象的大小。 騰訊Bugly特約作者: 姚潮生 最原始的內(nèi)存泄露測(cè)試 重復(fù)多次操作關(guān)鍵的可疑的路徑,從內(nèi)存監(jiān)控工具中觀察內(nèi)存曲線,是否存在不斷上升的趨勢(shì)且不會(huì)在程序返回時(shí)明顯回落。這種方式可以發(fā)現(xiàn)最基本,也是最明顯的內(nèi)存泄露問(wèn)題,對(duì)用戶價(jià)...

    sutaking 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<