¿Qué es una “dirección interna” en Java?

En el Javadoc para Object.hashCode () declara

Tanto como sea razonablemente práctico, el método hashCode definido por el Object clase devuelve enteros distintos para objetos distintos. (Esto se implementa típicamente al convertir la dirección interna del objeto en un entero, pero esta técnica de implementación no es requerida por el lenguaje de progtwigción Java ™).

Es una miconcepción común, esto tiene algo que ver con la dirección de la memoria, pero no puede cambiar sin previo aviso y el hashCode () no cambia y no debe cambiar para un objeto.

@Neet Proporcionó un enlace a una buena respuesta https://stackoverflow.com/a/565416/57695 pero estoy buscando más detalles.


Aquí hay un ejemplo para ilustrar mi preocupación

 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  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(); } 

huellas dactilares

 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 

Como nota al margen; Si miras este código

 if (value == 0) value = 0xBAD ; 

Parece que 0xBAD es dos veces más probable que cualquier hashCode ya que 0 se asigna a este valor. Si corres esto lo suficiente ves

 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."); } 

huellas dactilares

 0xBAD ratio is 2.0183116992481205 times expected. 

Esto es claramente específico de la implementación.

A continuación Object.hashCode() implementación Object.hashCode() utilizada en OpenJDK 7.

La función admite seis métodos de cálculo diferentes, de los cuales solo dos toman nota de la dirección del objeto (la “dirección” es la intptr_t C ++ oop a intptr_t ). Uno de los dos métodos usa la dirección tal como está, mientras que el otro hace algunos giros y luego mezcla el resultado con un número aleatorio actualizado con poca frecuencia.

De los métodos restantes, uno devuelve una constante (presumiblemente para la prueba), uno devuelve números secuenciales, y el rest se basa en secuencias pseudoaleatorias.

Parece que el método se puede elegir en tiempo de ejecución, y el valor predeterminado parece ser el método 0, que es os::random() . Este último es un generador congruente lineal , con una supuesta condición de carrera lanzada. 🙂 La condición de carrera es aceptable porque en el peor de los casos resultaría en dos objetos que comparten el mismo código hash; esto no rompe ninguna invariante.

El cálculo se realiza la primera vez que se requiere un código hash. Para mantener la coherencia, el resultado se almacena en el encabezado del objeto y se devuelve en llamadas posteriores a hashCode() . El almacenamiento en caché se realiza fuera de esta función.

En resumen, la noción de que Object.hashCode() se basa en la dirección del objeto es en gran medida un artefacto histórico que ha quedado obsoleto por las propiedades de los recolectores de basura modernos.

 // 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's 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's 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's 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; } 

Normalmente es la dirección de memoria del objeto. Sin embargo, la primera vez que se llama al método de hashcode en un objeto, el entero se almacena en el encabezado de ese objeto para que la siguiente invocación devuelva el mismo valor (como dices, compactar la recolección de basura puede cambiar la dirección). Que yo sepa, así es como se implementa en Oracle JVM.

EDITAR: profundizando en el código fuente de JVM, esto es lo que aparece (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's 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's 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's 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; } 

Entonces, 6 maneras diferentes de hacerlo en Oracle JVM, una de ellas es equivalente a lo que dije … El valor se almacena en el encabezado del objeto mediante el método que llama a get_next_hash (que se llama FastHashCode y se llama desde el versión nativa de Object.hashCode() .

En mi humilde opinión, aunque depende de la implementación, las referencias a objetos en JVM nunca son direcciones de memoria reales, sino referencias de JVM internas que apuntan a direcciones reales. Estas referencias internas se generan inicialmente basadas en la dirección de memoria, pero permanecen asociadas con el objeto hasta que se descarta.

Digo esto porque los GC de HotSpot de Java están copiando recostackdores de una u otra forma, y ​​funcionan atravesando live objects y copiándolos de un montón a otro, destruyendo posteriormente el old heap . Por lo tanto, cuando se produce GC, la JVM no tiene que actualizar las referencias en todos los objetos, sino simplemente cambiar la dirección de memoria asignada a la referencia interna.

La sugerencia de Javadoc para Object.hashCode () que se implementa derivando el código hash de la dirección de memoria simplemente está desactualizada.

Probablemente nadie se molestó (ni se dio cuenta) de que esta ruta de implementación ya no es posible con un recolector de basura copiado (ya que cambiaría el código hash cuando el Objeto se copia a otra ubicación de memoria).

Tenía mucho sentido implementar el código hash de esa manera antes de que hubiera un recolector de basura copiado, ya que ahorraría espacio en el montón. Un GC (CMS) que no copia podría seguir implementando el hashcode de esa manera hoy.

    Intereting Posts