Home » noviembre 2012
Tuning Servidor de Apache Tomcat

Apache Tomcat
En mi trabajo hemos arrastrado un problema de de rendimiento del servidor de aplicaciones Apache-Tomcat, El que básicamente consiste en que si la maquina recibe muchas peticiones el contenedor web se pega y no es capas de resolver (En este caso solo nos queda reiniciar el contenedor).

1. Lo básico: versión de JVM, modo de ejecución y tamaño de heap de Java 

El contenedor web esta configurado con los siguientes parámetros para el catalina_opts /apache-tomcat-6,0,16/bin/catalina.sh

-Xms1024
-Xmx1024
-Xmn512
-XX:PermSize=128
-XX:MaxPermSize=512

2. Tuning del recolector de basura: hilos de ejecución

Por default, el número de hilos de ejecución asignados al GC son equivalentes al número de threads disponibles en el procesador. Sin embargo, esto puede ser muy ineficiente en sistemas con una buena cantidad de multithreading, siendo de 1/2 a 1 la proporción aconsejable.

-XX:ParallelGCThreads=6

3. Tuning del recolector de basura: incrementando la generación joven

Cuando se ejecuta un programa en Java, todos los objetos que se van creando pertenecen a tres generaciones, como se muestra en el siguiente esquema:

Las tres generaciones de objetos en Java. (Fuente: java.sun.com) Cuando se crea un objeto nuevo en Java con la instrucción new, éste inicialmente se encuentra en el espacio Edén (Eden space). Conforme se van ejecutando varios ciclos de recolección de basura o se van creando nuevos objetos, éstos van migrando a través de “espacios de supervivencia” (Survivor spaces) al ser copiados sobre áreas menos transaccionales de la memoria. La región tenured es la más importante pues en ésta se genera la mayoría de las operaciones en Java. Finalmente, aquellos objetos que han permanecido activos por mucho tiempo pasan a formar parte del espacio permanente (perm space) pues difícilmente serán eliminados.

Así entonces, en un ambiente altamente transaccional, es conveniente que entre el 6 y el 12% de la pila de memoria sea parte de la generación joven, pues se están creando muchos objetos en Java que serán migrados rápidamente. El tamaño por default es de apenas 2 MB y puede crecer de manera ilimitada, quemándose todo el espacio dedicado al tenured o perm, por lo que siempre es conveniente definir su tamaño explícitamente:
-XX:MaxNewSize=64m
-XX:NewSize=64m

4. Tuning del recolector de basura: corriendo en paralelo

Por default, el Garbage Collector de la máquina virtual de Java usa el modo “serial” de recolección, pero esto sólo sirve en máquinas con un solo CPU. Para servidores de aplicaciones con 2 o más cores, tenemos la dos opciones (modos) :

El Modo paralelo
-XX:+UseParallelGC

modalidad de “pequeños impulsos frecuentes”. Esta opción disminuye un poco el rendimiento del sistema a cambio de no congelar la aplicación cada que se llena el GC.

-XX:+UseConcMarkSweepGC
ó -XX:+UseConcMarkSweepGC
-XX:+UseParNewGC

5. Tuning del recolector de basura: incrementando la generación permanente

Así como estamos definiendo un tamaño para la generación joven, también es posible definir uno para el espacio permanente (perm space) que en aplicaciones con muchos objetos estáticos y utilerías (sobre todo en Ajax) pueden generar el temido java.lang.OutOfMemoryError: PermGen space. Cabe destacar que si estamos encontrando constantemente errores de este tipo aunque incrementemos el espacio considerablemente (>25% del espacio asignado al heap), significa que tenemos un problema de objetos no recolectados que requiere echarse un clavado en el código.

-XX:MaxPermSize=128m

6. Paginación de la memoria

El objetivo de la paginación en Java es optimizar los búferes de traducción y búsqueda en memoria (Translation-Lookaside Buffers – TLB). Estos son en pocas palabras, caches que almacenan los últimos mapeos de memoria virtual a física. Modificando los valores correspondientes se incrementa la eficiencia del uso memoria. En la mayoría de los casos no se recomienda pasar de 6 MB de paginación pues puede ser contraproducente.

-XX:+UseLargePages
-XX:LargePageSizeInBytes=5m

7. Non-Uniform Memory Architecture (NUMA)

