memoria - ¿Por qué los enteros no están en caché en Java?
como borrar el cache del navegador firefox (15)
Sé que hay publicaciones similares sobre el tema, pero no responden por completo a mi pregunta. Cuando tu lo hagas:
Integer a = 10;
Integer b = 10;
System.out.println("a == b: " + (a == b));
Esto (aparentemente) se imprimirá true
mayor parte del tiempo porque los enteros en el rango [-128, 127] de alguna manera se almacenan en caché. Pero:
Integer a = new Integer(10);
Integer b = new Integer(10);
System.out.println("a == b: " + (a == b));
Volverá false
. Entiendo que estoy pidiendo nuevas instancias de un Integer, pero como las primitivas en caja son inmutables en Java, y la maquinaria ya está ahí para hacer lo "correcto" (como se vio en el primer caso), ¿por qué sucede esto?
¿No tendría más sentido si todas las instancias de un Integer con un 10 fueran el mismo objeto en la memoria? En otras palabras, ¿por qué no tenemos "Interner Integer" que sería similar a "Interning String"?
Mejor aún, ¿no tendría más sentido si las instancias de una primitiva en caja representando lo mismo, independientemente del valor (y el tipo) , sean el mismo objeto? ¿O al menos responder correctamente a ==
?
¿No tendría más sentido si todas las instancias de un Integer con un 10 fueran el mismo objeto en la memoria? En otras palabras, ¿por qué no tenemos "Interner Integer" que es similar a "Interning String"?
¡Porque sería horrible!
Primero, este código lanzaría un OutOfMemoryError
:
for (int i = 0; i <= Integer.MAX_VALUE; i++) {
System.out.printf("%d/n", i);
}
La mayoría de los objetos enteros son probablemente de corta duración.
Segundo, ¿cómo mantendrías ese conjunto de objetos enteros canónicos? Con algún tipo de tabla o mapa. ¿Y cómo arbitrarías el acceso a ese mapa? Con algún tipo de bloqueo. Así que de repente el autoboxing se convertiría en una pesadilla de sincronización que mata el rendimiento para el código subprocesado.
BTW, si lo haces
Integer a = 234345;
Integer b = 234345;
if (a == b) {}
Es posible que esto sea cierto.
Esto se debe a que, como no usó el nuevo Integer (), la JVM (no el código de clase) tiene permitido almacenar en caché sus propias copias de los Integers si lo considera conveniente. Ahora no debe escribir código basado en esto, pero cuando diga nuevo Integer (234345), la especificación le garantiza que definitivamente tendrá diferentes objetos.
Debe quedar muy claro que el almacenamiento en caché tiene un impacto de rendimiento inaceptable, un extra si las instrucciones y la búsqueda de memoria cada vez que crea un entero. Eso solo eclipsa cualquier otra razón y el resto de la agonía en este hilo.
En cuanto a responder "correctamente" a ==, el OP se equivoca al suponer que es correcto. Los enteros responden correctamente a == por la expectativa general de corrección de la comunidad de Java y, por supuesto, por la definición de corrección de la especificación. Es decir, si dos referencias apuntan al mismo objeto, son ==
. Si dos referencias apuntan a objetos diferentes , no son ==
incluso si tienen los mismos contenidos. Por lo tanto, no debería sorprender que el new Integer(5) == new Integer(5)
evalúe como false
.
La pregunta más interesante es por qué new Object();
¿Debería requerirse crear una instancia única cada vez? es decir, por qué es new Object();
no se permite el almacenamiento en caché? La respuesta es la wait(...)
y notify(...)
llamadas. El almacenamiento en caché de los new Object()
causaría incorrectamente que los hilos se sincronizaran entre sí cuando no deberían.
Si no fuera por eso, entonces las implementaciones de Java podrían cachear totalmente los new Object()
s con un singleton.
Y eso debería explicar por qué el new Integer(5)
realizado 7 veces debe ser requerido para crear 7 objetos Integer
únicos, cada uno con el valor 5 (porque Integer
extiende el Object
).
Material secundario, menos importante: un problema en este esquema, por lo demás agradable, se debe a la característica de autoajuste y autounboxing. Sin la característica no se podrían hacer comparaciones como el new Integer(5) == 5
. Para habilitarlos, Java desempaqueta el objeto (y no encierra el primitivo). Por lo tanto, el new Integer(5) == 5
se convierte a: new Integer(5).intValue() == 5
(y no el new Integer(5) == new Integer(5)
.
Una última cosa que hay que entender es que el autoboxing de n
no se realiza mediante el new Integer(n)
. Se realiza internamente mediante una llamada a Integer.valueOf(n)
.
Si crees que lo entiendes y quieres probarte a ti mismo, predice el resultado del siguiente programa:
public class Foo {
public static void main (String[] args) {
System.out.println(Integer.valueOf(5000) == Integer.valueOf(5000));
System.out.println(Integer.valueOf(5000) == new Integer(5000));
System.out.println(Integer.valueOf(5000) == 5000);
System.out.println(new Integer(5000) == Integer.valueOf(5000));
System.out.println(new Integer(5000) == new Integer(5000));
System.out.println(new Integer(5000) == 5000);
System.out.println(5000 == Integer.valueOf(5000));
System.out.println(5000 == new Integer(5000));
System.out.println(5000 == 5000);
System.out.println("=====");
System.out.println(Integer.valueOf(5) == Integer.valueOf(5));
System.out.println(Integer.valueOf(5) == new Integer(5));
System.out.println(Integer.valueOf(5) == 5);
System.out.println(new Integer(5) == Integer.valueOf(5));
System.out.println(new Integer(5) == new Integer(5));
System.out.println(new Integer(5) == 5);
System.out.println(5 == Integer.valueOf(5));
System.out.println(5 == new Integer(5));
System.out.println(5 == 5);
System.out.println("=====");
test(5000, 5000);
test(5, 5);
}
public static void test (Integer a, Integer b) {
System.out.println(a == b);
}
}
Para crédito adicional, también predice la salida si todos los ==
se cambian a .equals(...)
Actualización: gracias al comentario del usuario @sactiw: "el rango predeterminado de caché es -128 a 127 y java 1.6 en adelante, puede restablecer el valor superior> = 127 al pasar -XX: AutoBoxCacheMax = desde la línea de comando"
En Java, cada vez que llama al new
operador, asigna nueva memoria y crea un nuevo objeto . Ese es el comportamiento del lenguaje estándar, y que yo sepa, no hay manera de evitar este comportamiento. Incluso las clases estándar tienen que cumplir con esta regla.
Es porque estás usando la new
declaración para construir los objetos.
Integer a = Integer.valueOf(10);
Integer b = Integer.valueOf(10);
System.out.println("a == b: " + (a == b));
Eso se imprimirá true
. Raro, pero Java.
Esto potencialmente rompería el código escrito antes de este cambio de diseño, cuando todos asumieran con razón que dos instancias recién creadas eran instancias diferentes. Se podría hacer para el autoboxing, porque el autoboxing no existía antes, pero cambiar el significado de nuevo es demasiado peligroso, y probablemente no traiga mucha ganancia. El costo de los objetos de corta duración no es grande en Java, e incluso podría ser más bajo que el costo de mantener un caché de objetos de larga duración.
Para objetos Integer
use la a.equals(b)
para comparar.
El compilador no hará el desempaquetado por usted mientras compara, a menos que asigne el valor a un tipo básico.
Permítanme ampliar un poco las respuestas de ChrisJ y EboMike proporcionando enlaces a las secciones relevantes de JLS.
new
es una palabra clave en Java, permitida en las expresiones de creación de instancia de clase ( Sección 15.9 de JLS ). Esto es diferente de C ++, donde new
es un operador y se puede sobrecargar.
La expresión siempre trata de asignar memoria y genera un objeto nuevo cada vez que se evalúa ( Sección 15.9.4 ). Así que en ese momento ya es demasiado tarde para la búsqueda de caché.
Si marca la fuente que ve:
/**
* Returns an Integer instance representing the specified int value. If a new
* Integer instance is not required, this method should generally be used in
* preference to the constructor Integer(int), as this method is likely to
* yield significantly better space and time performance by caching frequently
* requested values.
*
* @Parameters: i an int value.
* @Returns: an Integer instance representing i.
* @Since: 1.5
*/
public static Integer valueOf(int i) {
final int offset = 128;
if (i >= -128 && i <= 127) { // must cache
return IntegerCache.cache[i + offset];
}
return new Integer(i);
}
Fuente: link
Son las razones de rendimiento por las que ==
devuelve boolean true con enteros, es totalmente un hack. Si desea comparar valores, entonces para eso tiene compareto
método de compareto
o equals
.
En otros idiomas, por ejemplo, puede usar ==
para comparar cadenas también, es básicamente la misma razón y se le llama uno de los mayores percances del lenguaje java.
int
es un tipo primitivo, predefinido por el idioma y nombrado por una palabra clave reservada. Como primitivo no contiene clase ni ninguna información asociada a la clase. Integer
es una clase primitiva inmutable, que se carga a través de un paquete, mecanismo nativo privado y se convierte en clase. Esto proporciona boxeo automático y se introdujo en JDK1.5. Anterior JDK1.5 int
y Integer
donde 2 cosas muy diferentes.
Su primer ejemplo es un subproducto de la especificación que requiere que los pesos de vuelo se creen en un cierto rango alrededor de 0. Nunca, nunca, se debe confiar en.
En cuanto a por qué Integer
no funciona como String
? Me imagino evitando los gastos generales a un proceso ya lento. La razón por la que usa primitivas donde puede es porque son mucho más rápidas y ocupan menos memoria.
Cambiarlo ahora podría romper el código existente porque está cambiando la funcionalidad del operador ==
.
Suponiendo que describa con precisión el comportamiento de su código, suena como que el autoboxing no está funcionando en el operador ''gets'' (=), en cambio, suena como Integer x = 10; le da al objeto un indicador de memoria xa de ''10'' en lugar de un valor de 10. Por lo tanto ((a == b) == verdadero) (se evaluará como verdadero porque == en los objetos opera en las direcciones de memoria que asignó a 10 .
Entonces, ¿cuándo deberías usar autoboxing y unboxing? Utilícelos solo cuando haya una "falta de coincidencia de impedancia" entre los tipos de referencia y los primitivos, por ejemplo, cuando tenga que poner valores numéricos en una colección. No es apropiado utilizar el autoboxing y el unboxing para la computación científica u otro código numérico sensible al rendimiento. Un entero no es un sustituto de un int; el autoboxing y el unboxing desdibujan la distinción entre tipos primitivos y tipos de referencia, pero no lo eliminan.
Qué oráculo tiene que decir sobre el tema.
Observe que la documentación no proporciona ningún ejemplo con el operador ''=''.
Tenga en cuenta también que el rango de caché era de -128 a 127 en Java 1.5, pero Java 1.6 en adelante es el rango predeterminado, es decir, puede establecer un valor superior> = 127 al pasar -XX: AutoBoxCacheMax = new_limit desde la línea de comandos
Tengo entendido que lo new
creará un nuevo objeto, pase lo que pase. El orden de las operaciones aquí es que primero llama a new
, lo que crea una instancia de un nuevo objeto, luego se llama al constructor. No hay lugar para que la JVM intervenga y convierta el new
en un "agarre un objeto Integer en caché basado en el valor pasado al constructor".
Por cierto, ¿has considerado Integer.valueOf
? Eso funciona.
Una nueva instancia es una nueva instancia, por lo que tienen el mismo valor, pero no son iguales que los objetos.
Así que a == b
no puede devolver true
.
Si fueran 1 objeto, como pides: a+=2;
agregaría 2 a todos int = 10
- eso sería horrible.
new
significa new
.
new Object()
no es frívolo.