Archive

Archive for the ‘Uncategorized’ Category

Hacer un proxy de un objeto

Estaba ojeando la parte de reflexión de Guava y he descubierto lo facil que es hacer un Proxy de un Objeto. El inconveniente es que solo se pueden hacer proxys de una interfaz, pero hay forma de poderlo apañar.

Por ejemplo, Date no es ninguna interfaz, y quiero hacer fechas que tengan los milisegundos a cero. Para ello me hago una interfaz Proxy que voy a rellenar con mi nuevo codigo.


import static org.junit.Assert.assertEquals;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Date;

import org.junit.Test;

import com.google.common.reflect.Reflection;

public class ProxyTest {

	@Test
	public void dateZeroMillis() {
		Date withMillis = new Date();
		@SuppressWarnings("unchecked")
		Proxy<Date> proxyObject = Reflection.newProxy(Proxy.class,
				new RemoveMillis(withMillis));
		Date date = proxyObject.get();
		System.out.println("Date was: " + withMillis.getTime()
				+ " but now is: " + date.getTime());
		assertEquals(0, date.getTime() % 1000);
	}

	public static class RemoveMillis implements InvocationHandler {

		private Date date;

		public RemoveMillis(Date date) {
			this.date = date;
		}

		@Override
		public Object invoke(Object proxy, Method method, Object[] args)
				throws Throwable {
			return new Date((date.getTime() / 1000) * 1000);
		}

	}

	public static interface Proxy<T> {
		T get();
	}
}

La salida del test es esta:
Date was: 1383914151227 but now is: 1383914151000

Advertisements
Categories: Uncategorized

Chain of responsibility eventbus

Hoy vengo a hablar de una nueva librería java que resuelve una limitación del patrón Cadena de responsabilidad:

http://es.wikipedia.org/wiki/Chain_of_Responsibility_%28patr%C3%B3n_de_dise%C3%B1o%29

Haciendo un breve resumen, con este patrón podemos modularizar nuestro código. Esto es bueno, porque obligamos a desarrollar el contenido en pequeñas partes, para luego ser reutilizado, eliminado y añadido mas facilmente. Estas pequeñas partes se ejecutarán en un orden, una detrás de otra, hasta que la cadena termine o un eslabón decida que no quiere continuar con la cadena.

Los eslabones no deberian tener referencias a otros eslabones concretos, porque estariamos rompiendo la modularidad. Generalmente, tienen una referencia al siguiente eslabón, pero se maneja en forma de interfaz.

El problema es el siguiente

Este patrón funciona muy bien cuando hay que desarrollar una serie de acciones aisladas, que poco tienen que ver unas con otras, ¿pero que pasa si algun eslabón necesita de un objeto que ya ha sido recuperado previamente por otro eslabón?. Con la habitual implementación habría que volverlo a recuperar. Esto puede ser lento y se puede repetir en muchas ocasiones a lo largo de toda la cadena.

Esta nueva librería aporta una solución sencilla a este problema. Consiste en la posibilidad de que las diferentes cadenas puedan postear cualquier objeto a un bus de eventos. El propio bus de eventos se encargará de asignar esos objetos al resto de eslabones que esten suscritos a objetos de ese tipo.

Os dejo el link:

https://code.google.com/p/chain-of-responsibility-eventbus/

Categories: Uncategorized

Programación orientada a aspectos con Guice

Me gusta la programación orientada a aspectos por la cantidad de ifs que te quita. Ademas son ifs que tienes que ir repitiendo a lo largo del código.
Se me ha ocurrido hacer un pequeño ejemplo con JUnit. Vamos a hacer las tipicas comprobaciones de:

Las vamos a hacer de las dos formas, con aspectos y sin aspectos, demostrando que el resultado es el mismo, pero con aspectos queda mas limpio.

Antes de empezar con el Test, hay que configurar Shiro. Explicandolo por encima, tenemos un fichero que se llama shiro.ini, donde he definido los usuarios con su rol.

[users]
root = secret, admin
guest = guest, guest

[roles]
admin = *

También hay que configurar el modulo de Shiro para Guice, donde se le dice que se configure en base al contenido de shiro.ini:

package com.tododev.shiro;

import org.apache.shiro.config.Ini;
import org.apache.shiro.guice.ShiroModule;
import org.apache.shiro.realm.text.IniRealm;

import com.google.inject.Provides;

public class MyShiroModule extends ShiroModule{

	protected void configureShiro() {
        try {
            bindRealm().toConstructor(IniRealm.class.getConstructor(Ini.class));
        } catch (NoSuchMethodException e) {
            addError(e);
        }
    }

    @Provides
    Ini loadShiroIni() {
        return Ini.fromResourcePath("classpath:shiro.ini");
    }

}

Ahora vamos ya con el test. Este test contiene 4 metodos de test, que comprueban lo siguiente sobre el método de la interfaz Interface.doSomething:

  • Si no estas autenticado lanza una UnauthenticatedException.
  • Si no tienes el rol de “admin” lanza una UnauthorizedException.
  • Si le pasas el parámetro a nulo lanza una ConstraintViolationException.
  • Si estas autenticado, con el rol de “admin” y no pasas el parámetro a nulo, no lanza excepciones.

