Java应用程序中的内存泄漏和内存管理
Java平臺最突出的功能之一是其自動內(nèi)存管理。 許多人錯誤地將此功能轉(zhuǎn)換為Java中沒有內(nèi)存泄漏 。 但是,事實并非如此,我給人的印象是,現(xiàn)代Java框架和基于Java的平臺,尤其是Android平臺,越來越與這種錯誤的假設(shè)相矛盾。 為了對Java平臺上的內(nèi)存泄漏如何產(chǎn)生印象,請查看以下堆棧實現(xiàn):
此堆棧實現(xiàn)以數(shù)組形式存儲其內(nèi)容,并另外管理一個指向當(dāng)前活動堆棧單元的整數(shù)。 每當(dāng)元素從堆棧頂部彈出時,此實現(xiàn)都會導(dǎo)致內(nèi)存泄漏。 更準(zhǔn)確地說,堆棧將保留對數(shù)組頂部元素的引用,即使不再使用它也是如此。 (除非再次將其壓入堆棧,否則將導(dǎo)致引用被完全相同的引用覆蓋。)因此,即使在釋放了對該對象的所有其他引用之后,Java也將無法對其進(jìn)行垃圾回收。 由于堆棧實現(xiàn)不允許直接訪問基礎(chǔ)對象池,因此,在將新元素推入堆棧的相同索引之前,此不可訪問的引用將阻止對引用對象進(jìn)行垃圾回收。
幸運的是,這種內(nèi)存泄漏很容易解決:
public Object pop() {if(pointer < 1) {throw new IllegalStateException("no elements on stack");}try {return objectPool[pointer];} finally {objectPool[pointer--] = null;}}當(dāng)然,在日常Java開發(fā)中,內(nèi)存結(jié)構(gòu)的實現(xiàn)并不是一項非常常見的任務(wù)。 因此,讓我們看一個更常見的Java內(nèi)存泄漏示例。 這種泄漏通常是由常用的觀察者模式引起的 :
class Observed {public interface Observer {void update();}private Collection<Observer> observers = new HashSet<Observer>();void addListener(Observer observer) {observers.add(observer);}void removeListener(Observer observer) {observers.remove(observer);}}這次,存在一種允許直接從基礎(chǔ)對象池中刪除引用的方法。 只要任何已注冊的觀察者在使用后都從外部取消注冊,就不會在此實現(xiàn)中擔(dān)心任何內(nèi)存泄漏。 但是,請設(shè)想一個場景,在這種情況下,您或框架的用戶在使用觀察器后會忘記注銷注冊。 同樣,觀察者將永遠(yuǎn)不會被垃圾回收,因為觀察者會一直引用它。 更糟糕的是,如果沒有對這個現(xiàn)在無用的觀察者的引用,就不可能從外部從觀察者的對象池中刪除觀察者。
但是,這種潛在的內(nèi)存泄漏也很容易解決,其中涉及使用弱引用 ,這是我個人希望程序員會更加意識到的Java平臺功能。 簡而言之,弱引用的行為類似于普通引用,但不會阻止垃圾回收。 因此,如果沒有剩余的強(qiáng)引用,并且JVM執(zhí)行了垃圾回收,則可以突然發(fā)現(xiàn)弱引用為null。 使用弱引用,我們可以像這樣更改上面的代碼:
private Collection<Observer> observers = Collections.newSetFromMap(new WeakHashMap<Observer, Boolean>());WeakHashMap是地圖的現(xiàn)成實現(xiàn),使用弱引用包裝其鍵。 通過此更改,被觀察者將不會阻止其觀察者進(jìn)行垃圾收集。 但是,您應(yīng)該始終在Java文檔中指出此行為! 如果您的代碼用戶想要像日志實用程序一樣向您的觀察者注冊永久觀察者,而他們不打算對其進(jìn)行引用,則可能會造成很大的混亂。 例如,Android的OnSharedPreferencesChangeListener使用弱引用來監(jiān)聽,而沒有記錄此功能。 這可以讓您徹夜難眠!
在本博客文章的開頭,我建議當(dāng)今的許多框架都需要其用戶進(jìn)行仔細(xì)的內(nèi)存管理,并且我想就該主題至少給出兩個示例來解釋這一問題。
Android平臺:
Android編程為核心應(yīng)用程序類引入了生命周期編程模型。 總而言之,這意味著您無法控制自己創(chuàng)建和管理這些類的對象實例,而是可以在需要時由Android OS為您創(chuàng)建它們。 (例如,如果您的應(yīng)用程序應(yīng)該顯示特定的屏幕。)以同樣的方式,Android將決定何時不再需要特定的實例(例如,當(dāng)用戶關(guān)閉應(yīng)用程序的屏幕時),并通知您有關(guān)信息。通過在實例上調(diào)用特定的生命周期方法進(jìn)行刪除。 但是,如果讓對該對象的引用進(jìn)入某些全局上下文,則Android JVM將無法按照其意圖進(jìn)行垃圾回收。 由于Android手機(jī)通常在內(nèi)存方面受到限制,并且因為Android的對象創(chuàng)建和銷毀例程甚至對于簡單的應(yīng)用程序都可能變得非常瘋狂,因此您必須格外小心以清理引用。
不幸的是,對核心應(yīng)用程序類的引用很容易消失。 在下面的示例中,您可以發(fā)現(xiàn)滑動參考嗎?
class ExampleActivity extends Activity {@Overridepublic void onCreate(Bundle bundle) {startService(new Intent(this, ExampleService.class).putExtra("mykey",new Serializable() {public String getInfo() {return "myinfo";}}));} }如果您認(rèn)為這是intent的構(gòu)造函數(shù)中的this引用,那您是錯誤的。 該意圖僅用作服務(wù)的啟動命令,并且在服務(wù)啟動后將被刪除。 取而代之的是,匿名內(nèi)部類將保留對其封閉類的引用,即ExampleActivity類。 如果接收的ExampleService保留對該匿名類的實例的引用,則結(jié)果還將保留對ExampleActivity實例的引用。 因此,我只能建議Android開發(fā)人員避免使用匿名類。
Web應(yīng)用程序框架(特別是
Web應(yīng)用程序框架通常在會話中存儲半永久性用戶數(shù)據(jù)。 無論您寫入會話的什么內(nèi)容,通常都會在內(nèi)存中保留不確定的時間。 如果您在有大量訪問者的情況下浪費了會話,則servlet容器的JVM遲早會打包。 Wicket框架是需要格外小心的一個極端示例:Wicket序列化用戶以版本化狀態(tài)訪問的任何頁面。 簡單地說,這意味著如果網(wǎng)站的訪問者之一單擊您的歡迎頁面十次,Wicket將以其默認(rèn)配置在您的硬盤驅(qū)動器上存儲十個序列化對象。 這需要格外小心,因為Wicket頁面對象持有的所有引用都將導(dǎo)致這些引用對象與頁面一起被序列化。 看一下這個不好的實踐Wicket示例:
class ExampleWelcomePage extends WebPage {private final List<People> peopleList;public ExampleWelcomePage (PageParameters pageParameters) {peopleList = new Service().getWorldPhonebook();} }通過十次單擊歡迎頁面,您的用戶僅將十本世界電話簿副本存儲在服務(wù)器硬盤驅(qū)動器上。 因此,請始終在Wicket應(yīng)用程序中使用LoadableDetachableModel ,它將為您提供參考管理。
跟蹤Java應(yīng)用程序中的內(nèi)存泄漏可能很麻煩,因此,我想將JProfiler命名為有用的(但不幸的是非免費的)調(diào)試工具。 它允許您以堆轉(zhuǎn)儲的形式瀏覽Java正在運行的應(yīng)用程序的內(nèi)部。 如果內(nèi)存泄漏對于您的應(yīng)用程序來說是一個問題,我建議您嘗試一下JProfiler。 有可用的評估許可證。
進(jìn)一步的閱讀 :如果要在自定義類加載器時看到另一個有趣的內(nèi)存泄漏事件,請參閱Zeroturnaround博客 。
翻譯自: https://www.javacodegeeks.com/2014/01/memory-leaks-and-memory-management-in-java-applications.html
總結(jié)
以上是生活随笔為你收集整理的Java应用程序中的内存泄漏和内存管理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 手机看片安卓下载(手机看片安卓)
- 下一篇: Spring Boot –现代Java应