Re: [問題] null 和 GC 與 LinkedList 的原始碼
※ 引述《s3748679 (冷羽翼塵)》之銘言:
...
: 在這邊小弟有個疑問,如果這邊的原始碼改成了這樣:
: private E remove(Entry<E> e) {
: if (e == header)
: throw new NoSuchElementException();
假設目前 linked list 中包括 header 的話只有兩個 entries (即 size 為 1),
則記憶體中變數所佔的空間及彼此間的關聯性會如下圖所示:
(請按空白或 PageDown 鍵翻頁 >>)
header ┌─┐ e ┌─┐
│ ┼┬→ element ┌───┐ │ │
└─┘│ │ null │ └┼┘
│ next ├───┤ │
│ │ ┼─┐ ↓
│ previous ├───┤ ├→ element ┌───┐ ┌───┐
│ │ ┼─┘ │ ┼→│ ... │
│ └───┘ next ├───┤ └───┘
│ │ ┼─┐
│ previous ├───┤ ├─┐
│ │ ┼─┘ │
│ └───┘ │
│ │
└───────────────────────────┘
: E result = e.element;
: e.previous.next = e.next;
: e.next.previous = e.previous;
在執行完上面三行後則會變成:
(請按空白或 PageDown 鍵翻頁 >>)
header ┌─┐ e ┌─┐ result ┌─┐
│ ┼┬→ element ┌───┐ │ │ │ │
└─┘│ │ null │ └┼┘ └┼┘
│ next ├───┤ │ │
│ │ ┼─┐ ↓ ↓
│ previous ├───┤ ├┐ element ┌───┐ ┌───┐
│ │ ┼─┘│ │ ┼→│ ... │
│ └───┘ │ next ├───┤ └───┘
│ │ │ ┼─┐
│ │ previous ├───┤ ├─┐
│ │ │ ┼─┘ │
│ │ └───┘ │
│ │ │
└─────────────┴─────────────┘
: // 把這二行砍了
: // e.next = e.previous = null;
: // e.element = null;
如果上面兩行有執行的話就變成:
(請按空白或 PageDown 鍵翻頁 >>)
header ┌─┐ e ┌─┐ result ┌─┐
│ ┼┬→ element ┌───┐ │ │ │ │
└─┘│ │ null │ └┼┘ └┼┘
│ next ├───┤ │ │
│ │ ┼─┐ ↓ ↓
│ previous ├───┤ ├┐ element ┌───┐ ┌───┐
│ │ ┼─┘│ │ null │ │ ... │
│ └───┘ │ next ├───┤ └───┘
│ │ │ null │
│ │ previous ├───┤
│ │ │ null │
│ │ └───┘
│ │
└─────────────┘
那麼在離開 remove(Entry<E> e) 這個方法後,
因為 e 和 result 變數也跟著消失,
上圖中深灰色部分的空間即可順利回收。
而原本 result 所指向的那塊空間,
若 result 值回傳後未被使用的話也可順利回收。
(請按空白或 PageDown 鍵翻頁 >>)
那麼如果那兩行沒執行呢?
從第二張圖的深灰色部分可知,
在離開 remove(Entry<E> e) 這個方法後,
e 和 result 變數也跟著消失,
而原本 result 所指向的那塊空間,
就算是 result 值回傳後未被使用的話,
因為仍有深灰色的 element 指向它,
所以 GC 會無法回收它。
那深灰色部分的空間能被順利回收嗎?
從這個 size 為 1 的例子來看是可以的。
但想想 size 在 2 以上的情形,
每當 remove 掉一個 entry 時,
該 entry 的 next 和 previous 都還繼續指著原本在它前、後的兩個 entries,
那在它所指的前或後的 entry 被 remove 後,
假設這時它尚未被回收的話,
GC 便會因為它還指著前或後的 entry 而無法回收該 (前或後的那個) entry 了。
: size--;
: modCount++;
: return result;
: }
: public void clear() {
: // 把這裡也砍了
: //Entry<E> e = header.next;
: //while (e != header) {
: // Entry<E> next = e.next;
: // e.next = e.previous = null;
: // e.element = null;
: // e = next;
: //}
: header.next = header.previous = header;
: size = 0;
: modCount++;
: }
這裡的情況和上面講的相同,
除非 GC 剛好依照原本 linked list 的順序去檢查能否回收,
不然一樣會遇到上述的問題。
: 這樣的話GC還回收得到中間的Entry嗎? 不曉得會不會造成記憶體遺失的問題@@"
: 還是說這樣做GC還是回收得到,只是會比較慢!? (猜測)
: PS: 看過了wiki,但我有看沒有懂:
: 雖然記憶體管理器可以回復不能存取的記憶體,但它不可以釋放可存取的記憶體因為仍有
: 可能需要使用。現代的記憶體管理器因此為程式設計員提供技術來標示記憶體的可用性,
: 以不同級別的「存取性」表示。記憶體管理器不會把需要存取可能較高的物件釋放。當物
: 件直接和一個強參照相關或者間接和一組強參照相關表示該物件存取性較強。(強參照相
: 對於弱參照,是防止物件被回收的一個參照。)要防止此類記憶體泄漏,開發者必須使用
: 物件後清理參照,一般都是在不再需要時將參照設成null,如果有可能,把維持強參照的
: 事件偵聽器全部登出。
Java 中的參照變數預設都是握有對物件的強參照,
本例中的 header, element, next, previous, e 均為強參照變數,
當物件有被任何強參照變數指到時就無法被 GC 回收。
至於弱參照的話我記得 Java 也有相關的機制可用,
詳情可參看 java.lang.ref package。
: wiki 內部記憶體洩漏
: 網址: http://zh.wikipedia.org/zh-tw/%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F
: 希望各位大大能對半路出家的我指點迷津... Thanks~
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 218.175.149.42
→
10/20 10:50, , 1F
10/20 10:50, 1F
→
10/20 10:52, , 2F
10/20 10:52, 2F
→
10/20 10:54, , 3F
10/20 10:54, 3F
推
10/20 12:31, , 4F
10/20 12:31, 4F
→
10/20 22:04, , 5F
10/20 22:04, 5F
推
10/20 23:14, , 6F
10/20 23:14, 6F
→
10/20 23:15, , 7F
10/20 23:15, 7F
討論串 (同標題文章)
本文引述了以下文章的的內容:
完整討論串 (本文為第 2 之 4 篇):