Memory management is the process of controlling and coordinating the memory, assigning portions called blocks to various running programs to optimize overall system performance. Memory management resides in hardware, in the OS (operating system), and in programs and applications. Memory management is a crucial thing in order to manage server performance. The servers are always expected to function 24/7 without any hints of delay or underperformance. Hence, accumulating data in an organized manner is a symbol of better conduct. A third party client always anticipates for whatever it has requested and wouldn’t wait for longer than a few seconds. The server, therefore, has to manage somehow that how it would allocate the free space, from where it would bring that free space, and so on.
Why memory management is important?
Memory management requires that the programmer provides ways to dynamically allocate portions of memory to programs when requested, and free it for reuse when it is no longer needed. When any program is loaded into the memory, it is organized into three parts called segments. These are -
The text segment is where the compiled code of the program itself resides. This is the machine language representation of the program steps to be carried out, including all functions making up the program, both user and system defined. So, in general, an executable program generated by a compiler (like GCC) will have memory organization. This segment is categorized as - Code segment which contains the code (executable or code binary). And Data segment divided into two parts - Initialised data segment where all the global, static and constant data are stored in the data segment. And uninitialized data segment where all the uninitialized data are stored in the Block Started by Symbol (BSS).
The stack is used to store local variables, and for passing arguments to the functions along with the return address of the instruction that is to be executed after the function call is over. When a new stack frame needs to be added (as a result of a newly called function), the stack grows downwards.
When the program allocates memory at runtime using the calloc and malloc functions, then memory gets allocated in heap. When some more memory needs to be allocated using the calloc and malloc functions, heap grows upwards.
Let's see the memory management -
There are two ways in which memory can be allocated in C -
By declaring variables, and by explicitly requesting space from C.
The C programming language provides several functions for memory allocation and management. These functions can be found in the <stdlib.h> header file.
The functions are -
void *calloc(int num, int size) - This function allocates an array of num elements. The size of each element in bytes will be size.
void free(void *address) - This function releases a memory block specified by an address.
void *malloc(int num) - This function allocates an array of num bytes and leaves them initialized.
void *realloc(void *address, int newsize) - This function reallocates memory extending it up to newsize.
Java has automatic memory management, a nice and quiet garbage collector that works in the background to clean up the unused objects and free up some memory.
In Java, generally, memory is divided into two big parts: the stack and the heap.
Stack memory is responsible for holding references to heap objects and for storing value types (also known in Java as primitive types), which hold the value itself rather than a reference to an object from the heap. In addition, variables on the stack have certain visibility, also called scope. Only objects from the active scope are used. Once the method completes and returns, the top of the stack pops out, and the active scope changes. The fact that the stack memory in Java is allocated per Thread. Therefore, each time a Thread is created and started, it has its own stack memory - and cannot access another thread’s stack memory.
This part of memory stores the actual object in memory. Those are referenced by the variables from the stack. The new keyword is responsible for ensuring that there is enough free space on the heap, creating an object of the StringBuilder type in memory and referring to it via the “builder” reference, which goes on the stack. There exists only one heap memory for each running JVM process. Therefore, this is a shared part of memory regardless of how many threads are running. Actually, the heap structure is a bit different than it is shown in the picture above. The heap itself is divided into a few parts, which facilitates the process of garbage collection.
In the Java programming language, we have different types of references: strong, weak, soft, and phantom references.
There are several implementations running on a variety of virtual machines: the original “CPython” implementation runs on its own virtual machine; IronPython runs on the Common Language Runtime; Jython on the Java Virtual Machine. CPython manages memory using a mixture of reference counting and non-moving mark-and-sweep garbage collection. Reference counting ensures prompt deletion of objects when their reference count falls to zero, while the garbage collector reclaims cyclic data structures.
The language supports finalization (classes may have a __del__ method, which is run just before the object is destroyed), and weak references (via the weakref module).
C++ is a (weakly) object-oriented language, extending the systems programming language C with a multiple-inheritance class mechanism and simple method dispatch. The standard library functions for memory management in C++ are new and delete. The higher abstraction level of C++ makes the bookkeeping required for manual memory management even harder. Although the standard library provides only manual memory management, with the Memory Pool System, or the Boehm–Demers–Weiser collector, it is now possible to use garbage collection. Smart pointers are another popular solution. The language is notorious for fostering memory management bugs, including - Using stack-allocated structures beyond their lifetimes (see use after free); Using heap-allocated structures after freeing them (see use after free); Neglecting to free heap-allocated objects when they are no longer required (see memory leak); Excessive copying by copy constructors (1); Unexpected sharing due to insufficient copying by copy constructors; Allocating insufficient memory for the intended contents; Accessing arrays with indexes that are out of bounds.