Memory Leaks: Part III - sessions and cfcs

    In part III of my memory leak posts, I will attempt to show an example of how variable references can increase within in the heap on every request. If you would like to know more about the tools I used to identify the problem, or would like to follow along with the example you should take a look at my previous posts,
"ColdFusion Memory Leaks: Part I - profiler introduction"
"ColdFusion Memory Leaks: Part II - variables scope"

    Note:  The example that I will demonstrate is 90% complete.  It is similar to the behavior that I experienced in my application, but not as severe.  This only works in specific circumstances, where my problem appeared 100% of the time.  Unfortunately, this is the closest I could come without including hundreds of thousands of lines of code.


   
For this example, we will expand the case that I demonstrated in the variables scope example.  We will use the same test.cfc as the previous example.

<!--- file: test.cfc --->
<cfcomponent name="test">
<cffunction name="init">
<cfreturn this >
</cffunction>

<cffunction name="doSomething">
<cfset var idx = "" />
<cfset var arrayToReturn = arrayNew(1) />
<cfloop from="1" to="100" index="idx">
<cfset arrayAppend(arrayToReturn,createObject("component","test").init()) />
</cfloop>
<cfreturn arrayToReturn >
</cffunction>
</cfcomponent>

    Next we will need to expand the cfm page, we'll make the following modifications.
  • add session timeout of 10 seconds (to see results quickly)
  • only load test.cfc into application on the first request
  • create an array on the session
  • create a new instance of test.cfc locally, and append it to the array on the session

<!--- file: test.cfm --->
<cfapplication name="memoryTest" sessionmanagement="true" sessiontimeout="#createTimeSpan(0,0,0,10)#" >

<!--- create an instance of test.cfc in application, adding reset to the URL, will cleanup all references --->
<cfif not structKeyExists(application,"test") OR isDefined("URL.reset")>
   <cfset application.test = createObject("component","test").init() />
   <cflocation url="#cgi.SCRIPT_NAME#" addtoken="false">
</cfif>

<cfset foo = application.test.doSomething() />

<cfif not structKeyExists(session,"sessionArray")>
   <cfset session.sessionArray = arrayNew(1)>
   creating a new session<br />
</cfif>

<cfset objectToPutInSession = createObject("component","test") >

<cfset arrayAppend(session.sessionArray,objectToPutInSession)>

completed request - session size = <cfoutput>#arrayLen(session.sessionArray)#</cfoutput>
<br /><br />
<cfoutput><a href="#cgi.SCRIPT_NAME#?reset=true">reset application.test (this clears all memory references)</a></cfoutput>
What should we expect in this example? (feel free to correct me if you think this should be different)
  • 1 instance of test.cfc should be loaded into application
  • foo exists in variables scope and creates a reference to an array of 100 instances of test.cfc (this is the example from Part II and should be destroyed at the end of the request)
  • an instance of test.cfc will be added to the session on every request. (5 request in a session = 5 test.cfc)
  • when the session expires, the array should be cleaned from the heap.
