We have a .Net 4 application that occasionally will throw an Out Of Memory Exception. Currently the application is compiled as 32 bits, as it uses a 32 bits COM component, so in principle this fact limits the memory space available to the application to 2 GBs. While looking into ways to avoid this issue I've taken some notes on Memory limits in Windows and .Net.
OS limits
The OS splits the virtual address space in 2 parts, one for the OS and one for the Application. We are interested in the latter (it's the application who could need enormous data sets, not the OS)
- For a 32 bits OS this means 2GBs for the OS and 2GBs for the application. There's a flag in the PE (portable executable) header, IMAGE_FILE_LARGE_ADDRESS_AWARE that allows you to change this to 3GBs. So well, for an application managing huge data sets this is for sure a limiting point.
- For a 64 bits OS the theoretical limit (the max addressable memory address) is 18.446.744.073.709.551.616 bytes, that is 16 Exabytes. Windows limits this a bit, you have an updated list here. Physical limits are huge enough to not be a concern for anyone I guess, but the first table, containing the per process limits is more interesting. To summarize:
For a 64 bits process and IMAGE_FILE_LARGE_ADDRESS_AWARE flag set (it's the default), the limit is 8 TBs, so we are good :-) For 32 bits processes there are good news, if we set IMAGE_FILE_LARGE_ADDRESS_AWARE (by default it's not set) for this binary, we'll be able to use 4 GBs of memory, this can make a difference for some applications. I've tried this with a test applicaton and it really works. The best thing is that you can apply this fix to a problematic application even if you don't have the source code. You can change the IMAGE_FILE_LARGE_ADDRESS_AWARE flag in an existing .exe just using the editbin tool that comes with Visual Studio, just type: EditBin.exe” “myApp.exe” /LARGEADDRESSAWARE. That's all, it will set the falt in the .exe header to 1 and you'll be able to use up to 4GBs of RAM. You can easily see if a binary has this flag on or off by using dumpbin /headers. By the way, another helpful use of editbin is changing the stack size for an application, which could be needed if you are using deep recursions.
For .Net applications there are additional limits. Until version 4.5 the maximum size of a .Net object (an object needs contiguous memory space) was 2 GBs, with .Net 4.5 and setting gcAllowVeryLargeObjects to true in your config file, you can skip this limit. You, can read more here. Great, so your objects can grow bigger and bigger now, but for the sake of understanding, how can you end up with such monster objects?
Well, obviously you'll need an array, and mainly an array of Value objects. Value objects are embedded in the array, while that for normal objects the array just contains the reference (memory address) to the object, that's just 32 or 64 bits depending on how you've compiled your application, so it would be quite unusual to hit the limit with an array of references. Notice that you can not switch from an Array to a List to skip this issue. A List uses internally an Array for its storage (when it reaches its capacity a new, bigger array is allocated and the data copied to it).
There's something more to consider regarding continuous memory space, fragmentation. .Net divides the memory heap in 2 blocks, the Small Object Heap (in turn divided in 3 blocks, the generation 0, generation 1 and generation 2 used by the GC), and the Large Object Heap (for objects bigger than 85 KBs). When the GC cleans the rubbish in the SOH, it also compacts the memory, but it won't do so for the LOH, as this can be a pretty costly operation. So even if large objects are eventually collected,they'll fragment your memory and can prevent you from finding a new large block in the future, causing an Out of Memory exception when indeed you have plenty of memory available for smaller objects. This is pretty well explained here. Things have changed a bit after that article were written, and now (since .net 4.5.1) the frameworks gives you the possibility of compacting the LOH, by means of a GC setting: GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;.
You can read about it in this excellent post
No comments:
Post a Comment