Что происходит с hashCode объекта при перемещении объекта в памяти современным GC в Java
Что проверяют
Знаете ли вы, что стоит за Object.hashCode() по умолчанию, и понимаете ли, почему перемещающий сборщик не имеет права его менять. Это вопрос про заголовок объекта, а не про контракт equals.
Как отвечать
Ключевое утверждение: identity hash, который возвращают Object.hashCode() и System.identityHashCode(), не является адресом объекта. В HotSpot он вычисляется лениво, при первом запросе, и запоминается в mark word, то есть в заголовке самого объекта. Заголовок переезжает вместе с объектом, поэтому после уплотнения кучи или копирования из молодого поколения в старое значение остаётся прежним.
Почему так сделано, стоит проговорить: адрес при перемещении меняется, а контракт требует, чтобы hashCode() возвращал одно и то же в пределах запуска. Будь хэш адресом, любой ключ в HashMap терялся бы после первой же сборки мусора.
Дальше полезные следствия:
- пока хэш не запрашивали, в заголовке его нет, и платить за него не приходится;
- заголовок занят не только хэшем: у заблокированного объекта там ссылка на блокировку, поэтому запрос identity hash вынуждает сохранить его в вытесненном заголовке или раздуть монитор;
- переопределённый
hashCode()к этой механике отношения не имеет, он считается по полям и про расположение в памяти ничего не знает.
Где спотыкаются
Отвечают, что hashCode это адрес в памяти. Миф старый и живучий, а вопрос задан ровно для того, чтобы его поймать: если бы это был адрес, перемещающий сборщик ломал бы любую хэш-структуру.
Второе: подменяют тему контрактом equals и hashCode. Про поля и про согласованность методов спрашивают отдельно, здесь же речь о значении по умолчанию, которое к полям не привязано вовсе.
Третье: не называют реальную опасность. Хэш ломает не сборщик, а изменяемый ключ: поменяли поле, участвующее в расчёте, у объекта, уже лежащего в HashMap, и запись перестаёт находиться, потому что её ищут в другом бакете.