Now for a flash movie that will show the results of what actually happens (my apologies if this is rushed, there's quite a bit going on in a short timeframe)


What happened??(also, how you can follow along if you execute the example)

  • On the first request, test.cfc is loaded into the application at the same time a new session is created
  • Every request loads an extra 100 references to test.cfc (which should only exist in variables scope)
  • The session that loads the cfc into application scope (and only this session) will persist all the references for the life of the application
  • All additional sessions will hold their references for the life of the session
  • Adding a structure to session instead of a cfc will create the proper behavior
Summary

    Why does this happen?  I can't tell you, I'm hoping the community, or adobe will be able to figure that out.  Some other notes that I would like to add.... The behavior occurs if it is a struct OR an array on the session.  The behavior also occurs if all the cfcs are different (I used test.cfc for all references to keep the example simple).  If you reload application.test, the references will be cleaned up, but the behavior will occur for the session associated with the request that created application.test.

How do I fix this in my application???
    I am not going to recommend a solution for this because I haven't found a "magic bullet" yet.  I am also not going to recommend a solution because every application is unique, and every memory leak is different.  I will however tell you that my problem was very similar to this.  We were keeping a history stack on the session that consisted of an array of simple beans.  Converting the beans to structures, solved a large part of the problem.  This isn't always an option if you are storing complex objects on the session.  I'm very interested to see if anyone else having a similar problem is storing cfcs on a session, as the root cause seems to be a wacky combination of instantiating cfcs on multiple scopes within a request.



Memory Leaks: Part II - variables scope leak

    In part II of my memory leak posts, I will attempt to demonstrate a simple example of how the CFML runtime engine can cause memory leaks.  If you would like to know more about the tools I used to identify the problem, or would like to follow along with the example you should take a look at my previous post,  "ColdFusion Memory Leaks: Part I - profiler introduction"

    For those of you that have ever experienced memory leaks, have there been cases where you have been convinced that the problem is not your code?  Well, you may actually be right.  This example will show a very simple case, then I will try to build on this to show a more problematic example in my next post.

    The first part of the example is to create a simple .cfc.  This cfc will have 2 methods: init() which returns itself, and doSomething()  which will return an array of 100 instances of itself.

<!--- file: test.cfc --->
<cfcomponent name="test">   
   <cffunction name="init">
      <cfreturn this >
   </cffunction>
   
   <cffunction name="doSomething">
      <cfset var idx = "" />
      <cfset var arrayToReturn = arrayNew(1) />
      <cfloop from="1" to="100" index="idx">
         <cfset arrayAppend(arrayToReturn,createObject("component","test").init()) />
      </cfloop>
      <cfreturn arrayToReturn >
   </cffunction>
</cfcomponent>
     The next part of the example will be a simple cfm file.  This file will create 1 instance of the test.cfc and load it into application scope, then it will call the doSomething() function and set the result to a local variable that resides in variables scope.
<!--- file: test.cfm --->
<cfapplication name="memoryTest">

<cfset application.test = createObject("component","test").init() />

<cfset foo = application.test.doSomething() />

<cfoutput>foo is an array of #arrayLen(foo)# elements</cfoutput>
     What should we expect from this example?  Well, we should have 1 instance of test.cfc persisted in the heap.  We will create 100 references to test.cfc, but those should only exist in the heap until the end of the request.  The garbage collector should pick them up immediately, as all references should only exist for the request.

Now, to show the results of the test, I created a flash movie that will show the behavior,  watch it to see what actually happens.




Results:
   
What happened?  As you can see, 101 instances were persisted in the heap, and not released.  These instances will live for the life of the application and NOT for the life of the request as intended.  In this case we should only see 1 instance of the cfc in the heap. Here are some of the behaviors I observed.

  1. references to objects created in application scope will be kept alive if they are set to a reference in the variables scope.
  2. Setting the variable in variables scope to empty string " " will destroy the reference.
  3. Setting the reference in request scope will clean up the reference correctly.
  4. Calling structClear(variables) in onRequestEnd.cfm will destroy unwanted references tied to variables scope
  5. The reference retained in the variables scope only seems to apply to the last request to the.cfm file (will not grow)
Summary :
     As you can see, this isn 't a horrible memory leak because it won't grow continually, but it could cause unwanted objects to be persisted in memory.   This IS a simple example, and I'm working to create a similar, but more complex example to build on this that will show that cfc references can grow in memory on every request.  Stay Tuned.

ColdFusion Memory Leaks: Part I - profiler introduction

Over the last year or so I've been dealing with memory leaks in ColdFusion.  Some have been code related, but others seem to be issues with the way ColdFusion handles garbage collection of CFCs.  Recently, we've made some breakthroughs at my company, so I thought I would share the steps that we took to identify memory leaks along with some interesting behaviors that we observed. 

Hopefully, when I am finished, you will be on your way to changing your memory profile to look something like this... (This is an actual application profile under load)

 
Before
 
After

In this post, I will show you the steps that I used to setup an environment that enabled me to identify the problem.

Step 1:  configure your jvm: 5.0 is the way to go

    The first thing that I tried was to use a profiling tool (i.e. jprobe or yourkit) against my existing coldfusion/jrun instance.  This proved to be a mistake because the profiler added so much overhead to the jvm that the application became unresponsive.  The solution was to use a Java 5 jvm.  Jrun does not support Java 5 (JVM 1.5), but for the most part your application should run to the point that you can profile effectively.

Step 2:  running with BEA Jrockit

    For our example, I will suggest using the 1.5 jrockit jvm provided by BEA.  The reason for this is because they have some powerful profiling tools that I will talk about later.

First, go to the following URL, and install the Jrockit 5.0 JDK

http://commerce.bea.com/products/weblogicjrockit/jrockit_prod_fam.jsp

(For the rest of the example I will assume that you installed the JDK at C:\Program_Files\Java\jrockit-R26.4.0-jdk1.5.0_06\ )

you will also need to download a Jrockit Mission Control 1.0 license key. (you may need to register for that), save this file named as license.bea and move it to the following folder.
\jrockit-R26.4.0-jdk1.5.0_06\jre\

After that, modify your jvm.config file and change java.home to the following... (make sure to use forward slashes)

java.home=C:/Program Files/Java/jrockit-R26.4.0-jdk1.5.0_06/jre

to enable profiling, you will need to add the following to your jvm args... (specify any port that you want)

-Xmanagement -Djrockit.managementserver.port=9010

Now restart the CF service and you are ready to go.

Step 3:  memory leak detector
   
    Now for the fun part... the Jrockit JDK ships with a tool called "Memory Leak Detector", To start using this, browse to the following folder...

C:\Program Files\Java\jrockit-R26.4.0-jdk1.5.0_06\memleak

Then double click on MemoryLeakDetector.jar (you may have to run it from the command line if that doesn't work).  The tool should start, and you should be able to specify the server and port (that you specified in jvm.config). 



The next step is critical to effectively profile a ColdFusion application.  From what I've observed, Internally, most of your variable data will be stored within objects of type coldfusion.runtime.variable.  CFC classes don't hold much data, so they take up a very small percentage of the jvm heap.  We need to change the memory leak detector settings to look at small heap sizes.  To do this, browse to File/Preferences/Trend and change "lowest heap usage to report" to 0.





Step 4:  example

To show you how to use the memory leak detector, we will create an example, for this example I will create a cfc, this cfc has 2 methods, init() that returns itself, and doSomething() which will load 100 instances of itself into the variables scope of the object.

<!--- file: test.cfc --->
<cfcomponent name="test">
   <cfset variables.holderArray = arrayNew(1)>
   
   <cffunction name="init">
      <cfreturn this>
   </cffunction>
   
   <cffunction name="doSomething">
      <cfset var idx = "">

      <cfloop from="1" to="100" index="idx">
         <cfset arrayAppend(variables.holderArray) = createObject("component","test").init() />
      </cfloop>
   </cffunction>
</cfcomponent>

Next, create a simple cfm page that will load the cfc into memory and call the doSomething() function.  Calling this .cfm page should create 101 instances of test in the application scope (1 loaded into application, and 100 loaded within itself)

<!--- file: test.cfm --->
<cfapplication name="test">

<cfset application.test = createObject("component","test")>

<cfset application.test.doSomething()>
After you have executed the page, return to your memory leak detector and filter by "cf" (you can also filter by "cold" to see internal ColdFusion representations).  You should now see something like this in the memory leak detector.


you will now see that we have created 101 instances of test.cfc in the memory heap (along with a reference to test.cfm that it is keeping alive, plus the empty classes it uses internally to identify the init() and doSomething() methods.

Congratulations!

If you have made it to this point, you are on your way to profiling your application.  If you leave the memory leak detector running under load, it will eventually track the growth of objects, and can be used to pinpoint potential problems. In my next post I will identify some of the potential problem behaviors of the ColdFusion engine, and how it can potentially "leak" object references in simple examples.

Credits and Reading materials
The basis of this strategy is outlined in greater detail in a pdf book created by Grant Straker.  He focuses on other aspects of optimizing cf performance besides memory usage as well.  If you are headed to Max, then I would highly recommend checking out his talk.

His pdf book is available at...

http://www.cfperformance.com/

In my opinion this could be the most valuable $50 that you will ever spend on CF knowledge, and is well worth the money.


BlogCFC was created by Raymond Camden. This blog is running version 5.9.002. Contact Blog Owner