Quantcast
Channel: Teclarios » Investigación
Viewing all articles
Browse latest Browse all 6

Heisenberg en Java

$
0
0

En 1927, el alemán Werner Heisenberg enunció su famoso principio de incertidumbre (o de indeterminación), con el que demostró que resulta imposible conocer con total precisión la posición y velocidad de un electrón simultáneamente. Esto no significa que no se pueda averiguar la posición o la velocidad; significa que no se puede hacer a la vez. Por tanto es imposible conocer su trayectoria.

Si trasladamos esto a informática, podemos renombrar electrón por programa, velocidad por tiempo de ejecución y posición por consumo de memoria. Medir empíricamente el tiempo total de ejecución de un programa (pongamos, uno que ordene un vector de un millón de números) es posible. En un sistema Unix, es sencillo con el comando time, aunque se estará añadiendo al tiempo de ejecución el del montaje del proceso, inicialización de la librería estándar de C/C++ (o lanzamiento de la JVM), y el cierre. También se podría tomar el tiempo “dentro” del programa, lanzando el cronómetro en el momento de empezar a ordenar, y deteniéndolo justo al acabar para evitar este problema.

Por otro lado, también es posible medir el consumo máximo de memoria. Esto es un poco más complicado, y normalmente necesitará hacer encuesta sobre el proceso. Y aquí es donde viene lo curioso. En principio podremos conseguir una gráfica tan detallada como queramos que muestre la evolución de consumo de memoria, pero estaremos incurriendo en un coste en uso de procesador que estropeará la medición de tiempo de ejecución. Heisenberg era un visionario.

Si nos centramos en Java, el asunto es un poco más peliagudo. Desde hace tiempo, la plataforma incluye lo que se llama JMX, que permite la “instrumentación” de la aplicación. Es un juguetito de lo más curioso para realizar, de manera sencilla, aplicaciones (normalmente servidoras) que puedan publicar información sobre su estado, útil para, por ejemplo, balanceadores de carga. Ese mismo framework es usado por el propio JDK para proporcionar clases con las que obtener el tiempo de ejecución de las hebras y el estado del heap (entre otras muchas cosas). El JDK contiene además la herramienta jconsole que permite “entrar” en una aplicación Java en ejecución y obtener gráficas precisamente sobre el consumo de memoria en el heap, número de hebras, estado, tiempo en ejecución, etcétera. Otra alternativa es VisualVM, que proporciona parte de la información dada por jconsole, y alguna otra.

Os pongo un ejemplo. Ante un código similar al siguiente:

public class Test {
   public static void main(String[] args) {
    while(true) {
      String foo = new String("foo");
    }
  }
}

VisualVM nos proporciona una gráfica de uso de memoria más o menos predecible:

Consumo de memoria de una aplicación simple

La ejecución la he hecho con una limitación bastante extrema de la memoria de heap disponible (opción -Xmx de Java) para que se vea mejor. Se aprecia claramente los puntos en los que hay recolección de basura y se eliminan todas las cadenas que se han ido acumulando en las últimas vueltas al bucle.

Esta gráfica se obtiene haciendo encuesta periódicamente, que, como comentaba antes, interfiere en el uso de CPU por lo que el tiempo total (obviamente, no en este ejemplo, pero sí en uno más realista) se verá afectado.

Lo curioso es que en este caso, la monitorización de la memoria supone además una contaminación de la propia medición. El API JMX mencionado antes es ligeramente intrusivo con la aplicación, dado que se le pregunta cual es su consumo de memoria, y para responder tiene que crear objetos Java con los datos a devolver; objetos que, naturalmente, irán en el heap y se contarán en la próxima medición. De modo que Heisenberg se quedó corto aquí. En Java, la medición del consumo de memoria afecta al consumo de memoria.

La gráfica anterior se consigue encuestando periódicamente a la aplicación Java sobre su consumo de memoria. Si la frecuencia de consultas no es muy alta, podríamos saltarnos “picos” en el consumo. Por ejemplo, la aplicación podría pedir 100 MB de memoria y desprenderse del objeto, la JVM liberarlo, y el pico en el consumo no lo veríamos a no ser que fuéramos lo bastante afortunados para tomar la muestra entre medias.

La solución es no utilizar encuesta sino pedirle a la JVM que estemos monitorizando que nos avise cuando salte el recolector de basura. Así sabremos que siempre detectaremos los picos de consumo. Y, es más, gracias al API JMX ni siquiera necesitamos jconsole o VisualVM; una aplicación puede enterarse de cuando salta su propio recolector de basura para autocontrolarse. Os pongo un ejemplo de código:

import javax.management.*;
import java.lang.management.*;
import java.util.List;

class GCListener implements NotificationListener {
  public void handleNotification(Notification notification,
                                (Object handback) {
    // notification tiene el estado de la memoria antes y
    // después de que pasara el recolector.
 }
} // class GCLIstener

public class Test2 {

  public static void main(String[] args) {
    GCListener listener = new GCListener();
    List<GarbageCollectorMXBean> gcs;
    gcs = ManagementFactory.getGarbageCollectorMXBeans();
    for (GarbageCollectorMXBean gc : gcs) {
      NotificationEmitter emitter = (NotificationEmitter) gc;
      emitter.addNotificationListener(listener, null, null);
    } // for

    while (true) {
      String foo = new String("foo");
    }
  } // main
} // class Test2

Aunque ahora el código es un poco más largo, sigue siendo fácil de seguir. Nos registramos como oyentes de un objeto de instrumentación de Java, y nos llamará tras cada ejecución del recolector, con la información del estado de memoria antes y después de ella.

Y claro… nos envía un objeto en Java. ¡Más incertidumbre, amigo Heisenberg! Para poder darnos el estado de la memoria antes de ejecutar el recolector, tiene que crear un objeto Java que lo guarde. Y justo después de ejecutarlo, tiene que crear otro con el nuevo estado. Esto no sólo contamina la muestra… sino que si el recolector ha saltado porque no hay memoria para nuevos objetos, el intento de crear el objeto que guarde el estado de la memoria antes de “limpiarla” no tendrá memoria y lo hará saltar todo por los aires. ¡¡Es como si al mirar el cuentakilómetros del coche corriéramos el riesgo de que se gripe el motor!!

Pero no, amigos teclarios. Esto no acaba aquí… porque la implementación actual de la JVM de Sun/Oracle (al menos la que yo estoy usando, 1.7.0_147 de 64 bits sobre Linux) tiene un bug… y los objetos con los informes del estado de la memoria… no son recolectados nunca. Es decir… los objetos que te han dado para informarte sobre el estado de la memoria, aunque no te los guardes, nunca serán liberados. La JVM, con su cacareado recolector de basura, tiene una fuga de memoria. Mirad la gráfica que nos muestra VisualVM con el programa anterior, ejecutado también, por acortar, con un heap pequeño:

Consumo de memoria del segundo ejemplo.

Podéis ver cómo el consumo de memoria del heap crece contínuamente hasta el final, en el que se sufre la famosa OutOfMemoryException. Cada vez que miras el cuentakilómetros, el coche acelera un poquito más… y claro, el motor termina gripándose.

De modo que Heisenberg fue un visionario. Pero la JVM le ha superado en incertidumbre.

Me da miedo pensar en la posibilidad de que sea verdad eso de que los ordenadores cuánticos, con sus bits valiendo 0, o 1 o … 0.5, llegarán algún día a nuestras casas. No sé qué se les ocurrirá hacer a nuestros amigos de Sun/Oracle con Schrödinger y su gato.


Viewing all articles
Browse latest Browse all 6

Latest Images





Latest Images