關於面試題java內存泄漏想到的(2)

Java資料下載地址:

http://blog.sina.com.cn/s/blog_67ba07d60100lolk.html

2java書籍找齊,一本一本查找關於內存管理的介紹,以及內存泄漏的介紹:

1、JavaLanguage Specification, Third Edition,此書編寫風格有些類似於C++他爹寫的The C++Programming Language,書中可運行的例子較少,不像java編程思想那麼容易理解的例子,基本是能省略就省略,語言非常簡介,層次非常清楚,內容非常全面詳細無論大小知識,都很規範的給出說明,畢竟是規範嘛。關於內存泄漏沒有說明,只有java垃圾回收機制:12.6小結(Finalization of Class Instances)

2、EffectiveJava , Second Edition第六條,消除過期引用的對象,很清楚的說明了這個問題,百度的面試題,以及JAVA高級工程師的面試題應該是源於此書

3、JavaConcurrency in Practice,簡歷上面寫着精通java多線程編程,我只能說我做過java多線程的例子,不看此書不要說精通多線程編程。示例豐富,講解詳細。

4、JavaPuzzles: Traps, Pitfalls and Corner Cases也是非常適合出筆試題的一本書籍,java細節性的東西,稍不注意,編程就忽略掉的編程細節,只是不知道有沒有關於內存泄漏的章節,大致看了一下,似乎沒有,但是這是一本對java用編程經驗的水平人的非常值得看的書籍,不看這些書籍,我想就是你再寫十萬行代碼也是無用。

 

5、java編程思想,大家公認的java經典書籍,剛畢業那會兒一直看不懂,一直以爲是很經典的書籍,現在看來,真的是入門級的書籍。不過是Java Language Specification的擴大版本,Java LanguageSpecification裏面通常只有理論,沒有示例,Thinking in java裏面配上了示例,配上了淺顯的語言。4.3節Cleanup: finalization and garbage collection有介紹java垃圾回收機制的

6Better,faster, lighter Java沒有下載到能打開的格式,本人電腦chm格式的壞掉。

7Core Javathinking injava更深入些,但是內容不想後者那麼全面,沒有介紹內存有垃圾回收的章節

8The JavaVirtual Machine Specification  SUN公司的官方介紹java虛擬機的,自然也有垃圾回收,內存分配的事情,可惜沒有下載到中文版,1.5 Garbage Collected Heap,垃圾回收堆,然後就幾句話(總書也就84頁)The Java heapis the runtime data area from which class instances (objects) are allocated.The Java language is designed to be garbage collected — it does not give theprogrammer the ability to deallocate objects explicitly.

Java does notpresuppose any particular kind of garbage collection; various algorithms may beused depending on system requirements

翻譯一下吧:JAVA 的堆是運行時數據區域,類實例(對象)從這些堆中被收集。JAVA語言被設計爲垃圾回收的機制,不給程序員明確分配對象的權力。Java不預定任何特別形式的垃圾集合,各種垃圾回收算法依賴於具體系統需求。翻譯完了,得不到什麼答案。

9、ExceptionHandling, Testing, and Debugging看名字應該是一本不錯的書籍,但是沒有下載到電子版

10、Java CodeConvention java官網的說明,基本都是變量怎麼命名,類怎麼命名之類的常規介紹

11、網上搜到一本書籍《深入理解Java虛擬機JVM高級特性與最佳實踐》,感覺就是一本從網上東拉西扯的一本書,作者竟然用著,不過畢竟是中國人編寫的書,語言還算容易理解,結合java編程思想看,還不錯,裏面就是把jvm之類的文章要點給整理了一下。

可以參考的書目:EffectiveJava,java編程思想,深入理解Java虛擬機JVM高級特性與最佳實踐》

 

針對問題而來的書籍Effective Java

第二章 第六條Eliminate obsoleteobject references(消除過期的對象的引用)

內容不多,摘抄原文,並翻譯一下:

When you switchfrom a language with manual memory management, such as C

or C++, to agarbage-collected language, your job as a programmer is made much

easier by thefact that your objects are automatically reclaimed when you’re

through withthem. It seems almost like magic when you first experience it. It can

easily lead tothe impression that you don’t have to think about memory management, but thisisn’t quite true.

Consider thefollowing simple stack implementation:

// Can you spot the"memory leak"?

 

當你從手動管理內存的語言例如,C,C++,轉移到垃圾回收機制的語言上面,你作爲程序員的工作由於以下的這個事情變的更容易,就是你的對象在你使用完之後,會自動的被回收利用。第一次這麼使用的體驗,讓你覺得這是多麼的神奇。這種體驗很容易是你產生一個想法,“你再也不用管理內存了的”,這是個相當錯誤的想法。

考慮下面的小例子的棧實現。

你能找出裏面的內存泄漏嗎?

 

public classStack {

private Object[]elements;

private int size= 0;

private staticfinal int DEFAULT_INITIAL_CAPACITY = 16;

public Stack() {

elements = newObject[DEFAULT_INITIAL_CAPACITY];

}

public voidpush(Object e) {

ensureCapacity();

elements[size++]= e;

}

public Objectpop() {

if (size == 0)

throw new EmptyStackException();

returnelements[--size];

}

/**

* Ensure spacefor at least one more element, roughly

* doubling thecapacity each time the array needs to grow.

*/

private voidensureCapacity() {

if(elements.length == size)

elements =Arrays.copyOf(elements, 2 * size + 1);

}

}

 

拷貝程序運行,導入包

importjava.util.Arrays;

importjava.util.EmptyStackException;

size表示棧指針,默認大小16,構造函數按照默認值構造數組。入棧時候檢查棧頂指針是不是最大了,如果是,則利用Arrays的copyOf,重新內存分配。

測試方法

    @Test

    public void testPop() {

       Stack stack = new Stack();

       String str = "fanjf";

       for (int i = 0; i < 20; i++) {

           stack.push(str + i);

       }

 

       for (int i = 0; i < 20; i++) {

 

           String strPop = (String) stack.pop();

           System.out.println(strPop);

       }

    }

 

There’s nothingobviously wrong with this program (but see Item 26 for a

genericversion). You could test it exhaustively, and it would pass every test with

flying colors,but there’s a problem lurking. Loosely speaking, the program has a

“memory leak,” whichcan silently manifest itself as reduced performance due to

increasedgarbage collector activity or increased memory footprint. In extreme

cases, suchmemory leaks can cause disk paging and even program failure with an

OutOfMemoryError,but such failures are relatively rare.

這個沒有明顯錯誤的程序(但是請看第26條的常見泛型的版,此處比較第26條的泛型版本只有pop 函數有差異elements[size] = null;對象彈出後進行了置空)。你不能耗盡一切的測試它,它總能帶着勝利的旗幟通過你的測試,但是它真的有潛在問題。坦率的說,這個程序存在內存泄漏。隨着垃圾回收活動增加或者內存佔用的增加,這個程序會慢慢的展示出性能下降的情況。極端情況下,這種內存泄漏,會引起內存分頁調度,甚至程序因爲內存溢出而導致程序失敗,儘管這種失敗不常見。疑問1:怎麼查出它是內存泄漏的?

 

So where is thememory leak? If a stack grows and then shrinks, the objects

that were poppedoff the stack will not be garbage collected, even if the program

using the stackhas no more references to them. This is because the stack maintains obsoletereferences to these objects. An obsolete reference is simply a reference thatwill never be dereferenced again. In this case, any references outside of the“active portion” of the element array are obsolete. The active portion consistsof the elements whose index is less than size.

然而哪裏有內存泄漏呢?如果棧增加或者然後收縮,出棧的元素不會被垃圾回收器回收,甚至用棧的程序不再引用這些對象。因爲棧維持者這些對象的舊的引用。一個過期的引用是指那些再也不被間接引用不會被解除的簡單引用。這個例子中,任何數組元素有效(活動)部分之外的引用都是過期的。有效部分包含那些索引小於棧大小的元素

Memory leaks ingarbage-collected languages (more properly known as unintentional object retentions)are insidious. If an object reference is unintentionally retained, not only isthat object excluded from garbage collection, but so too are any objectsreferenced by that object, and so on. Even if only a few object references areunintentionally retained, many, many objects may be prevented from  being garbage collected, with potentiallylarge effects on performance.

內存泄漏在有垃圾回收機制的語言中是不明顯的(更恰當的應該說是無意的對象保留),

如果對象被無意中保留了,那麼不僅僅這些對象超出垃圾回收機制,這些對象引用的對象也超出垃圾回收機制了。甚至只是很少的對象引用被無意保留,但是越來越多的對象超越垃圾回收機制,最終會導致系統性能下降,最終對系統性能造成潛在的大影響。

The fix for thissort of problem is simple: null out references once they become obsolete. Inthe case of our Stackclass, the reference to an item becomes obsolete as soonas it’s popped off the stack.  Thecorrected version of the pop

處理這種問題是簡單的。一旦引用過期,指向null就可以了,在我們的Stack類示例中個,當一個條目的引用當出棧之後就會過期。

當前版本的出棧代碼如下

method lookslike this:

public Objectpop() {

if (size == 0)

throw newEmptyStackException();

Object result =elements[--size];

elements[size] =null; // Eliminate obsolete reference

return result;

}

An added benefitof nulling out obsolete references is that, if they are subsequentlydereferenced by mistake, the program will immediately fail with a NullPointerException,rather than quietly doing the wrong thing. It is always beneficial to detectprogrammingerrors as quickly as possible.

過期引用的null指向的清空過期引用另外一個好處是,如果以後他們被錯誤的解除引用,隨之而來的錯誤指向,程序會立即報空指針異常,而不是做錯的事情。這對儘可能快的發現程序錯誤是有利的。

When programmersare first stung by this problem, they may overcompensate by nulling out everyobject reference as soon as the program is finished using it.This is neithernecessary nor desirable, as it clutters up the program unnecessarily.Nullingout object references should be the exception rather than the norm.The best wayto eliminate an obsolete reference is to let the variable that contained thereference fall out of scope. This occurs naturally if you define each variablein the narrowest possible scope (Item 45).

當程序員第一次遇到這問題的時候,或許會過度的清空當程序使用完之後的每個對象的的引用。這是不必要的也是不合適的,因爲這會使程序不必要的混亂起來。爲什麼?清空對象的引用不是java語言的規範,是例外情況。清楚過期引用的最好方式是讓包含這個引用的變量放棄指向的區域。這個當你在定義每個變量在必須的最小的區域時候自然而然的就發生。

So when shouldyou null out a reference? What aspect of the Stack class makes it susceptibleto memory leaks? Simply put, it manages its own memory. The storage pool consistsof the elements of the elements array (the object reference cells, not theobjects themselves). The elements in the active portion of the array (asdefined earlier) are allocated, and those in the remainder of the array are free.The garbage collector has no way of knowing this; to the garbage collector,allof the object references in the elements array  are equally valid. Only the programmer knowsthat the inactive portion of the array is unimportant. The programmereffectively communicates this fact to the garbage collector by manually nullingout array elements as soon as they become part of the inactive portion.

然而,什麼時候你應該置空一個引用呢?Stack類的哪些方面讓它更容易內存泄漏呢?簡單的入棧,它自己管理自己的內存。存儲池包含着元素數組中的元素(是對象的引用不是對象本身)。在數組有效區域的元素(如早期定義的)是被分配的,數組其餘的部分則是自由的。但是垃圾回收器並不知道這些情況,對於垃圾回收器數組中所有對象的引用都是同樣有效的。只有程序員知道無效的數組區域是不重要的。當數組元素變到無效部分,程序員需要通知這個事情給垃圾回收器。

Generallyspeaking, whenever a class manages its own memory, the programmer should bealert for memory leaks. Whenever an element is freed, any object referencescontained in the element should be nulled out.

一般而言,無論什麼時候一個類管理自己的內存,程序員都應該注意內存泄漏。無論什麼時候,一個元素被釋放,包含在元素中的任何對象引用應該被清空。

Another commonsource of memory leaks is caches.Once you put an object reference into a cache,it’s easy to forget that it’s there and leave it in the cache long after itbecomes irrelevant . There are several solutions to this problem. If you’relucky enough to implement a cache for which an entry is relevant exactly solong as there are references to its key outside of the cache, represent thecache as a Weak HashMap; entries will be removed automatically after theybecome obsolete. Remember that Weak HashMap is useful only if the desiredlifetime of cache

