Ir al contenido principal

Controla el estado de la memoria en aplicaciones Java

En muchas ocasiones nos encontramos con aplicaciones que tienen leak de memoria, y se va consumiendo este recurso sin que sea liberado adecuadamente. La solución: corregir el código, que suele ser el principal origen del problema. Pero cuando eso no depende de ti y tienes que ofrecer disponibilidad en dicha aplicación, una posible solución es controlar el consumo que hace la aplicación de la memoria, y en función de esta, realizar un reinicio antes de su degradación.

Aquí dejo un script que puede resultar útil. Se basa en controlar como se consume la memoria en las zonas Eden y Old Generation. Al haber una fuga de memoria, la zona Old Generation no se liberará como debe, y la zona Eden se consumirá también, imposibilitando la creación de nuevos objetos.

El script lo que hace es controlar estas zonas y cuando se alcance cierto umbral durante cierto número de ciclos consecutivos, reiniciar la aplicación (evitamos que la aplicación se acabe degradando y que al final la tengamos que reiniciar a mano):



#!/bin/bash

#Se obtiene el PID del proceso java utilizando jps y filtrando por Bootstrap
PID=`jps | grep Bootstrap | cut -d " " -f 1`
DATE=`date +%d-%m-%y----%H:%M:%S`
#Log para registrar las acciones del propio script
LOG="/scripts/reinicio_mem.log"
#Fichero temporal
TMP="/tmp/valores.txt"
#Se lanza el jmap -heap para obtener los valores del Eden y Old Generation
su - tomcat -c "jmap -heap $PID | grep -A4 -e Eden -e Old | grep % | cut -d '%' -f 1" > $TMP
CONT=1
FICH_CONTADOR="/tmp/contador"
FICH_SEGUIDO="/tmp/seguido"
#Script para levantar tomcat
SCRIPT_ARRANQUE="/scripts/arranque_tomcat.sh"
#Script para parar tomcat
SCRIPT_PARADA="/scripts/parada_tomcat.sh"
#Script para el envío de correo
SCRIPT_CORREO="/scripts/envia_correo.py"

#Función para reiniciar tomcat
reinicio () {
    $SCRIPT_PARADA &
    wait
    $SCRIPT_ARRANQUE &
    wait
    $SCRIPT_CORREO &
    wait
    return 0
}


#Función para controlar el conteo de veces seguidas que supera el valor umbral. 
#Tras 5 veces seguidas se procede a reiniciar tomcat
contador () {
    if [ -e $FICH_SEGUIDO ]; then
        VALOR_CONTADOR=`cat $FICH_CONTADOR`
        VALOR_CONTADOR=`expr $VALOR_CONTADOR + 1`
        if [ $VALOR_CONTADOR -eq 5 ]; then
            rm -rf $FICH_SEGUIDO
            echo "Se alcanzan valores por encima del 99% durante 5 veces consecutivas. Se reinicia tomcat." >> $LOG
            echo 0 > $FICH_CONTADOR
            reinicio
        else
            echo $VALOR_CONTADOR > $FICH_CONTADOR
        fi
    else
        touch $FICH_SEGUIDO
        echo 1 > $FICH_CONTADOR
    fi
}

#Se asigna de forma dinámica el nombre de la variable y su valor
for i in $( cat $TMP | tail -2); do
    eval export "VAR_${CONT}=`echo $i`"
    CONT=2
done

echo $DATE >> $LOG
echo "Eden: " $VAR_1"%" >> $LOG
echo "Old: " $VAR_2"%" >> $LOG
let EDEN=`echo $VAR_1 | cut -d "." -f 1`
let OLD=`echo $VAR_2 | cut -d "." -f 1`
if [ $EDEN -ge 99 ] && [ $OLD -ge 99 ]; then
    echo "Eden y Old Generation por encima del 99%" >> $LOG
    contador
else
    echo "Eden y Old Generation por debajo del 99%" >> $LOG
    rm -rf $FICH_SEGUIDO
fi

Para el envío de correo, podemos utilizar un script como este:


 #!/usr/bin/env python
# -*- coding: iso-8859-15 -*-

import os, string,smtplib, socket
from email.MIMEBase import MIMEBase
from email.MIMEText import MIMEText
from email.Utils import COMMASPACE, formatdate
from email import Encoders

