Утечка памяти при удалении записи в параллельной хэш-карте

Допустим, мы удаляем запись <k,v> из ConcurrentHashMap. Операция удаления в ConcurrentHashMap клонирует узлы, предшествующие узлу, который необходимо удалить.

Теперь мой вопрос: как мусор Java собирает исходные узлы, предшествующие узлам, которые необходимо удалить.

Допустим, _4 _---> _ 5 _---> _ 6 _---> _ 7 _---> _ 8 _---> _ 9_ - это список хеш-аутентификации, который поддерживается ConcurrentHashMap. Теперь мы хотим удалить 3.

Следующий код - это фрагмент кода метода удаления в Java ConcurrentHashMap

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-й итерации

    1--->2--->3--->4--->5--->6

    1A--->2A--->4--->5--->6

    Будет создан новый узел 1A, который будет указывать на 4. Исходный узел 3 все еще указывает на узел 4. Таким образом, узел 4 указывается двумя узлами

  • После 2-й итерации

    1--->2--->3--->4--->5--->6

    1A--->2A--->4--->5--->6

    Узел 4 теперь является указателем на два списка (_42 _---> _ 43 _---> _ 44_) и (_45 _---> _ 46_). Исходные узлы, предшествующие узлу 4 (_48 _---> _ 49 _---> _ 50_), никогда не удаляются из списка хеш-аутентификации.

Разве это не случай утечки памяти. Как сборщик мусора будет собирать (_51 _---> _ 52 _---> _ 53_), если на них все еще ссылается ConcurrentHashMap?


person chetan sharma    schedule 09.07.2015    source источник


Ответы (1)


Я думаю, вы немного неправильно прочитали этот код. Во-первых, цикл выглядит так:

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, поэтому он имеет право на сборку мусора и, как следствие, 2 и 3.

P.s .: Также обратите внимание на обратный порядок 2A и 1A в новом списке. Это не должно иметь практического влияния, если у вас не слишком много столкновений, но в этом случае сами столкновения представляют собой гораздо большую проблему, чем небольшое несоответствие времени, которое это вносит.

person biziclop    schedule 09.07.2015
comment
--- ›Благодарим за быструю помощь и приносим свои извинения за неправильную установку скобки '}' в цикле for. На самом деле хотел упомянуть tab [index] = newFirst после цикла for. Я не понимаю, как 1,2 и 3 будут собираться мусором. Но ваш комментарий ** жирным шрифтом. Как вы можете видеть, после шага 3 ничего не указывает на 1, поэтому он имеет право на сборку мусора и, как следствие, 2 и 3 ** жирным шрифтом объясняют все. Теперь я могу понять, как 2 и 3 получают право на GC. Большое спасибо за действительно простое объяснение. - person chetan sharma; 09.07.2015