Memory Management
Application Level Memory Management
When the application manages memory. The application involves both the program and the runtime.
Memory Leaks
A memory leak happens when an application allocates memory but fails to free it when it’s no longer required. This can occur due to incorrect memory management by the program. Although rare, it can sometimes occur at OS level due to some bug.
Generational Hypothesis / Infant Mortality
Its the idea that most objects are more likely to die young. The ones who survive have very long lifespan.
Manual Memory Management
Programmer level Memory Management
You can use Program Components#Pointers to manually control and allocate blocks of memory.
Memory Allocation
- Static Memory Allocation: At build time. ^3a2afe
- Dynamic Memory Allocation: At runtime.
Implicit Memory Management
Or Runtime Level Memory Management
When the runtime environment manages memory.
Garbage Collection
- Garbage collection (GC) is a process that reclaims unused memory during program execution.
- When a program allocates memory for objects (such as variables, data structures, or objects created dynamically), some of that memory may become unreachable (i.e., no longer referenced by any part of the program).
- The garbage collector identifies and frees up this unreferenced memory, making it available for future allocations.
Does implicit memory management really prevent #Memory Leaks from happening?
"No memory leaks" is a common argument given in support of automatic memory management. Although, with languages supporting garbage collection, you are leaking memory constantly. Since, you are allocating memory (you have some control there at least), but the language forces you to forget about the allocated memory. There always some garbage in the system inherently.
Why it is bad?
Although it doesn't necessarily cause much issues in the application itself. The application consumes more memory than necessary, leaving the memory, which could've been utilized by other processes, hogged.
Garbage collection is a selfish algorithm. As in, it cares only about the application. Unless it works closely with the OS and the OS can ask it to free up some memory, other processes may suffer from shortage of free memory.
Generational garbage collection
This technique uses the generational hypothesis to improve performance by reducing overhead. It involves:
- Gathering objects into generations
- Allocating new objects in the youngest generation
- Promoting objects to older generations if they survive
- Condemning objects in older generations less frequently
Old generation: This is a long-term storage area for objects that have survived multiple minor collections. The garbage collector performs occasional major collections in the old generation.
System(OS) Level Memory Management
Components used by OS for managing memory
Physical Memory
Physical memory is the actual hardware component installed in the system.
Virtual Memory
Memory management technique that allows the system to run larger applications or execute more programs than the available physical RAM would allow.
It is a conceptual layer on top of physical memory.
It is typically larger than the available physical #RAM. The illusion of a larger address space is what makes it “virtual.” How is this possible? Read on to find.
Virtual Address Space
Addresses of virtual memory locations -> also bigger than the physical address space.
Page table
Page tables store mappings between virtual addresses and physical addresses. These are also stored in #RAM, more specifically in #Kernel Space.
Pagefile (Swap space)
Space or file on disk (not RAM).
Processes involved in managing memory
Swapping
Swapping involves moving parts of a process’s memory between RAM (physical memory) and disk (usually a swap file or partition) at runtime.
Managing memory - Lifecycle of an address space
- The OS assigns virtual memory for the #Stack Space when a thread is created.
- Physical memory is assigned to that virtual memory on demand using the #Page table.
- The OS identifies “swappable” pages (memory chunks) based on their usage patterns. Basically, any memory chunk not being used that frequently or actively.
- These pages are swapped in and out as needed.
Swapping allows the OS to free up RAM for other processes but doing it frequently can degrade system performance - so there is a sweet spot to reach for the OS.
RAM
RAM holds the actively used portions of a process's memory during a program execution.
It provides fast access for executing instructions and storing data.
Kernel Space
- Higher part of the virtual memory address space.
- Contains the operating system kernel and its components (such as device drivers, file systems, and process management).
- Not directly accessible from a user process.
- Kernel code and data structures reside here.
- Handles privileged operations (e.g., hardware access, memory management, scheduling).
- Provides services to user processes via syscalls.
- Kernel stack is part of the kernel space and is used for handling interrupts and exceptions.
User Space
Space given to applications.
Heap Space
- For objects and data structures with longer lifespan.
- It is a dynamic memory pool provided by the OS which can be allocated and deallocated by the programs. Therefore, the applications have control of the heap memory. It is not managed by OS.
- Not thread-specific. All threads can access it => Not as safe as #Stack Space
- Heap memory can lead to memory leaks.
- Bigger than #Stack Space
- Can have #Manual Memory Management or #Implicit Memory Management depending upon the implementation. But even an implicit memory management strategy needs to have an entity (garbage collector) cleaning up the data.
- Prone to #Memory Leaks
Why choose Heap data structure for this memory space?
The Heap space doesn't actually adhere to the heap data structure. It is just called so because it is a free store where memory blocks are allocated and deallocated dynamically - like a "heap" of memory blocks.
Stack Space
Har thread ka ek stack. Har method ka ek stack frame.
- Used for static memory allocation.
- Used for local variables, function call frames, static thangs, with short lifespan.
- Limited size, more predictable.
- Automatically managed by OS
- Is thread safe - Each thread has its own stack.
Stack allocation
This is controlled by the OS. One stack is allocated to the program for each running thread.
Stack Size Estimation
- The OS allocates a stack for each system-level thread when the thread is created.
- The size of the stack is typically determined by the OS and compiler settings.
- For instance, on many systems, the default stack size might be around 1 MB to 2 MB.
- This default size is generally sufficient for most routine operations and function calls.
Dynamic Allocation and Swapping
- Some systems can automatically grow the stack if there’s room in the virtual address space.
Customization While the OS provides a default stack size, some applications or languages allow customization. Developers can adjust the stack size based on their specific requirements.
Be careful of Issues#Stack Overflow though.
Why choose Stack for this purpose?
The design decision by OS Gods to choose this data structure to represent this memory space comes from the below reasoning.
Each function call creates a new #Stack Frame on the stack, containing local variables, return addresses, and other bookkeeping information.
The LIFO (last in, first out) structure of the stack ensures that function calls are properly nested and managed.
Stack Frame
aka Activation Record
Stack frame is created for function calls. A new one is created each time a function is called.
Contains the following components/memory areas:
- Function Return Address:
- The return address (memory address) of the instruction to execute after the function call completes.
- Pushed onto the stack first.
- Function Arguments:
- Space for function arguments (parameters) passed to the method.
- Followed by any local variables.
- Local Variables:
- Variables declared within the method.
- Allocated space for these variables.
- Frame Pointer (Optional):
- Some architectures use a frame pointer (FP) to point to the start of the stack frame.
- Helps access local variables and function arguments efficiently.
- Not always present (depends on the calling convention).
Lifecycle of a Stack frame involves these stages:
- Function Call
- When a method is called, a new stack frame is pushed onto the call stack.
- The return address, arguments, and local variables are set up.
- Function Execution
- The method executes, accessing its local variables and performing computations.
- Function Return
- When the method completes, its stack frame is popped from the stack.
- The return address is used to continue execution from where the method was called.