Странный код в java.util.concurrent.LinkedBlockingQueue
Все!
Я нашел странный код в LinkedBlockingQueue:
private E dequeue() {
// assert takeLock.isHeldByCurrentThread();
Node<E> h = head;
Node<E> first = h.next;
h.next = h; // help GC
head = first;
E x = first.item;
first.item = null;
return x;
}
Кто может объяснить, зачем нам нужна локальная переменная h? Как это может помочь GC?
Ответы
Ответ 1
Чтобы лучше понять, что происходит, посмотрите, как выглядит список после выполнения кода. Сначала рассмотрим начальный список:
1 -> 2 -> 3
Затем h
указывает на head
и first
на h.next
:
1 -> 2 -> 3
| |
h first
Затем h.next
указывает на h
и head
указывает на first
:
1 -> 2 -> 3
| / \
h head first
Теперь практически мы знаем, что есть только активная ссылка, указывающая на первый элемент, который сам по себе (h.next = h
), и мы также знаем, что GC собирает объекты, которые не имеют более активных ссылок, поэтому, когда метод заканчивается, (старая) глава списка может быть безопасно собрана GC, поскольку h
существует только в рамках метода.
Сказав это, было указано, и я согласен с этим, что даже с классическим методом dequeue (т.е. просто сделать first
точкой head.next
и head
указать на first
), нет больше ссылок, указывающих на старую голову. Однако в этом сценарии старая голова остается болтающейся в памяти и все еще имеет поле next
, указывающее на first
, тогда как в отправленном вами коде остается только один объект, указывающий на себя. Это может привести к тому, что GC будет действовать быстрее.
Ответ 2
Если вы посмотрите на jsr166 src, вы обнаружите оскорбительную фиксацию
http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/main/java/util/concurrent/LinkedBlockingQueue.java?view=log
(см. v 1.51)
Это показывает, что ответ указан в этом отчете об ошибке
http://bugs.sun.com/view_bug.do?bug_id=6805775
Полное обсуждение в этой теме
http://thread.gmane.org/gmane.comp.java.jsr.166-concurrency/5758
"Помощник GC" - это то, что вы избегаете кровотечения.
Приветствия
Matt
Ответ 3
Возможно, немного поздно, но текущее объяснение для меня совершенно неудовлетворительное, и я думаю, что у меня есть более разумное объяснение.
Прежде всего, каждый java GC делает какую-то трассировку из корневого набора так или иначе. Это означает, что если старая голова будет собрана, мы все равно не будем читать переменную next
- нет причин для этого. Следовательно, IF голова собирается на следующей итерации, это не имеет значения.
ИФ в приведенном выше предложении является важной частью здесь. Разница между настройкой рядом с чем-то другим не имеет значения для сбора самой головки, но может иметь значение для других объектов.
Пусть предполагается простая генерация GC: если голова находится в молодом наборе, она будет собрана в следующем GC в любом случае. Но если это в старом наборе, оно будет собрано только тогда, когда мы сделаем полный GC, который случается редко.
Итак, что происходит, если голова находится в старом наборе, и мы делаем молодой GC? В этом случае JVM предполагает, что каждый объект старой кучи все еще жив и добавляет каждую ссылку от старых к юным объектам в корневой набор для молодой GC. И это именно то, чего здесь не позволяет присвоение: запись в старую кучу обычно защищается барьером записи или чем-то другим, чтобы JVM мог улавливать такие назначения и обрабатывать их правильно - в нашем случае он удаляет объект next
, указанный на который имеет последствия.
Краткий пример:
Предположим, что 1 (old) -> 2 (young) -> 3 (xx)
. Если мы удалим 1 и 2 из нашего списка, мы можем ожидать, что оба элемента будут собраны следующим GC. Но если происходит только молодой GC, и мы НЕ удалили указатель next
в старом, оба элемента 1 и 2 не будут собраны. В отличие от этого, если мы удалили указатель в 1, 2 будет собран молодым GC..
Ответ 4
Вот пример кода, который иллюстрирует вопрос: http://pastebin.com/zTsLpUpq.
Выполнение GC после runWith()
и получение дампа кучи для обеих версий говорит только один экземпляр элемента.