Programming__WebObjects-EOF-Using EOF-Memory Management

Version 10.1 by smmccraw on 2007/07/08 09:44

Overview

Memory Management in Java is a bit of a black art.  When combined with EOF, there are certain issues that you need to be aware of.  In WebObjects 5.2, there was a major change to the memory management system in EOF that provided support for WeakReferences to EO snapshots rather than only relying on the previous system of reference counting.  This provides for vastly superior memory performance under the same conditions with the same app in 5.2 and above, because there are many cases where references will be released by Java's memory system rather than be held onto by your EOEditingContext.

There is a large overlap between the content about Caching and Freshness and that of Memory Management.  Both should be read to fully understand how caching and memory management should play a part in your application design.

General Issues

One important issue to consider prior to taking any drastic measures is whether or not your application really just requires more memory than you are giving it.  By default, a Java Virtual Machine gives a Java application 64M of memory.  This is not that much memory for an enterprise web application.  You can adjust this by setting the "mx" flag on your VM at launch time (-Xmx256M would give you VM 256M of heap to work with instead of the default 64M).  Other than just hunting aimlessly in your code, profiling your application is the only way to truly determine your application's memory situation.

The following profiling tools have been verified to work with WebObjects:

  • JProfiler ( http://www.ej-technologies.com/products/jprofiler/overview.html ) has explicit support for WebObjects.  It can automatically update your WebObjects Application launch script to insert itself at runtime to allow you to remotely attach to the application and profile it.

Raw Rows

The use of Raw Rows can dramatically decrease your memory usage (and increase your application's performance) at the cost of convenience.  This uses less memory than normal EO's, allows better support for fetch limits, and you can still promote them to full-blown EO's when need be.  Check out the Raw Rows topic for more information.

Qualify your Fetches

If you are not careful when you fetch, you may bring in huge datasets.  When you use an EOFetchSpecification, be mindful to specify the most restrictive EOQualifier that still returns the data you need.  Otherwise, you will be potentially faulting in much more data than your application will use, and you decrease your overall application performance because of the data that must be transferred from your database.

Relationships from Enumerated Types to other EO's

It is very common to have EO's that represent "Enumerated Types".  For instance, you might have a bug tracking system that has a Severity entity (with individual instances of "Crasher", "Minor", etc) that are referenced by all bugs in the system.  It is tempting to always create relationship back-references (i.e. Bug=>Severity, Severity=>>Bug).  However, be aware that if you create the reference from Severity back to Bug, any time a Bug is added and you properly use the addObjectToBothSidesOfRelationshipWithKey method, EOF will fault in the ENTIRE "bugs" relationship on Severity prior to adding your new bug to the end of the list.  The consequences of this can be crippling to your application.  As the number of bugs increases, it will take longer and longer to add a new bug.  The recommended best practice for this is to only create Bug=>Severity and NOT create the reverse relationship.  In the event that you need to perform that fetch, you can manually construct the EOFetchSpecification rather than use the automatic approach.

Note that this is a filed bug against EOF.  One approach that EOF could use is to only add the object to Severity's bugs relationship if the bugs relationship has already been faulted into memory.  If the array has not yet been faulted, then causing the fault on insert is potentially very expensive.  The reasoning for the current behavior is to maintain the ability to access the newly committed Bug via the Severity bugs relationship prior to committing your transaction (i.e. faulting the relationship from the database wouldn't yet return the newly inserted Bug).  This is very important to maintaining object graph consistency pre-saveChanges, but does come at a high performance cost.

Dispose()ing your EOEditingContext

Calling dispose() on your EOEditingContext is not strictly required.  When the EOEditingContext is no longer referenced, it will be automatically disposed.  However, the question is how long it takes the GC to get to your object.  If you call dispose() manually, you won't hurt anything, and you immediately decrement the snapshot reference counts.  If your EC is falling out of scope or you set it to null, then it's probably a tossup as to who gets to it first -- GC finalizing your EC or you manually calling dispose().  It is recommended that you dispose() your EOEditingContext when you are in a long running process in a single method where the EOEditingContext will not go out-of-scope until the end of the method.

Calling System.gc()

Calling System.gc() is usually a bad idea and can cause performance problems in your application.  Additionally, System.gc() is not an order, it is a request, so if your application is relying on this command to execute, you should re-evaluate your application's design, because you may run into problems with this.

Anecdotes on editingContext.invalidateAllObjects()

It can be tempting to resolve memory problems by calling editingContext.invalidateObject(..) or editingContext.invalidateAllObjects().  Avoid this.  You will only hurt yourself in the long run.  Invalidating is very aggressive and WILL cause exceptions to be thrown if someone in another EOEditingContext is modifying an EO that you invalidate in your EOEditingContext (the shared snapshost will be destroyed, resulting in an exception on .saveChanges in the other context).  It should only be used under very controlled situations where you understand the implications of the method.

Chuck Hill

As for invalidating objects, this is something to be avoided.  I admit that I have been driven to desperation a couple of times and used this, but only with regrets and reservations.

Art Isbell

I just have a bias against using invalidateAllObjects() because, for me, it's caused more problems than it's solved.

Mike Schrag

The couple of times I resorted to this, I regretted it later, because it ended up screwing me.  The biggest thing is you toss snapshots that people might be in the middle of editing, and that's going to cause really funky problems.  It's a really heavy-handed way to go.

NSUndoManager

One of the most common memory problems people run into with WebObjects is the NSUndoManager.  WebObjects has a particularly cool feature in that it supports an undo stack for EO transactions.  Each time you saveChanges, you push an undoable set of changes onto the stack.  The downside of this behavior is that you end up with strong references to old EO states, which can be deadly in a large EOF operation.

There are several fixes for this.  One is to simply disable the NSUndoManager on your editing context by calling:

  editingContext.setUndoManager(null); 

It has been referenced that setting the undo manager to null may cause issues with deletes under certain circumstances.  In testing this with WebObjects 5.3, the situation could not be reproduced, but if you use WebObjects 5.2 or lower, you should be aware that you might run into problems.

The recommended alternative in this case is to disable the registration of undo events by calling:

  editingContext.undoManager().disableUndoRegistration();

Note that setting the undo levels to 0 using setLevelsOfUndo() does not work, as 0 means no limit!

Priorto5.2,theNSUndoManagerhadeffectivelyinfiniteundolevelsbydefault.ThejavadocofWO5.3stillmentionsthefollowing:

EOEditingContext'sundosupportisarbitrarilydeep;youcanundoanobjectrepeatedlyuntilyourestoreittothestateitwasinwhenitwasfirstcreatedorfetchedintoitseditingcontext.Evenaftersaving,youcanundoachange.Tosupportthisfeature,theNSUndoManagercankeepalotofdatainmemory.

However,asof5.2,ifyouareusingthesessiondefaultEditingContext,WOSessionsetsthedefaultundostacksizeto10ifthereisnodefaultsizespecifiedwiththeWODEFAULTUNDOSTACKLIMITparameter.

LastlyyoucanmanuallyclearyourNSUndoManagerwhenyouaredonewithanoperationbycalling:

editingContext.undoManager().removeAllActions();

IfyouuseanNSUndoManager,itisrecommendedthatyoucallthisafterperformingverylargesaveChangesthatinvolvedlargeamountsofdata.

WebObjects5.2+

Thisisadescriptionofthecurrentbehaviorin5.3thatonemightbeabletogatherifonewereto,say,decompileandreviewtheentireprocess--notthatiwouldeverdothisorcondoneit,ofcourse--butifyouDID,youwouldprobablygatherexactlythisinfo,whichisprettywelldocumented(andseemstobehaveto-spec)inthe5.2releasenotes:

Asof5.2,thewayitworksis:_The_snapshots_in_EODatabase_have_a_reference_count.__Each_editing_context_that_fetches_an_EO_increments_the_reference_count.__The_EC_holds_onto_that_EO_via_a_WeakReference.__When_the_WeakReference_is_reclaimed,thesnapshotreferencecountcandecrease(noteCAN,notIMMEDIATELYWILL--theeditingcontextkeepsreferencequeuewhichisonlyprocessedperiodically).Whenthecountgetstozero,thedatabaseforgetsthesnapshot.Ifyouhaveentitycachingenabled,thenEODatabaseignorereferencecount(orkeepsitat"1"asaminimum)anditwillnotgoawayinaread-onlyscenario.IfyoumodifyanyentityofthattypeandsaveChangesinyourEditingContext,acachedentity'scachewillbeentirelyflushed.(NB:_Keep_this_in_mind,becauseifyouarecachingalargeamountofdatathatiswritable,itwillNOTbeverysmartaboutupdatingthatcache--It'sblownawaywitheveryupdateandthenitimmediatelyreloadstheentiresetofobjectsforthatentityatthenextaccess)

