Mostrando las entradas con la etiqueta garbage collection. Mostrar todas las entradas
Mostrando las entradas con la etiqueta garbage collection. Mostrar todas las entradas

lunes, mayo 09, 2011

Oracle Coherence afirma haber conseguido rendimientos de RAM con SSD

lunes, mayo 09, 2011 por Martín

Llevo unos días bastante liado y con el blog un poquillo abandonado. A ver si lo solucionamos. Hoy, repasando mi lista de feeds he visto una noticia que ha salido en varios medios sobre el lanzamiento de Oracle Coherence 3.7.

Oracle compró Coherence en el 2007 (wow, como pasa el tiempo, este link apunta a mi propio blog en el Marzo del 2007, esto me ha dado un poco de vertigo). El producto es muy potente y es muy utilizado en sistemas que requiren alto rendimiento. En partícular es muy popular en sistemas de trading.

martes, diciembre 28, 2010

Lucene, Grids, Heaps y otras cosas del montón

martes, diciembre 28, 2010 por Martín

En el penúltimo post explicaba la importancia en ciertos escenarios de que los objetos tengan una vida de corta duración. Only the good die young. Uno de esos escenarios es el caso en el que tengamos una Heap enorme, caso en el que la recolección de basura puede llevar mucho tiempo.

¿Y qué tamaño puede tener uno de esos Heaps tan enormes? Recuerdo hace años que tener un Heap de 512Mb ya era la caña. Después, cuando estabamos haciendo aplicaciones de trading hace ya casi cinco años, jugábamos con Heaps de 4gb, que no estaba mal pero ya resultaba bastante pequeño, pero era fruto sobre todo de las limitaciones de los sistemas operativos de 64 bits y en producción si que nos íbamos a 8, pero con la incertidumbre de no saber con exactitud que iba a pasar.

jueves, diciembre 23, 2010

GC: Only the good die young

jueves, diciembre 23, 2010 por Martín


Una frase popular dentro del frikimundo de la Garbage Collection es "Only the good die young", que proviene además de una canción bastante popular de Billy Joel y que le viene que ni pintada.

Un poco de background para la frase. Todo esto depende de la máquina virtual utilizada, pero tomando como referencia Hotspot que es una máquina virtual generacional, tenemos que la memoria se divide en dos generaciónes: Young generation y Tenured generation. Cada una de estas zonas a su vez está dividida en otras zonas, pero esto lo vamos a obviar por razones de simplicidad.

La Young Generation es mucho más pequeña que la Tenured Generation. Por lo tanto, la recolección de basura ahí es mucho más rápida. Pero aún más importante, la recolección de basura en la Young Generation se hace constantemente y no requiere parar los diferentes threads de la máquina virtual de Java, mientras que normalmente (recordad, esto es una visión meramente pedagógica y simplificada) la recolección de basura en la Tenured Generation exige parar todos los threads de la máquina virtual.

sábado, noviembre 03, 2007

Una razón algo más técnica para no escribir métodos largos

sábado, noviembre 03, 2007 por Martín

Hoy en día creo que más o menos todo el mundo tiene claro que escribir métodos muy largos es una mala práctica. Nos lo dicen en la universidad, nos lo dicen en los libros de programación, de ingeniería del software, de patrones o de refactorización. Los paquetes de métricas y de detección estática de errores nos avisan de que nuestros métodos son demasiado largos y el jefe nos echará una buena bronca si ve que hacemos un método de 5.000 líneas.

Aún así, cuando uno está programando siempre hay esos momentos en que se siente un poco vago con algún método al que va añadiendo cosas y cosas, porque total, estamos haciendo sólo un boceto, ¿no? El problema es que esos bocetos se convierten en la versión definitiva (doy fe, que tengo un par de métodos grandes por ahí que verguenza me dan) y ahí ha quedado. Pero bueno, funciona, ¿no?. Y no lo va a tocar nadie nunca jamás de los jamases, ¿no?, ¿qué hay de malo entonces?. Pues sí, incluso creyéndonos que nadie verá ese código nunca, hay algo que puede ser malo:

Hoy en día los lenguajes populares se ejecutan en máquinas virtuales. Estas máquinas virtuales disponen de optimizadores que traducen el bytecode de nuestros programas en código máquina, pero que además realizan numerosas optimizaciones sobre el mismo para obtener mucho más rendimiento. Estas optimizaciones no sólo se limitan a reordenaciones, inlining y todas estas cosas sino que en algunos lenguajes como Java (no sé si en otros, supongo que sí) se almacenan mapas de objetos en los que se especifican donde se encuentran los objetos a los que apuntan las referencias: la referencia A está en un registro, la referencia B está en la pila, etc.

Dicha información se utiliza durante la recolección de basura para acelerar la ejecución. Si no se dipone de esos mapas de objetos en el momento de la recolección, el recolector los tendrá que crear por si mismo. Para crear estos mapas, el recolector trata de simular la ejecución del método en relación a la posición de la referencia que se pretende recolectar. A métodos más grandes, simulaciones más complejas. Por cierto, que además la simulación depende de en que línea salte la GC así que aunque los mapas se guardan, ésto no sirve de mucho si el método es grande.

¿Y qué tiene que ver esto con el tamaño de los métodos? Pues resulta que Java tiene un límite para el tamaño de los métodos a los que el JIT aplicará optimizaciones. Si los métodos sobrepasan ese límite no pasarán por el JIT, y por lo tanto estos mapas de objetos no se generarán, y por lo tanto la recolección de basura será mucho más larga de lo que debería ser. Más concretamente el límite son 8000 bytes de bytecode. Para deshabilitar este límite en la máquina virtual de Sun, se puede usar la opción "-XX:-DontCompileHugeMethods". Pero ojo, esto no es una buena idea ya que lo más probable es que el JIT también sufra a la hora de optimizar, a fin de cuentas si el límite lo han puesto así será por algo. Así que mejor refactorizar y hacer más métodos y más pequeños.

Bueno, ahora igual he engañado a alguno que estará pensando. ¡Qué listo es Martín, cuanto sabe! Pues no. Todo esto no me lo he encontrado, ni lo he vivido, ni nada de nada. No es más que un resumen, bueno más bien una traducción porque me ha quedado bastante largo, de lo que cuenta el genial Jon Masamitsu en su blog, en el que ha publicado una entrada en la que explica algunas experiencias que le han pasado con clientes, relacionadas con parámetros raros de la máquina virtual. Entre estas experiencias está la de un cliente que veía que los tiempos de GC eran enormes y que no sabía por que, pero que de pronto descubren que comentando el código los tiempos de GC son mucho menores.

Pues ya sabéis, ahroa si alguna vez habláis con alguien sobre esto de los métodos largos, y os dice que eso de la refactorización no son más que chorradas, os podéis sacar de la manga esta explicación que es de las de dejar con la boca abierta.

jueves, octubre 18, 2007

Una nueva utilidad para monitorizar las máquinas virtuales Java

jueves, octubre 18, 2007 por Martín

Ayer descubrí VisualVM que es una verdadera joya. Se trata de una aplicación que es capaz de descubrir los diferentes procesos Java que se ejecutan en una máquina, tanto local como remota, y que te ofrece montones de información interna sobre como están funcionando estos procesos.



VisualVM está preparado para funcionar sobre todo con Java 6. En realidad, es capaz de funcionar con Java 5 o Java 1.4, pero con estas dos últimas máquinas virtuales no obtendremos realmente mucha información.

Ahora bien, si estamos utilizando Java 6 entonces esta herramienta se convierte en fundamental. Ofrece muchísima información. Nada que no se pueda obtener con otras herramientas como jhat o jstat, pero lo mejor es que viene de forma gráfica y encima no hay que configurar nada en la máquina virtual, simplemente te conectas a ella y listo.



A mi lo que más me ha gustado es la facilidad para obtener un volcado de memoria o de threads simplemente con un click. Que maravilla. No os imagináis lo útil que es esto cuando estás ejecutando un Applet ya que normalmente suele ser bastante engorroso el andar habilitando JMX para usar JConsole, o habilitando agentes en la configuración de tu Java plugin, etc. etc. Por cierto, que esta herramienta es muy similar en funcionamiento y concepto a Mission Control de BEA de la que ya hablé hace tiempo.

¿Algún otro truquillo/herramienta que utilicéis?

miércoles, julio 11, 2007

GC para tiempos bajos de respuesta (y II)

miércoles, julio 11, 2007 por Martín

Continuo y finalizo el post de ayer sobre recolección de basura en sistemas que necesitan tiempos rápidos de respuesta.

  1. Optimizar el tamaño de los TLABs : Esta es para nota. Un TLA (TLAB en Sun JDK, Jon Masamitsu escribió hace un año una excelente entrada sobre esto) es una zona de memoria en la que un thread tiene permiso exclusivo para acceder lo que significa que el thread puede reservar memoria sin ningún tipo de bloqueo por lo que el rendimiento es mayor.

    Configurar un tamaño adecuado de estos TLAs es algo complicado, de hecho se hace una tarea muy ardua sin una buena herramienta para controlar los cambios en el rendimiento, pero puede incrementar considerablemente el rendimiento.
  2. Ajustar el límite para la recolección de basura : Las recolecciones completas de basura no se realizan cuando el heap está lleno, ya que en ese momento se lanzaría una OutOfMemoryException, sino que se lanzan al llegar a un determinado límite. Las máquinas virtuales permiten cambiar ese límite.

    En ocasiones este límite puede ser demasiado bajo, y si realmente se conoce muy bien el funcionamiento de la aplicación se puede ajustar para que sea un poco mayor y de este modo quepan más objetos en memoria (con el consiguiente coste de recolección).
  3. Evitar la fragmentación y optimizar el ratio de compactación: Las máquinas virtuales sufren el mismo problema que la memoria física o los discos duros. El almacenamiento continuo de objetos crea fragmentación. Para solucionar esto, existe lo que se llama compactación. Periodicamente, con cada recolección de basura, la máquina virtual tratará de compactar algo del espacio del heap para que cojan más objetos.

    Las máquinas virtuales suelen ofrecer parámetros avanzados para configurar el modo en el que se compacta la memoria. Por ejemplo en JRockit es posible establecer el tanto por ciento de heap que se quiere compactar en cada recolección. Un porcentaje alto hará que se gaste mucho tiempo compactando, mientras que un porcentaje bajo puede incrementar demasiado la fragmentación, llegando incluso en el caso límite a provocar OutOfMemoryException cuando no hay más espacio para colocar objetos en memoria. Otra opción típica es en lugar de especificar el ratio de compactación sería especificar directamente el máximo número de objetos que pueden ser compactados en cada recolección de basura.
  4. Conocer el tamaño de los objetos: Conocer el tamaño de los objetos que la aplicación maneja también es importante. En un caso extremo, si se están almacenando objetos demasiado grandes, que no cojan en la young generation (e.g. enormes imágenes) irán directamente a parar a la old generation, incluso cuando se usen tan sólo temporalmente. En este caso, y si nuestra aplicación maneja frecuentemente este tipo de objetos, podemos caer en una situación en las recolecciones completas de basura sean muy frecuentes, o en la que simplemente obtengamos OutOfMemoryExceptions.

Bueno, más o menos estas son las cosillas que recogí en el análisis y que quizás a alguien le valgan. Dejo aquí unos enlaces con mucha más información y que he utilizado de fuente:

BEA JRockit configuration and tuning guide
BEA Memory Management Concepts Guide
IBM Garbage Collection Policies
Tuning Garbage Collection with the 5.0 Virtual Machine
Jon Masamitsu's Weblog

Si alguno tenéis alguna recomendación, añadido, corrección, etc., pues los comentarios son más que bien recibidos.

martes, julio 10, 2007

GC para tiempos bajos de respuesta (I)

martes, julio 10, 2007 por Martín

Hace unas semanas tuve que realizar un pequeño análisis de la recolección de basura en JRockit para llegar a una recomendacción de argumentos, políticas, etc. Voy a poner aquí algunas de las notas por si a alguno le resultan útiles, aunque la verdad es que muchos seguro que ya las conocéis de sobra.