La gracia es que el test lo vamos a hacer con dos implementaciones diferentes de Interface.class:

  • Aop.class
  • NoAop.class

Es decir, se van a hacer 2 x 4 = 8 tests. El resultado tiene que se el mismo.


package com.tododev.shiro;

import static org.junit.Assert.fail;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.constraints.NotNull;

import org.apache.bval.guice.Validate;
import org.apache.bval.guice.ValidationModule;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.guice.aop.ShiroAopModule;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;

/**
 * Vamos a hacer que el test se ejecute con la clase Parameterized. Esta clase
 * nos ejecutara 2 veces todos los tests, insertando en el constructor los
 * valores del metodo data().
 * 
 * @author jorge
 * 
 */
@RunWith(Parameterized.class)
public class GuiceAopTest {

	private final static String ADMIN_ROLE = "admin";
	private final UsernamePasswordToken ADMIN = new UsernamePasswordToken(
			"root", "secret");
	private final UsernamePasswordToken GUEST = new UsernamePasswordToken(
			"guest", "guest");
	private final Injector injector;

	/**
	 * Cuando se crea la instancia de GuiceAopTest.class, se le inserta uno de
	 * los valores del metodo data().
	 * 
	 * @param injector
	 */
	public GuiceAopTest(Injector injector) {
		this.injector = injector;
	}

	/**
	 * Devuelve una coleccion que contiene un inyector con AOP y otro que no. La
	 * idea es demostrar que los tests pasan satisfactoriamente de las dos
	 * formas posibles.
	 * 
	 * @return
	 */
	@Parameters
	public static Collection<Object[]> data() {
		Injector withAop = Guice.createInjector(new ShiroAopModule(),
				new MyShiroModule(), new ValidationModule(),
				new AbstractModule() {
					@Override
					protected void configure() {
						bind(Interface.class).to(Aop.class);
					}
				});
		Injector noAop = Guice.createInjector(new MyShiroModule(),
				new AbstractModule() {
					@Override
					protected void configure() {
						bind(Interface.class).to(NoAop.class);
					}
				});

		Object[][] data = { { noAop }, { withAop } };
		return Arrays.asList(data);
	}

	public static interface Interface {
		void doSomething(String value);
	}

	/**
	 * Implementacion preparada para tener aspectos.
	 * 
	 * @author jorge
	 * 
	 */
	public static class Aop implements Interface {

		@Validate
		@RequiresRoles(ADMIN_ROLE)
		@Override
		public void doSomething(@NotNull String value) {
			System.out.println(getClass() + ": " + value);
		}

	}

	/**
	 * Hace lo mismo que hace Aop, pero esta no usara aspectos.
	 * 
	 * @author jorge
	 * 
	 */
	public static class NoAop implements Interface {

		@Override
		public void doSomething(String value) {
			if (value != null) {
				Subject currentUser = SecurityUtils.getSubject();
				if (currentUser.isAuthenticated()) {
					if (currentUser.isPermitted(ADMIN_ROLE)) {
						System.out.println(getClass() + ": " + value);
					} else {
						throw new UnauthorizedException();
					}
				} else {
					throw new UnauthenticatedException();
				}
			} else {
				throw new ConstraintViolationException(
						new HashSet<ConstraintViolation<?>>());
			}
		}

	}

	/**
	 * Preparamos Shiro para ser usado.
	 */
	@Before
	public void before() {
		SecurityManager securityManager = injector
				.getInstance(SecurityManager.class);
		SecurityUtils.setSecurityManager(securityManager);
	}

	/**
	 * Deslogueamos al usuario si estuviera logueado.
	 */
	@After
	public void after() {
		Subject currentUser = SecurityUtils.getSubject();
		currentUser.logout();
	}

	/**
	 * Vefirica que si no estas logueado salta una UnauthenticatedException
	 */
	@Test(expected = UnauthenticatedException.class)
	public void unauthenticated() {
		Interface classToTest = injector.getInstance(Interface.class);
		classToTest.doSomething("any value");
		fail("Unreachable code");
	}

	/**
	 * Verifica que si no tienes los permisos adecuados salta una
	 * UnauthorizedException
	 */
	@Test(expected = UnauthorizedException.class)
	public void unauthorized() {
		Subject currentUser = SecurityUtils.getSubject();
		currentUser.login(GUEST);
		Interface classToTest = injector.getInstance(Interface.class);
		classToTest.doSomething("any value");
		fail("Unreachable code");
	}

	/**
	 * Verifica que si llamas al metodo con el String nulo, salta una
	 * ConstraintViolationException
	 */
	@Test(expected = ConstraintViolationException.class)
	public void constraintViolation() {
		Subject currentUser = SecurityUtils.getSubject();
		currentUser.login(ADMIN);
		Interface classToTest = injector.getInstance(Interface.class);
		classToTest.doSomething(null);
		fail("Unreachable code");
	}

	/**
	 * Verifica el caso de que todo es correcto y no hay excepciones
	 */
	@Test
	public void allowed() {
		Subject currentUser = SecurityUtils.getSubject();
		currentUser.login(ADMIN);
		Interface classToTest = injector.getInstance(Interface.class);
		classToTest.doSomething("any value");
	}

}


El resultado de los test es este:


Los 8 tests han pasado bien.

Este es el resultado que se muestra en la consola:

