溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Java應(yīng)用程序中內(nèi)存泄漏及內(nèi)存管理的示例分析

發(fā)布時(shí)間:2021-12-30 16:54:06 來源:億速云 閱讀:159 作者:小新 欄目:編程語言

這篇文章主要介紹Java應(yīng)用程序中內(nèi)存泄漏及內(nèi)存管理的示例分析,文中介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們一定要看完!

btw,一些靜態(tài)代碼掃描工具也能檢測(cè)出不好的編程習(xí)慣帶來潛在的內(nèi)存泄露的風(fēng)險(xiǎn)。

Java平臺(tái)的一個(gè)突出的特性是自動(dòng)內(nèi)存管理。很多人把這種特性誤讀為Java沒有內(nèi)存泄露。然而,在我印象中,現(xiàn)代Java框架以及基于Java的平臺(tái)并非如此。特別是Android平臺(tái),能舉出很多反例。為了讓大家對(duì)Java平臺(tái)的內(nèi)存泄露有一個(gè)初步的認(rèn)識(shí),我們先來看一個(gè)Java實(shí)現(xiàn)的棧:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

classSimpleStack {

 

    privatefinalObject[] objectPool =newObject[10];

    privateintpointer = -1;

 

    publicObject pop() {

        if(pointer <0) {

            thrownewIllegalStateException("no elements on stack");

        }

        returnobjectPool[pointer--];

    }

 

    publicObject peek() {

        if(pointer <0) {

            thrownewIllegalStateException("no elements on stack");

        }

        returnobjectPool[pointer];

 

    }

 

    publicvoidpush(Object object) {

        if(pointer >8) {

            thrownewIllegalStateException("stack overflow");

        }

        objectPool[++pointer] = object;

    }

}

這個(gè)棧的實(shí)現(xiàn)基于一個(gè)對(duì)象數(shù)組,并維護(hù)了一個(gè)用于指向棧內(nèi)當(dāng)前可用單元的整型指針。上面的實(shí)現(xiàn)中,每次從棧頂彈出元素都會(huì)產(chǎn)生內(nèi)存泄露。確切的說,即使不再使用棧頂元素,對(duì)象數(shù)組會(huì)繼續(xù)持有棧頂元素的引用(除非棧頂元素再次入棧,棧頂元素的引用會(huì)被完全相同的引用覆蓋)。因此,即便這個(gè)對(duì)象的其他引用都被釋放,Java虛擬機(jī)也不能回收這個(gè)對(duì)象。由于這種棧實(shí)現(xiàn)并不允許外界直接訪問其底層的對(duì)象池,因此除非有新元素入棧并被放置在棧內(nèi)的同一個(gè)位置上,否則這個(gè)無法訪問的引用將阻止垃圾回收器回收該對(duì)象。

幸運(yùn)的是,這個(gè)內(nèi)存泄露很容易修復(fù):

1

2

3

4

5

6

7

8

9

10

publicObject pop() {

    if(pointer <1) {

        thrownewIllegalStateException("no elements on stack");

    }

    try{

        returnobjectPool[pointer];

    }finally{

        objectPool[pointer--] =null;

    }

}

當(dāng)然,在日常的Java開發(fā)中一般不會(huì)去實(shí)現(xiàn)一個(gè)內(nèi)存數(shù)據(jù)結(jié)構(gòu)。因此,讓我們來看一個(gè)更常見的Java內(nèi)存泄漏的例子。在Java開發(fā)中經(jīng)常用到的觀察者模式就會(huì)引起內(nèi)存泄露:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

classObserved {

 

    publicinterfaceObserver {

        voidupdate();

    }

 

    privateCollection<Observer> observers =newHashSet<Observer>();

 

    voidaddListener(Observer observer) {

        observers.add(observer);

    }

 

    voidremoveListener(Observer observer) {

        observers.remove(observer);

    }

 

}

這次提供了一個(gè)直接刪除底層對(duì)象池引用的方法。基于這種實(shí)現(xiàn),任何已注冊(cè)的Observer在使用后只要被正確注銷,就不會(huì)存在內(nèi)存泄漏的風(fēng)險(xiǎn)。然而,假設(shè)這樣一個(gè)場(chǎng)景,框架的使用者在使用完Observer之后并沒有及時(shí)注銷。同理Observer將永遠(yuǎn)不會(huì)被回收,因?yàn)镺bserved一直保留著它的引用。更糟的是,沒有Observer引用,是無法從Observed對(duì)象池外部刪除Observer的,即無法回收未被及時(shí)注銷的Observer。

不過,有一種簡(jiǎn)單的方法能夠修復(fù)這種潛在的內(nèi)存泄露——弱引用。我個(gè)人認(rèn)為這是Java程序員都應(yīng)該知道的特性。簡(jiǎn)單地說,弱引用在功能上和普通的引用一樣,但它不會(huì)妨礙垃圾回收。因此JVM執(zhí)行垃圾回收時(shí),如果沒有發(fā)現(xiàn)強(qiáng)引用,那么你就會(huì)發(fā)現(xiàn)弱引用會(huì)被置為null。要使用弱引用,我們可以將上面的代碼改為:

1

2

privateCollection<Observer> observers = Collections.newSetFromMap(

        newWeakHashMap<Observer, Boolean>());