Gran parte de este análisis está basado en la búsqueda de tiempos de respuesta bajos. En el caso de intentar optimizar un sistema para un máximo throughput, sería necesario realizar algunos cambios. Son notas genéricas en la medida de lo posible aunque mi análisis estaba orientado a JRockit.

  1. Preferir GC estático sobre dinámico: Algunas máquinas virtuales, como por ejemplo JRockit, tienen un modo de ejecución dinámico en la que van modificando los parámetros del algoritmo de recolección de basura en base al rendimiento de la aplicación. Son capaces de aumentar el tamaño del heap, disminuirlo, cambiar el tamaño de las generaciones o cambiar los valores de los diferentes parámetros según crean conveniente.

    El problema de esto es que con un algoritmo dinámico pierdes el control de la gestión de basura, y aunque la máquina virtual es realmente inteligente, en muchas ocasiones te desea restringir lo máximo posible la libertad del GC para tener la situación controlada.

    Este es el caso de los sistemas donde no se puede tolerar de ningún modo un tiempo de respuesta alto, ya que puede significar el incumplimiento de un SLA y como consecuencia una perdida económica importante.
  2. Establecer el máximo el mínimo del heap al mismo valor: De lo contrario, la máquina virtual estará gastando valiosos ciclos de CPU en alargar y encoger el tamaño del heap continuamente según considere conveniente. Nuevamente esto puede afectar al rendimiento de la aplicación e influir negativamente en los tiempos de respuesta.
  3. Asegurarse de que el tamaño del heap no es tan o más grande que la memoria física: En un servidor (o cualquier ordenador) siempre hay más procesos ejecutándose a parte de la máquina virtual. Estos procesos requerirán su espacio de memoria física. Si el tamaño del heap de la máquina virtual es demasiado grande entonces podemos caer una situación en la que nuestro servidor esté continuamente paginando porque no habrá suficiente memoria física para todos los procesos, o sólo para él si ha pedido demasiada memoria. Esto originará continuos accesos a memoria virtual para intercambiar páginas de memoria, lo que enlentecerá todo el sistema, disminuyendo drásticamente el rendimiento del mismo.
  4. Utilizar un algoritmo de recolección concurrente: Hoy por hoy la mayoría de máquinas virtuales te ofrecen la posibilidad de utilizar diferentes algoritmos según tus necesidades. Dos de los algoritmos más típicos son el concurrente, orientado a tiempos de respuesta bajos, y el paralelo orientado a conseguir un máximo throughput.

    En el algoritmo concurrente la recolección de basura se hace paralelamente a la ejecución de la aplicación, en pequeñas pausas que dan la impresión de que no hay recolección de basura. La desventaja es que se dedica constantemente tiempo de CPU para realizar esta labor.

    En un algoritmo paralelo, el recolector simplemente no realiza la recolección de basura hasta que no se llena por completo el heap. Una vez que el heap está completo, se para la ejecución de todos los hilos y se inicia una enorme recolección de basura dedicando todas las CPUs disponibles a esa tarea. Todas las CPUs realizan la recolección de modo paralelo.

    El problema de este último algoritmo para sistemas de respuesta baja es que la recolección puede ser enormemente costosa y detener el sistema por segundos o incluso minutos, lo que significa con toda seguridad romper SLAs que tengas marcados. Es por ello que un sistema como estos no puede recurrir a una recolección de este estilo que para por completo la ejecución del sistema.
  5. Optimizar el tamaño de la young generation:
  6. Los algoritmos orientados a una rápida respuesta suelen estar basados en generaciones. Normalmente hay dos generaciones, una joven (young generation, nursery en JRockit) en la que se alojan los objetos recientemente creados, y otra vieja (old generation) en la que se van guardando los objetos que permancen más tiempo en memoria. Las recolecciones de basura en la young generation suelen ser cortas y rápidas. Cuando la young generation se llena, se realiza una recolección, se vacia la generación y se mueven los objetos que deban quedar en memoria a la old generation. El tamaño de esta young generation es bastante importante. La idea es mantener su tamaño lo más grande que sea posible al tiempo que se mantiene el tiempo de recolección en un rango aceptable. Si la young generation es demasiado grande, las recolecciones serán más costosas y esto impactará en el rendimiento del sistema. Al mismo tiempo, estaremos dejando menos espacio disponible para la old generation, lo que eventualmente puede hacer que las recolecciones completas de todo el heap sean más frecuentes. Por el contrario, si el tamaño de la young generation es demasiado pequeño, no daremos tiempo a que los objetos se liberen, y la mayor parte serán promocionados rápidamente a la old generation, lo que hará que ésta última se llene rápidamente causando recolecciones completas de todo el heap. Es importante pues mantener el tamaño de la young generation en un punto equilibrado. Lo suficientemente grande para darle tiempo a los objetos a ser desreferenciados pero sin que sea demasiado costoso realizar una recolección de basura en ella.


Como he visto que este post me queda bastante largo he decidido dividirlo en dos. Muy pronto pondré la segunda parte. Mientras tanto si alguien quiere aportar algo... bievenenido sea.

martes, mayo 15, 2007

Los peligros de GC ergonomics

martes, mayo 15, 2007 por Martín

Jon Masamitsu es el maestro de la garbage collection en Java y hace un par de semanas publicó una entrada muy interesante sobre como, y lo más importante, por que, alterar los valores de GC ergonomics.

GC ergonomics es un sistema que corre paralelamente al garbage collector y va ajustando los tamaños de los diferentes espacios del heap de modo que se ajusten mejor al funcionamiento aparente de la aplicación. De este modo, si la aplicación no tiene mucha actividad, GC ergonomics reducirá el tamaño del heap (de sus secciones en bases a factores de reducción individuales), y si la aplicación requiere más objetos entonces ampliará el tamaño.

El problema es que hay muchas aplicaciones a las que no les interesa este funcionamiento, porque en caso de que haya mucha actividad y que ésta no siga unos patrones predefinidos entonces el sistema de GC ergonomics puede hacer más daño que ayuda moviendo constantemente los límites y ocasionando major garbage collections innecesarios.

En el blog explica varios truquillos para tunning o como desactivar el sistema de GC ergonomics que en algunos casos es simplemente lo mejor.