Consistently, I am able to generate a Java Heap Space OutOfMemory exception with the following code on ColdFusion 9.01 (haven't tried earlier versions):
<cfset uuidGenerator = createObject("java", "java.util.UUID")>
<cfset transient = structNew()>
<cfloop from="1" to="100000" index="index">
<cfset transient[uuidGenerator.randomUUID().toString()] = true>
</cfloop>
The code above uses the Java UUID class because its faster than ColdFusion's. The structure itself does not exist after the request (i.e. it's not in some persistent scope such as application
).
As a test, I generate a heap dump just after initializing the server. Then I run this code several times and see the tenured generation fill through jConsole.开发者_StackOverflow社区 After, I run another heap dump. Using Eclipse Memory Analysis Tool's Leak report I can see one large object rooted on coldfusion.util.Key
.
I'm asking here in hopes others have hit similar problem, and if so, what they've done to work around it.
Not an ideal solution, but until Adobe fix the memory leak internally you can get access to the private member ConcurrentHasMap on the coldfusion.util.Key object and manually clear it.
We have setup a scheduled task to execute this nightly and then do a GC immediately afterwards.
Compile this into a JAR file and put it somewhere in your ColdFusion class path.
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!");
}
}
}
And then you can simply instantiate the new KeyEx object in coldfusion and call resetCache passing in the coldfusion.util.Key singleton.
<cfset keys = createObject("java", "coldfusion.util.Key") />
<cfset x = createObject("java", "KeyEx").init() />
<cfset x.resetCache(keys) />
These two example tests probably illustrate the problem a bit clearer:
-- 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>
Hit MemoryLeak.cfm on a CF 9.01+ box for 100 times and the memory leak is really obvious. Restart JVM and hit NoMemoryLeak.cfm as many times as you want, and the OldGen won't even notice it. I got up to 500,000 times before giving up.
I can't see OrangePips original bug# in the CF bug-base (looks like all old bugs went in the upgrade?), so I created a new one https://bugbase.adobe.com/index.cfm?event=bug&id=3119991 status currently confirmed & ToFix.
OK, so this is what I ended up doing to track down the root cause. Heap dumps were showing me that coldfusion.util.Key.keys
was holding on to many objects in a ConcurrentHashMap
of type coldfusion.util.Key
after I ran a load test for awhile. So I figured out which .jar contained the .class - /lib/cfusion.jar - and decompiled it to see the source. What I saw is that it holds onto these references in a static private field whose keys are never removed.
Basically this object gets created every time a key never used before is inserted into a structure. So it's called from all over any ColdFusion application. I believe the intent is to facilitate speedups around lookups, case insensitivity, and comparison.
Accepting then that I cannot change code to fix the issue, as I'm sure it would violate a license agreement when used in a production environment, I did change code to help me understand why so many objects were being created in the first place. I basically added the following to the constructor:
try {
throw new RuntimeException();
} catch (Exception e) {
e.printStackTrace(System.out);
}
Then I compiled and put the changed class into cfusion.jar.
Fortunately, the stack trace generated includes .cfm file names and line numbers. I started CF from a command line (i.e. jrun.exe -start coldfusion) so I could see what was going to System.out easily and then kicked off my load test again. So the heuristic was finding what code was calling this a lot and potentially changing that.
This allowed me to quickly see a .cfm file that was being called with every request. So I can change that file to no longer create structure keys all the time and this has resulted in a much longer running VM. It doesn't fix the problem completely, but lowers memory consumption dramatically.
精彩评论