当前位置: 首页 > 面试题库 >

由于多次搜索而导致OutOfMemoryError

潘意
2023-03-14
问题内容

我有一个经典的Java EE系统,具有JSF的Web层,用于BL的EJB 3和用于对DB2数据库进行数据访问的Hibernate
3。我在以下情况下苦苦挣扎:用户将启动一个过程,该过程涉及从数据库中检索大型数据集。检索过程花费一些时间,因此用户不会立即收到响应,变得不耐烦,并打开新的浏览器并再次启动检索,有时是多次。EJB容器显然没有意识到第一次检索不再相关的事实,并且当数据库返回结果集时,Hibernate开始填充一组占用大量内存的POJO,最终导致OutOfMemoryError

我想到的一个潜在解决方案是使用Hibernate Session的cancelQuery方法。但是,该cancelQuery方法仅
数据库返回结果集之前有效。一旦数据库返回结果集,并且Hibernate开始填充POJO,该cancelQuery方法将不再有效。在这种情况下,数据库查询本身会很快返回,并且大部分性能开销似乎都在填充POJO中,这时我们将无法再调用该cancelQuery方法。


问题答案:

实施的解决方案最终看起来像这样:

一般的想法是维护当前正在运行的查询的所有Hibernate会话到发起它们的用户的HttpSession的映射,这样,当用户关闭浏览器时,我们将能够杀死正在运行的查询。

这里要克服两个主要挑战。一种是将HTTP会话ID从Web层传播到EJB层,而不会干扰沿途的所有方法调用-
即不篡改系统中的现有代码。第二个挑战是弄清楚一旦数据库已经开始返回结果并且Hibernate用结果填充对象,如何取消查询。

根据我们的认识,第一个问题得以克服,因为我们意识到沿着堆栈调用的所有方法都由同一线程处理。这很有意义,因为我们的应用程序全部存在于一个容器中,并且没有任何远程调用。在这种情况下,我们创建了一个Servlet过滤器来拦截对应用程序的每次调用,并添加ThreadLocal带有当前HTTP会话ID
的变量。这样,HTTP会话ID将可沿行的每个方法调用使用。

第二个挑战是更具粘性。我们发现,负责运行查询并随后填充POJO的Hibernate方法已被调用doQuery并位于org.hibernate.loader.Loader.java该类中。(我们碰巧使用的是Hibernate
3.5.3,但对于较新版本的Hibernate也是如此。):

private List doQuery(
        final SessionImplementor session,
        final QueryParameters queryParameters,
        final boolean returnProxies) throws SQLException, HibernateException {

    final RowSelection selection = queryParameters.getRowSelection();
    final int maxRows = hasMaxRows( selection ) ?
            selection.getMaxRows().intValue() :
            Integer.MAX_VALUE;

    final int entitySpan = getEntityPersisters().length;

    final ArrayList hydratedObjects = entitySpan == 0 ? null : new ArrayList( entitySpan * 10 );
    final PreparedStatement st = prepareQueryStatement( queryParameters, false, session );
    final ResultSet rs = getResultSet( st, queryParameters.hasAutoDiscoverScalarTypes(), queryParameters.isCallable(), selection, session );

    final EntityKey optionalObjectKey = getOptionalObjectKey( queryParameters, session );
    final LockMode[] lockModesArray = getLockModes( queryParameters.getLockOptions() );
    final boolean createSubselects = isSubselectLoadingEnabled();
    final List subselectResultKeys = createSubselects ? new ArrayList() : null;
    final List results = new ArrayList();

    try {

        handleEmptyCollections( queryParameters.getCollectionKeys(), rs, session );

        EntityKey[] keys = new EntityKey[entitySpan]; //we can reuse it for each row

        if ( log.isTraceEnabled() ) log.trace( "processing result set" );

        int count;
        for ( count = 0; count < maxRows && rs.next(); count++ ) {

            if ( log.isTraceEnabled() ) log.debug("result set row: " + count);

            Object result = getRowFromResultSet( 
                    rs,
                    session,
                    queryParameters,
                    lockModesArray,
                    optionalObjectKey,
                    hydratedObjects,
                    keys,
                    returnProxies 
            );
            results.add( result );

            if ( createSubselects ) {
                subselectResultKeys.add(keys);
                keys = new EntityKey[entitySpan]; //can't reuse in this case
            }

        }

        if ( log.isTraceEnabled() ) {
            log.trace( "done processing result set (" + count + " rows)" );
        }

    }
    finally {
        session.getBatcher().closeQueryStatement( st, rs );
    }

    initializeEntitiesAndCollections( hydratedObjects, rs, session, queryParameters.isReadOnly( session ) );

    if ( createSubselects ) createSubselects( subselectResultKeys, queryParameters, session );

    return results; //getResultList(results);

}

在此方法中,您可以看到,首先以老式的形式从数据库中获取结果java.sql.ResultSet,然后在每个集合上循环运行并从中创建一个对象。initializeEntitiesAndCollections()在循环之后调用的方法中执行一些其他初始化。经过一点调试之后,我们发现大部分性能开销都在方法的这些部分中,而不是在java.sql.ResultSet从数据库中获取的部分中,但是该cancelQuery方法仅在第一部分有效。因此,解决方案是在for循环中添加一个附加条件,以检查线程是否被中断,如下所示:

for ( count = 0; count < maxRows && rs.next() && !currentThread.isInterrupted(); count++ ) {
// ...
}

以及在调用initializeEntitiesAndCollections()方法之前执行相同的检查:

if (!Thread.interrupted()) {

    initializeEntitiesAndCollections(hydratedObjects, rs, session,
                queryParameters.isReadOnly(session));
    if (createSubselects) {

        createSubselects(subselectResultKeys, queryParameters, session);
    }
}

另外,通过Thread.interrupted()在第二个检查中调用,该标志被清除,并且不影响程序的进一步功能。现在,当要取消查询时,取消方法将使用HTTP
session-id作为键访问Hibernate会话和存储在映射中的线程cancelQuery,在会话上调用该interrupt方法,然后调用该线程的方法。



 类似资料:
  • 我将eclipselink 2.6.5中的一些EJB实体用于Rest服务(TomEE服务器中的Jersy)。 在JDK-1.8中,它工作得很好,但是在新的JDK-10.0中,我在搜索持久性时得到了一个NullPointerException。 在我的JPA环境中是否存在错误或不匹配? 谢谢 斯特芬 我的persitence.xml http://xmlns.jcp.org/xml/ns/persi

  • 问题内容: 我有两个加载同一个类的ClassLoader。因此,显然这些不能互相投射。但是我需要访问在其他ClassLoader中创建的对象。 我可以访问两个ClassLoader。如何在其他课程中使用该对象?我不需要强制转换对象以匹配当前的ClassLoader。 但是问题在于返回的对象的类型为。因此,我必须放弃该对象才能访问某些方法。我怎样才能做到这一点?像下面这样的普通类型转换会导致Clas

  • 问题内容: 如何进行mysql查询以立即检查更多表?我的意思是,类似: 你可以这样吗? 我想要做的是向用户显示他从整个网站发布的所有帖子。 问题答案: 在您提供的查询中,它使用完整的外部联接。改用联合选择。

  • 我试图运行一个宏,允许用户在一次搜索中最多搜索15个值。用户有时可能只搜索1个值,但最终用户希望此选项可用。我现在的代码在中搜索一个值&找到后,它会将整行复制到,这运行良好。现在我正在尝试最多15个值。我当前的代码如下:

  • 问题内容: 数据库引擎如何内部处理varchar列? 对于定义为的列,DBMS在磁盘上分配100个连续字节。但是,对于定义为的列,大概不是这种情况,因为的重点是分配的空间不会超过存储该列中存储的实际数据值所需的空间。因此,当用户将一个包含空列的数据库行更新为一个包含80个字符的值时,那80个字符的空间是从哪里分配的呢? 看来,至少在列值最初以空白或NULL插入,然后以实际值更新的情况下,列必须导致

  • 我对正则表达式有很多困惑,我正在努力解决它们。这里有以下字符串: 我的两个不同正则表达式,其中我只更改了点的位置: 为什么第一个正则表达式吃最后一个“e”? 这个否定的前瞻性是如何使这个量词不贪婪的?我的意思是为什么它不能使用{end}之外的字符?