Может ли JVM GC перемещать объекты в середине сравнения ссылок, вызывая сбой сравнения, даже если обе стороны ссылаются на один и тот же объект?


хорошо известно, что GCs иногда перемещает объекты в памяти. И насколько я понимаю, пока все ссылки обновляются при перемещении объекта (до вызова любого пользовательского кода), это должно быть совершенно безопасно.

однако я видел, что кто-то упомянул, что сравнение ссылок может быть небезопасным из-за объекта, перемещаемого GC в середине сравнения ссылок, так что сравнение может завершиться неудачей, даже если обе ссылки должны ссылаться к тому же объекту?

ie, есть ли ситуация, при которой следующий код не будет печатать "true"?

Foo foo = new Foo();
Foo bar = foo;
if(foo == bar) {
    System.out.println("true");
}

Я попытался погуглить это, и отсутствие надежных результатов заставляет меня поверить, что человек, который заявил об этом, был неправ, но я нашел ассортимент сообщений на форуме (как это) это, казалось, указывало на то, что он был прав. Но в этой теме также есть люди, которые говорят, что это не должно быть так.

8 56

8 ответов:

инструкции байт-кода Java всегда атомарны по отношению к GC (т. е. никакой цикл не может произойти, пока выполняется одна инструкция).

единственный раз, когда GC будет работать между двумя инструкциями байт-кода.

глядя на байт-код, который javac генерирует для инструкции if в вашем коде, мы можем просто проверить, будет ли GC иметь какой-либо эффект:

// a GC here wouldn't change anything
ALOAD 1
// a GC cycle here would update all references accordingly, even the one on the stack
ALOAD 2
// same here. A GC cycle will update all references to the object on the stack
IF_ACMPNE L3
// this is the comparison of the two references. no cycle can happen while this comparison
// "is running" so there won't be any problems with this either

Aditionally, даже если GC удалось запустить во время выполнения байт-кода инструкции, ссылки на объект не изменится. Это все тот же объект до и после цикла.

Итак, вкратце ответ на ваш вопрос-нет, он всегда будет выводить значение true.

источник:

https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.21.3

короткий ответ, глядя на спецификацию java 8: нет.

на == оператор всегда будет выполнять проверку равенства (учитывая, что ни одна ссылка равна null). Даже если объект перемещается, объект все тот же объект.

Если вы видите такой эффект, вы только что нашли JVM ошибка. Иди представь это.

конечно, может быть, что какая-то неясная реализация JVM не обеспечивает этого по какой-то странной причине производительности. Если это так, было бы разумно просто перейти от этого JVM...

TL; DR

вы не должны думать о таких вещах, что так всегда, это темное место. Java четко заявила, что это спецификации, и вы не должны сомневаться в этом, никогда.

2.7. Представление объектов

виртуальная машина Java не требует определенной внутренней структуры объектов.

источник: JVMS SE8.

Я сомневаюсь в этом! если вы возможно, вы сомневаетесь в этом самом базовом операторе, вы можете сомневаться во всем остальном, разочаровываться и параноидально относиться к проблемам доверия-это не то место, где вы хотите быть.

что если это случится со мной? такая ошибка не должна существовать. Обсуждение Oracle, которое вы предоставили, сообщило об ошибке, которая произошла несколько лет назад, и каким-то образом обсуждение OP решило выскочить без причины, либо без надежной документации такой ошибки существовали теперь дни. Однако, если такая ошибка или любые другие пришло вам в голову, пожалуйста, отправьте его здесь.

чтобы ваши заботы ушли, Java настроила указатель на подход указателя в JVM указатель на таблицу, вы можете прочитать больше о его эффективности здесь.

GCs происходят только в точках программы, где состояние четко определено, и JVM имеет точное знание, где все находится в регистрах/стеке/в куче, поэтому все ссылки могут быть исправлены при перемещении объекта.

т. е. они не могут происходить между выполнением произвольных инструкций по сборке. Концептуально вы можете думать о том, что они происходят между инструкциями байт-кода JVM с GC, регулирующими все ссылки, которые были сгенерированы предыдущими инструкции.

вы задаете вопрос с неверной предпосылке. Так как == оператор не сравнивает ячейки памяти, он не чувствителен к изменениям ячейки памяти per se. Элемент == оператор, применяемый к ссылкам, сравнивает личность из упомянутых объектов, независимо от того, как JVM реализует его.

