Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

[Hibernate] OutOfMemoryErrors

3 réponses
Avatar
LR
Salut,

J'ai l'impression qu'il y a quelque spécialistes d'Hibernate ici, je vais
peut-être enfin trouver solution à mon problème...

C'est un message relativement long dont voici un résumé : j'ai plusieurs
fois par jour des OutOfMemoryError : PermGen space dans Tomcat et j'ai
l'impression que le problème vient soit de Tomcat qui utilise toujours plus
de mémoire à chaque redémarrage de contexte, soit de ma configuration de
Hibernate qui ne libère par les données de configuration lorsque je
redémarre mon contexte.

Ci-dessous la description détaillée du problème et encore plus bas, mon
fichier hibernate.cfg.xml, un fichier de mapping, ma classe HibernateUtil,
mon filtre HibernateFilter et un DAO typique.

Merci d'avance à quicquonque pourrait me fournir une piste pour régler ce
problème qui risque de mettre tout mon projet en péril.

===============================================================================

Voilà, je réalise mon premier projet J2EE avec Hibernate (v3.0). J'ai lu la
doc, beaucoup surfé sur le web mais je n'ai pas vraiment l'impression de
maîtriser le sujet. En réalité, tout fonctionne à merveille à l'exception
d'un problème récurrant : Tomcat (v5.5.4) me sort plusieurs fois par jour
des "OutOfMemoryError : PermGen space" et je dois le redémarrer.

Ma base de donnée est d'une taille raisonnable et le volume des données
qu'elle contient est plutôt petit (application de gestion pour une petite
association qui fonctionne un peu comme une boîte de placement ou une
société d'interim).

J'ai essayé de mettre en place le lazy loading en ne spécifiant pas
"lazy=false" dans les paramètres de mes collections.

Dans l'ensemble, j'ai surtout laissé les paramètres par défaut.

Depuis hier, je surveille l'utilisation mémoire faite par Tomcat par le biai
du gestionnaire des tâches de Windows (XP pro). D'après mes constatations,
Tomcat utilise :
- environ 30MB pour son fonctionnement normal (juste pour démarrer),
- entre 3 et 5 MB qui ne sont jamais libérés pour redémarrer le contexte,
- entre 10 et 12 MB qui ne sont jamais libérés pour lire les fichiers de
config d'Hibernate
- et à part ça, lorsque je teste l'application, l'utilisation de la mémoire
a plus tendance à monter qu'à descendre mais dans l'ensemble ça me paraît
assez stable.

Donc je ne sais pas si le problème surviendra également en production, mais
en tout cas en développement, étant donné que mon contexte redémarre très
souvent, j'arrive vite aux 120MB qui font planter Tomcat (d'ailleurs je suis
étonné par ce chiffre car il me semblait que par défaut Tomcat avait droit à
64MB)...

Merci d'avance !
Lilian

================================================================================
Ma classe HibernateUtil (pompée de CaveatEmptor) :

import org.apache.commons.logging.*;
import org.hibernate.*;
import org.hibernate.cfg.*;

import bab.admin.model.exceptions.*;

public class HibernateUtil
{

private static Log log = LogFactory.getLog( HibernateUtil.class );
private static int configurationReaded = 0;

private static Configuration configuration;
private static SessionFactory sessionFactory;
private static final ThreadLocal threadSession = new ThreadLocal();
private static final ThreadLocal threadTransaction = new ThreadLocal();
private static final ThreadLocal threadInterceptor = new ThreadLocal();

// Create the initial SessionFactory from the default configuration files
static
{
try
{
configuration = new Configuration();
sessionFactory = configuration.configure().buildSessionFactory();
System.out.println( "configuration lue : " +(++configurationReaded)+ "
fois." );
// We could also let Hibernate bind it to JNDI:
// configuration.configure().buildSessionFactory()
}
catch ( Throwable ex )
{
// We have to catch Throwable, otherwise we will miss
// NoClassDefFoundError and other subclasses of Error
log.error( "Building SessionFactory failed.", ex );
throw new ExceptionInInitializerError( ex );
}
}

/**
* Returns the SessionFactory used for this static class.
*
* @return SessionFactory
*/
public static SessionFactory getSessionFactory()
{
/* Instead of a static variable, use JNDI:
SessionFactory sessions = null;
try {
Context ctx = new InitialContext();
String jndiName = "java:hibernate/HibernateFactory";
sessions = (SessionFactory)ctx.lookup(jndiName);
} catch (NamingException ex) {
throw new InfrastructureException(ex);
}
return sessions;
*/
return sessionFactory;
}

/**
* Returns the original Hibernate configuration.
*
* @return Configuration
*/
public static Configuration getConfiguration()
{
return configuration;
}

/**
* Rebuild the SessionFactory with the static Configuration.
*
*/
public static void rebuildSessionFactory() throws InfrastructureException
{
synchronized ( sessionFactory )
{
try
{
sessionFactory = getConfiguration().buildSessionFactory();
}
catch ( Exception ex )
{
throw new InfrastructureException( ex );
}
}
}

/**
* Rebuild the SessionFactory with the given Hibernate Configuration.
*
* @param cfg
*/
public static void rebuildSessionFactory( Configuration cfg ) throws
InfrastructureException
{
synchronized ( sessionFactory )
{
try
{
sessionFactory = cfg.buildSessionFactory();
configuration = cfg;
}
catch ( Exception ex )
{
throw new InfrastructureException( ex );
}
}
}

/**
* Retrieves the current Session local to the thread.
* <p/>
* If no Session is open, opens a new Session for the running thread.
*
* @return Session
*/
public static Session getSession() throws InfrastructureException
{
Session s = (Session)threadSession.get();
try
{
if ( s == null )
{
System.out.println( "Ouverture d'une nouvelle session pour ce Thread" );
//log.debug( "Opening new Session for this thread." );
log.info( "Opening new Session for this thread." );
if ( getInterceptor() != null )
{
//log.debug( "Using interceptor: " + getInterceptor().getClass() );
log.info( "Using interceptor: " + getInterceptor().getClass() );
s = getSessionFactory().openSession( getInterceptor() );
}
else
{
s = getSessionFactory().openSession();
}
threadSession.set( s );
}
}
catch ( HibernateException ex )
{
throw new InfrastructureException( ex );
}
return s;
}

/**
* Closes the Session local to the thread.
*/
public static void closeSession() throws InfrastructureException
{
try
{
Session s = (Session)threadSession.get();
threadSession.set( null );
if ( s != null && s.isOpen() )
{
System.out.println( "Fermeture de la session pour ce thread + vidage du
cache + garbage collect");
//log.debug( "Closing Session of this thread." );
log.info( "Closing Session of this thread." );
s.clear();
s.close();
System.gc();
}
}
catch ( HibernateException ex )
{
throw new InfrastructureException( ex );
}
}

/**
* Start a new database transaction.
*/
public static void beginTransaction() throws InfrastructureException
{
Transaction tx = (Transaction)threadTransaction.get();
try
{
if ( tx == null )
{
System.out.println( "Début d'une nouvelle transaction pour ce Thread" );
log.debug( "Starting new database transaction in this thread." );
tx = getSession().beginTransaction();
threadTransaction.set( tx );
}
}
catch ( HibernateException ex )
{
throw new InfrastructureException( ex );
}
}

/**
* Commit the database transaction.
*/
public static void commitTransaction() throws InfrastructureException
{
Transaction tx = (Transaction)threadTransaction.get();
try
{
if ( tx != null && !tx.wasCommitted() && !tx.wasRolledBack() )
{
System.out.println( "Commit de la transaction pour ce thread" );
log.debug( "Committing database transaction of this thread." );
tx.commit();
}
threadTransaction.set( null );
}
catch ( HibernateException ex )
{
rollbackTransaction();
throw new InfrastructureException( ex );
}
}

/**
* Commit the database transaction.
*/
public static void rollbackTransaction() throws InfrastructureException
{
Transaction tx = (Transaction)threadTransaction.get();
try
{
threadTransaction.set( null );
if ( tx != null && !tx.wasCommitted() && !tx.wasRolledBack() )
{
System.out.println( "Rollback de la transaction pour ce thread ");
log.debug( "Tyring to rollback database transaction of this thread." );
tx.rollback();
}
}
catch ( HibernateException ex )
{
throw new InfrastructureException( ex );
}
finally
{
closeSession();
}
}

/**
* Reconnects a Hibernate Session to the current Thread.
*
* @param session The Hibernate Session to be reconnected.
*/
public static void reconnect( Session session ) throws
InfrastructureException
{
try
{
System.out.println( "reconnect session");
session.reconnect();
threadSession.set( session );
}
catch ( HibernateException ex )
{
throw new InfrastructureException( ex );
}
}

/**
* Disconnect and return Session from current Thread.
*
* @return Session the disconnected Session
*/
public static Session disconnectSession() throws InfrastructureException
{

Session session = getSession();
try
{
System.out.println( "disconnect session ");
threadSession.set( null );
if ( session.isConnected() && session.isOpen() ) session.disconnect();
}
catch ( HibernateException ex )
{
throw new InfrastructureException( ex );
}
return session;
}

/**
* Register a Hibernate interceptor with the current thread.
* <p>
* Every Session opened is opened with this interceptor after
* registration. Has no effect if the current Session of the
* thread is already open, effective on next close()/getSession().
*/
public static void registerInterceptor( Interceptor interceptor )
{
threadInterceptor.set( interceptor );
}

private static Interceptor getInterceptor()
{
Interceptor interceptor = (Interceptor)threadInterceptor.get();
return interceptor;
}
}

========================================================================
Mon filtre HibernateFilter (pompé de CaveatEmptor) :

/*
* Créé le 11 avr. 2005
*/
package bab.admin.web.filter;

import org.apache.commons.logging.*;

import bab.admin.model.*;

import java.io.IOException;

import javax.servlet.*;

/**
* A servlet filter that opens and closes a Hibernate Session for each
request.
* <p>
* This filter guarantees a sane state, committing any pending database
* transaction once all other filters (and servlets) have executed. It also
* guarantees that the Hibernate <tt>Session</tt> of the current thread will
* be closed before the response is send to the client.
* <p>
* Use this filter for the <b>session-per-request</b> pattern and if you are
* using <i>Detached Objects</i>.
*
* @see HibernateUtil
* @author Christian Bauer <christian@hibernate.org>
*
* @web.filter
* name="HibernateFilter"
* @web.filter-mapping url-pattern="/*"
*/
public class HibernateFilter implements Filter
{

private static Log log = LogFactory.getLog( HibernateFilter.class );

public void init( FilterConfig filterConfig ) throws ServletException
{
log.info( "Servlet filter init, now opening/closing a Session for each
request." );
}

public void doFilter( ServletRequest request, ServletResponse response,
FilterChain chain ) throws IOException, ServletException
{

// There is actually no explicit "opening" of a Session, the
// first call to HibernateUtil.beginTransaction() in control
// logic (e.g. use case controller/event handler) will get
// a fresh Session.
try
{
chain.doFilter( request, response );

// Commit any pending database transaction.
HibernateUtil.commitTransaction();

}
finally
{

// No matter what happens, close the Session.
HibernateUtil.closeSession();

}
}

public void destroy()
{
}

}

==========================================================================
Mon fichier de config hibernate.cfg.xml :

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="hibernate.connection.username">root</property>
<property
name="hibernate.dialect">org.hibernate.dialect.MySQLInnoDBDialect</property>
<property
name="hibernate.connection.url">jdbc:mysql://localhost/babadmin</property>
<property
name="hibernate.connection.driver_class">org.gjt.mm.mysql.Driver</property>
<property name="show_sql">true</property>

<property name="hibernate.generate_statistics">true</property>
<mapping resource="bab/admin/model/persistent/Worker.hbm.xml" />
<mapping resource="bab/admin/model/persistent/Activity.hbm.xml" />
<mapping resource="bab/admin/model/persistent/City.hbm.xml" />
<mapping resource="bab/admin/model/persistent/Quartier.hbm.xml" />
<mapping resource="bab/admin/model/persistent/ContactPoint.hbm.xml"
/>
<mapping
resource="bab/admin/model/persistent/MailContactPoint.hbm.xml" />
<mapping
resource="bab/admin/model/persistent/PhoneContactPoint.hbm.xml" />
<mapping
resource="bab/admin/model/persistent/EmailContactPoint.hbm.xml" />
<mapping
resource="bab/admin/model/persistent/FaxContactPoint.hbm.xml" />
<mapping
resource="bab/admin/model/persistent/MobileContactPoint.hbm.xml" />
<mapping resource="bab/admin/model/persistent/Employee.hbm.xml" />
<mapping resource="bab/admin/model/persistent/IParty.hbm.xml" />
<mapping resource="bab/admin/model/persistent/Speciality.hbm.xml" />
<mapping resource="bab/admin/model/persistent/Client.hbm.xml" />
<mapping resource="bab/admin/model/persistent/ClientPerson.hbm.xml"
/>
<mapping
resource="bab/admin/model/persistent/ClientOrganization.hbm.xml" />
<mapping resource="bab/admin/model/persistent/Contact.hbm.xml" />
<mapping resource="bab/admin/model/persistent/Evaluation.hbm.xml" />
<mapping
resource="bab/admin/model/persistent/ClientEvaluation.hbm.xml" />
<mapping
resource="bab/admin/model/persistent/WorkerEvaluation.hbm.xml" />
<mapping
resource="bab/admin/model/persistent/OrganizationType.hbm.xml" />
<mapping resource="bab/admin/model/persistent/Job.hbm.xml" />
<mapping resource="bab/admin/model/persistent/JobPeriod.hbm.xml" />
<mapping resource="bab/admin/model/persistent/JobWorker.hbm.xml" />
<mapping resource="bab/admin/model/persistent/PeriodWorker.hbm.xml"
/>
<mapping resource="bab/admin/model/persistent/Tarif.hbm.xml" />
<mapping resource="bab/admin/model/persistent/SalaryRule.hbm.xml" />
<mapping resource="bab/admin/model/persistent/Misc.hbm.xml" />
<mapping resource="bab/admin/model/persistent/PersonData.hbm.xml" />
</session-factory>
</hibernate-configuration>

=====================================================================
Un fichier de mapping typique :

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >

<hibernate-mapping>
<class name="bab.admin.model.persistent.Job" table="t_service"
dynamic-update="false" dynamic-insert="false"
select-before-update="false" optimistic-lock="version">

<id name="id" column="serviceId" type="java.lang.Integer">
<generator class="native">
</generator>
</id>

<many-to-one
name="client"
column="clientPartyId"
lazy="true" />
<many-to-one
name="contactForBAB"
column="employeePartyId"
lazy="true" />

<set
name="contactsForClient"
table="t_clientcontact"
inverse="true"
cascade="all">

<key column="serviceId" />

<many-to-many
class="bab.admin.model.persistent.Contact"
column="partyId" />
</set>

<set
name="periods"
inverse="true"
cascade="all"
order-by="startDate">

<key column="serviceId" />

<one-to-many
class="bab.admin.model.persistent.JobPeriod" />
</set>

<set
name="jobWorkers"
inverse="true"
cascade="all" >

<key column="serviceId" />

<one-to-many
class="bab.admin.model.persistent.JobWorker" />

</set>


<many-to-one
name="tarif"
column="tarifId"
cascade="all"
lazy="true" />

<one-to-one name="evaluation" />

<many-to-one
name="address"
cascade="all"
column="contactPointId" />

<property name="fileId" />

<property
name="title"
column="jobTitle" />

<property
name="description"
column="jobDescription" />

<property
name="begin"
column="contactDate" />

<property
name="close"
column="closeDate" />

<property
name="contract"
column="contractDate" />

<property
name="feedback"
column="babFeedback" />

<property
name="comment" />


<component name="invoice">

<property
name="sent"
column="invoiceDate" />

<property
name="number"
column="invoiceNumber" />

<property name="comment"
column="invoiceComment" />

<property name="otherCharges" />

<property name="otherChargesDescription" />

</component>




</class>

</hibernate-mapping>


=====================================================================
Un DAO typique :

/*
* Créé le 13 avr. 2005
*/
package bab.admin.model.dao;

import java.util.*;

import org.apache.commons.logging.*;
import org.hibernate.*;

import bab.admin.model.*;
import bab.admin.model.exceptions.*;

/**
* Fonctions de bases d'accès aux données de la base par Hibernate
* @author lilian
* TODO ajouter le logging des erreurs et autres
*/
public class DefaultDAO
{
private static DefaultDAO dao = new DefaultDAO();
private static Log log = LogFactory.getLog(HibernateUtil.class);

public static DefaultDAO instance()
{
return dao;
}

protected void log( String message, Throwable ex )
{
log.error( message, ex );
}

public Object getById( Integer id, Class clazz ) throws
InfrastructureException
{

Session session = HibernateUtil.getSession();
Object object = null;
try
{
object = session.get( clazz, id );
}
catch ( HibernateException ex )
{
throw new InfrastructureException( ex );
}
return object;
}

// ********************************************************** //

public Collection select( String hql ) throws InfrastructureException
{
Session session = HibernateUtil.getSession();
try
{
Query q = session.createQuery( hql );
return q.list();
}
catch ( InfrastructureException e )
{
throw new InfrastructureException( e );
}
}

public Collection findAll( Class clazz ) throws InfrastructureException
{
try
{
Session session = HibernateUtil.getSession();
Criteria crit = session.createCriteria( clazz );
return crit.list();
}
catch ( HibernateException ex )
{
throw new InfrastructureException( ex );
}
}

// ********************************************************** //

/**
* @return en général, l'id généré
*/
public Object save( Object object ) throws InfrastructureException
{
try
{
Session session = HibernateUtil.getSession();
Transaction tx = session.beginTransaction();
Object o = session.save( object );

tx.commit();
HibernateUtil.closeSession();
return o;
}
catch ( HibernateException ex )
{
throw new InfrastructureException( ex );
}
}

public void save( Collection objects ) throws InfrastructureException
{
try
{
Session session = HibernateUtil.getSession();
Transaction tx = session.beginTransaction();

//TODO flusher régulièrement les données dans le cas où la
collection serait très grande
for ( Iterator iter = objects.iterator(); iter.hasNext(); )
{
Object object = iter.next();
session.save( object );
}
HibernateUtil.closeSession();
}
catch ( HibernateException ex )
{
throw new InfrastructureException( ex );
}
}

// ********************************************************** //

public void update( Object object ) throws InfrastructureException
{
try
{
Session session = HibernateUtil.getSession();
Transaction tx = session.beginTransaction();
session.saveOrUpdate( object );

tx.commit();
HibernateUtil.closeSession();
}
catch ( HibernateException ex )
{
throw new InfrastructureException( ex );
}
}

// ********************************************************** //

public void delete( Object object ) throws InfrastructureException
{
try
{
Session session = HibernateUtil.getSession();
Transaction tx = session.beginTransaction();
session.delete( object );
tx.commit();

}
catch ( HibernateException ex )
{
throw new InfrastructureException( ex );
}
}
}

3 réponses

Avatar
ludo06
LR wrote:
Salut,

J'ai l'impression qu'il y a quelque spécialistes d'Hibernate ici, je vais
peut-être enfin trouver solution à mon problème...

C'est un message relativement long dont voici un résumé : j'ai plusieurs
fois par jour des OutOfMemoryError : PermGen space dans Tomcat et j'ai
l'impression que le problème vient soit de Tomcat qui utilise toujours plus
de mémoire à chaque redémarrage de contexte, soit de ma configuration de
Hibernate qui ne libère par les données de configuration lorsque je
redémarre mon contexte.

Ci-dessous la description détaillée du problème et encore plus bas, mon
fichier hibernate.cfg.xml, un fichier de mapping, ma classe HibernateUtil,
mon filtre HibernateFilter et un DAO typique.

Merci d'avance à quicquonque pourrait me fournir une piste pour régler ce
problème qui risque de mettre tout mon projet en péril.

==============================================================================
Une piste a utiliser pour tout probleme serieux:

- lancer tomcat en mode debug a l'interieur d'eclipse (ou le deboguer a
distance),
- le stresser (jmeter...)
- regarder les instances qui augmentent le plus (plugin eclipse pour le
profiling: http://eclipsecolorer.sourceforge.net/index_profiler.html)
- rentrer dans dans le code pour voir ou elles sont crees/pas detruites/...
My 2 cents, mais bien applique ca doit permettre de regler a peu pres tout,
--
Cordialement,
---
Ludo
----
http://www.ubik-products.com

Avatar
LR
Une piste a utiliser pour tout probleme serieux:
- lancer tomcat en mode debug a l'interieur d'eclipse (ou le deboguer a
distance),
- le stresser (jmeter...)
- regarder les instances qui augmentent le plus (plugin eclipse pour le
profiling: http://eclipsecolorer.sourceforge.net/index_profiler.html)
- rentrer dans dans le code pour voir ou elles sont crees/pas
detruites/...
My 2 cents, mais bien applique ca doit permettre de regler a peu pres
tout,
--
Cordialement,
---
Ludo


Merci pour ta réponse,

J'ai installé le plugin que tu as cité et je viens de passer un bon moment à
essayer de le faire fonctionner, j'utilise Tomcat 5 et les exemples fournis
sont fais pour tomcat 4. Je n'ai pas de fichier startup.bat pour y ajouter
les lignes décrites alors j'ai essayé de les ajouter en tant que variables
d'environnement. J'ai démarré tomcat dans Eclipse, ouvert la perspective
"Profiler" et cliqué sur "start profiling" mais tout ce que j'ai eu c'est
une NullPointerException.

Cet outil a l'air très complet et il mérite surement que j'y passe plus de
temps mais là je ne peux vraiment pas :-(

J'espère que quelqu'un aura une piste à me suggérer...

A+Lilian

Avatar
jlp
Une piste a utiliser pour tout probleme serieux:
- lancer tomcat en mode debug a l'interieur d'eclipse (ou le deboguer a
distance),
- le stresser (jmeter...)
- regarder les instances qui augmentent le plus (plugin eclipse pour le
profiling: http://eclipsecolorer.sourceforge.net/index_profiler.html)
- rentrer dans dans le code pour voir ou elles sont crees/pas
detruites/...
My 2 cents, mais bien applique ca doit permettre de regler a peu pres
tout,
--
Cordialement,
---
Ludo



Merci pour ta réponse,

J'ai installé le plugin que tu as cité et je viens de passer un bon moment à
essayer de le faire fonctionner, j'utilise Tomcat 5 et les exemples fournis
sont fais pour tomcat 4. Je n'ai pas de fichier startup.bat pour y ajouter
les lignes décrites alors j'ai essayé de les ajouter en tant que variables
d'environnement. J'ai démarré tomcat dans Eclipse, ouvert la perspective
"Profiler" et cliqué sur "start profiling" mais tout ce que j'ai eu c'est
une NullPointerException.

Cet outil a l'air très complet et il mérite surement que j'y passe plus de
temps mais là je ne peux vraiment pas :-(

J'espère que quelqu'un aura une piste à me suggérer...

A+Lilian


Autre profiler faisant partie du projet eclipse : Hyades

http://www.eclipse.org/downloads/index.php
choisir le download :
Eclipse Test and Performance Tools Platform Project