Have you ever wondered about what’s under the hood of the applications you develop?
Ever been surprised that there’s no need to worry about memory allocation and deallocation using high-level programming languages such as Java or C# after leaving the university* ?
Still remember (old) C++ times with delete statement?
By this post, I’d like to introduce a new “.NET Internals” series on the blog. I will be publishing a new post on .NET internal concepts every Wednesday morning. No end date for the moment 🙂
Posts within the series will be mostly about memory management and performance aspects of .NET applications. All discussed concepts are applicable to most of the modern programming platforms, but the examples will be based on .NET Framework.
If you’re interested in such topics, I encourage you to check this blog every Wednesday starting today 🙂
*there is 1 assumption and 1 fact here:
assumption: you used to program in C/C++ (before C++11) on the university,
fact: that’s not true you don’t need to worry about memory management using high-level programming languages; wait for the next posts to get to know why.
Each application targeting .NET is managed by CLR (Common Language Runtime), which is a part of .NET Framework, which must be installed on the computer on which the .NET application is launched – the exception are .NET Core self-contained apps, which we are not discussing in this series. By default, the CLR is loaded by each .NET process as a hosted process (so the CLR runs as a part of .NET application).
This CLR “layer” is responsible for various things while our application is running:
executing CIL (Common Intermediate Language),
ensuring type safety,
Already running application is represented as a process in the operating system. As software developers we know that such process saves some data into the memory, which is determined by the amount of RAM available on the computer. However, process never operates on this “open memory” – only OS has direct access to it. When the CLR is launched within .NET process, it requests some amount of RAM from the operating system. This memory chunk is called virtual address space. Each process has its own virtual address space, but all processes running on the same machine share the same physical memory.
Do you remember 32-bit editions of Windows XP, which could only use 4GBs of RAM (with even less visible to the user)? The reason for that is that in 32-bit operating systems total virtual address space is 4GBs, usually divided into two equal chunks – first one owned by the OS and the second one prepared for user processes. The diagram below presents.
In 64-bit Windows theoretical amount if virtual address space is 16 exabytes (264 bytes). In reality, only a part of it is used: 8-terabyte chunk is used for user space and portions of 248-terabyte are user for the OS:
Sharing memory between processes
What seems to be critical to understand is how this whole virtual memory is shared between processes. As we said before, multiple processes share the same memory, but accessing different (separate) chunks of it. Let us illustrate once more:
As can be seen, each process reserves different pages of memory when it’s needed. Even though the default 2GBs of virtual memory reserved for a single process may seem to be a lot, every developer needs to be aware that when an allocation (reservation of memory chunk) is made (manually or by memory management system) the virtual memory manager needs to find a single, continuous virtual memory block large enough to store the requested amount of data. This rule leads to the presence of “holes” in the memory. This virtual memory fragmentation is well-presented on the diagram below:
However, this requirement of finding a continuous block to be allocated refers only to the virtual memory. Physical memory is divided into pages, which can be stored in totally different places – as you can see on the diagram above with Notepad.exe application using continuous blocks of its virtual memory, but committed to noncontiguous pages scattered in various physical locations.
If the process requests for too much memory, which cannot be continuously allocated in the virtual address space (your process is out of virtual memory) or no more virtual memory can be committed (assigned) to the physical storage, it can result in OutOfMemoryException thrown by .NET code.
This was the introductory post of my new .NET Internals series. We saw what are the basic concepts of the memory structure, learnt what is virtual address space, how physical memory is shared between processes and why Windows XP was able to handle only 4GBs of RAM 🙂
See you next Wednesday!