class com.tododev.shiro.GuiceAopTest$NoAop: any value
class com.tododev.shiro.GuiceAopTest$Aop$$EnhancerByGuice$$4ee6b586: any value

Os podeis fijar que la clase Aop “ha sido mejorada” por Guice, añadiendole los aspectos de Shiro y BVal. Lo curioso es que en las clases compiladas vemos lo siguiente:


La clase que aparece señalada debería llamarse GuiceAopTest$Aop$$EnhancerByGuice$$4ee6b586, pero se llama GuiceAopTest$Aop. Esto es porque Guice no hace los aspectos en tiempo de compilación, sino en ejecución. Esto tiene algunas limitaciones, pero en muchos casos resulta ser suficiente.

En conclusión:

Se puede comprobar lo limpio que queda el código usando aspectos. Así es mas fácil ver qué es lo que hace el método doSomething(). Además, esas anotaciones las podemos aprovechar y llevar a otros métodos que requieran esas comprobaciones.

Testear la salida de un servlet

A veces me ha interesado saber que es lo que se escribe en la respuesta de un servlet, por ejemplo cuando quieres comprobar JSON. Con este pequeño ejemplo podemos analizar el objeto “buffer” para comprobar que se ha escrito lo que esperabamos a cada test:

import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.junit.Before;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

public class MyTest {

   @Mock
   private HttpServletRequest request;
   @Mock
   private HttpServletResponse response;

   private StringBuffer buffer;

   @Before
   public void before() throws IOException{
      MockitoAnnotations.initMocks(this);
      PrintWriter writer = mock(PrintWriter.class);
      when(response.getWriter()).thenReturn(writer);
      buffer=new StringBuffer();
      doAnswer(new Answer<Void>() {
            @Override
            public Void answer(InvocationOnMock invocation) throws Throwable {
               char[] chars=(char[])invocation.getArguments()[0];
               int init=(Integer)invocation.getArguments()[1];
               int end=(Integer)invocation.getArguments()[2];
               for(int i=init;i<end;i++){
                  buffer.append(chars[i]);
               }
               return null;
            }
      }).when(writer).write(any(char[].class), anyInt(), anyInt());
   }

}

El objeto buffer tendra el valor de lo que se haya escrito en el response.

Categories: Uncategorized

AutoBean, JSON con GWT

04/03/2012 1 comment

En GWT a la hora de interactuar desde la parte cliente con el servidor utilizamos los servicios y estamos acostumbrados a enviar objetos y recibir objetos. Cuando trabajamos con formularios podemos seguir aprovechándonos de esta característica enviando desde el servidor objetos convertidos a JSON y reconstruyéndolos en el cliente con AutoBean.

Antes de meternos en faena, formularios en GWT

Para entender mejor el uso que vamos a explicar de los AutoBean’s, vamos a describir brevemente como funcionan los formularios en GWT

El constructor sin parámetros de FormPanel, crea un iframe para este formulario de forma que cuando hagas submit se hará sobre este iframe evitando que se recargue toda la página. Cuando el servidor responda a esta petición, el formulario lanzará el evento onSubmitComplete y se podra recuperar el texto devuelto con el método getResults() del evento SubmitCompleteEvent. Este es el constructor que nosotros vamos a usar en esta entrada.

También hay otros constructores que te permiten relacionar tu formulario con un iframe, pero en estos casos el evento onSubmitComplete no es lanzado y hay que utilizar otros métodos para recuperar la respuesta del servidor.

Manos a la obra

Primero vamos a hacer un breve descripción de los pasos que va a dar nuestra aplicación:

  1. Se hace submit de un formulario
  2. El servidor procesa la petición y se genera un objeto de respuesta
  3. Con AutoBean serializamos el objeto en JSON y lo enviamos al cliente
  4. El cliente recupera el texto JSON y reconstruye el objeto inicial con AutoBean

Requisitos

El AutoBean que vamos a definir se utiliza tanto en la parte de cliente como en la parte de servidor, tiene que estar en una paquete compartido de GWT.

Los objetos que vamos a serializar (y deserializar) tienen que ser implementaciones de una interfaz y esa interfaz tiene que definir un bean, es decir, sus métodos tienen que ser getters (getX e isX). AutoBean trabaja con interfaces de objetos.

En el gwt.xml tenemos que importar el módulo de AutoBean:

<inherits name="com.google.web.bindery.autobean.AutoBean" />

Y tenemos que añadir la librería gwt-servlet-deps.jar al classpath de la aplicación.

AutoBeanFactory

Para crear AutoBean’s tenemos que definir una interfaz que extienda de la interfaz AutoBeanFactory y para cada objeto que vamos a serializar tenemos que tener un método en esa interfaz que devuelva un AutoBean parametrizado con la interfaz de dicho objeto que vemos a serializar.

public interface MyAutoBeanFactory extends AutoBeanFactory {
public AutoBean<IMyBean> makeMyBean();
}

MyAutoBeanFactory es la interfaz que vamos a utilizar para obtener los AutoBean’s

IMyBean es la interfaz que debe implementar el bean que vamos a querer serializar.

Parte Servidor

