Memoria con tomcat

¿Quien no ha tenido problemas de memoria en tomcat? el famoso java.lang.OutOfMemoryError: PermGen space. Esta memoria PermGen es usada para almacenar el código de las aplicaciones que corren en ella.

¿Por qué me quedo sin memoria? Debemos entender que no sólo ocupa memoria nuestro código, sino también las librerias, y esto pueden ser unos cuantos Mb que vamos metiendo en memoria.

¿Cómo saber en qué valores de memoria nos andamos? Tenemos dos comandos Java: jps y jstat que vienen en el JDK.

  • El jps es como un ps de Unix, nos indica los procesos Java que están en ejecución, devolviendo el PID del programa.
  • El jstat nos permite ver la memoria que usa ese proceso. El parámetro para ver la capacidad actual es «-gccapacity PID» y para medir sólo la memoria PermGen es «-gcpermcapacity PID»

Ejemplos:

[www@villacarralon scripts]$ jps
25873 Jps
18156 Bootstrap
[www@villacarralon scripts]$ ps -ef | grep java
www 18156 1 0 09:12 pts/0 00:00:49 /servicio/jdk1.5.0_11//bin/java -server ... org.apache.catalina.startup.Bootstrap start
www 25882 18101 0 11:08 pts/0 00:00:00 grep java


jstat -gccapacity 18156
NGCMN NGCMX NGC S0C S1C EC OGCMN OGCMX OGC OC PGCMN PGCMX PGC PC YGC FGC
58240,0 116480,0 58304,0 8128,0 8640,0 41024,0 466048,0 932096,0 466048,0 466048,0 131072,0 262144,0 131072,0 131072,0 24 0


jstat -gcpermcapacity 18156
PGCMN PGCMX PGC PC YGC FGC FGCT GCT
131072,0 262144,0 131072,0 131072,0 24 0 0,000 0,555

Tenemos tres valores interesantes a observar para la memoria PermGen:

  • el PGCMN: el tamaño mínimo reservado para la memoria PermGen
  • el PGCMX: la memoria máxima que puede usar (en KB) 
  • el PGC, la memoria usada actualmente.

Si vemos que el PGC se aproxima al PGCMX deberíamos de cambiar la opción del tomcat aumentando el tamaño máximo con -XX:MaxPermSize=256m a por ejemplo -XX:MaxPermSize=512m siempre que nuestro equipo nos lo permita.

Si necesitamos monitorizarlo durante una ejecución, podemos plantearnos hacer un scriptillo como:

#!/bin/bash
jps | grep Bootstrap | awk '{print "while true; do jstat -gcpermcapacity " $1 "; sleep 5; done" }' | bash

Hibernate con dos o mas orígenes de datos

Se me planteó un problema, ¿cómo hacer que hibernate tenga más de un orígen de datos? Estuve investigando y aunque encontré varias soluciones, no encontré la ideal, así que hice una mezcla de varias de ellas. Lo primero fue crear una clase HibernateUtil que os copio después, y para utilizarla es muy sencillo.

Primero la inicializamos en mi caso con un origen de datos por defecto y luego otros dos, uno para mysql y otro para una base de datos oracle:

HibernateUtil.createSessionFactory();
HibernateUtil.buildSessionFactory("oracle", "hibernate.oracle.cfg.xml");
HibernateUtil.buildSessionFactory("mysql", "hibernate.mysql.cfg.xml");

Estos ficheros deben estar en el mismo punto que a clase HibernateUtil, sino deberéis modificar su ruta según vuestro interés.

A continuación podemos guardar o leer usando Hibernate de la forma habitual:

public void save(Formulario objeto) {
Session session=HibernateUtil.currentSession("mysql");
Transaction tx = null;
try {
tx = session.beginTransaction();
session.save(objeto);
tx.commit();
} catch (RuntimeException e) {
if (tx != null) {
logger.error("Error al guardar " + e);
}
tx.rollback();
  }
}

 
Y ya por fin, el código de la clase HibernateUtil

package es.uva.pdf.dao;

import java.net.URL;
import java.util.HashMap;

import org.apache.log4j.Logger;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

import com.sun.tools.javac.util.Log;

import es.uva.pdf.struts2.action.ListadoPersonal;

