8

在我们的应用程序中,我们有几个(实际上很多,大约 30 个)Web 服务。每个 Web 服务都驻留在自己的 WAR 文件中,并有自己的 Spring 上下文,该上下文在应用程序启动时被初始化。

我们还有许多注释驱动的方面类,我们将它们应用于 Web 服务类。一开始,切入点表达式看起来像这样:

@Pointcut("execution(public * my.package.service.business.*BusinessServiceImpl.*(..))")
  public void methodsToBeLogged() {
  }

并且通过配置条目在服务上启用了 AOP。

但是当网络服务的数量增加时,我们开始OutOfMemoryException在我们的服务器上体验 s。在进行了一些分析和分析之后,似乎内存被 AspectJExpressionPointcut 类的实例保存的缓存占用。

每个实例的缓存大约为 5 MB。由于我们有 3 个方面和 30 个服务,因此总共有 90 个实例持有 450MB 的数据。

在检查缓存的内容后,我们意识到它包含 WAR 中存在的所有类的 Java 反射方法实例,即使是那些不属于 my.package.service.business 包的类。将切入点表达式修改为具有附加within子句后:

@Pointcut("execution(public * my.package.service.business.*BusinessServiceImpl.*(..)) && 
within(my.package.service.business..*)")
  public void methodsToBeLogged() {
  }

内存使用再次下降到正常水平。并且所有 AspectJExpressionPointcut 实例总共占用不到 1MB。

有人可以解释这是为什么吗?为什么第一个切入点表达式还不够?AspectJExpressionPointcut为什么不共享缓存?

4

1 回答 1

12

AspectJExpressionPointcut 使用缓存(shadowMatchCache),它可以根据切入点表达式加速决定是否应将 AOP 应用于某个方法调用。此缓存可能会消耗大量内存。

此外,在提供特定 bean 的所有方法以查看是否存在切入点表达式匹配之前,Spring 首先通过调用 AspectJExpressionPointcut.matches(Class targetClass)检查 bean 类是否可能匹配。此方法委托给 AspectJ 的 PointcutExpressionImpl.couldPossiblyMatch() 方法。这将快速检查一个类是否可以“可能”匹配切入点表达式或永远不会“明确”匹配。根据使用切入点的 AspectJ 开发人员的说法,会导致更明确的否定。他们还建议不要使用独立的切入点(执行、调用、获取、设置),而是将它们与 within 结合使用

但是 shadowMatchCache 不能共享,因为它包含每个切入点表达式匹配或不匹配的结果。

但至少你可以限制缓存的内容。我还认为,一旦 applicationContext 启动,Spring 可能会通过不保留整个缓存来改进这一点。当一个新的 bean 在已经启动后动态添加到 applicationContext 时,他们可能会丢弃所有不匹配的内容,但代价是重做一些匹配。

AspectJExpressionPointcut 类中另一个可能占用内存的地方是 pointCutParser。此解析器可能在 applicationContext 中的所有 AspectJExpressionPointcuts 之间共享。在 JIRA 票SPR-7678上掠夺。

于 2010-11-10T12:31:32.527 回答