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