public class HibernateUtil {

private static Logger log = Logger.getLogger(HibernateUtil.class);

private static HashMap sessionFactoryMap = new HashMap();

public static final ThreadLocal sessionMapsThreadLocal = new ThreadLocal();

public static Session currentSession(String key) throws HibernateException {

HashMap sessionMaps = (HashMap) sessionMapsThreadLocal
.get();

if (sessionMaps == null) {
sessionMaps = new HashMap();
sessionMapsThreadLocal.set(sessionMaps);
}

// Open a new Session, if this Thread has none yet
Session s = (Session) sessionMaps.get(key);
if (s == null) {
s = ((SessionFactory) sessionFactoryMap.get(key)).openSession();
sessionMaps.put(key, s);
}

return s;
}

public static Session currentSession() throws HibernateException {
return currentSession("");
}

public static void closeSessions() throws HibernateException {
HashMap sessionMaps = (HashMap) sessionMapsThreadLocal
.get();
sessionMapsThreadLocal.set(null);
if (sessionMaps != null) {
for (Session session : sessionMaps.values()) {
if (session.isOpen())
session.close();
}
;
}
}

public static void closeSession() {
HashMap sessionMaps = (HashMap) sessionMapsThreadLocal
.get();
sessionMapsThreadLocal.set(null);
if (sessionMaps != null) {
Session session = sessionMaps.get("");
if (session != null && session.isOpen())
session.close();
}
}

public static void closeSession(String key) {
HashMap sessionMaps = (HashMap) sessionMapsThreadLocal
.get();
if (sessionMaps != null) {
Session session = sessionMaps.get(key);
if (session != null && session.isOpen())
session.close();
}
}

public static void buildSessionFactories(HashMap configs) {
try {
// Create the SessionFactory
for (String key : configs.keySet()) {
URL url = HibernateUtil.class.getResource(configs.get(key));
SessionFactory sessionFactory = new Configuration().configure(
url).buildSessionFactory();
sessionFactoryMap.put(key, sessionFactory);
}

} catch (Exception ex) {
ex.printStackTrace(System.out);
log.error("Initial SessionFactory creation failed.", ex);
throw new ExceptionInInitializerError(ex);

} // end of the try - catch block
}

public static void buildSessionFactory(String key, String path) {
try {
// Create the SessionFactory
URL url = HibernateUtil.class.getResource(path);
SessionFactory sessionFactory = new Configuration().configure(url)
.buildSessionFactory();
sessionFactoryMap.put(key, sessionFactory);

} catch (Throwable ex) {

log.error("Initial SessionFactory creation failed.", ex);
throw new ExceptionInInitializerError(ex);

} // end of the try - catch block
}

public static void buildSessionFactory() {
// Por defecto
try {
// Create the SessionFactory
SessionFactory sessionFactory = new Configuration().configure()
.buildSessionFactory();
sessionFactoryMap.put("default", sessionFactory);
} catch (Throwable ex) {
log.error("Initial SessionFactory creation failed.", ex);
throw new ExceptionInInitializerError(ex);

} // end of the try - catch block
}

// alias
public static void createSessionFactory() {
buildSessionFactory();
}

>}

Java FDF: Rellenar y procesar formularios pdf

Para mí ha sido todo un descubrimiento. Con FDF puedes rellenar automáticamente los formularios PDF sin ningún problema, y lo más interesante, procesarles. En nuestro caso lo hemos hecho todo a través de J2EE, con Servlets y lo más divertido a sido el tema de los flags de los campos para decir si eran obligatorios, ocultos, sólo lectura, etc.

Los flags F son de anotación, y la posición de los bits son:

  1. invisible
  2. Oculto
  3. Imprimir el campo.
  4. No Zoom
  5. No rotar
  6. No ver
  7. Solo lectura.

Las combinaciones de estos bits nos permiten activando el oculto por ejemplo que nadie vea un campo de un formulario que usamos para almacenar el identificador del formulario, etc.

Los otros flags interesantes son los Ff, de campo:

  1. Sólo lectura, para que el usuario no pueda modificarlo.
  2. Obligatorio, para que el usuario tenga que rellenarlo antes de su envío.
  3. No exportar, es decir, que no se va a enviar al realizar el envío del formulario

Al final, en nuestro servlet tenemos en el doGet el autorellenado de formulario, ya que el usuario se a autenticado previamente, y le rellenamos los datos de nombre, apellido, etc para facilitarle la tarea. El código sería:

public void doGet(HttpServletRequest req, HttpServletResponse res)
throws IOException, ServletException {
FDFDoc outputFDF = null;
/* Create a new FDF. */
outputFDF = new FDFDoc();
outputFDF.SetFlags("AutorizadorUID", FDFItem.FDFSetF, 2); //Invisible
outputFDF.SetFlags("SolicitanteNombre", FDFItem.FDFSetFf, 1); //Solo lectura
outputFDF.SetFlags("SolicitudDireccionesIP", FDFItem.FDFSetFf, 2); //Obligatorio
outputFDF.SetFlags("SolicitudDireccionesIP", FDFItem.FDFClearFf, 1); //Escribible
try {
outputFDF.SetFile(fichPdf);
fillPDF(outputFDF, formulario);
res.setContentType("application/vnd.fdf");
OutputStream out = res.getOutputStream();
outputFDF.Save(out);
out.close();
} catch (FDFException e) {
/* We handle an error by emitting an html header */
res.setContentType("text/html");
PrintWriter out = res.getWriter();
out.println("Caught FDF exception");
out.println(e.toString());
System.err.println("Error "+e);
e.printStackTrace(out);
}
}

En cuanto al procesado del formulario tenemos algo parecido:

try {
FdfInput = null;
// get the length of incoming data
int howMany = req.getContentLength();
byte data[] = new byte[howMany];
// read data into byte array
req.getInputStream().read(data);
// create FDFDoc from data
FdfInput = new FDFDoc(data);
//Get ComunicadorUID value
String comunicadorUID=FdfInput.GetValue("ComunicadorUID");
} catch (FDFException e) {
reportError(req, res, "Error al generar el pdf. " + e.toString());
// Pte: DEjar un log del error
}

y luego obtenidas las variables del formulario, podemos guardarlo en una base de datos, mandar un correo electrónico, generar otro formulario, etc, etc.

Sinceramente, no me esperaba un tratamiento tan sencillo de los pdf’s. Es una muy buena idea, pero con una limitación, necesitamos acrobat reader 7 o superior, sino este invento no funciona.