Para obtener el objeto factoría que vamos a utilizar para obtener los AutoBean’s, llamamos al método create de la clase AutoBeanFactorySource pasándole la clase de la interfaz de nuestra factoría. Ahora podemos crear el AutoBean para el objeto que vamos a serializar, donde myBeanObject es el objeto que vamos a serializar. Es importante poner como contentType text/html para que el navegador lo pinte tal y como viene (no intente formatearlo de alguna manera).


MyAutoBeanFactory beanFactory = AutoBeanFactorySource.create(MyAutoBeanFactory.class);
AutoBean<IMyBean> bean = beanFactory.create(IMyBean.class, myBeanObject);
String json = AutoBeanCodex.encode(bean).getPayload();
BufferedWriter bf=new BufferedWriter(response.getWriter());
response.setContentType("text/html");
bf.write(json);
bf.flush();
bf.close();

este código serializaría el objeto myBeanObject y lo enviaría como respuesta al cliente. (El objeto response es de la clase HttpServletResponse).

Parte Cliente

Ahora vamos a ver el código de como recuperar el texto que ha enviado el servidor como respuesta del submit del formulario y convertir el texto JSON en nuestro objeto a través de AutoBean.


formPanel.addSubmitCompleteHandler(new SubmitCompleteHandler() {
@Override
public void onSubmitComplete(SubmitCompleteEvent event) {
MyAutoBeanFactory beanFactory = GWT.create(MyAutoBeanFactory.class);
String json = event.getResults();
AutoBean<IMyBean> autoBeanClone = AutoBeanCodex.decode(beanFactory, IMyBean.class, json);
IMyBean bean = autoBeanClone.as();
...
}
});

En el código de ejemplo, cuando se lanza el evento SubmitCompleteEvent del formulario, creamos el objeto MyAutoBeanFactory y el texto JSON del servidor, utilizamos el método AutoBeanCodex.decode(…) que nos recupera el AutoBean para nuestra interfaz.

Un posible uso de todo esto es a la hora de tener que subir un archivo al servidor. En este caso estamos casi obligados a usar un formulario y esta podría ser una buena forma de procesar la respuesta del servidor.

Entender, detectar y localizar memory leaks en aplicaciones web (java.lang.OutOfMemoryError)

02/16/2012 5 comments

Cuando aparece el mensaje java.lang.OutOfMemoryError, en cualquiera de sus variantes, en los logs de Tomcat o el contenedor de Servlets que uses, es muy posible que alguna de las aplicaciones desplegadas contenga un memory leak. Y es que, a pesar de la existencia del Garbage Collector (GC), que podría hacernos pensar lo contrario, las aplicaciones Java pueden crear memory leaks.

En este artículo hablaremos de  los distintos tipos de memory leak y sobre cómo detectarlos.  Y, lo que es mas importante,  proporcionaremos herramientas para localizar las causas que los provocan y consejos para evitarlos y solucionarlos. Prestaremos especial atención a los
Tomcat Classloader Memory Leaks, responsables del famoso (infame) error java.lang.OutOfMemoryError: PermGen space failure.

 

Motivos principales por los que puede aparece el error java.lang.OutOfMemoryError

 

La excepción OutOfMemoryError indica que la máquina virtual no dispone de la memoria necesaria para crear un objeto. Esto puede deberse a varios motivos, y no quiere decir necesariamente que la aplicación contenga un memory leak. Es posible que, simplemente, la que memoria asignada a la máquina virtual se haya agotado.

Por el contrario, un memory leak consiste en que la aplicación reserva memoria que luego no es posible liberar. En el caso concreto de aplicaciones Java, la aplicación reserva memoria que el GC no podrá liberar. La consecuencia es que, conforme transcurre el tiempo de ejecución, la aplicación consume mas y mas memoria, puesto que no es capaz de liberar la que ya no usa (o, al menos, no toda). En el momento en que el sistema se queda sin memoria disponible se produce el error OutOfMemoryError. En el caso de Tomcat, la única opción que nos queda, llegados este punto, es el reinicio.

En estos casos, la solución de incrementar la memoria del heap, o la de la generación PermGen, como se verá a continuación, simplemente retrasa el tiempo necesario para que la memoria se agote, pero NO soluciona el problema.

Un procedimiento sencillo, por lo visual, aunque grosero, de determinar si una aplicación contiene memory leaks consiste en observar el perfil del consumo de memoria de la aplicación. Esta operación es sencilla si se usa un profiler. Un profiler permite determinar, entre otras cosas (como el consumo de CPU), la memoria usada por la aplicación en tiempo real. Un ejemplo es VisualVM.

En el caso de Java, el perfil de consumo de memoria de una aplicación a lo largo del tiempos será similar a unos dientes de sierra. La explicación es que la aplicación irá reservando la memoria que necesite para atender las peticiones que recibe. Esto se traduce en un tramo ascendente temporalmente. En el momento en que se ejecuta el GC, se libera toda la memoria que ya no se necesita, por lo que la cantidad de memoria usada por la aplicación desciende abruptamente.

Si se precisara de un análisis mas detallado, habría que usar funcionalidad mas avanzada del profiler, como los volcados de memoria (Heap Dump) o la comparación del uso de memoria en distintos instantes.

Un artículo bastante completo sobre los usos de visualVM puede encontrarse en este artículo de JavaCodeGeeks.

