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 la versión de JDK:
HopSpot 64-Bit 1.8.0.171
IDEA 2019.1.1
Respuestas:
Esto se puede reproducir de manera confiable (o no, según lo que desee) con
openjdk version "1.8.0_222"
(utilizado en mi análisis), OpenJDK12.0.1
(según Oleksandr Pyrohov) y OpenJDK 13 (según Carlos Heuberger).Ejecuté el código con
-XX:+PrintCompilation
suficientes tiempos para obtener ambos comportamientos y aquí están las diferencias.Implementación con errores (muestra la salida):
Ejecución correcta (sin visualización):
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 el buggy la ejecucióntest()
se compila (o descompila) 5 veces.Además, al ejecutarse con
-XX:-TieredCompilation
(que interpreta o utilizaC2
) o con-Xbatch
(que obliga a la compilación a ejecutarse en el hilo principal, en lugar de en paralelo), se garantiza la salida y con 30000 iteraciones imprime muchas cosas, por lo que elC2
compilador parece ser el culpable Esto se confirma al ejecutar with-XX:TieredStopAtLevel=1
, que deshabilitaC2
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, se descartan las compilaciones anteriores (
made non entrant
) y se vuelve a compilar en el Nivel 3 (es decirC1
, ver enlace anterior).Entonces definitivamente es un error en
C2
, aunque no estoy absolutamente seguro de si el hecho de que esté volviendo a la compilación del Nivel 3 lo afecta (y por qué está volviendo 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 esto para habilitar la impresión de ensamblaje).
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 90, así que dejaré que alguien más inteligente que yo lo tome de aquí.
Es probable que ya haya un informe de error sobre esto, ya que el código fue presentado al OP por otra persona, y como todo el código C2 no está exento de errores . 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 :)
fuente
C2
: he mirado el código de ensamblador generado (y he tratado de entenderlo) usando JitWatch: elC1
código generado todavía se parece a bytecode,C2
es totalmente diferente (ni siquiera pude encontrar la inicialización dei
con 8)Esto es sinceramente bastante extraño, ya que ese código técnicamente nunca debería salir porque ...
... siempre debe resultar en
i
ser-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 a
String
, entonces no hay problema ...Solo dos puntos de buena práctica de codificación ...
String.valueOf()
.equals()
, en lugar de argumento, minimizando así NullPointerExceptions.La única forma en que conseguí que esto no ocurriera fue usando
String.format()
... esencialmente parece que Java necesita un poco de tiempo para recuperar el aliento :)
EDITAR: Esto puede ser una coincidencia completa, 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 mostrado es.
(valor decimal ASCII de 46)i
=-3
, el carácter mostrado es-
(valor decimal ASCII de 45)i
=-4
, el carácter mostrado 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 mostrado 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 mostrado 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.fuente
(int)'/' == 47
;(char)-1
no está definido0xFFFF
es <no es un personaje> en Unicode)getNumericValue()
relaciona con el código dado? y cómo convertir-1
a'/'
??? ¿Por qué no'-'
,getNumericValue('-')
también es-1
??? (Por cierto, muchos métodos regresan-1
)getNumericValue()
envalue
(/
) para obtener el valor del personaje. Está 100% correcto de que el valor decimal ASCII de/
debería ser 47 (era lo que también esperaba), perogetNumericValue()
estaba devolviendo -1 en ese punto como había agregadoSystem.out.println(Character.getNumericValue(value.toCharArray()[0]));
. Puedo ver la confusión a la que te refieres y he actualizado la publicación.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
i
dentro delfor
bucle.Si reemplaza la
String value = i + "";
línea conString value = String.valueOf(i) ;
su código funciona como se esperaba.La concatenación que se usa
+
para convertir el int en una cadena es nativa y puede tener errores (curiosamente, probablemente la estamos encontrando ahora) y causar ese 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 sobre el mismo.
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:
Mi versión de Java es la 8.
fuente
StringConcatFactory
(OpenJDK 13) oStringBuilder
(Java 8)StringConcatFactory
clase. pero hasta donde yo sé java hasta java 8 java don; t soporta la sobrecarga del operadorException in thread "main" java.lang.ArrayIndexOutOfBoundsException: -1
error. Extraño.i + ""
se compila exactamente comonew StringBuilder().append(i).append("").toString()
en Java 8, y su uso eventualmente también produce la salida