探查实体 Bean 和有状态会话 Bean 的 CacheFullException

问题描述
尽管对应用程序的分析显示同时处于活动状态的 EJB 数量少于 <max-beans-in-cache>,应用程序仍在 weblogic.ejb20.cache.EntityCache$Node.access$000 发生 CacheFullException 或 java.lang.NullPointerException。

故障排除
请注意,并非下面所有任务都需要完成。有些问题仅通过执行几项任务就可以解决。

快速链接:

为什么发生此问题?
不能再向缓存中插入 Bean 时将会抛出 CacheFullException。这是因为当前正在参与事务的 Bean 占用了所有缓存,或是其它原因导致无法从缓存中清除实例。如果发现缓存中的任一 Bean 未参与事务,将使用当前 Bean 替换该 Bean。缓存中 Bean 的索引是 (primary key, transaction)

CacheFullException - 实体 Bean
以下几个不同的原因可导致实体 BeanCacheFullException
请注意,对于 WLS 6.1,升级到 SP4 或更高版本的 Service Pack 后,该设置就可能成为问题,因为以往的数据库并发策略并没有虑及 <max-beans-in-cache> 设置。有关信息,请参阅 http://e-docs.bea.com/wls/docs61/notes/bugfixes2.html#1324349 (English)。
  • 主键类的实现不正确。对于每个可能的主键值,相关方法 equals()hashcode() 必须返回唯一值。如果不是这样实现,则缓存中可能就会存在未重复使用的实例,尽管这些实例不再参与事务,但并未从缓存中清除这些实例。这意味着缓存中可能包含不参与事务的实体 Bean 的实例,但由于加载到缓存中的索引因主键 (Primary Key, PK) 类未得到正确实现而未发挥作用,因此无法清除这些实例。
备注:好的做法是确保代码中正确实现了所有 PK 类。请参阅以下示例代码,在该代码中,一个小的编码错误引发了故障:

public boolean equals(Object o){
            if (o instanceof AbcPK) {
                AbcPK otherKey = (AbcPK)o;
                return (companyId.equals(otherKey.string1)&& string1.equals(otherKey.companyId));
            } else
                return false;
        }

代码比较了错误的字段,它所比较的是:
companyId.equals(otherKey.string1)
正确的应该是:
companyId.equals(otherKey.companyId)

string1.equals(otherKey.companyId)
正确的应该是:
string1.equals(otherKey.string1)

请参阅以下示例代码,该代码中的错误检测起来更为复杂:

public boolean equals(Object o){
            if (o instanceof OrderEventAAPK) {
                OrderEventAAPK otherKey = (OrderEventAAPK)o;
                if (this.orderNo.equals(otherKey.orderNo) &&
                    this.dateKeyFrom.equals(otherKey.dateKeyFrom) &&
                    this.dateKeyTo.equals(otherKey.dateKeyTo) &&
                    this.pageLength.equals(otherKey.pageLength) &&
                    this.pageStart.equals(otherKey.pageStart)) {

                    if ( (this.orderID != null && this.orderID.equals(otherKey.getOrderID()))
                        && (this.AcctNo != null && this.AcctNo.equals(otherKey.getAcctNo())) ) {
                        return true;
                    }
                    else {
                        return false;
                    }
                }
                else {
                    return false;
                }
            }
            else{
                return false;
            }
        }
)

问题出在 PK 类的 equals 方法上,该方法要求 AcctNo 和 OrderID 都为 null 或者都不为 null。
这一假设是不正确的,因为现实生活中,有可能其中一个值是 null。
该错误会导致带有 PK 的实体(其中只有一个字段的值为 null)永远留存在缓存中。

需要增加缓存的大小,以便 finder 在一个事务中运行时,缓存足以同时容纳所有这些 EJB。也可以将 <finders-load-bean> 设置为 false,这样只会在需要时实例化 EJB。
  • 如果使用的是 WebLogic Server 6.1,请确保设置了属性 <initial-beans-in-free-pool>,否则可能无法重复使用缓存中的 Bean。相关文档(例如,WLS 6.1 的文档或其它版本的文档)中提供了有关此属性的信息:http://e-docs.bea.com/wls/docs61/ejb/reference.html#1070831 (English)。
  • 如果使用的是 WLS 7.0 SP5 或 8.1 SP3 或更高版本,可以使用属性 <idle-timeout-seconds> 来指定在缓存中保留实体 Bean 的最长时间,该时间过后,将清除实体 Bean。这有助于释放缓存在高峰时间所使用的内存。相关文档(例如,WLS 8.1 的文档或其它版本的文档)中提供了有关此属性的信息:http://e-docs.bea.com/wls/docs81/ejb/DDreference-ejb-jar.html#1114380 (English)
  • 对于使用容器管理的关系的 EJB,可以启用 relationship-caching 来改善性能。请检查 weblogic-cmp-rdbms-jar.xml 部署描述符中的相关 <relationship-caching> 标志,以确定是否使用了该功能。设置 <max-beans-in-cache> 时需要考虑关系结果集的可能大小。相对而言,该数量可能比使用的父级 Bean 的数量大。
