始终如一,我能够在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 Memory Analysis Tool的Leak报告,我可以看到一个以coldfusion.util.Key
为根的大对象。
我在这里问,希望其他人遇到类似的问题,如果是这样的话,他们已经做了什么来解决它。
答案 0 :(得分:3)
不是理想的解决方案,但在Adobe内部修复内存泄漏之前,您可以访问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对象,并调用resetCache传递coldfusion.util.Key单例。
<cfset keys = createObject("java", "coldfusion.util.Key") />
<cfset x = createObject("java", "KeyEx").init() />
<cfset x.resetCache(keys) />
答案 1 :(得分: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 bug-base中看到OrangePips的原始bug#(看起来所有的旧bug都在升级?),所以我创建了一个新的https://bugbase.adobe.com/index.cfm?event=bug&id=3119991状态,目前已确认&amp;修理。
答案 2 :(得分:0)
好的,所以这就是我最终要追查的根本原因。在我运行负载测试一段时间之后,堆转储向我显示coldfusion.util.Key.keys
持有ConcurrentHashMap
类型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。它不能完全解决问题,但会大大降低内存消耗。