Hi,
[Disclaimer: Posted this on nhusers, but I realized that this is probably a more correct forum]
I have been trying to pin down a memory leak in my wep app these past few weeks - and digging into my application with WinDbg revealed something fishy with the NHibernate SessionFactory in memory.
Context: It's a standard ASP.NET application using NHibernate 2.0.0.4000. I only use 1 SessionFactory since that seems to be the defacto standard - and my Sessions are disposed using something along the lines of Ayende's unit of work.
I have been analyzing a memory dump of a ~800mb .NET heap to figure out why it kept growing. One thing I found was around 1.9 million NHibernate.SqlCommand.SqlStrings and ~90000 HQLQueryPlans
MT Count TotalSize Class Name
0f7fb3e4 92382 3695280 NHibernate.Engine.Query.HQLQueryPlan
0eafd714 1939496 31031936 NHibernate.SqlCommand.SqlString
Digging down into the roots of one of these SqlStrings revealed that it was being stored in the query plan cache in the SessionFactoryImpl:
067016bc(NHibernate.Impl.SessionFactoryImpl)->
06702470(NHibernate.Engine.Query.QueryPlanCache)->
06703050(NHibernate.Util.SoftLimitMRUCache)->
067030a0(NHibernate.Util.LRUMap)->
067030d4(System.Collections.Hashtable)->
0c8af170(System.Collections.Hashtable+bucket[])->
02e30ccc(NHibernate.Util.SequencedHashMap+Entry)->
02e2efdc(NHibernate.Engine.Query.HQLQueryPlan)->
02e2f1dc(System.Object[])->
02e2f1f0(NHibernate.Hql.Classic.QueryTranslator)->
02e2f4c4(System.Collections.Generic.List`1[[NHibernate.SqlCommand.SqlString, NHibernate]])
I checked and there was exactly 1 instance of the SessionFactoryImpl in the dump - checking it's size took a few hours, which is not surprising:
sizeof(067016bc) = 716798348 bytes (NHibernate.Impl.SessionFactoryImpl)
That is one big SessionFactory :-) Still tracking the SqlString I explored the query plan cache and ended up at the LRUMap, which according to the code is supposed to keep the last (128 in this case) most recently used queries in cache. The LRUMap looked like this:
Name: NHibernate.Util.LRUMap
Fields:
MT Field Offset Type VT Attr Value Name
0f76a554 4000e40 c ...ncedHashMap+Entry 0 instance 067030bc _sentinel
79101fe4 4000e41 10 ...ections.Hashtable 0 instance 067030d4 _entries
790ffcc8 4000e42 4 System.Int64 1 instance 104874 _modCount
79102290 4000e51 14 System.Int32 1 instance 128 maximumSize
That is, 128 max size. Examining the hashtable called Entries in the LRUMap revealed this though (some lines removed):
Name: System.Collections.Hashtable
Size: 56(0x38) bytes
Fields:
MT Field Offset Type VT Attr Value Name
7912d9bc 400092b 4 ...ashtable+bucket[] 0 instance 0c8af170 buckets
79102290 400092c 1c System.Int32 1 instance 92382 count
79102290 400092d 20 System.Int32 1 instance 35331 occupancy
79102290 400092e 24 System.Int32 1 instance 112634 loadsize
79102290 4000930 2c System.Int32 1 instance 92391 version
Supposedly it contains ~90000 objects (count field) and a big (reachable) object size:
sizeof(067030d4) = 716798264 bytes (System.Collections.Hashtable)
I have examined the LRUMap source and can't find any glaring mistakes, I looked at the LRUMapFixture, which didn't seem to contain any tests that verify that it actually is limited to it's max size. However, I made a unit test in my own test project:
[Test]
public void LRUTest()
{
LRUMap map = new LRUMap(10);
for (int i = 0; i < 20000; i++)
map.Add("str" + i, i);
Assert.AreEqual(10, map.Count);
}
and this passed just fine.
Now this is where I am stumped, somehow my application manages to squeeze all these objects into the cache, but I haven't been able to reproduce it with simple tests.
Does anyone have an idea what could be the culprit?
|