java string while-loop jvm jit

java - La salida-1 se convierte en una barra en el bucle



string while-loop (3)

Esto es sinceramente bastante extraño, ya que ese código técnicamente nunca debería salir porque ...

int i = 8; while ((i -= 3) > 0);

... siempre debe resultar en i siendo -1 (8 - 3 = 5; 5 - 3 = 2; 2 - 3 = -1). Lo que es aún más extraño es que nunca sale en el modo de depuración de mi IDE.

Curiosamente, en el momento en que agrego un cheque antes de la conversión a una String , entonces no hay problema ...

public void test() { int i = 8; while ((i -= 3) > 0); if(i != -1) { System.out.println("Not -1"); } String value = String.valueOf(i); if (!"-1".equalsIgnoreCase(value)) { System.out.println(value); System.out.println(i); } }

Solo dos puntos de buena práctica de codificación ...

  1. En lugar de usar String.valueOf()
  2. Algunos estándares de codificación especifican que los literales de cadena deben ser el objetivo de .equals() , en lugar de argumentos, minimizando así NullPointerExceptions.

La única forma en que conseguí que esto no ocurriera fue usando String.format()

public void test() { int i = 8; while ((i -= 3) > 0); String value = String.format("%d", i); if (!"-1".equalsIgnoreCase(value)) { System.out.println(value); System.out.println(i); } }

... esencialmente parece que Java necesita un poco de tiempo para recuperar el aliento :)

