您好,登錄后才能下訂單哦!
這篇文章主要介紹Java應用程序中內存泄漏及內存管理的示例分析,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!
btw,一些靜態代碼掃描工具也能檢測出不好的編程習慣帶來潛在的內存泄露的風險。
Java平臺的一個突出的特性是自動內存管理。很多人把這種特性誤讀為Java沒有內存泄露。然而,在我印象中,現代Java框架以及基于Java的平臺并非如此。特別是Android平臺,能舉出很多反例。為了讓大家對Java平臺的內存泄露有一個初步的認識,我們先來看一個Java實現的棧:
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; } } |
這個棧的實現基于一個對象數組,并維護了一個用于指向棧內當前可用單元的整型指針。上面的實現中,每次從棧頂彈出元素都會產生內存泄露。確切的說,即使不再使用棧頂元素,對象數組會繼續持有棧頂元素的引用(除非棧頂元素再次入棧,棧頂元素的引用會被完全相同的引用覆蓋)。因此,即便這個對象的其他引用都被釋放,Java虛擬機也不能回收這個對象。由于這種棧實現并不允許外界直接訪問其底層的對象池,因此除非有新元素入棧并被放置在棧內的同一個位置上,否則這個無法訪問的引用將阻止垃圾回收器回收該對象。
幸運的是,這個內存泄露很容易修復:
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; } } |
當然,在日常的Java開發中一般不會去實現一個內存數據結構。因此,讓我們來看一個更常見的Java內存泄漏的例子。在Java開發中經常用到的觀察者模式就會引起內存泄露:
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); }
} |
這次提供了一個直接刪除底層對象池引用的方法。基于這種實現,任何已注冊的Observer在使用后只要被正確注銷,就不會存在內存泄漏的風險。然而,假設這樣一個場景,框架的使用者在使用完Observer之后并沒有及時注銷。同理Observer將永遠不會被回收,因為Observed一直保留著它的引用。更糟的是,沒有Observer引用,是無法從Observed對象池外部刪除Observer的,即無法回收未被及時注銷的Observer。
不過,有一種簡單的方法能夠修復這種潛在的內存泄露——弱引用。我個人認為這是Java程序員都應該知道的特性。簡單地說,弱引用在功能上和普通的引用一樣,但它不會妨礙垃圾回收。因此JVM執行垃圾回收時,如果沒有發現強引用,那么你就會發現弱引用會被置為null。要使用弱引用,我們可以將上面的代碼改為:
1 2 | privateCollection<Observer> observers = Collections.newSetFromMap( newWeakHashMap<Observer, Boolean>()); |
WeakHashMap是一個現成的弱引用Map,Map的鍵都是弱引用對象。使用WeakHashMap后,被觀察者將不會阻止JVM對Observer進行垃圾回收。然而,你必須在代碼注釋中強調這一點。因為這個特性可能引起一些問題,比如使用者想要注冊一個常駐內存的Observer(例如日志庫),但他們并沒有打算維持一個Observer引用。例如,Android平臺上的OnSharedPreferencesChangeListener使用了弱引用,但文檔中并沒有聲明這一特性。這給開發者帶來了很多麻煩。
在本文的開頭我提到了,現在的很多框架都需要使用者謹慎地管理內存。我想至少有兩個例子可以印證這個觀點。
Android應用程序的核心類采用了基于生命周期的編程模型。這意味著你不能自行創建和管理這些類的實例,這些實例將由Android操作系統在需要的時候替你創建(比如應用程序需要顯示某個特定的畫面)。同理,Android操作系統將會決定應用何時不再需要某個特定實例(比如用戶關閉了應用界面),并通過調用該實例特定的生命周期方法來通知該實例即將被刪除。但是,如果你將這個實例的引用泄露到某個全局上下文,Android JVM將不能對這個實例進行回收。這與Android本身的設計理念相違背。由于Android手機通常沒有限制應用程序的內存,即使在非常簡單的應用中,也會頻繁創建和銷毀對象,所以在清理引用時必須格外小心。
不幸的是,應用程序核心類引用很容易被泄露到外部。你能看出下面的例子是如何泄露引用的嗎?
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"; } })); } } |
如果你認為是傳入Intent構造函數的this指針泄露了當前實例的引用,你就錯了。這個Intent對象僅用于啟動ExampleService,它會在ExampleService啟動之后被銷毀。然而,那個實現了Serializable接口的匿名內部類會持有閉包類ExampleActivity的引用。如果ExampleService一直維持著這個匿名類實例引用,那么也會持有這個ExampleActivity實例的引用。
出于這個原因,我建議Android開發者避免使用匿名類。
Web應用框架通常將半永久性的用戶數據存放在Session中。你在Session中寫入的任何數據都會在內存中滯留,而且滯留的時間無法確定。如果有一定數量的訪問者在你的Session中“亂扔垃圾”,運行Servlet容器的JVM早晚會掛掉。因此,你謹慎管理引用的另一個極端案例就是Wicket框架:Wicket框架會將用戶的所有訪問序列化成歷史版本。這種過分簡單的設計意味著,如果某個訪問者點擊十次歡迎頁面,Wicket框架會在硬盤默認路徑下序列化十個對象。Wicket頁面對象持有的所有對象引用都會和頁面對象一起被序列化到硬盤上,所以在管理引用時必須格外小心。
讓我們來看一個錯誤使用Wicket框架的示例:
1 2 3 4 5 6 7 8 | classExampleWelcomePageextendsWebPage {
privatefinalList<People> peopleList;
publicExampleWelcomePage (PageParameters pageParameters) { peopleList =newService().getWorldPhonebook(); } } |
用戶點擊十次歡迎頁面,就會在服務器硬盤上存儲十份WorldPhoneBook拷貝。因此,在你使用Wicket開發應用時,務必要使用LoadableDetachableModels管理引用。
以上是“Java應用程序中內存泄漏及內存管理的示例分析”這篇文章的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。