Debido a que una buena parte de las arquitecturas multiprocesador están basadas en el uso de memoria de acuerdo a posiciones relativas a otro procesador o a través de memoria compartida entre procesadores, es posible utilizar una opción de “escopetazo” denominada NUMA. El uso de este parámetro en combinación con -XX:+UseParallelGC puede incrementar significativamente el desempeño:

-XX:+UseParallelGC
-XX:+UseNUMA

8. Tuning del recolector de basura: pasando de generación

en generación El parámetro -XX:SurvivorRatio puede utilizarse para ajustar el tamaño de los espacios de supervivencia. Aunque no es tan importante para el rendimiento, sí permite ayudarnos a definir cuál será el espacio para el resto de las generaciones, pues si son demasiado pequeños, los nuevos objetos serán copiados directamente en el espacio tenured y si son demasiado grandes, se está desperdiciando memoria. Por ejemplo, -XX:SurvivorRatio=6 significa que existirá una relación de 6 a 1 entre el Edén y los survivor spaces.

Por otro lado, la JVM define por defecto un “porcentaje de ocupación” del 50% del survivor space actual para empezar a copiar los objetos que contiene al siguiente espacio. Esto puede significar un desperdicio del 50% de la memoria designada a los survivor spaces, por lo que conviene incrementarla para hacer un uso más eficiente de la misma. -XX:TargetSurvivorRatio permite definir el porcentaje de uso necesario para copiar los objetos del actual espacio al siguiente:

-XX:SurvivorRatio=8
-XX:TargetSurvivorRatio=90

Clonar Objetos en Java

Clonar Objetos en Java
La clonación es el proceso de duplicación de un objeto para que en memoria existan dos objetos idénticos en el mismo instante de tiempo. Usualmente el objeto se clona directamente llamando al método:cualquierObjeto.cualquierMetodo(miObjetoImportante.clone());

Al igual que en muchos lenguajes orientados-a-objeto, en Java los objetos son pasados por referencia. Esto implica que cualquier acción tomada por el método “llamado” afectará el objeto que tiene el método “que llamó”, esto debido a que ambos objetos son el mismo.

La clase java.lang.Object contiene una implementación native y protected del métodoclone. Esta implementación (que depende de la máquina sobre la que se ejecute el código) determina cuanta memoria está siendo usada por el objeto a ser clonado, reserva la misma cantidad de memoria para el objeto clon, y copia los valores de memoria de la vieja dirección de memoria a la nueva. Y al final se devuelve un java.lang.Object el cual es la referencia al nuevo objeto (el clon).

Para implementar la clonación en una clase, se deben hacer dos cosas:
La clase debe implementar la interfaz Cloneable, esta interfaz no tiene métodos que implementar. El propósito de Cloneable es indicar al método clone dejava.lang.Object que el programador ha dado permiso explícito a la clase para permitir que los objetos instanciados a partir de ella sean clonados.
El método clone de la clase java.lang.Object debe ser sobrescrito con un acceso de tipo public en vez de protected. Es en este método que se implementará el código que clona del objeto.

La excepción CloneNotSupportedException es arrojada por el método clone de la clase java.lang.Object para prevenir que la operación de clonación se ejecute si no se ha otorgado el permiso para ello (es decir, se implemente la interfaz Cloneable).

En términos sencillos el método clone de la clase java.lang.Object crea un nuevo objeto mediante la copia exacta de los bytes de memoria y devolviendo una referencia, de esto se tiene que los objetos miembros de un clon apuntan a los mismos objetos que los objetos miembros del objeto original.

Cuando un objeto de la clase CloneTest es creado y a su atributo miembro se le asigna un valor, por ejemplo, “prueba”, la memoria de este objeto realmente solo contiene los bits que representan una referencia a un objeto String que contiene “prueba”. Cuando el métodoclone es ejecutado, el objeto es duplicado byte por byte y el clon contendrá una copia de la misma referencia que apunta al mismo objeto Stringque contiene “prueba”. El resultado final es que existe solamente una copia de “prueba” y que una llamada sobre el objeto o su clon para modificar su valor resultará en cambios para ambos. Esto es conocido como ‘shallow copy’, donde solamente los miembros primitivos (y referencias a objetos) del objeto que están completamente contenidos en la memoria del objeto son duplicados, pero el resto de los objetos miembros en el objeto no lo son.

