删除并发哈希图中的条目期间发生内存泄漏
Memory Leakage during removal of an entry in concurrent hashmap
假设我们从 ConcurrentHashMap
中删除了条目 <k,v>
。 ConcurrentHashMap
中的删除操作将克隆必须删除的节点之前的节点。
现在我的问题是Java如何对必须删除的节点之前的原始节点进行垃圾回收。
假设 1
--->2
--->3
--->4
--->5
-- ->6
是由 ConcurrentHashMap
维护的哈希列表。现在我们要删除 3
.
以下代码是JavaConcurrentHashMap
中remove方法的代码片段
HashEntry newFirst = e.next;
for (HashEntry p = first; p != e; p = p.next) {
newFirst = new HashEntry(p.key, p.hash, newFirst, p.value);
tab[index]= newFirst;
}
第一次迭代后
1
--->2
--->3
--->4
--->5
---> 6
1A
--->2A
--->4
--->5
--->6
将创建一个指向 4
的新节点 1A
。
原始节点 3
仍指向节点 4
。
因此节点4
被2个节点
指向
第二次迭代后
1
--->2
--->3
--->4
--->5
---> 6
1A
--->2A
--->4
--->5
--->6
节点4
现在是指向两个列表的指针(1
--->2
--->3
)和(1A
--->2A
)。 Node 4
之前的原始节点 (1
--->2
--->3
) 永远不会从 hashentries 列表中删除。
这不就是内存泄漏吗。 GC 将如何收集 (1
--->2
--->3
),因为它们仍被 ConcurrentHashMap
引用?
我认为您有点误读了该代码。首先,循环看起来像这样:
HashEntry newFirst = e.next; // the element after the deleted one, in our case: 4
for (HashEntry p = first; p != e; p = p.next) {
newFirst = new HashEntry(p.key, p.hash, newFirst, p.value);
}
tab[index]= newFirst; // this is outside the loop
其次,这是一个单链表,所以迭代是这样的:
Step 0: tab[index] --> 1 --> 2 --> 3 --> 4 --> 5 --> 6
newFirst ----------------------^
Step 1: tab[index] --> 1 --> 2 --> 3 --> 4 --> 5 --> 6
newFirst --------------> 1A ---^
Step 2: tab[index] --> 1 --> 2 --> 3 --> 4 --> 5 --> 6
newFirst -------> 2A --> 1A ---^
Step 3: tab[index] --> 2A--> 1A--> 4 --> 5 --> 6
1 --> 2 --> 3 ----^
(第0步为初始状态,第1、2步为循环的两次迭代,第3步为tab[index] = newFirst
)
如您所见,在第 3 步之后没有任何内容指向 1
,因此它符合 GC 的条件,因此 2
和 [=15= 也是如此].
P.s.: 还要注意 2A
和 1A
的顺序在新列表中是如何颠倒的。这应该没有实际影响,除非你有太多的碰撞,但在那种情况下,碰撞本身比它引入的轻微时间不一致问题要大得多。
假设我们从 ConcurrentHashMap
中删除了条目 <k,v>
。 ConcurrentHashMap
中的删除操作将克隆必须删除的节点之前的节点。
现在我的问题是Java如何对必须删除的节点之前的原始节点进行垃圾回收。
假设 1
--->2
--->3
--->4
--->5
-- ->6
是由 ConcurrentHashMap
维护的哈希列表。现在我们要删除 3
.
以下代码是JavaConcurrentHashMap
HashEntry newFirst = e.next;
for (HashEntry p = first; p != e; p = p.next) {
newFirst = new HashEntry(p.key, p.hash, newFirst, p.value);
tab[index]= newFirst;
}
第一次迭代后
1
--->2
--->3
--->4
--->5
--->6
1A
--->2A
--->4
--->5
--->6
将创建一个指向
4
的新节点1A
。 原始节点3
仍指向节点4
。 因此节点4
被2个节点 指向
第二次迭代后
1
--->2
--->3
--->4
--->5
--->6
1A
--->2A
--->4
--->5
--->6
节点
4
现在是指向两个列表的指针(1
--->2
--->3
)和(1A
--->2A
)。 Node4
之前的原始节点 (1
--->2
--->3
) 永远不会从 hashentries 列表中删除。
这不就是内存泄漏吗。 GC 将如何收集 (1
--->2
--->3
),因为它们仍被 ConcurrentHashMap
引用?
我认为您有点误读了该代码。首先,循环看起来像这样:
HashEntry newFirst = e.next; // the element after the deleted one, in our case: 4
for (HashEntry p = first; p != e; p = p.next) {
newFirst = new HashEntry(p.key, p.hash, newFirst, p.value);
}
tab[index]= newFirst; // this is outside the loop
其次,这是一个单链表,所以迭代是这样的:
Step 0: tab[index] --> 1 --> 2 --> 3 --> 4 --> 5 --> 6
newFirst ----------------------^
Step 1: tab[index] --> 1 --> 2 --> 3 --> 4 --> 5 --> 6
newFirst --------------> 1A ---^
Step 2: tab[index] --> 1 --> 2 --> 3 --> 4 --> 5 --> 6
newFirst -------> 2A --> 1A ---^
Step 3: tab[index] --> 2A--> 1A--> 4 --> 5 --> 6
1 --> 2 --> 3 ----^
(第0步为初始状态,第1、2步为循环的两次迭代,第3步为tab[index] = newFirst
)
如您所见,在第 3 步之后没有任何内容指向 1
,因此它符合 GC 的条件,因此 2
和 [=15= 也是如此].
P.s.: 还要注意 2A
和 1A
的顺序在新列表中是如何颠倒的。这应该没有实际影响,除非你有太多的碰撞,但在那种情况下,碰撞本身比它引入的轻微时间不一致问题要大得多。