After my introduction to caching post, Doug Hughes asked whether it would be possible to wrap CFCs in soft references to create a memory-sensitive cache for use in CFML. I answered that it should, in theory, be possible, but that an implementation would have to take care of a few common design issues that occur when dealing with soft references.
Having said that, I sat down to write my own implementation to fill the dull hours between CFUNITED sessions…
Here’s softcache.cfc, a soft reference cache that you can drop into your applications. I have not tested this extensively; which is not to say that I haven’t tested it at all! If you do find any bugs, leave your comments here, and I’ll see what I can do about them. I’ve tried to comment the code as thoroughly as possible, but if there’s any particular point that is unclear, do leave a comment and I’ll try to explain it further.
Disclaimer: I spend most of my time writing Java code - if the CFML I write could be optimized further, do let me know.
Keep in mind that the only thing controlling the cache size is the garbage collector. Depending on its implementation, it may decide to be aggressive and free up cache memory even when there’s plenty of free memory in the JVM; or it may be nice and let the cache grow until there really is a memory crisis. Either way, you’re guaranteed that the cache will not run amok and eat all the JVM memory.
… with some exceptions… well, of course, there had to be exceptions!
My implementation calls reap() to clean up dead keys (keys pointing to garbage collected values) in the cache only when a dead key is found via a get() call, or when the cache size is checked via getSize(). If you have a cache where, for some reason, no attempt is ever made to retrieve dead keys from the cache, and the getSize() function is never called, the set of dead keys may increase out of bounds, causing an out of memory condition. This is really a way out there boundary situation, but do keep it in mind, just in case.
The more serious case where the cache could grow out of bounds is when the rate at which data is pushed into the cache exceeds the rate at which the garbage collector can clean it up. In the Java world this is sometimes referred to as object churn, and there’s not much that can be done about it without adding constraints to the cache which may affect performance.
If you need a really safe cache, you’ll have to add cflock tags over whatever scope might be appropriate, wrapping all calls to the cache - this will ensure that when the cache is being reaped, put() calls will be locked out, so that the cache will not be able to grow out of bounds.
Take it for a spin! Here’s the test code I wrote - you may want to push the loop iterations and string length up or down depending on the box you run this on. loadCache.cfm loads the cache with data, with the size of the data growing as the loop rolls out. checkCache.cfm tells you how many items there are in the cache, and the JVM free and total memory. Keep running checkCache.cfm while loadCache.cfm is executing to see how the cache is behaving vis-a-vis JVM free/total memory.
—loadCache.cfm—
<cfset server.cache = createObject("component", "softcache")>
<cfset bigstring = "xxxxlxxxxlxxxxlxxxxlxxxxlxxxxlxxxxlxxxxlxxxxlxxxxlxxxxlxxxxl">
<cfloop from="0" to="20000" index="i" step="1">
<cfset server.cache.put("key#i#", bigstring)>
<cfset bigstring = bigstring &
"xxxxlxxxxlxxxxlxxxxlxxxxlxxxxlxxxxlxxxxlxxxxlxxxxlxxxxlxxxxlxxxxlxxxxlxxxxl">
</cfloop>
<cfoutput>Inserted items, cache size: #server.cache.getSize()#</cfoutput>
—checkCache.cfm—
<cfset size = server.cache.getSize()>
<cfset runtime = createObject("java", "java.lang.Runtime").getRuntime()>
<cfoutput>JVM free memory: #runtime.freeMemory()#</cfoutput><br>
<cfoutput>JVM total memory: #runtime.totalMemory()#</cfoutput><br>
<cfoutput>Cache size: #size#</cfoutput>
If you push the loop iterations and/or the string size high enough, you should find that the cache size never hits the number of iterations in the loop. You should also see that as the loop rolls out, ever-larger entries are put into the cache, the JVM free memory shrinks, and garbage collections become more frequent, leading to a bit of a bungee effect on the JVM free memory - it keeps jumping up as the cache is loaded, and then goes back down as the garbage collector frees the soft references.
In my tests, with a loop size of 20000 strings as above, I ended up with a cache size of 957, which stayed fairly stable once it got there. So there you go, a cache that will resize itself based on available memory - enjoy!

