usar new icon example codigo borderfactory java memory jvm java-8 timing

example - new icon java



CuestiĆ³n de tiempo/memoria impar de Java 8 (2)

Me he topado con un problema bastante extraño que puedo crear al ejecutar Java 8. El problema se presenta como si algún tipo de error de tiempo ocurriera dentro de la propia JVM. Es de naturaleza intermitente, pero fácilmente reproducible (al menos dentro de mis entornos de prueba). El problema es que un valor de matriz que se establece explícitamente se destruye y se reemplaza con un 0.0 en determinadas circunstancias. Específicamente, en el siguiente código, la array[0] está evaluando a 0.0 después de la línea new Double(r.nextDouble()); . Luego, si mira de inmediato los contenidos de la array[0] nuevamente, ahora muestra que el valor es el valor correcto de 1.0. La salida de muestra de ejecutar este caso de prueba es:

claims array[0] != 1.0....array[0] = 1.0 claims array[0] now == 1.0...array[0] = 1.0`

Estoy ejecutando Windows 7 de 64 bits y puedo reproducir este problema, tanto desde Eclipse como desde la línea de comandos, con los JDK 1.8_45, 1.8_51 y 1.8_60. No puedo producir el problema ejecutando 1.7_51. Se han demostrado los mismos resultados en otra caja de Windows 7 de 64 bits.

Este problema apareció en una gran pieza de software no trivial, pero he logrado condensarlo en unas pocas líneas de código. A continuación se muestra un pequeño caso de prueba que demuestra el problema. Es un caso de prueba que parece bastante extraño, pero parece que todos son necesarios para causar el error. No se requiere el uso de Random : puedo reemplazar todo r.nextDouble() con cualquier valor doble y demostrar el problema. Curiosamente, si someArray[0] = .45; se reemplaza por someArray[0] = r.nextDouble(); , No podría replicar el problema (aunque no hay nada especial sobre .45 ). La depuración de Eclipse tampoco es de ayuda, ya que cambia el tiempo de tal manera que ya no ocurre. Incluso una System.err.println() bien situada provocará que el problema ya no aparezca.

Una vez más, el problema es intermitente, por lo que para reproducir el problema uno podría tener que ejecutar este caso de prueba varias veces. Creo que lo más que he tenido que ejecutar es alrededor de 10 veces antes de obtener el resultado que se muestra arriba. En Eclipse, le doy uno o dos segundos después de ejecutar y luego lo mato si no ha sucedido. Desde la línea de comandos, lo mismo: ejecútelo; si no ocurre, CTRL+C para salir e intente de nuevo. Parece que si va a suceder, sucede bastante rápido.

Me he encontrado con problemas como este en el pasado, pero todos eran problemas de subprocesos. No puedo entender qué está sucediendo aquí. Incluso he mirado bytecode (que era idéntico entre 1.7_51 y 1.8_45, por cierto).

¿Alguna idea sobre lo que está sucediendo aquí?

import java.util.Random; public class Test { Test(){ double array[] = new double[1]; Random r = new Random(); while(true){ double someArray[] = new double[1]; double someArray2 [] = new double [2]; for(int i = 0; i < someArray2.length; i++) { someArray2[i] = r.nextDouble(); } // for whatever reason, using r.nextDouble() here doesn''t seem // to show the problem, but the # you use doesn''t seem to matter either... someArray[0] = .45; array[0] = 1.0; // commented out lines also demonstrate problem new Double(r.nextDouble()); // new Float(r.nextDouble(); // double d = new Double(.1) * new Double(.3); // double d = new Double(.1) / new Double(.3); // double d = new Double(.1) + new Double(.3); // double d = new Double(.1) - new Double(.3); if(array[0] != 1.0){ System.err.println("claims array[0] != 1.0....array[0] = " + array[0]); if(array[0] != 1.0){ System.err.println("claims array[0] still != 1.0...array[0] = " + array[0]); }else { System.err.println("claims array[0] now == 1.0...array[0] = " + array[0]); } System.exit(0); }else if(r.nextBoolean()){ array = new double[1]; } } } public static void main(String[] args) { new Test(); } }


Puedo reproducir este error en Zulu (una compilación certificada de OpenJDK) con el código publicado en http://www.javaspecialists.eu/archive/Issue234.html

Con Oracle VM, solo puedo reproducir este error después de ejecutar el código en Zulu. Parece que Zulu contamina el caché de búsqueda compartida. La solución en este caso es ejecutar el código con -XX: -EnableSharedLookupCache.


Actualización : parece que mi respuesta original fue incorrecta y OnStackReplacement acaba de revelar el problema en este caso particular, pero el error original estaba en el código de análisis de escape. El análisis de escape es un subsistema de compilación que determina si el objeto escapa del método dado o no. Los objetos no escapados se pueden escalar (en lugar de la asignación en el montón) o totalmente optimizados. En nuestra prueba, el análisis de escape sí importa ya que varios objetos creados seguramente no escapan al método.

Descargué e instalé JDK 9 early access build 83 y noté que el error desaparece allí. Sin embargo, en JDK 9 Early Access Build 82 todavía existe. El changelog entre b82 y b83 muestra solo una corrección de errores relevante (corríjame si estoy equivocado): JDK-8134031 "Compilación JIT incorrecta del código complejo con análisis de entrada y escape". El caso de testcase comprometido es similar: bucle grande, varios cuadros (similares a los arrays de un elemento en nuestra prueba) que conducen al cambio repentino del valor dentro del cuadro, por lo que el resultado se vuelve silenciosamente incorrecto (sin bloqueo, sin excepción, solo valor incorrecto). Como en nuestro caso, se informa que el problema no aparece antes de 8u40. El arreglo introducido es muy corto: solo un cambio de una línea en el origen del análisis de escape.

De acuerdo con el rastreador de errores de OpenJDK, la solución ya se transfirió a la rama JDK 8u72, que está scheduled para ser lanzada en enero de 2016. Parece que ya era demasiado tarde para respaldar esta solución con el próximo 8u66 .

La solución alternativa sugerida es desactivar el análisis de escape (-XX: -DoEscapeAnalysis) o deshabilitar la eliminación de la optimización de asignaciones (-XX: -EliminateAllocations). Por lo tanto, @apangin estaba más cerca de la respuesta que yo.

A continuación está la respuesta original

En primer lugar, no puedo reproducir el problema con JDK 8u25, pero sí con JDK 8u40 y 8u60: a veces se ejecuta correctamente (atascado en un bucle infinito), a veces sale y sale. Entonces, si la degradación de JDK a 8u25 es aceptable para usted, puede considerar hacer esto. Tenga en cuenta que si necesita arreglos posteriores en javac (muchas cosas especialmente relacionadas con lambdas se arreglaron en 1.8u40), puede compilar con javac más nuevo, pero ejecutar en JVM anterior.

Para mí, parece que este problema en particular probablemente sea un error en el mecanismo OnStackReplacement (cuando OSR ocurre en el nivel 4). Si no está familiarizado con OSR, puede leer esta respuesta . El OSR seguramente ocurre en tu caso, pero de una manera un poco extraña. Aquí está -XX:+UnlockDiagnosticVMOptions -XX:+PrintCompilation -XX:+TraceNMethodInstalls para ejecución fallida ( % significa OSR JIT, @ 28 significa posición de bytecode de OSR, (3) y (4) significa nivel de nivel):

... 91 37 % 3 Test::<init> @ 28 (194 bytes) Installing osr method (3) Test.<init>()V @ 28 93 38 3 Test::<init> (194 bytes) Installing method (3) Test.<init>()V 94 39 % 4 Test::<init> @ 16 (194 bytes) Installing osr method (4) Test.<init>()V @ 16 102 40 % 4 Test::<init> @ 28 (194 bytes) 103 39 % 4 Test::<init> @ -2 (194 bytes) made not entrant ... Installing osr method (4) Test.<init>()V @ 28 113 37 % 3 Test::<init> @ -2 (194 bytes) made not entrant claims array[0] != 1.0....array[0] = 1.0 claims array[0] now == 1.0...array[0] = 1.0

Por lo tanto, la OSR en el nivel 4 se produce para dos desplazamientos de código de bytes diferentes: desplazamiento 16 (que es el punto de entrada de bucle while) y desplazamiento 28 (que es el punto de entrada de bucle anidado). Parece que se produce alguna condición de carrera durante la transferencia de contexto entre las dos versiones compiladas de OSR de su método, lo que da como resultado un contexto que no funciona. Cuando la ejecución se transfiere al método OSR, debe transferir el contexto actual, incluidos los valores de las variables locales, como array y r al método OSR. Algo malo sucede aquí: probablemente por poco tiempo, la versión <init>@16 OSR funciona, luego se reemplaza por <init>@28 , pero el contexto se actualiza con un poco de retraso. Es probable que la transferencia de contexto OSR interfiera con la optimización de "eliminar asignaciones" (como lo señala @apangin, desactivar esta optimización ayuda en su caso). Mi experiencia no es suficiente para profundizar más aquí, probablemente @apangin pueda comentar.

Por el contrario, en la ejecución normal, solo se crea e instala una copia del método OSR de nivel 4:

... Installing method (3) Test.<init>()V 88 43 % 4 Test::<init> @ 28 (194 bytes) Installing osr method (4) Test.<init>()V @ 28 100 40 % 3 Test::<init> @ -2 (194 bytes) made not entrant 4592 44 3 java.lang.StringBuilder::append (8 bytes) ...

Parece que en este caso no ocurre una carrera entre dos versiones de OSR y todo funciona perfectamente.

El problema también desaparece si mueve el cuerpo externo del lazo al método separado:

import java.util.Random; public class Test2 { private static void doTest(double[] array, Random r) { double someArray[] = new double[1]; double someArray2[] = new double[2]; for (int i = 0; i < someArray2.length; i++) { someArray2[i] = r.nextDouble(); } ... // rest of your code } Test2() { double array[] = new double[1]; Random r = new Random(); while (true) { doTest(array, r); } } public static void main(String[] args) { new Test2(); } }

También desenrollar manualmente el ciclo anidado elimina el error:

int i=0; someArray2[i++] = r.nextDouble(); someArray2[i++] = r.nextDouble();

Para golpear este error, parece que debe tener al menos dos bucles anidados en el mismo método, por lo que OSR puede ocurrir en diferentes posiciones de código de bytes. Entonces, para resolver el problema en su pieza de código en particular, puede hacer lo mismo: extraer el cuerpo del bucle en el método separado.

Una solución alternativa es deshabilitar completamente el OSR con -XX:-UseOnStackReplacement . Raramente ayuda en el código de producción. Los contadores de bucle todavía funcionan y si su método con muchas iteraciones-bucle se llama al menos dos veces, la segunda ejecución se compilará de todos modos. Además, incluso si su método con bucle largo no está compilado JIT debido a la OSR deshabilitada, los métodos que invoque seguirán compilados con JIT.