Introduction
On Linux, each process is assigned its own independent virtual address space.
However, this virtual address space is not simply one large block of memory. It is divided into multiple regions for different purposes, such as executable code, global variables, and dynamically allocated memory, with each region serving a different role.
For example, understanding a process’s memory layout can answer questions such as:
- Where is a program’s executable code stored?
- Where is memory allocated with
malloc()ornewplaced? - Where are local variables stored?
- Why do memory leaks and stack overflows occur?
When troubleshooting Linux systems, you may also inspect a process’s memory usage with tools such as pmap and /proc/<PID>/maps. Understanding the purpose of each memory region is essential for interpreting their output correctly.
This article explains the roles and characteristics of the regions in a Linux process’s memory layout. Later, it also shows how to inspect the layout of an actual process using commands.
Linux Memory Layout
On Linux, each process is assigned an independent virtual address space.
This address space is divided into regions according to purpose. Executable code, global variables, and dynamically allocated memory are each managed in different regions.
A typical memory layout is shown below.

Each of these regions plays a different role while a program is running.
For example, the CPU reads instructions from the Text segment when executing a program. Global variables are stored in the Data or BSS segment, and a stack frame is created in the Stack segment whenever a function is called. Memory dynamically allocated with malloc() or new is allocated in the Heap segment.
Shared libraries such as libc.so and memory-mapped files are placed in the mmap region. Linux therefore achieves efficient and secure memory management by assigning different roles to separate regions instead of managing everything in one memory area.
All of these regions exist in the virtual address space. When the CPU accesses them, the MMU (Memory Management Unit) translates virtual addresses into physical addresses, allowing access to physical memory (RAM).

In the following sections, we will examine each memory region in detail.
For more information about virtual addresses and virtual memory, see the following article:Linux Virtual Memory Explained: Page Tables, MMU, TLB, Page Faults, and Swap
Characteristics of Each Memory Region
Let us take a closer look at each memory region.
Text Segment
The Text segment stores the program’s executable code (machine code).
Source code written in C, C++, or another language is converted by a compiler into machine code that the CPU can understand. This machine code is placed in the Text segment.
Consider the following program.
#include <stdio.h>
int main(void)
{
printf("Hello Linux\n");
return 0;
}
Compiling this source code generates a sequence of instructions for the CPU. Linux places those instructions in the Text segment, and the CPU executes the program by reading them from there in sequence.
The Text segment has the following characteristics:
- Stores executable program code
- Is normally read-only
- Is executable
- Can be shared by processes using the same executable
The ability to be shared by multiple processes is especially important.
For example, when multiple processes run the same web server, such as Apache or Nginx, each process does not need its own copy of the executable code. Linux reduces physical memory usage by sharing the Text segment.
Modifying instructions while a program is running could cause unexpected behavior or security problems. The Text segment is therefore normally protected as read-only.
Data Segment
The Data segment stores initialized global variables and static variables.
In the following program, count and value are placed in the Data segment.
#include <stdio.h>
int count = 100;
static int value = 10;
int main(void)
{
printf("%d %d\n", count, value);
return 0;
}
These variables are assigned their specified initial values when the program starts and remain available until it terminates.
The Data segment has the following characteristics:
- Stores initialized global variables
- Stores initialized
staticvariables - Memory is allocated when the program starts
- Its contents remain until the program terminates
Unlike local variables, variables in the Data segment are not released when a function returns. They are used for data that must be shared across the program or retained for a long time.
Common examples include access counters and configuration information used throughout the program’s execution.
BSS Segment
The BSS segment (Block Started by Symbol) stores uninitialized global and static variables.
In the following program, count and value are placed in the BSS segment.
#include <stdio.h>
int count;
static int value;
int main(void)
{
printf("%d %d\n", count, value);
return 0;
}
Although no initial values are specified, Linux automatically initializes these variables to zero when the program starts.
The BSS segment has the following characteristics:
- Stores uninitialized global variables
- Stores uninitialized
staticvariables - Is automatically initialized to zero when the program starts
- Remains available until the program terminates
Why separate the Data and BSS segments?
You might wonder why uninitialized variables are not simply included in the Data segment. Separating the two provides a major advantage.
Suppose you declare char buffer[1024 * 1024];. This variable is approximately 1 MB. If it were stored in the executable as part of the Data segment, the executable would also need to contain 1 MB of zero bytes, increasing its file size.
Linux instead manages uninitialized variables in the BSS segment and records only an instruction in the executable to reserve a BSS region of the required size. When the program starts, the Linux kernel—or, more precisely, the program loader—allocates the required memory and automatically initializes it to zero.
Heap Segment
The Heap is a region used to allocate memory dynamically while a program is running.
During development, the amount of memory a program will require may not be known before execution.
Examples include:
- Text entered by a user
- Data retrieved from a database
- Requests received by a web server
- Variable-length data structures such as lists and trees
The required amount of memory for such data is not known until runtime, so it is handled using the Heap.
In C, for example, memory is allocated with malloc().
#include <stdlib.h>
int main(void)
{
int *p = malloc(sizeof(int));
*p = 100;
free(p);
return 0;
}
In this example, the memory allocated by malloc() is placed in the Heap.
C++ uses the new operator.
class Person
{
public:
int age;
};
int main()
{
Person *p = new Person();
delete p;
}
Here too, the created object is placed in the Heap.
The Heap has the following characteristics:
- Allows a program to allocate as much memory as needed at runtime
- Can be used freely until the program terminates
- Must be explicitly released when no longer needed
- Repeated allocation and release can cause fragmentation
The Heap generally grows from lower addresses toward higher addresses.
The actual behavior may differ depending on virtual memory management and the allocator implementation, such as glibc’s malloc().
Java Heap
When using Java, you may often hear that the “Heap is running out of space.” This normally refers not to the Linux Heap, but to a lack of free space in the Java Heap managed by the JVM. The Linux Heap and the Java Heap are not the same thing.
For more information about Java memory, see:
Essential for Java Beginners: Understanding Memory Management and Garbage Collection from the Ground Up
Stack Segment
The Stack is a region used to manage information required for function calls. Each time a function is called, a stack frame is created on the Stack.
A stack frame mainly stores:
- Local variables
- Function arguments
- The return address
- Saved registers
Consider the following program.
#include <stdio.h>
void func(void)
{
int x = 100;
printf("%d\n", x);
}
int main(void)
{
func();
return 0;
}
In this example, the local variable x is placed on the Stack. A stack frame is created when func() is called and removed when func() returns. Local variables are therefore released automatically after the function ends.
The Stack has the following characteristics:
- Manages local variables
- Manages function-call information
- Is released automatically when a function returns
- Generally grows from higher addresses toward lower addresses
Unlike Heap memory, Stack memory does not require the programmer to write explicit deallocation code.
Heap vs. Stack Summary

| Item | Heap | Stack |
|---|---|---|
| Main purpose | Dynamic memory allocation | Function calls |
| Stores | Data allocated with malloc() or new | Local variables, arguments, and return addresses |
| Allocation timing | As needed during execution | Automatically when a function is called |
| Deallocation | free() or delete (GC in Java) | Automatically when the function returns |
| Size | Relatively large | Relatively small |
| Common problems | Memory leaks and fragmentation | Stack overflow |
mmap Region
The mmap region is used to manage shared libraries, memory-mapped files, and large dynamic memory allocations.
Linux uses the mmap region for purposes that cannot be managed efficiently through the Heap alone, such as shared libraries and large allocations.
Unlike the Text and Heap segments, mappings are created dynamically as needed with the mmap() system call. Multiple mmap regions may therefore be created while a process runs, and many mappings can be seen in /proc/<PID>/maps.
In a typical process memory layout, the mmap region lies between the Heap and the Stack.
Its main uses include:
- Loading shared libraries such as
libc.so - Memory-mapping files
- Allocating large amounts of dynamic memory
- Managing anonymous mappings
We will examine each use in more detail below.
Loading Shared Libraries
One of the most common uses of mmap is loading shared libraries.
For example, when a C program uses printf(), that functionality is provided by libc.so, the standard C library.
include
int main(void)
{
printf("Hello Linux\n");
return 0;
}
When the program runs, the dynamic linker loads the required shared libraries and places them in mmap regions.
Mapping a File into Memory
mmap is also used to map a file directly into memory.
Normally, a file is read using the read() system call.
Disk
│
read()
│
User buffer
With mmap(), however, a file can be mapped directly into the virtual address space.
File on disk
│
mmap()
│
mmap region
The data can then be read and written like ordinary memory, which makes it possible to handle large files efficiently.
This mechanism is widely used by software that handles large volumes of data, including databases, search engines, and caching systems.
Using mmap for Large Allocations
Although the Heap is used for dynamic allocation through malloc(), Linux may use mmap instead of the Heap for very large allocations.
For example, glibc’s malloc() internally selects a mechanism according to the requested size:
Relatively small allocation: Heap (brk())
Relatively large allocation: mmap region (mmap())
This approach makes it easier to return large freed allocations to the OS and helps reduce memory fragmentation.
The size considered “large” varies according to the glibc configuration and environment.
Common Memory Problems
This section introduces common memory-related problems.
Memory Leaks
A memory leak is an important concern when using the Heap. It occurs when memory that is no longer needed is not released, causing the amount of available memory to gradually decrease.
For example, the following code has a problem.
#include <stdlib.h>
void func(void)
{
int *p = malloc(sizeof(int));
*p = 100;
}
This program never calls free() on the memory allocated by malloc(). The variable p itself disappears when func() returns, but the allocated Heap memory remains. Repeating this operation causes Heap usage to keep growing and may eventually exhaust memory. In C and C++, memory allocated with malloc() or new must therefore be released with free() or delete as soon as it is no longer needed.
Stack Overflow
The Stack has a limited size. Allocating large arrays or making deeply nested recursive calls can exhaust it.
Consider the following code.
void func(void)
{
char buffer[1024 * 1024];
func();
}
Each recursive call creates a new stack frame, rapidly consuming the Stack. Eventually, the program exceeds the available Stack space and causes a stack overflow.
On Linux, the stack size can be checked with ulimit -s.
[root@localhost ~]# ulimit -s
8192
[root@localhost ~]#
In this example, the stack size is set to 8 MB.
Memory Fragmentation
Memory is repeatedly allocated and released on the Heap through malloc() and free().
After these operations have been repeated for a long time, freed memory may become scattered, making it difficult to allocate a large contiguous region. This phenomenon is called memory fragmentation.
Consider the following situation.

When free space is divided into many small areas, it may be impossible to allocate a large contiguous block of memory even though the total amount of free space is sufficient.
As fragmentation increases, allocation becomes less efficient and application performance may suffer. The overhead can be significant for long-running web servers, databases, and similar applications.
Modern Linux systems mitigate fragmentation in several ways. For example, glibc’s malloc() adapts its management strategy to memory usage and uses mmap() for large regions.
As a result, ordinary applications rarely need to account for fragmentation directly. Nevertheless, it can affect applications that repeatedly allocate and release large amounts of memory, so understanding it remains important.
Summary
This article explained the memory layout of a Linux process.
Linux assigns each process an independent virtual address space, which is divided into multiple memory regions according to purpose.
Understanding these roles makes it easier to understand common memory problems, including why memory leaks and stack overflows occur.
If you’d like to learn more about Linux memory management, I’ve covered the topic in detail in the following book. Please check it out if you’re interested.

Learn How Linux Works Through Visual Explanations
This book explains how Linux memory management works through clear, visual illustrations, making the concepts easy to understand even for beginners.


コメント