Entradas etiquetadas con arranque

Administrando tu JBoss desde el telefono

Nos ha pasado a todos. En algún momento uno de nuestros servidores de aplicaciones se vuelve inestable en el peor momento. En mi caso se producía una vez al año. Se trataba de una aplicación utilizada para realizar un determinado tramite. El resto del año el servidor se estaba tocando las narices, pero en ese mes echaba humo. Como la aplicación tenia un leak de memoria, conforme iba recibiendo visitas disminuía la memoria que realmente podía utilizar. Este tipo de comportamientos es relativamente fácil de observar en la gráfica del Heap desde jconsole o visualvm. Finalmente, como no podía ser de otra manera, el servidor de aplicaciones dejaba de responder. ¿Que hacia en estos casos? Pues reiniciarlo de forma preventiva en las horas de menor afluencia. Ya se que no era la solución perfecta, pero hasta que no corrigieran el leak, no había otra. En el peor de los casos, el servidor colapsaba antes del siguiente reinicio: alarma en el móvil de guardia, carrera para arrancar el portátil, activar la VPN… ya sabes como va.

Afortunadamente, y como no estamos solos, hay quien se ha propuesto facilitarnos la vida: ya tenemos aplicaciones gratuitas para monitorizar y gestionar JBoss/WildFly desde nuestro smartphone. ¿No te fías? Pues puedes revisar, descargar y compilar el código tu mismo. La aplicación esta disponible para Android y para iOS. Y en github tienes el código tanto de una como de la otra.

Ahora podrás reiniciar ese servidor de aplicaciones maldito antes de que el teléfono de guardia desate el apocalipsis.

Como calcular el consumo de memoria de un servidor de aplicaciones

Logo de Java

Logo de Java

En ocasiones nos resultara necesario conocer cuanta memoria RAM esta consumiendo alguno de nuestros servidores Java. Conocer este consumo nos permite, por ejemplo, saber si podemos instalar otro servidor en la misma máquina: por nada del mundo queremos que la maquina utilice la swap. En el momento en que la memoria de intercambio entra por la puerta, el rendimiento salta por la ventana.

En realidad no es necesario conocer el consumo con exactitud en un determinado momento. Lo que necesitaremos es la cota máxima. Debemos asegurar que la máquina no utilice memoria swap en el peor de los escenarios, por lo cual controlaremos que la suma de la memoria utilizada por los servidores de aplicaciones instalados y el sistema operativo nunca supere a la cantidad de memoria RAM de la máquina.

Para calcular la cota máxima de consumo, sumaremos los siguientes elementos:

Heap maximo

Este valor lo obtendremos del parámetro -Xmx de la configuracion del JDK. En el caso de que no hayamos fijado dicho máximo en la configuración, tomaremos el valor máximo por defecto. El valor por defecto, desde la versión del JDK 6u18, se calcula principalmente a partir de la memoria física disponible con algunas otras condiciones que podéis ver en las release notes del JDK mencionado. Básicamente sera un cuarto de la memoria física con un limite de 1G para el JDK de 32 bits y 32G para el JDK de 64 bits.

PermGen maximo

Este segundo valor será el indicado por el parametro -XX:MaxPermSize. Como en el caso anterior, si no lo hemos configurado, se utilizara el valor por defecto: 64M por lo general. No es raro encontrar aplicaciones que consumen mucho mas, pero aunque no fuera el caso, debemos añadir el PermGen máximo a la suma.

Máxima cantidad de memoria utilizada por procesos

Cada vez que arrancamos un servidor de aplicaciones, se inician una serie de procesos: el número dependera del servidor de aplicaciones que usemos y su configuración, de las aplicaciones que tengamos desplegadas y de la carga del servidor. Así que necesitaremos saber cuantos procesos se utilizan y cuanto ocupa la pila de cada proceso.