EDITAR: Esto puede ser completamente una coincidencia, pero parece haber cierta correspondencia entre el valor que se está imprimiendo y la tabla ASCII .

  • i = -1 , el carácter mostrado es / (valor decimal ASCII de 47)
  • i = -2 , el carácter que se muestra es . (Valor decimal ASCII de 46)
  • i = -3 , el carácter que se muestra es - (valor decimal ASCII de 45)
  • i = -4 , el carácter que se muestra es , (valor decimal ASCII de 44)
  • i = -5 , el carácter que se muestra es + (valor decimal ASCII de 43)
  • i = -6 , el carácter que se muestra es * (valor decimal ASCII de 42)
  • i = -7 , el carácter que se muestra es ) (valor decimal ASCII de 41)
  • i = -8 , el carácter que se muestra es ( (valor decimal ASCII de 40)
  • i = -9 , el carácter que se muestra es '' (valor decimal ASCII de 39)

Lo que es realmente interesante es que el carácter en ASCII decimal 48 es el valor 0 y 48 - 1 = 47 (carácter / ), etc.

Sorprendentemente, el siguiente código sale:

/ -1

El código:

public class LoopOutPut { public static void main(String[] args) { LoopOutPut loopOutPut = new LoopOutPut(); for (int i = 0; i < 30000; i++) { loopOutPut.test(); } } public void test() { int i = 8; while ((i -= 3) > 0) ; String value = i + ""; if (!value.equals("-1")) { System.out.println(value); System.out.println(i); } } }

Intenté muchas veces determinar cuántas veces ocurriría esto, pero, desafortunadamente, fue en última instancia incierto, y descubrí que la producción de -2 a veces se convertía en un período. Además, también intenté eliminar el bucle while y la salida -1 sin ningún problema. ¿Quién me puede decir por qué?

Información de versión de JDK:

HopSpot 64-Bit 1.8.0.171 IDEA 2019.1.1


Esto se puede reproducir de manera confiable (o no, dependiendo de lo que desee) con la openjdk version "1.8.0_222" (utilizada en mi análisis), OpenJDK 12.0.1 (según Oleksandr Pyrohov) y OpenJDK 13 (según Carlos Heuberger) .

-XX:+PrintCompilation el código con -XX:+PrintCompilation suficientes veces para obtener ambos comportamientos y aquí están las diferencias.

Implementación con errores (muestra la salida):

--- Previous lines are identical in both 54 17 3 java.lang.AbstractStringBuilder::<init> (12 bytes) 54 23 3 LoopOutPut::test (57 bytes) 54 18 3 java.lang.String::<init> (82 bytes) 55 21 3 java.lang.AbstractStringBuilder::append (62 bytes) 55 26 4 java.lang.AbstractStringBuilder::ensureCapacityInternal (27 bytes) 55 20 3 java.lang.StringBuilder::<init> (7 bytes) 56 19 3 java.lang.StringBuilder::toString (17 bytes) 56 25 3 java.lang.Integer::getChars (131 bytes) 56 22 3 java.lang.StringBuilder::append (8 bytes) 56 27 4 java.lang.String::equals (81 bytes) 56 10 3 java.lang.AbstractStringBuilder::ensureCapacityInternal (27 bytes) made not entrant 56 28 4 java.lang.AbstractStringBuilder::append (50 bytes) 56 29 4 java.lang.String::getChars (62 bytes) 56 24 3 java.lang.Integer::stringSize (21 bytes) 58 14 3 java.lang.String::getChars (62 bytes) made not entrant 58 33 4 LoopOutPut::test (57 bytes) 59 13 3 java.lang.AbstractStringBuilder::append (50 bytes) made not entrant 59 34 4 java.lang.Integer::getChars (131 bytes) 60 3 3 java.lang.String::equals (81 bytes) made not entrant 60 30 4 java.util.Arrays::copyOfRange (63 bytes) 61 25 3 java.lang.Integer::getChars (131 bytes) made not entrant 61 32 4 java.lang.String::<init> (82 bytes) 61 16 3 java.util.Arrays::copyOfRange (63 bytes) made not entrant 61 31 4 java.lang.AbstractStringBuilder::append (62 bytes) 61 23 3 LoopOutPut::test (57 bytes) made not entrant 61 33 4 LoopOutPut::test (57 bytes) made not entrant 62 35 3 LoopOutPut::test (57 bytes) 63 36 4 java.lang.StringBuilder::append (8 bytes) 63 18 3 java.lang.String::<init> (82 bytes) made not entrant 63 38 4 java.lang.StringBuilder::append (8 bytes) 64 21 3 java.lang.AbstractStringBuilder::append (62 bytes) made not entrant

Ejecución correcta (sin visualización):

--- Previous lines identical in both 55 23 3 LoopOutPut::test (57 bytes) 55 17 3 java.lang.AbstractStringBuilder::<init> (12 bytes) 56 18 3 java.lang.String::<init> (82 bytes) 56 20 3 java.lang.StringBuilder::<init> (7 bytes) 56 21 3 java.lang.AbstractStringBuilder::append (62 bytes) 56 26 4 java.lang.AbstractStringBuilder::ensureCapacityInternal (27 bytes) 56 19 3 java.lang.StringBuilder::toString (17 bytes) 57 22 3 java.lang.StringBuilder::append (8 bytes) 57 24 3 java.lang.Integer::stringSize (21 bytes) 57 25 3 java.lang.Integer::getChars (131 bytes) 57 27 4 java.lang.String::equals (81 bytes) 57 28 4 java.lang.AbstractStringBuilder::append (50 bytes) 57 10 3 java.lang.AbstractStringBuilder::ensureCapacityInternal (27 bytes) made not entrant 57 29 4 java.util.Arrays::copyOfRange (63 bytes) 60 16 3 java.util.Arrays::copyOfRange (63 bytes) made not entrant 60 13 3 java.lang.AbstractStringBuilder::append (50 bytes) made not entrant 60 33 4 LoopOutPut::test (57 bytes) 60 34 4 java.lang.Integer::getChars (131 bytes) 61 3 3 java.lang.String::equals (81 bytes) made not entrant 61 32 4 java.lang.String::<init> (82 bytes) 62 25 3 java.lang.Integer::getChars (131 bytes) made not entrant 62 30 4 java.lang.AbstractStringBuilder::append (62 bytes) 63 18 3 java.lang.String::<init> (82 bytes) made not entrant 63 31 4 java.lang.String::getChars (62 bytes)

Podemos notar una diferencia significativa. Con la ejecución correcta compilamos test() dos veces. Una vez al principio, y una vez más después (presumiblemente porque el JIT nota cuán caliente es el método). En la test() ejecución con errores test() se compila (o descompila) 5 veces.

Además, ejecutando con -XX:-TieredCompilation (que interpreta o utiliza C2 ) o con -Xbatch (que obliga a la compilación a ejecutarse en el hilo principal, en lugar de en paralelo), la salida está garantizada y con 30000 iteraciones imprime un muchas cosas, por lo que el compilador C2 parece ser el culpable. Esto se confirma ejecutando con -XX:TieredStopAtLevel=1 , que deshabilita C2 y no produce salida (detenerse en el nivel 4 muestra el error nuevamente).

En la ejecución correcta, el método se compila primero con la compilación de Nivel 3 y luego con el Nivel 4.

En la ejecución con errores, las compilaciones anteriores se descartan (se made non entrant ) y se compilan nuevamente en el Nivel 3 (que es C1 , ver enlace anterior).

Por lo tanto, definitivamente es un error en C2 , aunque no estoy absolutamente seguro de si el hecho de que regrese a la compilación de Nivel 3 lo afecta (y por qué vuelve al nivel 3, todavía hay muchas incertidumbres).

Puede generar el código de ensamblaje con la siguiente línea para profundizar aún más en el agujero del conejo (también vea this para habilitar la impresión de ensamblaje).

java -XX:+PrintCompilation -Xbatch -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly LoopOutPut > broken.asm

En este punto, estoy empezando a quedarme sin habilidades, el comportamiento con errores comienza a exhibirse cuando se descartan las versiones compiladas anteriores, pero las pocas habilidades de ensamblaje que tengo son de los años 90, así que intentaré conseguir un poco de apangin ya que podría ser una de las personas que sabe lo que pasa.

Es probable que ya haya un informe de error al respecto, ya que el código fue presentado al OP por otra persona, y como todo el código C2 no está exento de errores (incluso podría estar relacionado con esto). Espero que este análisis haya sido tan informativo para otros como lo ha sido para mí.

Como el venerable apangin señaló en los comentarios, este es un error reciente . Muy agradecido con todas las personas interesadas y serviciales :)


No sé por qué Java está dando una salida tan aleatoria, pero el problema está en su concatenación que falla para valores más grandes de i dentro del ciclo for .

Si reemplaza String value = i + ""; línea con String value = String.valueOf(i) ; Su código funciona como se esperaba.

La concatenación que usa + para convertir el int en una cadena es nativa y puede tener errores (curiosamente, probablemente la estamos encontrando ahora) y causar tal problema.

Nota: reduje el valor de i inside for loop a 10000 y no tuve problemas con la concatenación.

Este problema se debe informar a las partes interesadas de Java y pueden dar su opinión al respecto.

Editar Actualicé el valor de i in for loop a 3 millones y vi un nuevo conjunto de errores como se muestra a continuación:

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: -1 at java.lang.Integer.getChars(Integer.java:463) at java.lang.Integer.toString(Integer.java:402) at java.lang.String.valueOf(String.java:3099) at solving.LoopOutPut.test(LoopOutPut.java:16) at solving.LoopOutPut.main(LoopOutPut.java:8)

Mi versión de Java es la 8.