Este enfoque no es suficientemente bueno para algunos casos, ya que en general los objetos no están compuestos solamente de tipos primitivos. String, Hashtable, Vector y otras clases que residen en el objeto a ser clonado, necesitan a su vez ser clonados, para que el proceso de clonación sea efectivo, esto es el que se conoce como clonación “profunda” (‘deep cloning’).

A pesar que la clonación puede ser necesaria, tambien existen situaciones (usualmente de seguridad) donde se desea prohibir la clonación de una clase, para esto existen varias alternativas, de las cuales las dos más importantes son:
Declarar la clase como final. Haciendo esto se previene que se puedan definir subclases para esta clase, e impedir que cualquiera pueda llamar al método clone desde dentro de la clase (usando el alcance protected). Si se usa esta alternativa para impedir la clonación se debe verificar que ninguna de las superclases se pueda clonar. En el caso que esto sea asi, se debe sobreescribir el método clone en la clase final y arrojar unaCloneNotSupportedException. A pesar que esta es la alternativa efectivamente impide la clonación, establece restricciones acerca de las capacidades de extender la clase, las cuales pueden no ser aceptable.
Implementar Cloneable y sobreescribir el método clone con un alcance public, y hacer que arroje una CloneNotSupportedException. A pesar que esto impide que la clase sea clonada, cualquier subclase puede hacer su propia implementación del métodoclone e implementar la clonación copiando los atributos uno por uno.

Existe otra forma (considerada como “oscura”) de clonar un objeto, que es la serialización (‘serializing’) del objeto. La serialización usa un método nativo externo al objeto, es similiar a la clonación en el sentido en que el estado binario del objeto es capturado desde memoria, pero en vez de copiarlo hacia otra dirección de memoria es copiado hacia un ‘stream’. En un futuro post pienso conversar un poco acerca de la serialización.

Ejemplo de Clonación en Java:

Clase Punto:

package clonico;

public class Punto implements Cloneable{
    private int x;
    private int y;
   public Punto(int x, int y) {
        this.x = x;
     this.y = y;
    }
    public Punto() {
        x=0;
        y=0;
    }
    public Object clone(){
        Object obj=null;
        try{
            obj=super.clone();
        }catch(CloneNotSupportedException ex){
            System.out.println(" no se puede duplicar");
        }
        return obj;
    }
    public void trasladar(int dx, int dy){
        x+=dx;
        y+=dy;
    }
    public String toString(){
        String texto="origen: ("+x+", "+y+")";
        return texto;
    }
}

Clase Rectángulo

package clonico;

public class Rectangulo implements Cloneable{
    private int ancho ;
    private int alto ;
    private Punto origen;

    public Rectangulo() {
     origen = new Punto(0, 0);
     ancho=0;
     alto=0;
    }
    public Rectangulo(Punto p) {
     this(p, 0, 0);
    }
    public Rectangulo(int w, int h) {
     this(new Punto(0, 0), w, h);
    }
    public Rectangulo(Punto p, int w, int h) {
     origen = p;
     ancho = w;
     alto = h;
    }
    public Object clone(){
        Rectangulo obj=null;
        try{
            obj=(Rectangulo)super.clone();
        }catch(CloneNotSupportedException ex){
            System.out.println(" no se puede duplicar");
        }
        obj.origen=(Punto)obj.origen.clone();
        return obj;
    }
    public void mover(int dx, int dy) {
     origen.trasladar(dx, dy);
    }
    public int area() {
     return ancho * alto;
    }
    public String toString(){
        String texto=origen+" ancho: "+ancho+" alto: "+alto;
        return texto;
    }
}

Ejecutar la clonación

package clonico;
public class ClonicoApp {
    public static void main(String[] args) {
        Punto punto=new Punto(20, 30);
        Punto pCopia=(Punto)punto.clone();
        System.out.println("punto "+ punto);
        System.out.println("copia "+ pCopia);

        Rectangulo rect=new Rectangulo(new Punto(0, 0), 4, 5);
        Rectangulo rCopia=(Rectangulo)rect.clone();
        System.out.println("rectángulo "+ rect);
        System.out.println("copia "+ rCopia);

        try  {
//espera la pulsación de una tecla y luego RETORNO
            System.in.read();
        }catch (Exception e) {  }
    }
}

Entradas populares