En el caso de que la aplicación no contenga ningún memory leak, la forma de solucionar el problema java.lang.OutOfMemoryError es sencilla: aumentar la memoria de la que dispone la JVM. Ni que decir tiene que un paso previo debería ser revisar el código de la aplicación para confirmar que la memoria se usa eficientemente !!

En el caso particular de encontrarnos con el mensaje java.lang.OutOfMemoryError: Java heap space, se debe aumentar la memoria asignada al heap. Las opciones de la VM que permiten modificar el tamaño del heap son –Xmx (tamaño máximo del heap) y –Xms (tamaño mínimo del heap o, mas intuitivamente, memoria reservada inicialmente). Si la cantidad de memoria no es problema, no es mala idea asignar el mismo valor a las dos opciones, puesto que puede ayudar a mejorar el rendimiento. Por ejemplo, para asignar 256 M:

java -Xms256m -Xmx256m

En el caso de Tomcat deben modificarse las variables de entorno . Una forma es editar el archivo setenv.sh (setenv.bat en sistemas Windows). Por ejemplo:

$ cat setenv.sh
export JAVA_OPTS=" -Xms256m -Xmx256m"

Por el contrario, cuando el mensaje de error hace referencia a la generación PermGen (java.lang.OutOfMemoryError: PermGen space failure), el problema es distinto. En este caso la memoria que se agota no es la heap en general, sino la zona particular asignada a la Permanent Generation. En esta zona se almacena metainformación sobre las clases que la JVM necesita (por ejemplo, los objetos ClassMethod y Field de las clases que cargan). Es posible llegar al límite de memoria asignado por defecto a esta zona cuando se usan librerías mas o menos pesadas (implementaciones de JPA, WebServices,…) debido a la cantidad de clases que pueden llegar a cargar.

Aumentar el tamaño del heap no ayudará en este caso: seguirá produciéndose el mismo error. La solución consiste en aumentar el tamaño del espacio asignado específicamente a la generación PermGen. Para ello se dispone de la opción -XX:MaxPermSize, que permita especificar el tamaño de la memoria asignada a dicha generación.

Todas estas opciones (como todas las que comienzan por -X o -XX) no son estándares y, aunque funcionan en la JVM HotSpot de Sun (perdón, Oracle), no se garantiza su soporte en otras implementaciones.

Cuando se trata del primer tipo comentado de memory leaks, los que agotan la memoria mediante la creación de nuevas instancias mientras que el GC es incapaz de eliminar instancias antiguas, no hay diferencia alguna entre una aplicación de Desktop y otra desplegada en cualquier  servidor de aplicaciones o contenedor de Servlets.

Sin embargo, en el caso de aplicaciones desplegadas en  Tomcat (y presumiblemente, varios otros contenedores de Servlets), debido a la gestión que se realiza de las aplicaciones desplegadas, es relativamente sencillo, si no se tiene cuidado, generar memory leaks del tipo que acaban consumiendo la memoria asignada a PermGen. Además, el mecanismo por el que se crean estos leaks es sutil y su detección y, sobre todo, la localización de la causa que lo provoca, no es sencilla si no se dispone de las herramientas adecuadas.

En este tipo de memory leaks, que suelen conocerse como Classloaders Memory Leaks, se centra el resto del artículo.

 

Tomcat Classloader Memory Leaks

 

El síntoma mas común que lleva a pensar en un memory leak de este tipo es que se agota el espacio asignado a la PermGen tras varios deploys y undeploys, dejando el conocido mensaje en el log (java.lang.OutOfMemoryError: PermGen space failure) y sin mas solución que reiniciar  Tomcat.

El motivo por el que suelen aparecer este tipo de leaks tiene que ver con la gestión de las aplicaciones que implementa Tomcat. Dicha gestión se basa en la creación de varios classloaders organizados jerárquicamente en forma similar a un árbol. Cada aplicación desplegada tiene su propio classloader (clase WebappClassLoader), pero estos web application classloaders tienen un modelo de delegación distinto al usual: cuando se necesita crear una clase desde una aplicación web se busca primero en los repositorios locales (de la propia aplicación), en lugar de delegar la búsqueda en el classloader padre (excepto algunas excepciones, como las clases que forman parte de la JRE).

Se recomienda consultar los detalles en la doc de Tomcat.

Esta característica permite que, cuando se realiza el repliegue (undeploy) de una aplicación, el classloader se descarta, y con él todas las clases cargadas por él, de manera que dicho repliegue no afecta a las demás aplicaciones. Así se consigue que se puedan tanto desplegar (deploy) y replegar aplicaciones sin necesidad de reiniciar Tomcat y sin afectar a las demás aplicaciones.

Pero, a su vez, esta característica abre la puerta a posibles memory leaks: si en un classloader por encima del nivel del web application classloader correspondiente se mantiene alguna referencia a alguna clase cargada por dicho web application class loader, el GC no lo eliminará. Así, quedará en memoria toda la metainformación (objetos Class, Method, Field, …) de las clases cargadas por dicho classloader. Dependiendo de las políticas de prevención de leaks del contenedor, podrían quedar también objetos referenciados desde dicho web application classloader.

En función de la importancia, en términos de memoria, del leak, el contenedor soportará mas o menos repliegues y despliegues de la aplicación antes de soltar el mensaje java.lang.OutOfMemoryError: PermGen space failure en el log.

