Что такое "внутренний адрес" на Java?
В Javadoc для Object.hashCode() он указывает
Насколько это разумно практично, метод hashCode, определенный классом Object
, возвращает разные целые числа для разных объектов. (Обычно это реализуется путем преобразования внутреннего адреса объекта в целое число, но этот метод реализации не требуется языком программирования Java ™.)
Это обычное средство, которое имеет какое-то отношение к адресу памяти, но оно не может измениться без уведомления, и hashCode() не может и не должен изменяться для объекта.
@Neet При условии ссылки на хороший ответ qaru.site/info/4169/..., но я ищу более подробную информацию.
Вот пример, иллюстрирующий мою озабоченность
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafe.get(null);
for (int t = 0; t < 10; t++) {
System.gc();
Object[] objects = new Object[10];
for (int i = 0; i < objects.length; i++)
objects[i] = new Object();
for (int i = 0; i < objects.length; i++) {
if (i > 0) System.out.print(", ");
int location = unsafe.getInt(objects, Unsafe.ARRAY_OBJECT_BASE_OFFSET + Unsafe.ARRAY_OBJECT_INDEX_SCALE * i);
System.out.printf("%08x: hc= %08x", location, objects[i].hashCode());
}
System.out.println();
}
печатает
eac00038: hc= 4f47e0ba, eac00048: hc= 2342d884, eac00058: hc= 7994d431, eac00068: hc= 19f71b53, eac00078: hc= 2e22f376, eac00088: hc= 789ddfa3, eac00098: hc= 44c58432, eac000a8: hc= 036a11e4, eac000b8: hc= 28bc917c, eac000c8: hc= 73f378c8
eac00038: hc= 30813486, eac00048: hc= 729f624a, eac00058: hc= 3dee2310, eac00068: hc= 5d400f33, eac00078: hc= 18a60d19, eac00088: hc= 3da5f0f3, eac00098: hc= 596e0123, eac000a8: hc= 450cceb3, eac000b8: hc= 4bd66d2f, eac000c8: hc= 6a9a4f8e
eac00038: hc= 711dc088, eac00048: hc= 584b5abc, eac00058: hc= 3b3219ed, eac00068: hc= 564434f7, eac00078: hc= 17f17060, eac00088: hc= 6c08bae7, eac00098: hc= 3126cb1a, eac000a8: hc= 69e0312b, eac000b8: hc= 7dbc345a, eac000c8: hc= 4f114133
eac00038: hc= 50c8c3b8, eac00048: hc= 2ca98e77, eac00058: hc= 2fc83d89, eac00068: hc= 034005e1, eac00078: hc= 6041f871, eac00088: hc= 0b1df416, eac00098: hc= 5b83d60d, eac000a8: hc= 2c5a1e6b, eac000b8: hc= 5083198c, eac000c8: hc= 4f025f9f
eac00038: hc= 00c5eb8a, eac00048: hc= 41eab16b, eac00058: hc= 1726099c, eac00068: hc= 4240eca3, eac00078: hc= 346fe350, eac00088: hc= 1db4b415, eac00098: hc= 429addef, eac000a8: hc= 45609812, eac000b8: hc= 489fe953, eac000c8: hc= 7a8f6d64
eac00038: hc= 7e628e42, eac00048: hc= 7869cfe0, eac00058: hc= 6aceb8e2, eac00068: hc= 29cc3436, eac00078: hc= 1d77daaa, eac00088: hc= 27b4de03, eac00098: hc= 535bab52, eac000a8: hc= 274cbf3f, eac000b8: hc= 1f9fd541, eac000c8: hc= 3669ae9f
eac00038: hc= 772a3766, eac00048: hc= 749b46a8, eac00058: hc= 7e3bfb66, eac00068: hc= 13f62649, eac00078: hc= 054b8cdc, eac00088: hc= 230cc23b, eac00098: hc= 1aa3c177, eac000a8: hc= 74f2794a, eac000b8: hc= 5af92541, eac000c8: hc= 1afcfd10
eac00038: hc= 396e1dd8, eac00048: hc= 6c696d5c, eac00058: hc= 7d8aea9e, eac00068: hc= 2b316b76, eac00078: hc= 39862621, eac00088: hc= 16315e08, eac00098: hc= 03146a9a, eac000a8: hc= 3162a60a, eac000b8: hc= 4382f3da, eac000c8: hc= 4a578fd6
eac00038: hc= 225765b0, eac00048: hc= 17d5176d, eac00058: hc= 26f50154, eac00068: hc= 1f2a45c7, eac00078: hc= 104b1bcd, eac00088: hc= 330e3816, eac00098: hc= 6a844689, eac000a8: hc= 12330301, eac000b8: hc= 530a3ffc, eac000c8: hc= 45eee3fb
eac00038: hc= 3f9432e0, eac00048: hc= 1a9830bc, eac00058: hc= 7da79447, eac00068: hc= 04f801c4, eac00078: hc= 363bed68, eac00088: hc= 185f62a9, eac00098: hc= 1e4651bf, eac000a8: hc= 1aa0e220, eac000b8: hc= 385db088, eac000c8: hc= 0ef0cda1
В качестве побочного примечания; Если вы посмотрите на этот код
if (value == 0) value = 0xBAD ;
Похоже, что 0xBAD в два раза чаще, чем любой хэш-код, поскольку 0 сопоставляется с этим значением. Если вы запустите это достаточно долго, вы увидите
long count = 0, countBAD = 0;
while (true) {
for (int i = 0; i < 200000000; i++) {
int hc = new Object().hashCode();
if (hc == 0xBAD)
countBAD++;
count++;
}
System.out.println("0xBAD ratio is " + (double) (countBAD << 32) / count + " times expected.");
}
печатает
0xBAD ratio is 2.0183116992481205 times expected.
Ответы
Ответ 1
Это явно специфично для реализации.
Ниже я включаю реализацию Object.hashCode()
, используемую в OpenJDK 7.
Функция поддерживает шесть разных методов расчета, только два из которых принимают любое уведомление об адресе объекта ( "адрес", являющийся С++ oop
, отличным от intptr_t
). Один из двух методов использует адрес as-is, тогда как другой выполняет некоторую битовую сглаживание, а затем выдает результат с редко обновляемым случайным числом.
Из оставшихся методов возвращается константа (предположительно для тестирования), одна возвращает порядковые номера, а остальные основываются на псевдослучайных последовательностях.
Похоже, что метод можно выбрать во время выполнения, а по умолчанию - метод 0, который равен os::random()
. Последний является линейным конгруэнтным генератором, с предполагаемым состоянием гонки, брошенным.:-) Состояние гонки приемлемо, потому что в худшем случае это приведет к тому, что два объекта будут одинаковыми хэш-код; это не нарушает никаких инвариантов.
Вычисление выполняется в первый раз, когда требуется хэш-код. Чтобы сохранить согласованность, результат затем сохраняется в заголовке объекта и возвращается при последующих вызовах hashCode()
. Кэширование выполняется вне этой функции.
Таким образом, понятие о том, что Object.hashCode()
основано на адресе объекта, в значительной степени является историческим артефактом, устаревшим по свойствам современных сборщиков мусора.
// hotspot/src/share/vm/runtime/synchronizer.hpp
// hashCode() generation :
//
// Possibilities:
// * MD5Digest of {obj,stwRandom}
// * CRC32 of {obj,stwRandom} or any linear-feedback shift register function.
// * A DES- or AES-style SBox[] mechanism
// * One of the Phi-based schemes, such as:
// 2654435761 = 2^32 * Phi (golden ratio)
// HashCodeValue = ((uintptr_t(obj) >> 3) * 2654435761) ^ GVars.stwRandom ;
// * A variation of Marsaglia shift-xor RNG scheme.
// * (obj ^ stwRandom) is appealing, but can result
// in undesirable regularity in the hashCode values of adjacent objects
// (objects allocated back-to-back, in particular). This could potentially
// result in hashtable collisions and reduced hashtable efficiency.
// There are simple ways to "diffuse" the middle address bits over the
// generated hashCode values:
//
static inline intptr_t get_next_hash(Thread * Self, oop obj) {
intptr_t value = 0 ;
if (hashCode == 0) {
// This form uses an unguarded global Park-Miller RNG,
// so it possible for two threads to race and generate the same RNG.
// On MP system we'll have lots of RW access to a global, so the
// mechanism induces lots of coherency traffic.
value = os::random() ;
} else
if (hashCode == 1) {
// This variation has the property of being stable (idempotent)
// between STW operations. This can be useful in some of the 1-0
// synchronization schemes.
intptr_t addrBits = intptr_t(obj) >> 3 ;
value = addrBits ^ (addrBits >> 5) ^ GVars.stwRandom ;
} else
if (hashCode == 2) {
value = 1 ; // for sensitivity testing
} else
if (hashCode == 3) {
value = ++GVars.hcSequence ;
} else
if (hashCode == 4) {
value = intptr_t(obj) ;
} else {
// Marsaglia xor-shift scheme with thread-specific state
// This is probably the best overall implementation -- we'll
// likely make this the default in future releases.
unsigned t = Self->_hashStateX ;
t ^= (t << 11) ;
Self->_hashStateX = Self->_hashStateY ;
Self->_hashStateY = Self->_hashStateZ ;
Self->_hashStateZ = Self->_hashStateW ;
unsigned v = Self->_hashStateW ;
v = (v ^ (v >> 19)) ^ (t ^ (t >> 8)) ;
Self->_hashStateW = v ;
value = v ;
}
value &= markOopDesc::hash_mask;
if (value == 0) value = 0xBAD ;
assert (value != markOopDesc::no_hash, "invariant") ;
TEVENT (hashCode: GENERATE) ;
return value;
}
Ответ 2
Обычно - адрес памяти объекта. Однако в первый раз, когда метод hashcode
вызывается для объекта, целое число хранится в заголовке этого объекта, так что следующий вызов будет возвращать одно и то же значение (как вы говорите, уплотнение коллекции мусора может изменить адрес). Насколько я знаю, так оно реализовано в JVM Oracle.
РЕДАКТИРОВАТЬ: выкапывая исходный код JVM, это то, что отображается (synchronizer.cpp):
// hashCode() generation :
//
// Possibilities:
// * MD5Digest of {obj,stwRandom}
// * CRC32 of {obj,stwRandom} or any linear-feedback shift register function.
// * A DES- or AES-style SBox[] mechanism
// * One of the Phi-based schemes, such as:
// 2654435761 = 2^32 * Phi (golden ratio)
// HashCodeValue = ((uintptr_t(obj) >> 3) * 2654435761) ^ GVars.stwRandom ;
// * A variation of Marsaglia shift-xor RNG scheme.
// * (obj ^ stwRandom) is appealing, but can result
// in undesirable regularity in the hashCode values of adjacent objects
// (objects allocated back-to-back, in particular). This could potentially
// result in hashtable collisions and reduced hashtable efficiency.
// There are simple ways to "diffuse" the middle address bits over the
// generated hashCode values:
//
static inline intptr_t get_next_hash(Thread * Self, oop obj) {
intptr_t value = 0 ;
if (hashCode == 0) {
// This form uses an unguarded global Park-Miller RNG,
// so it possible for two threads to race and generate the same RNG.
// On MP system we'll have lots of RW access to a global, so the
// mechanism induces lots of coherency traffic.
value = os::random() ;
} else
if (hashCode == 1) {
// This variation has the property of being stable (idempotent)
// between STW operations. This can be useful in some of the 1-0
// synchronization schemes.
intptr_t addrBits = intptr_t(obj) >> 3 ;
value = addrBits ^ (addrBits >> 5) ^ GVars.stwRandom ;
} else
if (hashCode == 2) {
value = 1 ; // for sensitivity testing
} else
if (hashCode == 3) {
value = ++GVars.hcSequence ;
} else
if (hashCode == 4) {
value = intptr_t(obj) ;
} else {
// Marsaglia xor-shift scheme with thread-specific state
// This is probably the best overall implementation -- we'll
// likely make this the default in future releases.
unsigned t = Self->_hashStateX ;
t ^= (t << 11) ;
Self->_hashStateX = Self->_hashStateY ;
Self->_hashStateY = Self->_hashStateZ ;
Self->_hashStateZ = Self->_hashStateW ;
unsigned v = Self->_hashStateW ;
v = (v ^ (v >> 19)) ^ (t ^ (t >> 8)) ;
Self->_hashStateW = v ;
value = v ;
}
value &= markOopDesc::hash_mask;
if (value == 0) value = 0xBAD ;
assert (value != markOopDesc::no_hash, "invariant") ;
TEVENT (hashCode: GENERATE) ;
return value;
}
Так что 6 разных способов сделать это в JVM Oracle, один из них эквивалентен тому, что я сказал... Значение хранится в заголовке объекта методом, вызывающим get_next_hash
(тот, который вызывается FastHashCode
и вызывается из собственной версии Object.hashCode()
.
Ответ 3
IMHO, хотя и зависит от реализации, ссылки на объекты в JVM никогда не являются фактическими адресами памяти, но внутренние ссылки JVM, которые указывают на фактические адреса. Эти внутренние ссылки ARE генерируются первоначально на основе адреса памяти, но они остаются связанными с объектом до тех пор, пока он не будет отброшен.
Я говорю это, потому что Java HotSpot GCs копируют сборщики какой-либо формы или другой, и они работают путем перемещения live objects
и копирования их из одной кучи в другую, впоследствии уничтожая old heap
. Поэтому, когда GC происходит, JVM не нуждается в обновлении ссылок во всех объектах, а просто измените фактический адрес памяти, сопоставленный с внутренней ссылкой.
Ответ 4
Предложение Javadoc для Object.hashCode(), которое реализовано путем вывода хэш-кода из адреса памяти, просто устарело.
Вероятно, никто не беспокоился (или заметил), что этот путь реализации больше невозможен с помощью копировального сборщика мусора (поскольку он изменит хэш-код, когда объект будет скопирован в другое место памяти).
Было очень важно реализовать hashcode таким образом, прежде чем появился копировальный сборщик мусора, поскольку он спасет кучу пространства. Не копирующий GC (CMS) все еще может реализовать хэш-код сегодня.