Empecemos por conocer el numero de procesos utilizados. Esto nos resultará muy sencillo si tenemos monitorizado nuestro servidor de aplicaciones por JMX. Veamos una captura de jconsole:

Comportamiento de Tomcat durante la prueba

Comportamiento de Tomcat durante una prueba (pulsa para agrandar)

Fijándonos en la parte superior derecha, veremos un gráfico con el titulo Threads. En el gráfico podemos ver que el servidor utiliza unos 40 procesos.

Ahora veamos como obtener cuanta memoria utiliza cada proceso. Esta cantidad se define con el parámetro -Xss y si no lo tenemos definido, los valores por defecto son 320K para la maquina de 32 bits y 1024k para la de 64. Por lo cual tendremos que:

Máxima memoria de procesos = Número de procesos * Memoria de cada proceso

Conclusión

Una vez que hemos obtenido todos los valores, solo necesitamos hacer una suma para conocer la cota máxima de consumo de nuestro servidor:

Heap máximo + PermGen máximo + Máxima memoria de procesos

De ahora en adelante, ya podrás calcular si puedes instalar otro servidor en una de tus maquinas o asegurarte de que hay RAM disponible para aumentar el Heap de alguno de ellos en caso de necesitarlo.

Los tres errores de memoria mas comunes que pueden detener tu servidor y como solucionarlos

Logo de JavaA nosotros, como responsables de los servidores de aplicaciones de la organización, lo peor que puede sucedernos es que alguno de ellos “decida” detenerse. Si eso ocurre, encontrar la causa y subsanarla es prioritario: el tiempo corre en contra de nuestros SLA y podemos incurrir en penalizaciones. Por otro lado, conocer las partes de la memoria del JDK y su funcionamiento te permitirá minimizar el consumo y optimizar los recursos de tus maquinas.

Para conocer el consumo de memoria y CPU de tu servidor es necesario configurar el protocolo JMX en el arranque. En esta entrada tienes un ejemplo de como se hace.

Errores de memoria

java.lang.OutOfMemoryError: Java heap Exception

Este error aparece cuando se ha llenado el Heap y no es capaz de liberar memoria. El Heap es la porción mas importante y grande que usa la maquina virtual. Almacena objetos java, en el sentido de instancias de clases. Este valor irá en función del numero de usuarios concurrentes: a mas usuarios, mayor deberá ser este valor. Para un numero alto de usuarios, tendremos que darle el máximo que podamos sin pasar los 4G para la maquina de 64bits. Si con eso no es suficiente, es preferible montar otro servidor de aplicaciones con la misma aplicación buscando algún tipo de solución LB-HA. Los parámetros de arranque a configurar:

-Xms<valor inicial>g -Xmx<valor máximo>g

Idealmente daremos el mismo valor a la asignación máxima y a la inicial para que no haya cortes por el redimensionamiento de la memoria.

java.lang.OutOfMemoryError: PermGen space

El área PermGen almacena los metadatos de las clases que ha sido creadas/cargadas. Esta porción de memoria esta aparte del Heap. Los valores por defecto son 64M para la JVM de 32 bits y de 96M para la de 64 bits. Si lo dimensionamos adecuadamente durante las pruebas iniciales de la aplicación, es raro que nos de problemas, salvo que hagamos redespliegues de la aplicación.

-XX:PermSize=<valor inicial>m -XX:MaxPermSize=<valor máximo>m

Como no varia en función del numero de usuarios concurrentes, una vez identificado el máximo valor de memoria consumida, asignaremos dicho valor como tamaño inicial y como tamaño máximo utilizaremos un valor un 15% superior, por seguridad.

Caused by: java.lang.StackOverflowError

La pila de memoria de Java también esta aparte del Heap. Su funcionamiento es muy similar al de la pila de C. Cada uno de los procesos del servidor tiene su propia pila. Este error aparece cuando alguno de los procesos ha llenado su pila y necesita mas. Es un error que puede aparecer si la aplicación utiliza algoritmos recursivos: por ejemplo recorrido de arboles. Los valores por defecto son 320K para la maquina de 32 bits y 1024k para la de 64. El tamaño de la pila se define con el siguiente parámetro:

-Xss<tam pila>k

En ocasiones es necesario cambiar este parámetro temporalmente. Por ejemplo, si necesitamos regenerar unos indices de una aplicación, subiremos este valor, realizaremos la reindexación y lo volveremos a bajar para no desperdiciar memoria durante el funcionamiento normal de la aplicación. Como en los casos anteriores, estamos definiendo un parámetro de arranque, por lo cual necesitaremos reiniciar el servidor de aplicaciones para que los cambios sean efectivos.

Conclusiones

Si nuestro servidor no responde y vemos uno de estos errores en el log:

  1. Detener completamente el servidor. Aunque no responda, el proceso del servidor puede seguir corriendo.
  2. Aumentar el valor de memoria que corresponda en función del error que tengamos.
  3. Arrancar el servidor.
  4. Identificar la causa del error.

Lo primero es lo primero: reducir lo máximo posible el tiempo de corte. Una vez recuperado el servicio, establece las medidas correctivas necesarias para que la interrupción no se repita.

Comparativa de tiempos de arranque de JbossAS

Hace unos días, buscando documentación sobre JbossAS, encontré en su página principal el siguiente texto:

Blazing fast start-up
Experience ground breaking startup speed!
In the highly optimized boot process of AS 7, services are started concurrently to eliminate unnecessary waits and to tap into the power of multi-core processors. Non-critical services are kept on ice until first use.
Subsequent boots save additional time by leveraging cached or indexed metadata.
As a result, AS 7 offers a 10-fold reduction in startup time over previous versions and even gives Jetty and Tomcat a run for their money.

Me pareció tan llamativo, que quise comparar las diferentes versiones de JbossAS out-of-the-box. Utilizando una maquina Windows con varios procesadores y los servidores extraídos directamente de sus paquetes. No se ha configurado nada: se ha usado el JDK por defecto de la consola (JDK 1.6.0_22) y se ha utilizado el  servidor default para Jboss5 y Jboss6 y standalone para Jboss7.

La comparativa no pretende ser en absoluto científica, ni pretende establecer valoraciones absolutas. No importan si las cifras obtenidas son altas o bajas, sino si son mayores o menores que las de otra versión.  Y como digo, el entorno es lo de menos.

Vayamos ahora a la metodología seguida. Se han descomprimido los paquetes descargados de la web de JBoss. No se ha cambiado ninguno de los parámetros por defecto. Después se ha realizado un arranque inicial, se han anotado los tiempos, se han realizado varios reinicios y se han anotado esos tiempos. He repetido estas pruebas  un par de veces para comprobar que no obtenía resultados inconsistentes. Los resultados se muestran en intervalos.

Servidor Tiempo de arranque
(Primer arranque)
Tiempo de arranque
(Reinicio)
jboss-5.1.0.GA 26-27 seg. 19-20 seg.
jboss-6.1.0.Final 20-21 seg. 11-12 seg.
jboss-as-7.1.1.Final 13-14 seg. 1-2 seg.

Puede verse como se reduce el tiempo de arranque conforme avanzan las versiones. Veamos ahora el consumo de cpu de un reinicio:

jboss-5.1.0.GA

consumo_cpu_reinicio_jboss5

jboss-6.1.0.Final

consumo_cpu_reinicio_jboss6

jboss-as-7.1.1.Final

consumo_cpu_reinicio_jboss7