IfyouhaveretainsAllRegisteredObjectsenabledonyoureditingcontext,itwillNOTuseWeakReferences.Underthiscircumstance,theEOreferencecountisonlydecreasedwhen1)youdisposetheeditingcontextor2)youforgetorinvalidatetheobject.

Whenyoumodifyanobjectinaneditingcontext,theeditingcontextkeepsastrongreferencetotheobjectsuntilyousaveChanges(orrevert,reset,etc),atwhichpointthestrongreferencesareclearedandtheonlyremainingreferenceistheweakreferencelikebefore.

Ifyouhaveanundomanagerenabled,itwillkeepastrongreferencetotheaffectedEO'saslongastheundoisaround.

IdowonderifEC'sshouldbeusingSoftReferencesinsteadofWeakReferences...WouldseemtobemorefriendlytotheusersofthoseEO's.

IfyouareusingWOpre5.2,thennoneoftheWeakReferencestuffapplies,andeverythingispurelydonewithsnapshotreferencecounting--itshouldbehavelikeretainsAllRegisteredObjects=truein5.2.

WebObjectsPre5.2

Priorto5.2,allsnapshotsweresimplyreferencecountedbyEOEditingContext.IfanEOwasfaultedintoyourEOEditingContext,itwouldnotgoawayuntiltheEOEditingContextitselfwasdisposed.Asaresult,ifyouhaveanapplicationdeployedonapre-5.2WebObjects,youwillneedtoadoptastrategyofperiodicallydisposingyourEOEditingContextandcreatinganewone.