maquina = socket.gethostbyaddr(socket.gethostname())[0]

#Funcion para el envio de correo
def sendMail(to, subject, text, server="servidor.domino.es"):
    assert type(to)==list
    fro = maquina + "@dominio.es"

    COMMASPACE = ', '
    msg = MIMEText(text)
    msg['From'] = fro
    msg['To'] = COMMASPACE.join(to)
    msg['Date'] = formatdate(localtime=True)
    msg['Subject'] = subject


    smtp = smtplib.SMTP(server)
    smtp.sendmail(fro, to, msg.as_string() )
    smtp.close()


sendMail(
        ["destinatario@dominio.es"],
        "Reinicio " + maquina,"Se ha reiniciado la " + maquina + " tras 5 ciclos seguidos con valores por encima del umbral de uso de memoria."
        )

Comentarios

Entradas populares de este blog

Conexión a bbdd oracle desde python

Para poder acceder a una bbdd oracle desde python tan sólo necesitaremos tener instalado: - cliente oracle (lo puedes obtener de la página de oracle y registrándote en la misma) - extensión cx_Oracle (lo puedes descargar desde la página http://cx-oracle.sourceforge.net/) La forma de utilizarlo lo podemos ver en el siguiente ejemplo: Con este script se pretende actualizar el campo de una tabla pasándole tres argumentos, dos para filtrar el dato y uno que será el nuevo valor. También hacemos uso de optparse para pasear los argumentos. #!/usr/bin/python # -*- coding: iso-8859-15 -*- import cx_Oracle, sys, os, datetime from optparse import OptionParser conn_str='usuario/pass@host:port/bbdd' log = '/ruta/para/log/script.log' #Fucion para escribir log def log (texto):         now = datetime.datetime.now()         f = open(log_propio, 'a')         f.write(str (now.ctime()) + ' -> ' + texto + '\n')         f.close() #Se parsea

Curso Django Segunda Parte

Continuamos con la segunda parte del mini curso de django. Respecto a la primera parte, he añadido una par de cosas: - La instalación de un paquete más: python-pygraphviz - Y la aplicación de un parche para django-smart-selects: https://github.com/GrAndSE/django-smart-selects/commit/7e2a8d572b3615cc39a3c9f9d60e1b60de06f46f Pues bien, ya tenemos creado un proyecto llamado misitio. Ahora es el momento de crear nuestra aplicación, la cual llamaremos inventario. Para crear un aplicación, simplemente hacemos: cd /opt/djcode/misitio python manage.py startapp inventario Tras la ejecución de este comando (que no devuelve nada por pantalla), tendremos un nuevo directorio bajo el proyecto misitio: ls -l inventario/ -rw-r--r-- 1 root root   0 mar 11 12:27 __init__.py -rw-r--r-- 1 root root  57 mar 11 12:27 models.py -rw-r--r-- 1 root root 383 mar 11 12:27 tests.py -rw-r--r-- 1 root root  26 mar 11 12:27 views.py De los ficheros que nos podemos encontrar, tenemos:

Configurar Nano Wifi TL-WN725N en Raspberry pi

Hace poco me regalaron una raspberry pi, y junto con ella, un dongle wifi usb TP-LINK, modelo TL-WN725N. En principio se supone que no debe haber problemas de compatibilidad entre este dongle wifi y nuestra raspberry, pero si la versión de nuestro dongle wifi es la 2 (en la caja viene como Ver:2.0) la cosa cambia. En mi caso tenía instada la última versión de raspbian, la cual traía una versión de kernel superior a la 3.10.18. Esta versión de kernel es la que funciona con nuestra modelo de dongle wifi (al menos según he podido averiguar). De modo que para poder reconocer el dongle wifi, tendremos que bajar a esta versión del kernel: sudo rpi-update 8fd111f77895450323abc5b34efde19548ffc480 Tras reiniciar, tendremos el siguiente kernel: Linux raspberrypi 3.10.18+ #587 Ahora sólo nos queda instalar el driver: wget https://dl.dropboxusercontent.com/u/80256631/8188eu-20131110.tar.gz tar -zxvf 8188eu-20131110.tar.gz                                          cat README