El mecanismo de generación de memory leaks está muy bien explicado en  el artículo Classloader leaks: the dreaded “java.lang.OutOfMemoryError: PermGen space” exception

Como el asunto es relativamente complejo, vamos a ilustrarlo con un pequeño ejemplo. Para ello, añadiremos al directorio de librerías comunes del contenedor (en Tomcat 6 es el directorio lib) un jar conteniendo la clases LeakerRegister y el interfaz Salutation:

package com.wordpress.tododev.leaker.register;

import java.util.HashMap;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class LeakerRegister {

private static final Map<String, Class<? extends Salutation>> implementations = new HashMap<String, Class<? extends Salutation>>();

private final Log log = LogFactory.getLog(getClass());

public void register(String implName, Class<? extends Salutation> clazz){
  log.debug("Received register request for " + implName + ", implementation " + clazz.getName());

if (!implementations.containsKey(implName))
  implementations.put(implName, clazz);
else
  log.debug("already exists " + implName);
}

public void cleanup(){
  implementations.clear();
}

private Salutation createInstance(Class<? extends Salutation> clazz){

if (clazz == null)
  return null;

Salutation impl;
try {
  impl = clazz.newInstance();
  return impl;
} catch (InstantiationException e) {
  log.debug("Failed to instantiate " + clazz.getName(), e);
  return null;
} catch (IllegalAccessException e) {
  log.debug("Failed to instantiate " + clazz.getName(), e);
  return null;
}
}

public Salutation getImplementation(String implName){
  return createInstance(implementations.get(implName));
}

}
//// Salutation Interface
package com.wordpress.tododev.leaker.register;

public interface Salutation {
public String salute();
}

El objetivo de la clase LeakerRegister es mantener un registro de implementaciones para el interfaz Salutation. Para ello usa un Map estático (implementations) donde almacena las clases de las implementaciones registradas. Cuando se le solicita una implementación, crea una instancia de la clase correspondiente.

Por otra parte, crearemos un war, que será la aplicación web que se despliegue en el contenedor de Servlets, con las siguientes clases:

package com.wordpress.tododev;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.wordpress.tododev.leaker.register.LeakerRegister;
import com.wordpress.tododev.leaker.register.Salutation;

public class Leaker extends HttpServlet{

private static final long serialVersionUID = 6549546920601945792L;
private static final String PARAM_LANG = "lang";
private static final String LANG_ES = "es";
private static final String LANG_EN = "en";
private static final String LANG_IT = "it";

private final Log log = LogFactory.getLog(getClass());

private final LeakerRegister register = new LeakerRegister();

private Salutation getInstance (String implName){
  return register.getImplementation(implName);
}

@Override
public void init() throws ServletException {
  super.init();
  registerImplementations();
}

private void registerImplementations(){
  // TODO load from properties, servlet context, ... whatever
  register.register(LANG_ES, SalutationEsp.class);
  register.register(LANG_EN, SalutationEng.class);
  register.register(LANG_IT, SalutationIta.class);
}

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {

final Salutation salutation = getInstance(req.getParameter(PARAM_LANG));
if (log.isDebugEnabled()){
  log.debug("Provided lang " + req.getParameter(PARAM_LANG));
  log.debug("Got implementation " + salutation);
}
resp.getOutputStream().println((salutation == null)?"!!??":salutation.salute());
}

}

// Saludo en español
package com.wordpress.tododev;

import com.wordpress.tododev.leaker.register.Salutation;

public class SalutationEsp implements Salutation {
@Override
public String salute() {
  return "Hola !!";
}
}

// Saludo en inglés
package com.wordpress.tododev;

import com.wordpress.tododev.leaker.register.Salutation;

public class SalutationEng implements Salutation {
@Override
public String salute() {
  return "Hi !!";
}

}

// Saludo en italiano
package com.wordpress.tododev;

import com.wordpress.tododev.leaker.register.Salutation;

public class SalutationIta implements Salutation {
@Override
public String salute() {
  return "Ciao !!";
}
}

Como puede verse, lo único que hace es registrar las implementaciones para el saludo para los idiomas español, inglés e italiano y, posteriormente, comienza a atender peticiones. Hemos intentado que la lógica sea lo mas simple posible para no distraer la atención del tema que nos ocupa.

Nota: se ha usado Tomcat en su versión 6.0.24. El soporte para la detección e incluso solución de este tipo de leaks en Tomcat 6 y Tomcat 7 está en continua mejora, por lo que es posible que no se pudiera reproduciren versiones posteriores.

 

Detección de Tomcat Classloader Memory Leaks

 

Una vez que se conoce el motivo por el cual suelen generarse estos leaks es relativamente sencillo detectarlo:  en algún sitio del heap tendremos una instancia de la clase WebappClassLoader que no debería estar. Un profiler facilitará la búsqueda de dicho classloader. En nuestro caso usaremos VisualVM.

Una vez tengamos la aplicación desplegada y se hayan hecho varias peticiones, haremos un volcado de memoria (Heap Dump). En dicho dump vamos a buscar las instancias de la clase org.apache.catalina.loader.WebappClassLoader. Para buscar las instancias hay que seleccionar la pestaña Classes e introducir la expresión regular a buscar (por ejemplo, webappclassloader).