相关文档(例如,WLS 8.1 的文档或其它版本的文档)中提供了有关此属性的信息:http://e-docs.bea.com/wls/docs81/ejb/DDreference-cmp-jar.html#1214730 (English)

返回页首

CacheFullException - 有状态会话 Bean
以下原因可导致有状态会话 Bean (Stateful Session Bean, SFSB)CacheFullException
  • 如果 Bean 数量达到 <max-beans-in-cache> 且缓存中有未使用的 EJB 实例,WebLogic Server 会钝化其中的一些 Bean。即使未使用的 Bean 的数量没有达到 <idle-timeout-seconds> 极限,也会发生这种情况。如果 Bean 数量达到了 <max-beans-in-cache> 且缓存中的所有 EJB 都被客户端使用,WebLogic Server 将抛出 CacheFullException。请检查是否为这些 Bean 调用了 remove()。这是强制性的调用,否则会话 Bean 将始终留存在缓存中,直至 <idle-timeout-seconds> 秒后超时为止。如果未调用 remove(),则会先出现钝化 Bean 数量增加的情况,然后才会发生 CacheFullException
可通过若干种方法监视有状态会话 Bean 的钝化计数,比较典型的是下文中述及的通过控制台进行监视的方法:
http://e-docs.bea.com/wls/docs81/ConsoleHelp/domain_ejbcomponent_monitor_monitor_ejbstateful.html#1109762 (English)

返回页首


背景信息和相关阅读材料
如果容器不能将当前请求的 Bean 从 POOLED 状态变为 READY ACTIVE 状态,将抛出实体 Bean 的 CacheFullException。要将 EJB 变为 READY 状态,需要将它添加到缓存中。如果抛出 CacheFullException,则意味着缓存中的所有 Bean 当前都在参与事务,或出于其它原因无法从缓存中清除,这通常是因为主键类的实现不正确。由于加载到缓存的索引是 (transaction, primary key),缓存会使用主键类中的 equals()hashcode() 方法来辨认缓存中的实例。

实体 Bean 状态
容器以 3 种不同的状态存储实体 Bean 对象。

POOLED Bean
POOLED Bean 是匿名实例。
这些实例用于 finder 方法和 home 方法。business 方法也可以使用这些实例,但会先调用这些实例上的 ejbActivate。池大小由 <max-beans-in-free-pool> 决定,在部署过程中分配给池的 Bean 的数量由 <inital-beans-in-free-pool> 决定。

READY Bean
READY Bean 有标识(关联主键)。
READY Bean 当前不在事务中登记。最多可有 <max-beans-in-cache> 个 READY Bean。每个主键可有一个以上 READY Bean。如果从 READY 缓存中清除某个 Bean,则会先调用该 Bean 的 ejbPassivate,然后将其返回到 POOL 中。

ACTIVE Bean
ACTIVE Bean 有标识,且当前在事务中登记。
缓存中的 Active Bean 按事务和主键建立索引。对于独占并发策略,每个主键只有一个实例。对于其它并发策略,即使这些 Bean 的主键相同,每个事务也将使用自己的实例。

各实体 Bean 状态之间的关系
READY 实体 Bean 和 ACTIVE 实体 Bean 存储在缓存中。POOLED Bean 存储在空闲 POOL 中。管理控制台“monitoring”选项卡中的 Current Beans in Cache 字段显示 READY Bean 和 ACTIVE Bean 的计数。缓存中允许存储的实例数量由 <max-beans-in-cache> 属性和为该 Bean 定义的并发策略共同决定。有关详细信息,请查阅以下文档:
http://e-docs.bea.com/wls/docs81/ejb/entity.html#1187666 (English)。

返回页首


缓存的工作方式
缓存的工作方式大体如下:
  1. 如果创建了一个 Bean(将其从 POOLED 状态变为 ACTIVE 状态),而缓存中有可用空间,就将该 Bean 添加到缓存中。
  2. 如果缓存中没有可用空间(缓存中已包含 <max-beans-in-cache> 个实例),则会检查是否可以清除缓存中的某个实例。如果可以清除某个实例,则清除该实例,并将当前 Bean 存储到缓存中,替代该实例。
  3. 如果没有可用空间,也无法清除缓存中的任何实例,则抛出 CacheFullException
返回页首

解决与缓存有关的 OutOfMemoryError 的技巧

在设置 <max-beans-in-cache> 大小时需要考虑的一个非常重要的问题是,定义的所有 EJB 缓存的堆大小必须足以容纳 <max-beans-in-cache> 个 Bean 实例。新实例将存储在缓存中,而不会清除或钝化不再需要的实例,直至实例数达到 <max-beans-in-cache> 为止。如果堆大小不够大,即使缓存中可能存在不再需要的实例,也会造成 OutOfMemoryError。这是因为缓存变满(缓存中存储的实例数达到 <max-beans-in-cache>)时才会钝化 SFSB 或清除其中的实体 Bean。

OutOfMemoryError 的一个可行的解决办法是减少 <max-beans-in-cache>。这样,由于堆已满,缓存会在 JVM 抛出 OutOfMemoryError 前先清除或钝化 Bean。要了解如何设置 EJB 缓存大小,先从脚本 ejbcache_stat.wlsh(用于 wlshell)或 ejbCacheRuntime.py(用于 WLST/Jython)着手为好。该脚本会检查 WebLogic Server 中的可用内存,还会输出从定义的阈值 (FREEHEAPTRIGGER) 开始的所有 EJB 缓存的相关信息。由于这些信息与所用应用程序高度相关,因此在测试系统中对应用程序运行该脚本时,测试系统的负载应尽可能与生产系统的实际负载相近。

在确定不同 EJB 缓存的最佳值时,请考虑这一因素,即 WebLogic Server 是一个多线程的环境。在用户很少的环境中运行良好的 EJB 缓存配置,在更多用户参与时就可能会发生 OutOfMemoryErrors。这一问题的幕后原因是,从不同用户处调用 finder 方法时,该方法会根据所定义的并发策略,将同一主键的一个以上 EJB 实例加载到缓存中。关键是堆大小足以容纳所有这些对象。

此外,部署新应用程序模块(具有使用不同缓存的新 EJB 实现)也可导致 OutOfMemoryError,因为堆容量不足以同时容纳 EJB 缓存中的所有对象。此问题的一个可行的解决办法是,使用应用程序级别的缓存,这类缓存可以容纳不同类型的 EJB 实例。请参阅:http://e-docs.bea.com/wls/docs81/perform/EJBTuning.html#1136455http://e-docs.bea.com/wls/docs81/ejb/entity.html#Application-LevelversusBean-LevelCaching (English)。

返回页首

有关缓存的其它参考资料
有关 EJB 生命周期和缓存的信息,请参阅以下 EJB 文档:http://e-docs.bea.com/wls/docs81/ejb/entity.html#1183577 (English)

可以在 weblogic-ejb-jar.xml 中通过 <cache-type> 属性设置缓存类型。请参阅:http://e-docs.bea.com/wls/docs81/ejb/DDreference-ejb-jar.html#1113204 (English)。有效值为 NRU(最近未使用)和 LRU(最近最少使用)。有关 NRU 缓存和 LRU 缓存间差异的说明,请参阅:http://e-docs.bea.com/wls/docs81/faq/ejb.html#257399 (English)。

外部资源
如果遇到 OutofMemoryError,可以使用以下实用程序来帮助您研究如何设置 EJB 缓存大小。有关更多详细信息,请参阅解决与缓存有关的 OutofMemoryError 的技巧

返回页首



已知问题
您可以定期查看所用 WLS 版本的“Release Notes”,了解 Service Pack 中的“Known Issues”或“Resolved Issues”的详细信息及浏览与 Bean 或缓存有关的问题。方便起见,下面提供了这些发行说明的链接:
使用搜索功能也可以搜索到“Release Notes”,还可以搜索到其它支持解决办法及与 CR 有关的信息,如需要更多帮助?中所提到的内容。如果客户签订了技术支持合同,则可以登录 http://support.bea.com/,登录后会看到为 Solutions 和 Bug Central 提供的 Browse portlet,可在其中按产品版本浏览最新提供的 CR。

需要更多帮助?
如果您已经理解这个模式,但仍需要更多帮助,您可以:
  1. http://support.bea.com/ 上查询 AskBEA(例如,使用“cachefullexception”),以查找其它已发布的解决办法。技术支持合同客户:确保已经登录,可以访问提供的与 CR 有关的信息。
  2. http://newsgroups.bea.com/ 上,向 BEA 的某个新闻组提出更详细具体的问题。
如果这还不能解决您的问题,并且您拥有有效的技术支持合同,您可以通过登录以下网站来打开支持案例:http://support.bea.com/

反馈

请给我们提供您的意见,说明此支持诊断模式“探查 CacheFullExceptions”一文是否有所帮助、您需要的任何解释,以及对支持诊断模式的新主题的任何要求。


免责声明:

依据 BEA 与您签署的维护和支持协议条款,BEA Systems, Inc. 在本网站上提供技术技巧和补丁供您使用。虽然您可以将这些信息和代码与您获得 BEA 授权的软件一起使用,但 BEA 并不对所提供的技术技巧和补丁做任何形式的担保,无论是明确的还是隐含的。

本文档中引用的任何商标是其各自所有者的财产。有关完整的商标信息,请参考您的产品手册。