WeakHashMap是一個(gè)現(xiàn)成的弱引用Map,Map的鍵都是弱引用對(duì)象。使用WeakHashMap后,被觀察者將不會(huì)阻止JVM對(duì)Observer進(jìn)行垃圾回收。然而,你必須在代碼注釋中強(qiáng)調(diào)這一點(diǎn)。因?yàn)檫@個(gè)特性可能引起一些問題,比如使用者想要注冊(cè)一個(gè)常駐內(nèi)存的Observer(例如日志庫),但他們并沒有打算維持一個(gè)Observer引用。例如,Android平臺(tái)上的OnSharedPreferencesChangeListener使用了弱引用,但文檔中并沒有聲明這一特性。這給開發(fā)者帶來了很多麻煩。

在本文的開頭我提到了,現(xiàn)在的很多框架都需要使用者謹(jǐn)慎地管理內(nèi)存。我想至少有兩個(gè)例子可以印證這個(gè)觀點(diǎn)。

Android平臺(tái)

Android應(yīng)用程序的核心類采用了基于生命周期的編程模型。這意味著你不能自行創(chuàng)建和管理這些類的實(shí)例,這些實(shí)例將由Android操作系統(tǒng)在需要的時(shí)候替你創(chuàng)建(比如應(yīng)用程序需要顯示某個(gè)特定的畫面)。同理,Android操作系統(tǒng)將會(huì)決定應(yīng)用何時(shí)不再需要某個(gè)特定實(shí)例(比如用戶關(guān)閉了應(yīng)用界面),并通過調(diào)用該實(shí)例特定的生命周期方法來通知該實(shí)例即將被刪除。但是,如果你將這個(gè)實(shí)例的引用泄露到某個(gè)全局上下文,Android JVM將不能對(duì)這個(gè)實(shí)例進(jìn)行回收。這與Android本身的設(shè)計(jì)理念相違背。由于Android手機(jī)通常沒有限制應(yīng)用程序的內(nèi)存,即使在非常簡(jiǎn)單的應(yīng)用中,也會(huì)頻繁創(chuàng)建和銷毀對(duì)象,所以在清理引用時(shí)必須格外小心。

不幸的是,應(yīng)用程序核心類引用很容易被泄露到外部。你能看出下面的例子是如何泄露引用的嗎?

1

2

3

4

5

6

7

8

9

10

11

12

classExampleActivityextendsActivity {

 

    @Override

    publicvoidonCreate(Bundle bundle) {

        startService(newIntent(this, ExampleService.class).putExtra("mykey",

                newSerializable() {

                    publicString getInfo() {

                        return"myinfo";

                    }

                }));

    }

}

如果你認(rèn)為是傳入Intent構(gòu)造函數(shù)的this指針泄露了當(dāng)前實(shí)例的引用,你就錯(cuò)了。這個(gè)Intent對(duì)象僅用于啟動(dòng)ExampleService,它會(huì)在ExampleService啟動(dòng)之后被銷毀。然而,那個(gè)實(shí)現(xiàn)了Serializable接口的匿名內(nèi)部類會(huì)持有閉包類ExampleActivity的引用。如果ExampleService一直維持著這個(gè)匿名類實(shí)例引用,那么也會(huì)持有這個(gè)ExampleActivity實(shí)例的引用。

出于這個(gè)原因,我建議Android開發(fā)者避免使用匿名類。

Web應(yīng)用框架(特別是Wicket)

Web應(yīng)用框架通常將半永久性的用戶數(shù)據(jù)存放在Session中。你在Session中寫入的任何數(shù)據(jù)都會(huì)在內(nèi)存中滯留,而且滯留的時(shí)間無法確定。如果有一定數(shù)量的訪問者在你的Session中“亂扔垃圾”,運(yùn)行Servlet容器的JVM早晚會(huì)掛掉。因此,你謹(jǐn)慎管理引用的另一個(gè)極端案例就是Wicket框架:Wicket框架會(huì)將用戶的所有訪問序列化成歷史版本。這種過分簡(jiǎn)單的設(shè)計(jì)意味著,如果某個(gè)訪問者點(diǎn)擊十次歡迎頁面,Wicket框架會(huì)在硬盤默認(rèn)路徑下序列化十個(gè)對(duì)象。Wicket頁面對(duì)象持有的所有對(duì)象引用都會(huì)和頁面對(duì)象一起被序列化到硬盤上,所以在管理引用時(shí)必須格外小心。

讓我們來看一個(gè)錯(cuò)誤使用Wicket框架的示例:

1

2

3

4

5

6

7

8

classExampleWelcomePageextendsWebPage {

 

    privatefinalList<People> peopleList;

 

    publicExampleWelcomePage (PageParameters pageParameters) {

        peopleList =newService().getWorldPhonebook();

    }

}

用戶點(diǎn)擊十次歡迎頁面,就會(huì)在服務(wù)器硬盤上存儲(chǔ)十份WorldPhoneBook拷貝。因此,在你使用Wicket開發(fā)應(yīng)用時(shí),務(wù)必要使用LoadableDetachableModels管理引用。

以上是“Java應(yīng)用程序中內(nèi)存泄漏及內(nèi)存管理的示例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對(duì)大家有幫助,更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI