Archivos de Marzo, 2008

Causas y solución de java.lang.OutOfMemoryError: PermGen space failure

28 de Marzo de 2008

La memoria de la Máquina Virtual se divide en varias regiones. Una de estas regiones es el PermGen, el área de memoria utilizada para, entre otras cosas, guardar el metadata de las clases como los atributos y sus tipos de datos, métodos etc. Esta memoria es de tipo non-heap. Las instancias de las clases se cargan en la memoria de tipo heap, a la que se van añadiendo y eliminando las instancias de las clases según se van utilizando y eliminándose por el recolector de basura (Garbage Collector, en adelante GC).

No vamos a entrar en el mecanismo de la carga y eliminación de clases en java porque excede este artículo, pero sí lo comentaremos brevemente para poder entender el porqué de la excepción OutOfMemoryException: para que una clase pueda ser eliminada por el recolector de basura (GC), es necesario que no esté referenciada por ninguna otra clase. Para que se pueda eliminar la memoria ocupada por una clase en el espacio PermGen, es necesario además que se elimine el classloader que cargó la clase.

El valor por defecto del espacio PermGen es de 64 Mb en la máquina virtual (VM) de Sun. Este valor es suficiente normalmente para las aplicaciones que corren de forma independiente. En el caso de una aplicación que corra en un servidor web tipo Tomcat o un servidor de aplicaciones, hay casos en los que este valor no es suficiente.

En primer lugar, es posible que la aplicación necesite más espacio por su propia arquitectura. Las librerías Spring e Hibernate son librerías grandes, que cargan muchas clases y además hacen uso de proxies y carga dinámica de clases que hacen uso del espacio PermGen por lo que puede que los 64Mb no sean suficientes. Este caso se da cuando la excepción con el error se produce nada mas arrancar el servidor web o de aplicaciones o al acceder a la aplicación. Para solventar este problema bastará con aumentar el tamaño máximo de la memoria de tipo PermGen según veremos más adelante.

En segundo lugar, la causa más probable de una excepción java.lang.OutOfMemoryError: PermGen space failure se produce cuando la aplicación se reinstala en el servidor sin reiniciar el servidor. La forma de reinstalar las aplicaciones es eliminando el classloader que cargó la aplicación por primera vez y creando un nuevo classloader para la nueva instancia de la aplicación. En teoría, esto proporciona una forma limpia de reinstalar aplicaciones, pero si el servidor web o de aplicaciones guarda una referencia al classloader antiguo, se produce una fuga de memoria (memory leak) y el resultado es que tendremos las clases de la aplicación cargadas dos veces en memoria. Es cuestión del número de recargas y de la memoria gastada por la aplicación el que se produzca la excepción  java.lang.OutOfMemoryError: PermGen space failure. En este caso no basta con aumentar el tamaño máximo de la memoria, ya que esto sólo retrasaría el problema a unas cuantas reinstalaciones más. En este caso la única solución consiste en averiguar la causa de la fuga de memoria. Esto también excede el contenido de este artículo, aunque lo intentarmos tratar con profundidad en otra ocasión. Por el momento sólo proporcionamos un enlace muy interesante, aunque en inglés, acerca de posibles causas de fugas de memoria en java: http://opensource.atlassian.com/confluence/spring/pages/viewpage.action?pageId=2669

Por último, a continuación incluimos el argumento que es necesario incluir al arrancar la máquina virtual para especificar el tamaño de esta región PermGen en Mb, XX:MaxPermSize=128m para el tamaño máximo y XX:PermSize=128m para que la máquina virtual ya arranque con este tamaño y no tenga que reservar más espacio en tiempo de ejecución.

Algoritmo de verificación de cuenta de banco válida

26 de Marzo de 2008

Este código realiza la verificación de si la cadena de entrada es una cuenta de banco válida, de 20 dígitos y con los dígitos de control correctos

public static boolean esCuentaBancaria (String cuenta){
 
	_log.debug("Cuenta del Banco "+cuenta);
 
	Pattern cuentaPattern = Pattern.compile("\\d{20}");
	Matcher m = cuentaPattern.matcher(cuenta);
	if(m.matches()){
		_log.debug("cuenta cumple el patrón (20 dígitos)");
 
		String banco = "00"+cuenta.substring(0,8);
		_log.debug("Banco (con 00) "+banco);
 
		int suma = Integer.parseInt(banco.substring(0,1))*1+
			Integer.parseInt(banco.substring(1,2))*2+
			Integer.parseInt(banco.substring(2,3))*4+
			Integer.parseInt(banco.substring(3,4))*8+
			Integer.parseInt(banco.substring(4,5))*5+
			Integer.parseInt(banco.substring(5,6))*10+
			Integer.parseInt(banco.substring(6,7))*9+
			Integer.parseInt(banco.substring(7,8))*7+
			Integer.parseInt(banco.substring(8,9))*3+
			Integer.parseInt(banco.substring(9,10))*6;
 
		int control= 11 - (suma%11);
		_log.debug("control banco después del modulo 11 "+control);
		if (control==10)
			control=1;
		else if (control==11)
			control=0;
 
		_log.debug("control "+control );
 
		int controlBanco = Integer.parseInt(cuenta.substring(8,9));
		if (controlBanco!=control)
			return false;
		_log.debug("El control del banco está bien");
 
		_log.debug("cuenta "+cuenta.substring(10,20));
 
		suma = Integer.parseInt(cuenta.substring(10,11))*1+
			Integer.parseInt(cuenta.substring(11,12))*2+
			Integer.parseInt(cuenta.substring(12,13))*4+
			Integer.parseInt(cuenta.substring(13,14))*8+
			Integer.parseInt(cuenta.substring(14,15))*5+
			Integer.parseInt(cuenta.substring(15,16))*10+
			Integer.parseInt(cuenta.substring(16,17))*9+
			Integer.parseInt(cuenta.substring(17,18))*7+
			Integer.parseInt(cuenta.substring(18,19))*3+
			Integer.parseInt(cuenta.substring(19,20))*6;
 
		control= 11 - (suma%11);
		_log.debug("control cuenta después del modulo 11 "+control);
		if (control==10)
			control=1;
		else if (control==11)
			control=0;
 
		_log.debug("control "+control);
 
		int controlcuenta = Integer.parseInt(cuenta.substring(9,10));
		if (controlcuenta!=control)
			return false;
		else
			return true;
 
	}
	else
		return false;
 
}

Verificación de NIF o NIE válido

19 de Marzo de 2008

El siguiente código realiza la verificación de que la cadena suministrada como entrada sea un NIF o NIE válido. Un NIE sólo lleva una X, Y o Z  antepuesta al resto de la cadena, por lo que si la cadena comienza por X, Y o Z, se elimina de la cadena a verificar.

public static boolean isNifNie(String nif){
 
   _log.debug("NIF "+nif);
   //si es NIE, eliminar la x,y,z inicial para tratarlo como nif
   if (nif.toUpperCase().startsWith("X")||nif.toUpperCase().startsWith("Y")||nif.toUpperCase().startsWith("Z"))
nif = nif.substring(1);
 
Pattern nifPattern =
Pattern.compile("(\\d{1,8})([TRWAGMYFPDXBNJZSQVHLCKEtrwagmyfpdxbnjzsqvhlcke])");
Matcher m = nifPattern.matcher(nif);
if(m.matches()){
String letra = m.group(2);
//Extraer letra del NIF
String letras = "TRWAGMYFPDXBNJZSQVHLCKE";
int dni = Integer.parseInt(m.group(1));
dni = dni % 23;
String reference = letras.substring(dni,dni+1);
 
if (reference.equalsIgnoreCase(letra)){
_log.debug("son iguales. Es NIF. "+letra+" "+reference);
return true;
}else{
_log.debug("NO son iguales. NO es NIF. "+letra+" "+reference);
return false;
}
}
else
return false;
}

Generación de números primos

14 de Marzo de 2008

El siguiente extracto de código genera los números primos que existan hasta un número límite que se marca como parámetro. Se ha codificado como un método para que pueda ser reutilizado, por ejemplo añadiéndolo a la librería de operaciones matemáticas que se presentaba en la entrada Como saber si un número es par o impar

public static int[] generarNumerosPrimos(int max) {
if (max < 2) {
return new int[0];
}
else {
boolean[] booleanArrayTmp = new boolean[max + 1];
 
for (int i = 2; i < booleanArrayTmp.length; i++) {
booleanArrayTmp[i] = false;
}
 
int limit = (int)Math.sqrt(booleanArrayTmp.length);
 
for (int i = 2; i <= limit; i++) {
if (!booleanArrayTmp[i]) {
for (int multiple = 2 * i; multiple < booleanArrayTmp.length;
multiple += i) {
 
booleanArrayTmp[multiple] = true;
}
}
}
 
int tmpInt = 0;
 
for (int i = 2; i < booleanArrayTmp.length; i++) {
if (!booleanArrayTmp[i]) {
tmpInt++;
}
}
 
int[] resultado = new int[tmpInt];
 
for (int i = 2, j = 0; i < booleanArrayTmp.length; i++) {
if (!booleanArrayTmp[i]) {
resultado[j++] = i;
}
}
 
return resultado;
}
}

Como verificar si un número es par o impar

10 de Marzo de 2008

En esta entrada vamos a implementar 2 sencillos métodos para verificar si un entero, que será la variable, es un número par o un número impar. Tener implementados estos métodos den verificación en una clase de utilidades matemáticas nos permitirá posteriormente acceder a los métodos sin tener que pensar en su implementación otra vez cuando nos surja la necesidad.

El hecho de declarar ambos métodos que realizan la verificación como estáticos permitirá posteriormente invocar el método sin necesidad de crear una nueva instancia de la clase.

public final class UtilidadesMatematicas {
	public UtilidadesMatematicas () {
	}
 
	public static boolean esPar(int x) {
		if ((x % 2) == 0) {
			return true;
		}
 
		return false;
	}
 
	public static boolean esImpar(int x) {
		return !esPar(x);
	}
}