A continuación, hacemos un repliegue y despliegue de la aplicación y haremos un nuevo volcado de memoria. En este caso aparece una instancia de mas, aunque el número de aplicaciones desplegadas es el mismo que en caso anterior: tenemos un memory leak.

Haciendo doble click sobre la clase WebappClassLoader aparecerán todas las instancias de la misma. Debería haber una por cada aplicación desplegada en Tomcat. Seleccionamos cada instancia de la clase WebappClassLoader para inspeccionar los distintos miembros.

La instancia responsable del leak debe contener el valor false en el campo booleano started. Es un indicio claro de que tenemos un leak de classloaders (a no ser que tengamos aplicaciones detenidas intencionadamente). Es decir, alguna clase externa a nuestra aplicación mantiene una referencia a alguna clase de la aplicación, de manera que el GC, al encontrar referencias a dicha clase, no la elimine. Esto provoca que tampoco elimine el classloader, que mantiene referencias a todas las clases de la aplicación. Así que, dependiendo de la aplicación, el leak podría ser bastante importante.

Una forma de tener algo mas de seguridad sobre si realmente es un leak o no es echar un vistazo al Vector classes, donde aparecen las clases cargadas por el classloader correspondiente, y ver si se corresponden con las de nuestra aplicación.

En nuestro caso, el leak lo introduce el Map estático implementations de la clase LeakerRegister. Al ser un jar común al contenedor de aplicaciones, las clases serán cargadas por un classloader superior al WebappClassLoader. Cuando, desde la aplicación (Leaker servlet), se crea la primera instancia de LeakerRegister, se inicializa el miembro implementations. A continuación, al registrar las diferentes implementaciones, se almacenan en dicho Map referencias a cada una de las implementaciones de Salutation, que son cargadas por el WebappClassLoader de la aplicación.

Al ser un Map estático, perdura incluso cuando se repliega (elimina) la aplicación y, por lo tanto, la única instancia de LeakerRegister. Al mantener dicho Map referencias a clases cargadas con el WebappClassLoader, el GC no puede recolectar y eliminar dicho classloader, con lo que aparece el leak.

 

Encontrando la causa del memory leak

 

Memory leak confirmado. Ahora se trata de encontrar desde dónde se hace esa referencia a alguna clase cargada por el WebappClassLoader  que impide que el GC lo elimine. La idea es sencilla, pero prácticamente imposible de llevar a cabo usando únicamente VisualVM.

Afortunadamente, disponemos de la herramienta jhat que será de gran ayuda en este proceso. Viene incluido en el JDK6, pero debemos usar una versión relativamente reciente para asegurarnos de que incluye la funcionalidad que necesitamos. La ventaja de jhat es que permite navegar por los paths de referencias, lo que hace mas sencillo identificar la causa del memory leak. Esta herramienta trabaja con volcados de memoria (Heap Dumps) por lo que debemos guardar a disco el Head Dump que creamos con visualVM que contiene el leak.

Los pasos a seguir para encontrar la causa del leak son:

  • Arrancar jhat: jhat -J-Xmx512m heapdump-XXXXXXXX.hprof,  donde el archivo con extensión .hprof es el heapdump generado con visualVM.
  • Abrir la url http://localhost:7000 en un navegador web. Veremos una lista con todas las clases presentes en el dump.
  • Buscar alguna de las clases de nuestra aplicación, que no deberían estar ahí si hubieran sido eliminadas por el GC, ya que el Heap Dump se realizó despues de hacer un undeploy, sin hacer un deploy a continuación.
  • Seleccionar el link del ClassLoader (Loader Detail / ClassLoader), que nos llevará a la instancia del ClassLoader que mantiene alguna referencia a nuestra clase. Se puede comprobar que campo started está a false.
  • Seleccionar Chains from Rootset / Exclude weak refs. Nos aparecen todas las clases que contienen al classloader seleccionado en el path de sus referencias !!

De esta forma podemos saber que clases mantienen referencias al classloader que el GC no pudo eliminar. No deberían ser muchas, porque entonces tendríamos varias causas distintas para el leak. Ahora es cuestión de investigar por qué se mantienen dichas referencias y solucionarlo. Muchas veces la solución pasa por añadir código que “haga limpieza” en el ServletContextListener de la aplicación, sobre todo cuando el origen del problema se localiza en una librería externa.

Esto viene también, muchísimo mejor explicado, en el artículo How to fix the dreaded “java.lang.OutOfMemoryError: PermGen space” exception (classloader leaks)

En nuestro caso, en el Heap Dump encontramos las siguientes clases:

Seleccionando una de ellas, podemos llegar al Classloader conflictivo. Es ahora cuando seleccionamos Chains from Rootset / Exclude weak refs. Y se obtiene:

Puede apreciarse como se mantiene una referencia al Classloader a través de una entrada del HashMap de LeakerRegister. En concreto, hemos localizado la referencia a través de la entrada correspondiente a la clase SalutationIta.

Nota: Es posible que jhat también esté en versiones anteriores del JDK, pero no nos serviría para nuestro propósito, puesto que dichas versiones tienen una limitación que no permite encontrar el path completo de referencias.

 

Posibles soluciones

 