ChuckHill

Inourexperience,EOFisnottoogoodatbulkoperationslikethis.Ifwehavealottodo,anddon'tneedlogicfromtheEO,wewanderdowntotheEOAccesslevel,andwhipupsomebatchinsertstatements.YoumightevenwanttoconsiderstraightJDBC.

OKthatsaid,

  • saveperiodically
  • don'tretainreferencestoobjectsyouhaveinserted
  • ifthatdoesnotwork,trycreatinganewECperiodically
  • abitheavyhanded,butec.invalidateAllObjects()shoulddumpoutallthesnapshotsaswell.

Acoupleofusefulcomments:
http://lists.apple.com/archives/webobjects-dev/2004/Sep/msg00225.html
http://lists.apple.com/archives/webobjects-dev/2004/Sep/msg00228.html

ItisalwaysinterestingtoreadtheEOFdocs,theJavaDocsareforAPIonlywhiletheEOFdocscontainmuchvaluableinformation.
http://developer.apple.com/documentation/WebObjects/Enterprise_Objects/index.html

"EOEditingContextsuseweakreferencestotheEOEnterpriseObjectsregisteredwiththem....EOEditingContextsholdallinserted,deleted,orupdatedobjectsbystrongreferences.ThesestrongreferencesareclearedbytheEOEditingContextmethodssaveChanges..."--http://developer.apple.com/documentation/WebObjects/Enterprise_Objects/Managing/chapter_7_section_9.html

AlanWard

Re:_creating_a_new_EC_periodically
I'djumpstraighttothisoption(basedonmyexperience).Ihavedoneacoupleofsimilartasksand
found
thatthebestwaytoensurethatyourappdoesn'tballoonupistoperiodicallyditchtheEC
and
createanewone.Seemslikeitshouldn'tbenecessary....butitworks.

KenAnderson

Myguessisthememoryincreaseisduetosnapshots,which,asIunderstandit,willnevergoaway,andwouldn'tbeconsideredabug(tomeanyway).

Fordealingwithaforever-importproblemIhad,whereIdidn'twantthestronghanded'newEC'plan,Iwrotethismethod,whichkeepsthesnapshotsdown:

public_static_void_forgetObjectsAndKeypaths(EOEditingContext_ec,_NSArray_eos,_NSArray_keypaths)_{
Enumeration_eoEnum_=_eos.objectEnumerator();
while_(eoEnum.hasMoreElements())_{
EOGenericRecord_eo_=_(EOGenericRecord)_eoEnum.nextElement();
Enumeration_keypathEnum_=_keypaths.objectEnumerator();
while_(keypathEnum.hasMoreElements())_{
String_keypath_=_(String)_keypathEnum.nextElement();
EOFaulting_faulting_=_(EOFaulting)_eo.valueForKeyPath(keypath);
if_(faulting_!= null && !faulting.isFault())_{
if_(eo.isToManyKey(keypath))_{
NSArray_relEos_=_(NSArray)_eo.valueForKeyPath(keypath);
EOHelper.forgetObjects(ec,_relEos);
}_else_{
ec.forgetObject((EOCustomObject)_eo.valueForKeyPath(keypath));
}
}
}
ec.forgetObject(eo);
}
}

Youcanuseitlikethis:

EOHelper.forgetObjectsAndKeypaths(ec,_arrayOfEOsToForget,_new_NSArray(new_String[]{"rel1",_"rel2"});

Ofcourse,alltheEOsinthearrayneedtobethesameentityforkeypathstowork!

Category:WebObjects