【问题标题】:coldfusion.util.Key Memory Leak - Issue with Structure Keys?Coldfusion.util.Key 内存泄漏 - 结构键问题?
【发布时间】:2011-05-05 13:15:25
【问题描述】:

始终如一,我能够在 ColdFusion 9.01 上使用以下代码生成 Java Heap Space OutOfMemory 异常(未尝试过早期版本):

<cfset uuidGenerator = createObject("java", "java.util.UUID")>
<cfset transient = structNew()>
<cfloop from="1" to="100000" index="index">
    <cfset transient[uuidGenerator.randomUUID().toString()] = true>
</cfloop>

上面的代码使用 Java UUID 类,因为它比 ColdFusion 的更快。请求后结构本身不存在(即它不在某些持久范围内,例如application)。

作为测试,我在初始化服务器后生成了一个堆转储。然后我多次运行此代码,并通过 jConsole 看到终身代填充。之后,我运行另一个堆转储。使用 Eclipse 内存分析工具的泄漏报告,我可以看到一个基于 coldfusion.util.Key 的大对象。

我在这里询问是希望其他人遇到类似的问题,如果是这样,他们为解决这个问题做了什么。

【问题讨论】:

  • 不是答案,但我不确定 java.util.UUID 现在是否快得多。在 CF8 上是这样,但在 9+ 中,本地 UUID 创建速度快了 1000 倍。
  • 您能否使用本机 UUID 重现内存泄漏,还是仅使用 Java UUID?
  • 我还想问一下——您是否认真使用了一个包含 100K 键的结构?
  • @CF Jedi Master:不,我没有使用包含 100K 键的结构。这个测试用例改进了很多试验,检查堆转储时将应用程序置于负载下并注意到这个 Coldfusion.util.Key 作为根。
  • 大约 2 个月前我已经对此进行了一些测试,我可以发誓差异可以忽略不计 - 但这是惊人的。仍然 - 我想不出很多次我需要那么多该死的 UUID。 @orangepips - 在这一点上,我唯一能建议的就是提交一个错误。也可以尝试使用 java 哈希映射而不是结构来查看问题是否消失。如果您对此感到痛苦,那么哈希映射可能是一种临时解决方法,直到 Adob​​e 可以修补它。

标签: memory-leaks coldfusion


【解决方案1】:

不是一个理想的解决方案,但在 Adob​​e 内部修复内存泄漏之前,您可以访问 Coldfusion.util.Key 对象上的私有成员 ConcurrentHasMap 并手动清除它。

我们设置了一个计划任务,每晚执行一次,然后立即执行 GC。

将其编译成 JAR 文件并将其放在您的 ColdFusion 类路径中的某个位置。

import coldfusion.util.Key;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

class KeyEx
{
    public KeyEx()
    {
    }

    public void resetCache(Object k)
    {
        try
        {
            Field f = Key.class.getDeclaredField("keys");
            f.setAccessible(true);
            ConcurrentHashMap chm = (ConcurrentHashMap)f.get(k);
            chm.clear();
        }
        catch (Exception ex)
        {
            System.out.println("ZOMG something went epically wrong!");
        }
    }
}

然后您可以简单地在 Coldfusion 中实例化新的 KeyEx 对象,并在 Coldfusion.util.Key 单例中调用 resetCache。

<cfset keys = createObject("java", "coldfusion.util.Key") />
<cfset x = createObject("java", "KeyEx").init() />
<cfset x.resetCache(keys) />

【讨论】:

  • 如果您需要任何异常详细信息,只需删除 TRY/CATCH 块并将方法声明更改为:'public void resetCache(Object k) throws NoSuchFieldException, IllegalAccessException'
  • +1 了解如何通过使用反射 API 来解决这个问题。
【解决方案2】:

这两个示例测试可能更清楚地说明了问题:

-- MemoryLeak.cfm

<cfset transient = structNew() />
<cfset base = getTickCount() />
<cfloop from="1" to="10000" index="index">
  <cfset transient[hash("#base##index#")] = true >
</cfloop>
<cfoutput>
Done
</cfoutput>

-- NoMemoryLeak.cfm

 <cfset transient = structNew() />
 <cfloop from="1" to="10000" index="index">
 <cfset transient[hash("#index#")] = true >
 </cfloop>
 <cfoutput>
 Done
 </cfoutput>

在 CF 9.01+ 机器上点击 MemoryLeak.cfm 100 次,内存泄漏非常明显。重新启动 JVM 并根据需要多次点击 NoMemoryLeak.cfm,OldGen 甚至都不会注意到它。在放弃之前,我达到了 500,000 次。

我在 CF 错误库中看不到 OrangePips 原始错误#(看起来所有旧错误都在升级中?),所以我创建了一个新的 https://bugbase.adobe.com/index.cfm?event=bug&id=3119991 状态当前已确认和 ToFix。

【讨论】:

  • 反之亦然 - 仅当密钥大小 ,就没有问题了。
【解决方案3】:

好的,这就是我最终为追查根本原因所做的事情。在我运行了一段时间的负载测试后,堆转储显示coldfusion.util.Key.keysConcurrentHashMap 类型为coldfusion.util.Key 中持有许多 对象。所以我找出了哪个 .jar 包含 .class - /lib/cfusion.jar - 并反编译它以查看源代码。我看到的是,它在一个静态私有字段中保留这些引用,其键从不被删除。

基本上,每次将以前从未使用过的键插入结构时,都会创建此对象。所以它是从任何 ColdFusion 应用程序中调用的。我相信其目的是促进查找、不区分大小写和比较的加速。

接受我无法更改代码来解决问题,因为我确信在生产环境中使用它会违反许可协议,我确实更改了代码以帮助我理解为什么在第一名。我基本上在构造函数中添加了以下内容:

try {
  throw new RuntimeException();
} catch (Exception e) {
  e.printStackTrace(System.out);
}

然后我编译并将更改后的类放入 cfusion.jar。

幸运的是,生成的堆栈跟踪包括 .cfm 文件名和行号。我从命令行启动了 CF(即 jrun.exe -start Coldfusion),所以我可以很容易地看到 System.out 发生了什么,然后再次开始我的负载测试。因此,启发式方法是找出哪些代码大量调用并可能改变它。

这让我可以快速查看每个请求都调用的 .cfm 文件。因此,我可以更改该文件以不再始终创建结构键,这导致运行时间更长的 VM。它并不能完全解决问题,但会显着降低内存消耗。

【讨论】:

  • 用 java.util.Hashtable 代替 CF 结构怎么样?您仍然可以访问它,只需确保以正确的大小写传递字符串。
  • @Henry:问题是我不知道什么结构,并且有很多分散在应用程序中,以如此无限的方式增长。因此堆栈跟踪。你是正确的,我也可以将它更改为使用 Map 而不是减少生成的条目数。
猜你喜欢
  • 2016-03-28
  • 1970-01-01
  • 2016-07-28
相关资源
最近更新 更多