entries isdetermined by external references to the key, not the value.

另外一個內存泄漏的公共代碼是緩存。一旦你放一個對象的引用到緩存中個,很容易忘記它,當不用它時候,就會長時間把它放在緩存。關於此問題有一些解決方案。如果你足夠幸運去實現一個緩存,入口恰好相當重要的一個緩存,只要有key的引用在緩存之外,表現那個緩存是弱HashMap.入口將會在使用完之後自動被移除。記住了,弱HashMap是有用的,僅僅在期望的緩存生命週期,入口被外部引用的key所決定,而不是值。

內存泄漏另一個常見的來源是緩存。一旦你把對象引用放到緩存中,就很容易遺忘它,從而使得他再無用之後很長一段時間留在緩存中。對於這個問題有幾種可能的解決方案。如果你正好要實現這樣的緩存:只要在緩存之外存在對某項的鍵的引用個,該項就有意義,那就可以用Weak HaspMap(什麼東西?)代表緩存;當緩存項過期之後,他們就自動被刪除,記住只有當索要的緩存項的生命週期是有該鍵的外部引用而不是由值決定時,weakHashmap纔有用處。

More commonly,the useful lifetime of a cache entry is less well defined, with entriesbecoming less valuable over time. Under these circumstances, the cache shouldoccasionally be cleansed of entries that have fallen into disuse. This can be doneby a background thread (perhaps a Timeror Scheduled Thread Pool Executor) or asa side effect of adding new entries to the cache. The LinkedHashMap classfacilitates the latter approach with its remove Eldest Entry method. For

moresophisticated caches, you may need to use java.lang.ref directly.

更常見的是:緩存項的生命週期是否有用是無法確定的,隨着時間的推移,緩存項越來越無用。這種情況下,緩存應偶爾的清理廢棄不用的項。這能通過一個後臺線程(最好是定時器,或者是日程線程池執行者),或者另外一種方-添加新的項目到緩存中。LinkedHashMap類實現了後者促進後者逼近它移除邊界的方法。更多複雜緩存,你可能需要直接使用java.lang.ref

java.lang.ref?

 A third common source of memory leaks islisteners and other callbacks.If you implement an API where clients registercallbacks but don’t deregister them explicitly, they will accumulate unless youtake some action. The best way to ensure that callbacks are garbage collectedpromptly is to store only weak references to them, for instance, by storingthem only as keys in a Weak HashMap.

另外的內存泄漏的公共代碼是監聽器和其他回調。如果你實現了一個API,客戶端註冊了回調,但是沒有明確地註銷,他們可能累積,直到你採取一些措施。確保垃圾回收器能夠迅速回收的最好的方式是用弱引用保存他們,例如,存儲這些對象的鍵僅僅在weak HashMap

僅僅保存他們到WeakHashMap中的鍵。

 

Because memoryleaks typically do not manifest themselves as obvious failures, they may remainpresent in a system for years. They are typically discovered only as a resultof careful code inspection or with the aid of a debugging tool known as a heapprofiler. Therefore, it is very desirable to learn to anticipate problems likethis before they occur and prevent them from happening.

由於內存泄漏非常典型的不能證明他們明顯的錯誤,他們可能存在系統中數年。他們典型的被發現是由於代碼審查,或者堆分析的調試工具(heap profiler)的幫助。然而,去學習這些提前知道這樣的問題是非常值得的,尤其是在這些問題發生之前,預防他們發生

總結:JAVA內存泄漏原因只有一種,那就是過期的引用沒有被清除,導致GC無法回收。本書介紹三種情況:1、全局集合持有臨時對象,GC無法回收。2、緩存中的項目可能無用了,但是GC是無法回收的;更惡劣的情況是緩存還在不停的增加。3、監聽器等反覆的註冊,使用回調函數,又沒有明確的註銷,導致對象一直都存在。

解決方案:1、全局集合持有臨時對象的解決方案,是將全局對象不再使用的對象清空;2、緩存,思路有兩個,一是緩存中的引用都用弱引用,二就是緩存清空,重新加載,可以寫定時任務線程,定時清空緩存,重新加載,或者在加載新項目的時候考慮順便清空;3、弱引用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章