Doug Hughes | 13-Jul-06 at 11:57 am | Permalink
Wow! Man, this is cool! If you don’t mind, I’d almost guarantee that this would end up in the next version of Reactor.
Thanks for the example, I really appreciate it!
ashwin | 13-Jul-06 at 12:02 pm | Permalink
I don’t mind at all - help yourself! Let me know if you need any help integrating it.
Sami Hoda | 17-Jul-06 at 5:12 pm | Permalink
Very nice Ashwin!
ashwin | 18-Jul-06 at 12:36 am | Permalink
Thanks, Sami.
Raymond Camden | 19-Jul-06 at 10:59 am | Permalink
One nitpicky comment. You should add output=”false” to cfcomponent, and all your cffunction tags. It helps cut down on the white space generated.
ashwin | 20-Jul-06 at 5:45 am | Permalink
Point noted, Ray, and updated. Thanks.
charlie arehart | 31-Jan-07 at 9:22 pm | Permalink
Ashwin, do you have any update on this, either from further work or perhaps from evaluating it in light of the new Scorpio monitoring that you’ve now got? Just curious.
ashwin | 01-Feb-07 at 10:41 am | Permalink
I haven’t tried it at all with the server monitor, Charlie. The results should be the same, since the server monitor uses the same JVM APIs to get overall JVM memory consumption data. The CF-specific memory tracking that the server monitor provides will not work with softcache.cfc, since the references to the CF objects are wrapped in Java SoftReference objects. CF memory tracking relies on being able to traverse a graph of CFML object references, estimating the memory consumed by each. Any Java objects are not tracked, since there is no way to determine the actual size of a Java object at runtime without turning on debugging on the JVM (requiring a server restart and a big performance hit), which we wanted to avoid.
Give it a go; if you have any issues, let me know, and I’ll be happy to work through them with you.
Mark Mandel | 15-Feb-07 at 2:07 am | Permalink
Ashwin,
I based my caching layer in Transfer around soft references to help with memory management, but something very strange seems to be happening -
I have 2 soft references pointing to the same CFC, and it seems that one is getting collected, while the other one isn’t.
This doesn’t seem to make any sense, as if one is collected by the garbage collector, so should the other. I’m wondering if there is something to the nature of CFCs that would allow that?
Looking at my debug code as well, it almost seems to be that there is no strong reference between CFCs and any sub-CFCs they hold within them.
Any help there at all?
Mark
Mark Mandel | 15-Feb-07 at 4:07 am | Permalink
Actually Ashwin, I believe that my issue was caused by the fact I had some SoftReferences being created on my ColdFusion ClassLoader, and some being loaded on my URLClassLoader (JavaLoader to be exact), which I believe allowed the JVM to gc one set, but not the other.
Thanks for the great article.
ashwin | 15-Feb-07 at 6:32 am | Permalink
Hmmm… One thing to keep in mind, perhaps, is that your URLClassLoader is probably a child classloader to the classloader for the template on which it is used. When the template changes, the template’s classloader is thrown away (this is how CF handles hot deployment of templates, but that’s a subject for a blog entry, coming soon!), which may be another reason for the CFC SoftReference loaded by the URLClassLoader being thrown away, but not the one loaded by the CF classloader.
Mark Mandel | 16-Feb-07 at 1:08 am | Permalink
Ashwin -
Since the SoftReference is created inside a CFC, which is a singleton, I would then assume that it is for the classLoader that loads and handles that CFC (because a CFC is just another template, is it not?)
As long as the CFC is resident in a shared scope, the ClassLoader for it won’t be thrown away, I would have thought.
My issue was more actually that the SoftReference at the CF layer were being cleared, but the ones at the URLCLassLoader layer weren’t.
To get around this issue I’m simply creating one softReference for each object I want to let the JVM clear() as required, and passing that around my system. That was it doesn’t actually matter who/what clears() it - once it is cleared, nothing has a reference to it.
Ahhhh complex caching fun :oD
hungry | 21-Mar-07 at 9:35 pm | Permalink
the link to softcache.cfc seems to be down…is it still possible to get it?
thanks,
jerad
Ashwin | 22-Mar-07 at 5:44 am | Permalink
Jerad - thanks for pointing that out. Yet another victim of an abortive software upgrade attempt a couple of weeks ago! The link is fixed now.
Hendrik | 27-Mar-07 at 9:35 am | Permalink
By using the softcache.cfc, i’ve noticed the same behaviour of returning corrupted copies of large structures as with the duplicate() function before ColdFusion 7.02 with is fixed with the Cumulative Hot Fix 1 ( see http://www.adobe.com/cfusion/knowledgebase/index.cfm?id=kb400074 ID #63910 ). Any clues? Or do i have to wddx the structure first to ensure that it’s correctly returned?
Ashwin | 27-Mar-07 at 9:58 am | Permalink
Hendrik - softcache.cfc does not touch the data put into it at all, so you should be getting it back unchanged. I’m sorry, but I’m really not sure what the root of your particular problem might be.