2012-03-05 19 views
6

Donde trabajo, estamos experimentando problemas con la JVM quedando sin espacio de almacenamiento en una de nuestras aplicaciones. He hecho un poco de búsqueda de la causa de esto, incluso mirando un heap-dump con un generador de perfiles, pero ahora estoy bastante atascado.Hibernate, SessionFactoryObjectFactory y OutOfMemoryError: espacio de almacenamiento Java

Primero, un poco sobre el sistema en cuestión: es una aplicación Java, que utiliza Spring e Hibernate, para mantener registros sobre las organizaciones. El sistema está compuesto por un conjunto de clientes de servicios web que se utiliza para recuperar datos sobre organizaciones de la institución gubernamental responsable de este tipo de datos. Además, el sistema mantiene una base de datos local con dichos datos, actuando como un caché para las llamadas al servicio web, de modo que la primera vez que se solicita información sobre una organización, se guarda en una base de datos relacional local, y se utiliza para la recuperación de datos para las siguientes solicitudes. Hibernate se utiliza para comunicarse con esta base de datos.

El problema, como se indicó anteriormente, es que después de un período de tiempo, la aplicación comienza a fallar con OutOfMemoryError: espacio de almacenamiento dinámico de Java. Miré un montón de volcados usando Eclipse + MAT e identifiqué al culpable como SessionFactoryObjectFactory de Hibernate, ocupando aproximadamente el 85% de la memoria asignada (toda la memoria retenida). Me ha resultado un poco difícil identificar exactamente qué tipo de objetos se guardan en este. En el nivel superior se encuentra Glassfish WebappClassLoader, que contiene org.hibernate.impl.SessionFactoryObjectFactory. Contenido en esto es un org.hibernate.util.FastHashMap, que a su vez contiene un java.util.HashMap. Este contiene un número de entradas, cada una de las cuales contiene una entrada HashMap, una org.hibernate.impl.SessionFactoryImpl y una cadena. La entrada HashMap a su vez contiene los mismos tres objetos, una entrada HashMap, una SessionFactoryImpl y una cadena, y esta estructura se repite varias veces. SessionFactoryImpls contiene una serie de objetos, en particular org.hibernate.persister.entity.SingleTableEntityPersister, que contiene varias Strings y HashMaps. Algunas de las cadenas se refieren a variables en objetos de dominio, y algunas contienen sentencias sql.

Parecía a primera vista que este objeto ocupaba cantidades innecesarias de memoria (el archivo de volcado era de 800 MB, de los cuales 650 MB estaban ocupados por SessionFactoryObjectFactory), así que habilité el registro de carga y descarga de objetos e intenté preguntar al sistema para datos sobre una organización (a través de una llamada de servicio web desde otro sistema). Lo que noté aquí fue que había muchos mensajes para cargar objetos, pero muy pocos sobre objetos descargados (los únicos que estaban allí eran la descarga de objetos de la biblioteca). Esto me llevó a creer que una vez que un objeto (por ejemplo, una organización) se cargó en la memoria, nunca se descarga, lo que significa que con el tiempo, el sistema se quedará sin memoria. (¿Es esto una suposición justa basada en lo que se encontró en el registro?)

Luego, traté de encontrar la causa de esto, pero esto fue mucho más difícil. Dado que los objetos cargados por Hibernate vivirán mientras dure su sesión, intenté modificar la forma en que se manejaban las sesiones, reemplazando las llamadas a Spring's HibernateDaoSupport # getSession(), a HibernateDaoSupport # getSessionFactory(). GetCurrentSession(). Esto no tuvo ningún efecto aparente en el problema. También traté de agregar llamadas a ... getCurrentSession(). Flush() y .clear() en el bloque final de algunos de los métodos Dao en cuestión, también sin efecto aparente. (Los métodos Dao están todos anotados con @Transactional, lo que debería significar que una sesión solo debe estar activa dentro del método @ Transactional, y las llamadas consecutivas al método deben tener sesiones diferentes al llamar a getCurrentSession() (?))

Entonces, ahora estoy bastante atascado cuando se trata de encontrar otras áreas para verificar. ¿Alguien tiene una idea o un puntero sobre dónde buscar y qué buscar?

El montón de volcados mostró que hay muchas instancias de org.hibernate.impl.SessionFactoryImpl, ¿esto es como se esperaba? (Hubiera pensado que solo debería haber una instancia de SessionFactory, o algunas tapas.)

Gracias de antemano por cualquier respuesta.

Saludos,

Tobb

Editar:

creo que en realidad he mananged para resolver el problema:

Resultó que forma dependencias a otros objetos se manejan de la webservice-classes fue el problema. Esto se resolvió llamando al nuevo ClassPathXmlApplicationContext (...) en el constructor de las clases de servicios web. Esto llevó a que se cargaran muchos objetos para cada solicitud (o al menos cada sesión), que no se descargaron nuevamente (principalmente SessionFactoryImpl de Hibernate). Cambié las clases de servicio web para que, en su lugar, inyectaran su dependencia, y formaran lo que he visto usando los perfiles hasta ahora, el problema con múltiples objetos SessionFactoryImpl ha sido resuelto.

Creo que el problema podría haber empeorado al actualizar de GlassFish 2.xa GlassFish 3.x, podría haber algunas diferencias en cuanto a cómo se instancian las clases de servicios web.

Respuesta

5

que también podría añadir la solución a este problema en una respuesta, en lugar de en la propia pregunta:

El culpable aquí es cómo la carga de resorte en grano se realizó en diversos objetos, sobre todo en webservice-classes. Esto se hizo llamando al

new ClassPathXmlApplicationContext(...)

En las clases de servicio web individuales. Hacer esto tiene el desagradable efecto secundario de los objetos cargados evitando que se recolecte basura (supongo que porque algunos de los componentes internos de Spring lo mencionan). Parece que un cambio en la versión de Glassfish hizo algo con la instanciación de objetos del servicio web, lo que llevó a llamadas a muchas más llamadas a esto nuevo, y por lo tanto muchos objetos basura que ocupan memoria, hasta que se llena y se cuelga.

La solución al problema era pasar las llamadas a

new ClassPathXmlApplicationContext(...)

a cabo en una clase diferente, utilizando la fábrica de patrón estático, algo como esto:

public class ContextHolder { 
    private static ClassPathXmlApplicationContext context; 

    public static getSpringContext() { 
     if (context == null) { 
      context = new ClassPathXmlApplicationContext("applicationContext.xml"); 
     } 
     return context; 
    } 
} 

Y llamando a esto en las clases de servicio web, en lugar de new-ing ClassPathXmlApplicationContext.

Actualización:

ClassPathXmlApplicationContext es Closeable/Autocloseable, por lo try-with-resource es otra posibilidad:

try (final ClassPathXmlApplicationContext applicationContext = 
      new ClassPathXmlApplicationContext("applicationContext.xml")) { 
    //do stuff 
} 
+0

Como alternativa a la fábrica estática, creo que el '' ClassPathXmlApplicationContext' tiene una close'-método que puede ser llamado para evitar este problema. – Tobb

Cuestiones relacionadas