чтобы назвать пример, который противоречит обычному пониманию, распределенная JVM может иметь объекты, хранящиеся в ОЗУ разных компьютеров, в том числе возможность локальных копий. Так что простое сравнение адресов не будет работать. Конечно, это зависит от реализации JVM, чтобы гарантировать, что семантика, как определено в спецификации языка Java, не меняются.

если конкретная реализация JVM реализует сравнение ссылок путем непосредственного сравнения ячеек памяти объектов и имеет сборщик мусора, который может изменять места памяти, конечно, это зависит от JVM, чтобы гарантировать, что эти два функции не могут вмешиваться друг в друга несовместимым образом.

Если вам интересно, как это может работать, например, внутри оптимизированного JIT-скомпилированного кода, детализация не так хороша, как вы могли бы подумать. Каждый последовательный код, включая прямые ветви, можно считать достаточно быстрым, чтобы позволить задержать сборку мусора до ее завершения. Таким образом, сбор мусора не может происходить в любое время внутри оптимизированного кода, но должен быть разрешен в определенных точках, например,

  • обратные ветви (обратите внимание, что из-за разворачивания цикла не каждая итерация цикла подразумевает обратную ветвь)
  • выделение памяти
  • действия синхронизации потоков
  • вызов метода, который не был встроен/анализируемого
  • может быть что-то особенное, я забыл

таким образом, JVM выдает код, содержащий определенные "безопасные точки", в которых он известен, какие ссылки в настоящее время хранятся, как заменяйте их, если это необходимо и, конечно же, смена локаций никак не влияет на правильность. Между этими точками код может работать без необходимости заботиться о возможности изменения местоположения памяти, тогда как сборщик мусора будет ждать, когда код достигнет безопасной точки, когда это необходимо, что гарантированно произойдет в конечном, довольно коротком времени.

но, как уже было сказано,это детали реализации. На формальном уровне такие вещи, как изменение местоположения памяти, не существуют, поэтому нет необходимости явно указывать, что они не могут менять семантику кода Java. Никакая деталь реализации не допускается для этого.

Я понимаю, что вы задаете этот вопрос после того, как кто-то говорит, что он ведет себя именно так, но на самом деле спрашивать, ведет ли он себя именно так, не является правильным подходом к оценке того, что они сказали.

то, что вы действительно должны спрашивать (в первую очередь себя, других, только если вы не можете решить ответ), имеет ли смысл для GC позволять вызывать сравнение, которое логически должно быть успешным (в основном любое сравнение, которое не включает слабый ссылка.)

ответ на это, очевидно," нет", так как это нарушило бы почти все, что выходит за рамки" Привет, мир " и, вероятно, даже это.

Итак, если это разрешено, это ошибка-либо в спецификации, либо в реализации. Теперь, поскольку и спецификация, и реализация были написаны людьми, возможно, такая ошибка существует. Если да, то он будет сообщен и почти наверняка исправлен.

нет, потому что это было бы вопиюще смешно и патент ошибка.

GC принимает много заботы за кулисами, чтобы избежать катастрофически нарушая все. В частности, он будет перемещать объекты только тогда, когда потоки приостановлены в safepoints, которые являются определенными местами в запущенном коде, сгенерированном JVM для потоков, которые должны быть приостановлены. Поток в безопасной точке находится в известном состоянии, где позиции всех возможных объектов ссылки в регистрах и памяти известны, поэтому GC может обновить их, чтобы указать на новый адрес объекта. Сборка мусора не нарушит ваши операции сравнения.

объект Java содержит ссылку на" объект", а не на пространство памяти, в котором хранится объект.

Java делает это, потому что это позволяет JVM управлять использованием памяти самостоятельно (например, сборщик мусора) и улучшать глобальное использование, не влияя непосредственно на клиентскую программу.

как пример для улучшения, первый X int (я не помню, сколько) всегда выделяется в памяти для выполнения цикла fatser (например:for (int i =0; i<10; i++))

и как пример для ссылка на объект, просто попробуйте создать и попробуйте распечатать его

int[] i = {1,2,3};
System.out.println(i);

вы увидите, что Java возвращает что-то, начиная с [I@. Он говорит, что это точка на "массиве int at", а затем ссылка на объект. Не зона памяти!