En las gráficas puede verse que también conforme avanzan las versiones se hace un uso mayor de los múltiples núcleos del procesador. Sin embargo, en mi opinión, ese uso no justifica (en exclusiva) esa reducción tan brutal que se produce entre el tiempo de arranque de JbossAS6 y JbossAS7, sobre todo en los reinicios. En la version 7, al finalizar el arranque, aparece el siguiente mensaje: Started 133 of 208 services (74 services are passive or on-demand). Teóricamente, estos servicios no son importantes. Sin embargo, la reducción de tiempo es tan grande que me preocupa que sean servicios que tarde o temprano se vayan a cargar. De ser así, se reduciría el tiempo de arranque a costa de provocar pausas en la ejecución de nuestras aplicaciones.

Hacer convivir varias instalaciones de JBossAS en el mismo servidor

En entornos de producción suele ser de lo mas corriente dedicar determinados servidores (físicos o virtuales) a la instalación de servidores de aplicaciones, de forma que encontremos instancias de Tomcat,  JBossAS e incluso WebLogic compartiendo recursos. Esto, como tantas otras cosas, dependerá de la forma en que se ha querido (o podido) ir organizando el servicio. Si se dispone de grandes maquinas con muchos procesadores y RAM, esta sera la tónica general. Si por el contrario se ha optado por virtualizar, sera mas común encontrarnos con máquinas virtuales mas pequeñas dedicadas casi en exclusiva a servir de soporte a un único servidor de aplicaciones.

Si nos centramos en el primer caso, donde tenemos una maquina donde conviven varios servidores de aplicaciones, tendremos que asegurarnos de que los puertos que utilizan cada uno de estos servidores no colisionan con los demás. Una primera aproximación, sería asignar puertos al azar. Por absurdo que parezca, he encontrado esta solución en algunos sitios: se asignaba el puerto de escucha de forma incremental (8080 al primero, 8081 al segundo…) pero el resto de puertos que abría cada servidor de aplicaciones era cambiado de forma aleatoria. La frase que justificaba tal decisión solía ser una variante de “Total, si aunque los abra, esos puertos no se usan…”. Lo primero que se puede argumentar ante esto es que si ese puerto no se usa, asegúrate de que no se abra. Lo segundo es que si algún día tienes un conflicto con los puertos, no vas a saber que servidores son los que colisionan.

La mejor forma de hacer esto consiste en sumar una determinada cantidad a todos los puertos que abre el servidor de aplicaciones, y no solo al puerto de escucha. Personalmente, recomiendo sumar 100 a cada puerto. Es una cantidad que te permite al hacer por ejemplo un netstat visualizar fácilmente a que servidor de aplicaciones corresponde cada puerto y evita las colisiones de los diferentes puertos, cosa que por ejemplo no soluciona el sumar 1.

En Tomcat esta tarea la tendremos que realizar manualmente. En JBossAS podemos hacerlo de forma automática, con un parámetro en el arranque. Esto nos facilitara el trabajo.

JBossAS 5 y 6

Con las versiones 5 y 6 de JBossAS, utilizaremos el parametro -Djboss.service.binding.set. Por ejemplo, si queremos sumar 100 a todos los puertos lanzaremos el servidor de aplicaciones con -Djboss.service.binding.set=ports-01. En el caso de querer sumar 200, cambiaremos el parametro a ports-02 y sucesivamente. Los valores de los puertos se definen en el fichero server/CONFIGURACION/conf/bindingservice.beans/META-INF/bindings-jboss-beans.xml, siendo CONFIGURACION el nombre de la configuración que estemos usando. Por defecto, se usa la configuración default.

JBossAS 7

Para la ultima version de JBossAS, esto ha cambiado y utilizaremos el parametro -Djboss.socket.binding.port-offset. Para sumar 100 a todos los puertos arrancaremos el servidor con -Djboss.socket.binding.port-offset=100. Esta forma de hacerlo es mas practica, aunque menos flexible.

Usando estas opciones, tu trabajo de administración sera mas cómodo y las instalaciones mas rápidas, reduciendo a la vez las posibilidades de que surjan errores y el tiempo necesario para resolverlos si aparecen.

Ir arriba