Obviamente, las soluciones dependen de la causa del leak. En nuestro caso, existen al menos dos formas distintas de afrontar el problema. La primera de ellas es aplicable cuando todo el código, tanto el del jar como el de la aplicación web, está bajo nuestro control. En ese caso, la solución pasa por hacer el Map implementations de LeakerRegister un miembro de la clase en lugar de usarlo como estático. De esta forma, al eliminar la referencia a la instancia LeakerRegister desde el servlet Leaker se elminará el Map implementations, con lo que no quedarán referencias a las diferentes implementaciones de Salutation. En este caso, se podría aprovechar para dotar a LeakerRegister del soporte para genéricos, de manera que sería válido para cualquier tipo de implementaciones, y no sólo con las de el interfaz Salutation. Quedaría algo así:

package com.wordpress.tododev.leaker.register;

import java.util.HashMap;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class LeakerRegister<T> {

private final Map<String, Class<? extends T>> implementations = new HashMap<String, Class<? extends T>>();

private final Log log = LogFactory.getLog(getClass());

public void register(String implName, Class<? extends T> clazz){
  log.debug("Received register request for " + implName + ", implementation " + clazz.getName());

  if (!implementations.containsKey(implName))
    implementations.put(implName, clazz);
  else
    log.debug("already exists " + implName);
}

public void cleanup(){
  implementations.clear();
}

private T createInstance(Class<? extends T> clazz){

  if (clazz == null)
    return null;

  T impl;
  try {
    impl = clazz.newInstance();
    return impl;
   } catch (InstantiationException e) {
     log.debug("Failed to instantiate " + clazz.getName(), e);
     return null;
   } catch (IllegalAccessException e) {
     log.debug("Failed to instantiate " + clazz.getName(), e);
     return null;
   }
}

public T getImplementation(String implName){
   return createInstance(implementations.get(implName));
}

}

// Servlet Leaker 

package com.wordpress.tododev;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.wordpress.tododev.leaker.register.LeakerRegister;
import com.wordpress.tododev.leaker.register.Salutation;

public class Leaker extends HttpServlet{

private static final long serialVersionUID = 6549546920601945792L;
private static final String PARAM_LANG = "lang";
private static final String LANG_ES = "es";
private static final String LANG_EN = "en";
private static final String LANG_IT = "it";

private final Log log = LogFactory.getLog(getClass());

private final LeakerRegister<Salutation> register = new LeakerRegister<Salutation>();

private Salutation getInstance (String implName){
  return register.getImplementation(implName);
}

@Override
public void init() throws ServletException {
  super.init();
  registerImplementations();
}

private void registerImplementations(){
  // TODO load from properties, servlet context, ... whatever
  register.register(LANG_ES, SalutationEsp.class);
  register.register(LANG_EN, SalutationEng.class);
  register.register(LANG_IT, SalutationIta.class);
}

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {

final Salutation salutation = getInstance(req.getParameter(PARAM_LANG));
if (log.isDebugEnabled()){
  log.debug("Provided lang " + req.getParameter(PARAM_LANG));
  log.debug("Got implementation " + salutation);
}
  resp.getOutputStream().println((salutation == null)?"!!??":salutation.salute());
}

}

Sin embargo, en ocasiones, el jar podría ser una librería que no hayamos desarrollado nosotros. En este caso, el mismo problema se podría abordar desde otro enfoque: crear un Servlet Context Listener que eliminase todos los elementos del Map implementations, llamando al método cleanup de LeakerRegister, al destruirse el contexto. De esta forma también se eliminan las referencias a las clases cargadas por el WebappClassLoader.

En cualquier caso, aunque la solución depende completamente de la causa del memory leak y no hay recetas universales (aunque sí varias recomendaciones, como éstas de la Tomcat Wiki), esperamos que al menos haya servido para identificar los memory leaks, entender el mecanismo por el que se producen, e identificar las posibles causas.

Algunos ejemplos de leaks conocidos son el del  JDBC Driver Manager, o el Query Timeout del Connector/J de MySQL. ¿ Con cuáles te has encontrado tú ? ¿ Cómo los has solucionado ?

El pavimento inteligente llega a Madrid con conexión WiFi, Bluetooth y RDIF

El ayuntamiento de Madrid está apoyando una iniciativa con la que muchos ciudadanos disfrutarán de conexión a internet gratuita en plena Puerta del Sol, además de recibir mensajes de alerta con información de interés y otro tipo de servicios como la posibilidad de descargar mapas de la ciudad o planos del transporte público. Todo ello es posible gracias a la utilización del llamado Pavimento Inteligente, una superficie que consiste en una baldosa a la que se le ha integrado un controlador con sensores y antenas que permitirá recolectar información como la temperatura y la presión del ambiente, así como enviar mensajes a través de Bluetooth a los ciudadanos que por allí pasen e incluso informar sobre la situación de una persona a través de etiquetas RDIF. Se trata de un proyecto experimental desarrollado por ASEPI (Agrupación Empresarial Innovadora del Pavimento Inteligente en España) que esperan ir incorporando en más zonas de la ciudad, así que esperamos que tenga buena acogida y podamos ver más calles equipadas con esta interesante tecnología. A continuación te dejamos con un vídeo en el que podrás ver una explicación de la tecnología.

Vídeo

Enlace a Engadget

%d bloggers like this: