ASP.NET Memory Leak Case Study: Sessions Sessions Sessions…
In ASP.NET 1.1 as you probably know, there are 3 different locations to store session objects.
In proc which stores session variables in the cache, State server which stores it in the state service and finally SQL Server.
There are of course pros and cons of each but no matter which one you use, you should be careful with how much you store in session state if you want your application to be scalable.
For in-proc session state, storing too much in session means high memory usage. For state server and SQL server you have the performance cost of serializing and de-serializing the session data.
One thing that may not be readily apparent is that if you use either of the out of process storage locations you will be reading in and de-serializing all the session data for a particular user on each web request.
Lot’s of data in out of proc session state may not only cause your performance to suffer, but depending on what you store in session scope you may be suffering a memory hit there too. Take for example a dataset, this will normally be serialized with the binary formatter (unless you don’t create your own dataset surrogate) into an XML format. The problem here is that when using a generic tool to do a specific task a lot of generalizations have to be made.
Say for example that you have a dataset like so:
ID | CustomerFirstName | CustomerLastName |
1 | John | Doe |
2 | Jane | Doe |
Instead of just storing the data “1, Jane, Doe, 2, John, Doe” you store xml markup for the dataset, tables, records etc. and each individual data item looks something like <CustomerFirstName>Jane</CustomerFirstName>.
Add to this, that the binary formatter calculates approximately how many bytes it think its going to need to store this and then adds a little (sometimes a lot) to make sure that it wont have to reallocate the buffer.
Long story short… I have seen situations where the buffer used for serialization was around 10 to 15 times as big as the original dataset because of the length of column names vs. actual data etc.
See this article https://support.microsoft.com/default.aspx?scid=kb;en-us;829740 for a more in-depth discussion on the issue.
So if we take a pretty common scenario… You develop an on-line shop application for a customer with relatively few concurrent users. The initial setup is one standalone web server, using in-proc session state, and your application is storing some datasets per user with info about their past orders etc.
Suddenly the application gets more popular and a larger company wants to use it. It needs to work on a multi-server web farm, so you move to SQL Server session state.
What is wrong with this picture???
The original idea of storing the datasets in session scope was probably based on that it would be faster to get the datasets from cache rather than by doing database requests. Now you are stuck in a situation where not only do you still have to retrieve the datasets from a database, but you do it for every single request whether you are going to use it or not on that page… and every time it has to be de-serialized and/or serialized.
Makes you think twice about what you are storing in session state doesn’t it:)
Let’s take a look at a case study…
Problem description
The memory usage for the ASP.NET process is very high (800MB – 1GB), sometimes resulting in out of memory exceptions or unwanted recycles of the process.
Gathering data
As in the case of the event handlers post I would suggest multiple dumps as the memory increases, but even if that is not possible, a dump when the memory is very high will give us a good clue about what is going on.
Debugging
The dump size is 1.473.913 bytes so we are using close to 1.4 GB private bytes which is pretty excessive.
Usually one of the first things I do is to run !eeheap –gc to get a feel for if most of the memory is on the managed heap, in which case I can for the most part rule out native leaks or problems with the loader heap.
0:023> !eeheap -gc
Number of GC Heaps: 2
------------------------------
Heap 0 (0x000b7198)
generation 0 starts at 0x022104d4
generation 1 starts at 0x022037c0
generation 2 starts at 0x02170030
ephemeral segment allocation context: none
segment begin allocated size
0x2170000 0x2170030 0x224a4e0 0xda4b0(894,128)
Large object heap starts at 0x0a170030
segment begin allocated size
0x0a170000 0x0a170030 0x0acf0b20 0x00b80af0(12,061,424)
0x0d490000 0x0d490030 0x0e3d2450 0x00f42420(16,000,032)
0x12010000 0x12010030 0x12f52460 0x00f42430(16,000,048)
0x13010000 0x13010030 0x13f52460 0x00f42430(16,000,048)
0x15010000 0x15010030 0x15f52460 0x00f42430(16,000,048)
0x1a010000 0x1a010030 0x1af52460 0x00f42430(16,000,048)
…
0x71ca0000 0x71ca0030 0x72be2470 0x00f42440(16,000,064)
0x748b0000 0x748b0030 0x757f2470 0x00f42440(16,000,064)
0x7d0e0000 0x7d0e0030 0x7d881250 0x007a1220(8,000,032)
Heap Size 0x2d5b4e10(760,958,480)
------------------------------
Heap 1 (0x000ede88)
generation 0 starts at 0x06249b58
generation 1 starts at 0x0623e190
generation 2 starts at 0x06170030
ephemeral segment allocation context: none
segment begin allocated size
0x6170000 0x6170030 0x6283b64 0x113b34(1,129,268)
Large object heap starts at 0x0b170030
segment begin allocated size
0x0b170000 0x0b170030 0x0b9f1c90 0x00881c60(8,920,160)
0x0c3e0000 0x0c3e0030 0x0d322460 0x00f42430(16,000,048)
0x0e490000 0x0e490030 0x0f3d2460 0x00f42430(16,000,048)
0x11010000 0x11010030 0x11f52460 0x00f42430(16,000,048)
0x14010000 0x14010030 0x14f52450 0x00f42420(16,000,032)
0x16010000 0x16010030 0x16f52480 0x00f42450(16,000,080)
0x17010000 0x17010030 0x17b81b60 0x00b71b30(12,000,048)
0x18010000 0x18010030 0x18b81b60 0x00b71b30(12,000,048)
0x19010000 0x19010030 0x19f52460 0x00f42430(16,000,048)
0x1b010000 0x1b010030 0x1bf52460 0x00f42430(16,000,048)
…
0x61010000 0x61010030 0x61f52470 0x00f42440(16,000,064)
0x62db0000 0x62db0030 0x63cf2470 0x00f42440(16,000,064)
0x657e0000 0x657e0030 0x66722470 0x00f42440(16,000,064)
0x685c0000 0x685c0030 0x69502470 0x00f42440(16,000,064)
0x6e110000 0x6e110030 0x6ec81b70 0x00b71b40(12,000,064)
0x72ca0000 0x72ca0030 0x73be2470 0x00f42440(16,000,064)
0x77ff0000 0x77ff0030 0x78f32470 0x00f42440(16,000,064)
0x7e0e0000 0x7e0e0030 0x7f022470 0x00f42440(16,000,064)
Heap Size 0x286a4124(678,052,132)
------------------------------
GC Heap Size 0x55c58f34(1,439,010,612)
So !eeheap –gc tells us that a) the GC Heap Size is around 1.4 GB which is very close to the total amount of memory, meaning that most of our memory is on the managed heap, and b) that most of our memory is in the large object segments, i.e. objects over 85000 bytes.
I have shortened the output here to save space. In the original output we had around a hundred large object segments. In fact this example is a bit exaggerated, it would be very rare to see this situation (hardly anything in the small-object segments, and tonnes in the large object segments), but the case study should still give you an idea of how to troubleshoot these types of issues in the more general case.
Naturally from here the next step is to find out what is on the large object heaps.
We can start off with a summary of the objects stored on the large object heap, to get a feel for what objects we are looking for.
Since the objects stored on the large object heap are all 85000 bytes or above we can dump out the large object heaps by using the –min 85000 switch for !dumpheap, and then –stat to show just the statistics.
0:023> !dumpheap -min 85000 -stat
Using our cache to search the heap.
Statistics:
MT Count TotalSize Class Name
0x000eda20 1 920,144 Free
0x01b2209c 33 132,000,528 System.Object[]
0x01b226b0 163 1,304,001,956 System.Int32[]
Total 197 objects, Total size: 1,436,922,628
Most of the memory is in Int32 arrays but a good chunk (around 132 MB) is used for byte arrays.
We should note here that this is just for the structures (i.e. the arrays themselves). The 132 MB does not include the size of the objects stored in the Object arrays, same for the Int32 arrays. To find out the actual size of each object array or int32 array including the objects stored in them we would have to run !objsize.
The next step is to take a closer look at some of the individual arrays more closely by dumping all objects on the large object heap (without the –stat switch).
0:023> !dumpheap -min 85000
Using our cache to search the heap.
Address MT Size Gen
0x0b170030 0x000eda20 920,144 -1 Free
0x0a920210 0x01b2209c 4,000,016 -1 System.Object[]
0x247b1250 0x01b2209c 4,000,016 -1 System.Object[]
0x277b1250 0x01b2209c 4,000,016 -1 System.Object[]
0x287b1250 0x01b2209c 4,000,016 -1 System.Object[]
…
0x51af0030 0x01b226b0 8,000,012 -1 System.Int32[]
0x52291250 0x01b226b0 8,000,012 -1 System.Int32[]
0x53b40950 0x01b226b0 8,000,012 -1 System.Int32[]
0x56620030 0x01b226b0 8,000,012 -1 System.Int32[]
0x57620030 0x01b226b0 8,000,012 -1 System.Int32[]
0x5a440030 0x01b226b0 8,000,012 -1 System.Int32[]
0x5abe1250 0x01b226b0 8,000,012 -1 System.Int32[]
0x5c660030 0x01b226b0 8,000,012 -1 System.Int32[]
0x5ce01250 0x01b226b0 8,000,012 -1 System.Int32[]
0x61010030 0x01b226b0 8,000,012 -1 System.Int32[]
0x617b1250 0x01b226b0 8,000,012 -1 System.Int32[]
0x62db0030 0x01b226b0 8,000,012 -1 System.Int32[]
0x63551250 0x01b226b0 8,000,012 -1 System.Int32[]
0x657e0030 0x01b226b0 8,000,012 -1 System.Int32[]
0x65f81250 0x01b226b0 8,000,012 -1 System.Int32[]
Picking one of the Int32 arrays at random, we can do !gcroot on it to find out where it is rooted to find out why it won’t get garbage collected.
0:023> !gcroot 0x72ca0030
Scan Thread 16 (0xbd8)
ESP:1a3f5e0:Root:0x6280d38(System.Web.HttpContext)->
0x62809ec(System.Web.Hosting.ISAPIWorkerRequestInProcForIIS6)->
0x61b7030(System.Web.HttpWorkerRequest/EndOfSendNotification)->
0x61b47d4(System.Web.HttpRuntime)->
0x61b4ca0(System.Web.Caching.CacheMultiple)->0x61b4cc4(System.Object[])->
0x61b4cdc(System.Web.Caching.CacheSingle)->
0x61b4dac(System.Web.Caching.CacheExpires)->0x61b4ff8(System.Object[])->
0x61b5ab8(System.Web.Caching.ExpiresBucket)->
0x222e63c(System.Web.Caching.ExpiresEntry[])->
0x220292c(System.Web.Caching.CacheEntry)->
0x22028fc(System.Web.SessionState.InProcSessionState) ->
0x2202690(System.Web.SessionState.SessionDictionary)->
0x220273c(System.Collections.Hashtable)->
0x2202770(System.Collections.Hashtable/bucket[])->
0x2202810(System.Collections.Specialized.NameObjectCollectionBase/NameObjectEntry)->
0x72ca0030(System.Int32[])
Scan Thread 20 (0x89c)
Scan Thread 22 (0xa5c)
Scan HandleTable 0xdc9d8
Scan HandleTable 0xea6e8
Scan HandleTable 0x1531e8
The root chain tells us that this particular Int32 array is rooted in cache, more specifically in the InProcSessionState object, indicating that it is stored in in-proc session scope.
Once we get here a search through the code for session assignment, focusing on Int32 arrays may be in order. However if the code base is large or there is another reason we can’t just search the code, we can dig further to get a bit more information.
Debugging Tip: From the HttpContext object above you can information about the application that the particular session object belongs to (path etc) if you have many different applications.
Taking a look at the cache (where the sessions are stored) using !dumpaspnetcache –stat we get this
0:023> !dumpaspnetcache -stat
Going to dump the ASP.NET Cache.
MT Count TotalSize Class Name
0x0211cc9c 1 20 System.Web.Security.FileSecurityDescriptorWrapper
0x020c242c 2 56 System.Web.UI.ParserCacheItem
0x0206c66c 5 260 System.Web.Configuration.HttpConfigurationRecord
0x0c2e7014 1 316 System.Web.Mobile.MobileCapabilities
0x79b94638 4 376 System.String
0x0c2eaeb4 151 7,248 System.Web.SessionState.InProcSessionState
Total 164 objects, Total size: 8,276
So we have 151 concurrent sessions (each InProcSessionState object holds the objects for one session)
To find out how much is stored in them we can use a for each loop using the method table for InProcSessionState from above and run !objsize on each InProcSessionState object.
0:023> .foreach (obj {!dumpheap -mt 0x0c2eaeb4 -short}){!objsize ${obj}}
sizeof(0x22028fc) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x2202a10) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x2202cfc) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x2202fe8) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x22032d4) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x22035c0) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x2203a38) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x2203d24) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x2204010) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
…
sizeof(0x6248080) = 12,000,772 (0xb71e04) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x6248334) = 12,000,772 (0xb71e04) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x62485e8) = 12,000,772 (0xb71e04) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x624a84c) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x624d7b8) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x6250724) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x6253690) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x62565fc) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x6259568) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x625c4d4) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x625f440) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x62623ac) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x6265318) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x6268284) = 8,000,812 (0x7a152c) bytes (System.Web.SessionState.InProcSessionState)
sizeof(0x626b1b8) = 12,000,772 (0xb71e04) bytes (System.Web.SessionState.InProcSessionState)
Wow, 151 sessions and each holds about 8-12 MB of data, yeah that could certainly cause a memory issue:)
Let’s pick one of them and see what we actually store in session …
0:023> !do 0x626b1b8
Name: System.Web.SessionState.InProcSessionState
MethodTable 0x0c2eaeb4
EEClass 0x0c1c5660
Size 48(0x30) bytes
GC Generation: 0
mdToken: 0x02000132 (c:\windows\assembly\gac\system.web\1.0.5000.0__b03f5f7f11d50a3a\system.web.dll)
FieldDesc*: 0x0c2eae0c
MT Field Offset Type Attr Value Name
0x0c2eaeb4 0x40009f2 0x4 CLASS instance 0x06269f74 dict
0x0c2eaeb4 0x40009f3 0x8 CLASS instance 0x00000000 staticObjects
0x0c2eaeb4 0x40009f4 0xc System.Int32 instance 20 timeout
0x0c2eaeb4 0x40009f5 0x18 System.Boolean instance 0 isCookieless
0x0c2eaeb4 0x40009f6 0x10 System.Int32 instance 0 streamLength
0x0c2eaeb4 0x40009f7 0x19 System.Boolean instance 0 locked
0x0c2eaeb4 0x40009f8 0x1c VALUETYPE instance start at 0x0626b1d4 utcLockDate
0x0c2eaeb4 0x40009f9 0x14 System.Int32 instance 1 lockCookie
0x0c2eaeb4 0x40009fa 0x24 VALUETYPE instance start at 0x0626b1dc spinLock
Each InProcSessionState object has a dict (dictionary) member variable that holds the actual session objects in its _entriesArray.
0:023> !do 0x06269f74
Name: System.Web.SessionState.SessionDictionary
MethodTable 0x0c2e0c54
EEClass 0x0c1c1308
Size 44(0x2c) bytes
GC Generation: 0
mdToken: 0x0200013b (c:\windows\assembly\gac\system.web\1.0.5000.0__b03f5f7f11d50a3a\system.web.dll)
FieldDesc*: 0x0c2e0b30
MT Field Offset Type Attr Value Name
0x0206b338 0x4000a8b 0x24 System.Boolean instance 0 _readOnly
0x0206b338 0x4000a8c 0x4 CLASS instance 0x06269fb8 _entriesArray
0x0206b338 0x4000a8d 0x8 CLASS instance 0x06269fa0 _hashProvider
0x0206b338 0x4000a8e 0xc CLASS instance 0x06269fac _comparer
0x0206b338 0x4000a8f 0x10 CLASS instance 0x0626a020 _entriesTable
0x0206b338 0x4000a90 0x14 CLASS instance 0x00000000 _nullKeyEntry
0x0206b338 0x4000a91 0x18 CLASS instance 0x00000000 _keys
0x0206b338 0x4000a92 0x1c CLASS instance 0x00000000 _serializationInfo
0x0206b338 0x4000a93 0x20 System.Int32 instance 4 _version
0x0c2e0c54 0x4000a0f 0x25 System.Boolean instance 1 _dirty
0x0c2e0c54 0x4000a0e 0 CLASS shared static s_immutableTypes
>> Domain:Value 0x000dad08:NotInit 0x00104f30:0x021be0dc <<
0:023> !do 0x06269fb8
Name: System.Collections.ArrayList
MethodTable 0x79ba2ee4
EEClass 0x79ba3020
Size 24(0x18) bytes
GC Generation: 0
mdToken: 0x02000100 (c:\windows\microsoft.net\framework\v1.1.4322\mscorlib.dll)
FieldDesc*: 0x79ba3084
MT Field Offset Type Attr Value Name
0x79ba2ee4 0x4000362 0x4 CLASS instance 0x06269fd0 _items
0x79ba2ee4 0x4000363 0xc System.Int32 instance 3 _size
0x79ba2ee4 0x4000364 0x10 System.Int32 instance 3 _version
0x79ba2ee4 0x4000365 0x8 CLASS instance 0x00000000 _syncRoot
The entries array is an ArrayList, and to print out all the objects in an arraylist in one shot we can run !do –v on it’s _items member variable
0:023> !do -v 0x06269fd0
Name: System.Object[]
MethodTable 0x01b2209c
EEClass 0x01b22018
Size 80(0x50) bytes
GC Generation: 0
Array: Rank 1, Type CLASS
Element Type: System.Object
Content: 16 items
------ Will only dump out valid managed objects ----
Address MT Class Name
0x0626a81c 0x0206b784 System.Collections.Specialized.NameObjectCollectionBase/NameObjectEntry
0x0626a82c 0x0206b784 System.Collections.Specialized.NameObjectCollectionBase/NameObjectEntry
0x0626a83c 0x0206b784 System.Collections.Specialized.NameObjectCollectionBase/NameObjectEntry
----------
We can either dump out these objects individually or we can let the debugger do the work for us.
If we use !do –v –short instead of just !do –v, we just get the addresses of the NameObject entries so we can use .foreach on them.
0:023> !do -v 0x06269fd0 -short
0x0626a81c
0x0626a82c
0x0626a83c
In this specific case the .foreach might seem like overkill but it’s good to know how to do it for larger collections.
For each object, we print out the session variable name at offset 0x4, the object at offset 0x8 and the size of the object.
(To learn more about how this works, read the post on “how much are you caching”)
0:023> .foreach (obj {!do -v 0x06269fd0 -short}){.echo ***;!do poi(${obj}+0x4);!do poi(${obj}+0x8);!objsize ${obj}}
***
String: somestring
String: this is a string i stored in session scope
sizeof(0x626a81c) = 160 ( 0xa0) bytes (System.Collections.Specialized.NameObjectCollectionBase/NameObjectEntry)
***
String: alargeintarray
Name: System.Int32[]
MethodTable 0x01b226b0
EEClass 0x01b22638
Size 8000012(0x7a120c) bytes
GC Generation: 3
Array: Rank 1, Type System.Int32
Element Type: System.Int32
Content: 2,000,000 items
sizeof(0x626a82c) = 8,000,076 (0x7a124c) bytes (System.Collections.Specialized.NameObjectCollectionBase/NameObjectEntry)
***
String: sometimesbig
Name: System.Object[]
MethodTable 0x01b2209c
EEClass 0x01b22018
Size 4000016(0x3d0910) bytes
GC Generation: 3
Array: Rank 1, Type CLASS
Element Type: System.Object
Content: 1,000,000 items
sizeof(0x626a83c) = 4,000,076 (0x3d094c) bytes (System.Collections.Specialized.NameObjectCollectionBase/NameObjectEntry)
So for this user we are storing a pretty small string “this is a string i stored in session scope“ in Session[“somestring”], an 8 MB Int32 array in Session[“alargeintarray”] and a 4 MB object array in Session[“sometimesbig”].
At this point we have the issue pretty well defined and the task will be how to reduce the sizes of these objects, but that I will leave to the developers:) My work here is done:)
Based on the number of issues I have seen with high memory caused by storing too much in cache or sessions, I have made it a standard step in my troubleshooting of high mem issues to look at the size and contents of the cache and session state.
Side note
If the number of users on your site is pretty constant but the number of concurrent sessions (in performance monitor) keeps increasing abnormally, you may be running in to a problem with timers not firing https://support.microsoft.com/kb/900822/en-us. You can obtain this hotfix by contacting Microsoft Product Support.
Over and out…
Comments
Anonymous
February 02, 2006
Hi,
What about fragmented heap? How do you handle them? Especially in .NET framework 1.1?Anonymous
February 02, 2006
Hi Naveen,
If you are talking about managed heap fragmentation, there are two kinds, 1) heap fragmentation caused by pinned objects, in which case you can run !objsize (without parameters) to see what your pinned objects are and where they are located. And then try to reduce the amount of time they are pinned (i.e. shorten the time for remoting requests etc.) If you are pinning objects yourself, you can opt to create a buffer of pinned objects so all pinned objects are created in one specific place in memory, thus reducing greatly the fragmentation in other segments.
2) heap fragmentation in the LOH. Here i would recommend examining the objects on the LOH and see if any can be reduced in size to not end up on the LOH. The problem gets worse if the objects stored on the LOH keep increasing in size since we can then not reuse a previous free object (as it doesnt fit), and the LOH ends up looking like a swiss cheeze.
I'll try to expand a bit on this in a later post...
If you are talking about native heap fragmentation, please see my answer to marks comment on the OOM post http://blogs.msdn.com/tess/archive/2005/11/25/496898.aspx.
Hope this answers the question a little bit, I'll definitely expand on this later on though...Anonymous
February 03, 2006
I am talking about heap fragmentation in LOH.Anonymous
February 04, 2006
Very interesting...Anonymous
March 16, 2006
Weird, why my !dumpheap command doesn't show the address field? it only has MT, Count and so on. I need the Address field so I can do gcroot and do command.
Any ideas?Anonymous
March 16, 2006
That sounds very weird, could you post the exact command you use and part of the output?
Are you using the clr10sos.dll that comes with windbg?Anonymous
March 28, 2006
in which console we can know about memory in GC and identify any memory leakeages for the objects which i am using in my application
ex: in the above article i saw !eeheap -gc command, in which consoleAnonymous
March 29, 2006
Hi Vijenra,
This is in the windbg.exe console either when live debugging an application or when looking at a dump from your application.Anonymous
May 15, 2006
The comment has been removedAnonymous
May 17, 2006
Great article...I need this. One question, arent session objects supposed to be released after 20 minutes of inactivity instead of just building up?Anonymous
May 17, 2006
Yes they are. That is not so much the problem... Its the amount of data and the amount of concurrent sessions...Anonymous
May 17, 2006
That was my thought, but the memory builds and during long periods of site inactivity, it sits there...so I must be doing something else. Thanks again for the great article.Anonymous
May 17, 2006
A small comment on that: Since garbage collection only happens on allocation (with a few exceptions such as reaching the memory limit in asp.net or manually calling GC.Collect), it might still be that objects that were previously rooted in Session scope might still lay around during inactivity.
Also, if the total number of sessions keep creeping up you might need the fix in the article mentioned in the end of the post.Anonymous
November 07, 2006
Great article, any plans to update this for asp.net v2? In particular the windbg commands you would need to run?Anonymous
November 07, 2006
Hi Morgan, You can get to the same data in 2.0 but it is a little bit more cumbersome without the !dumpaspnetcache and !do -v The way to do it is to just dump out the InProcSessionState items from the mt from !dumpheap -stat and then instead of !do -v you can do dc on the object array and dump out all the dwords from dword 4 and on with !do Functions are slowly being migrated to sos.dll 2.0 so anytime you get a new SP or hotifx there may be an update to sos.dllAnonymous
January 18, 2007
Correct me if I'm wrong, but the data from the sizing foreach loop could be a little misleading. If you have in-proc objects and are storing references to (threadsafe of course) shared objects in the session, then that shared object's size will get counted in each session. That's what I noticed at least. And so if your sessions seem bigger than you think they should be, you can drill into them with !do, find the shared objects, and verify that the address is the same.Anonymous
January 21, 2007
Hi Rob, Yes, you are absolutely right, What !objsize does is that adds the size of the current object with that of all its member variables and its member variables in turn. eg. if you have a class like this Person _FirstName _LastName _CommonData !objsize will contain the size of the Person object + _FirstName (including any membervariables it might have) + _LastName (including any membervariables it might have) + _CommonData (including any membervariables it might have) So if _CommonData is common to many different persons it will be included in the !objsize for all those objects It does stop when it reaches a loop though, i.e. if some members of _CommonData happens to have a link back to the original Person object for example it will not just continue. One interesting special case here that we see from time to time and that I think I might blog about soon is if you store a web control in session state. This is a very bad thing as a web control usually has an aspx page linked as a parent, so if you store it in session scope you effectively store the aspx page and all its contents in session scope as well. The aspx page has an indirect link to the cache, so !objsizing any of these objects will show the size of the whole cache. I'm sure your case is quite different, but if anyone is reading this and currently store web controls or anything that link to aspx pages in session, I would advise that you stop and instead perhaps store data in session scope to rebuild the web controls when needed. TessAnonymous
August 13, 2007
The comment has been removedAnonymous
August 15, 2007
Wow guys I am impressed with some of the answers put up. What are the various ways that an object canAnonymous
October 09, 2007
Stupid question? How do you get the dump of the site? Do you perform a dump on the process? adplus -hang -pn aspnet_wp-exe MTmaceAnonymous
October 09, 2007
Yepp, thats correct... Btw check out my latest post (Sept) with a link to carlos post on how to grab dumps, or go to the post index list and check for a post on how to grab dumps if you need more specific info, but the above should do.Anonymous
December 15, 2007
Awesome article. Thank You Very Much.Anonymous
March 13, 2008
How I lost my WinDbg virginityAnonymous
August 27, 2008
Tess, I would like to comment on "A small comment on that: Since garbage collection only happens on allocation (with a few exceptions such as reaching the memory limit in asp.net or manually calling GC.Collect), it might still be that objects that were previously rooted in Session scope might still lay around during inactivity." We discovered the following. We occasionally have to put larger objects into the session, so with the next request the end user doesn't need to pull the data again. Of course, when we do not need it anymore, we remove it from the session with Session.Remove(key) by its key. If you try to access it after it that, it is gone. Well done! Not at all! For some reason (maybe the one you mentioned above in the quote) even though the object is not accessible anymore by its key the session with this object does get serialized out to the database and of course (and even worse) it gets deserialized on the next server request. Only after a few clicks more (or serialize/deserialize actions) all of a sudden it is gone (gc'ed?) from the session (and its serialized copy in the session database). The only way to get around this is a trick we found out: Instead of removing the key, we set the key's value to null, which then has the effect that the key refering to that object will be serialized but this time with an "object" containing null and not the big one it was referencing to. Do you have an idea why this happens and whether there is a better way to copy with that?Anonymous
April 03, 2009
A few weeks back me and Micke (one of our Architect Evangelists) had a session at TechDays where we talkedAnonymous
December 21, 2010
When the session/cached data will be deleting from heap/LOH ??. When the user close browser, How does the memory knows user no more using this session data. When I run stat I see they are lots of free LOH blocks in the segment. As reading msdn noticed LOH free blocks not going to turn to OS. In that case is the free block is going to use for other new LOH allocations?? Somehow I see outofmemoryexceptions even I see free blocks in LOH ?? Can you give your insight on this situation.Anonymous
January 02, 2011
Kasi, Sessions will be alive until they time out (default is a sliding timeout of 20 mins from when it is last updated) or until you call Session.Abandon(). Sessions are not exited when you close down the browser unless you code something that abandons the session on window.close() Once the session times out, unless the object is referenced by something else it is then available for garbage collection the next time a GC for that generation happens. OOMs occur because a new GC segment can't be allocated, this can be totally unrelated to available memory on the LOH, it would depend on the object you are trying to allocate, and how much free space you have in the segment that it tries to allocate in, i.e. if it doesnt fit a new segment is created and if it can't create it, or if it can't do a GC because it can't fit the structures used during a GC into memory it'll give an OOM.Anonymous
November 04, 2015
Tess, With regard to storing a web control in a session variable, I am doing this in one case only. I am saving a treeview in a session variable. However, the control is never added to the page and does not get sent to the client. It is instantiated globally in the code behind each time the page is hit. On the first page load some content is added to the tree and then the tree is saved to a session variable. All subsequent post backs are via ajax. For each post back, the tree is retrieved from the session variable, then possibly modified, and then saved back to the session variable. In all post backs the trees content is used in various places to do certain things. My question is this, in this usage, where the web control is not actually added to the page, would saving it in a session variable still also store the aspx page, and do your comments about not assigning web controls to session variables still hold? Thanks.