Internal Fragmentation in OS
Waste sealed inside a block you were given. You asked for 5 KB, received two 4 KB pages, and 3 KB of what you hold can never be used.
What Internal Fragmentation in OS Means
Internal fragmentation in OS is memory that has been allocated to a process but cannot be used by it. The process holds the space; the space serves no purpose.
It appears whenever an allocator rounds a request up to a fixed unit. Fixed-size units are what make allocation cheap — the allocator never has to track odd sizes — and the rounding is the price.
Note carefully what distinguishes it from the other kind. The wasted memory is not sitting between blocks where another process might eventually claim it. It is sealed inside a block that is already spoken for, and it stays wasted until that whole block is freed.
- Cause: rounding a request up to a fixed allocation unit
- Location: inside an allocated block
- Visibility: counted as used, so it hides inside utilisation figures
Internal Fragmentation Example
A system uses 4 KB pages. A process requests 5 KB.
5 KB does not fit in one page, so the allocator grants two pages = 8 KB. The first page is entirely used. The second page holds just 1 KB of real data.
The remaining 8 − 5 = 3 KB is internal fragmentation. It belongs to this process, it is charged against it, and neither this process nor any other can put anything there.
Now change the request to 8 KB exactly: two full pages, zero waste. Change it to 9 KB: three pages, 3 KB wasted again. The waste depends entirely on how close the request lands to a page boundary — which is why the average case is the useful figure, not the worst one.
- Granted: 8 KB · Used: 5 KB · Wasted: 3 KB
- Worst case per allocation: one byte short of a full page
- Average case: half a page ≈ 2 KB at a 4 KB page size
Internal vs External Fragmentation, and Why the Bound Matters
The decisive property of internal fragmentation is that it is bounded. A request wastes at most one unit minus one byte, and across many requests the average settles at half a unit. With 4 KB pages that is roughly 2 KB per allocation — predictable, and it does not compound as the system runs.
External fragmentation has no such bound. Free memory scatters into ever-shorter runs, and a request can fail while far more than enough memory sits free. That difference is the entire argument for paging: it accepts a small capped waste in order to eliminate an unbounded one.
Page size is the dial. Smaller pages waste less per allocation but need a larger page table and put more pressure on the TLB; larger pages do the reverse. 4 KB has been the common compromise for decades, with large-page options for workloads that prefer the other side of the trade.
- Internal: inside a block · bounded · caused by rounding up
- External: between blocks · unbounded · caused by variable-sized churn
- Paging trades the second for the first, deliberately
Internal fragmentation — 14 KB requested, 20 KB granted, 6 KB stranded
"""Internal fragmentation: memory granted minus memory actually used."""
import math
PAGE_SIZE_KB = 4
def pages_needed(request_kb, page_size_kb=PAGE_SIZE_KB):
"""Whole pages required to hold a request of this size."""
return math.ceil(request_kb / page_size_kb)
def internal_fragmentation(request_kb, page_size_kb=PAGE_SIZE_KB):
"""KB granted to the process that it cannot use."""
granted = pages_needed(request_kb, page_size_kb) * page_size_kb
return granted - request_kb
def main():
for request in (5, 8, 9):
pages = pages_needed(request)
granted = pages * PAGE_SIZE_KB
wasted = internal_fragmentation(request)
print(f"{request:2d} KB -> {pages} pages = {granted:2d} KB granted, "
f"{wasted} KB wasted")
worst = PAGE_SIZE_KB - 1
print(f"worst case per allocation: {worst} KB")
print(f"average across many requests: about {PAGE_SIZE_KB / 2} KB")
if __name__ == "__main__":
main()
// Internal fragmentation: memory granted minus memory actually used.
#include <iostream>
constexpr int kPageSizeKb = 4;
// Whole pages required to hold a request of this size.
int pagesNeeded(int requestKb, int pageSizeKb = kPageSizeKb) {
return (requestKb + pageSizeKb - 1) / pageSizeKb; // integer ceiling
}
// KB granted to the process that it cannot use.
int internalFragmentation(int requestKb, int pageSizeKb = kPageSizeKb) {
return pagesNeeded(requestKb, pageSizeKb) * pageSizeKb - requestKb;
}
int main() {
for (int request : {5, 8, 9}) {
const int pages = pagesNeeded(request);
std::cout << request << " KB -> " << pages << " pages = "
<< pages * kPageSizeKb << " KB granted, "
<< internalFragmentation(request) << " KB wasted\n";
}
std::cout << "worst case per allocation: " << kPageSizeKb - 1 << " KB\n";
}// Internal fragmentation: memory granted minus memory actually used.
public class InternalFragmentation {
static final int PAGE_SIZE_KB = 4;
/** Whole pages required to hold a request of this size. */
static int pagesNeeded(int requestKb) {
return (requestKb + PAGE_SIZE_KB - 1) / PAGE_SIZE_KB; // integer ceiling
}
/** KB granted to the process that it cannot use. */
static int internalFragmentation(int requestKb) {
return pagesNeeded(requestKb) * PAGE_SIZE_KB - requestKb;
}
public static void main(String[] args) {
for (int request : new int[]{5, 8, 9}) {
int pages = pagesNeeded(request);
System.out.printf("%2d KB -> %d pages = %2d KB granted, %d KB wasted%n",
request, pages, pages * PAGE_SIZE_KB,
internalFragmentation(request));
}
System.out.println("worst case per allocation: "
+ (PAGE_SIZE_KB - 1) + " KB");
}
}Step through it
Running on 32 KB region, 4 KB pages; A requests 8 KB, B requests 5 KB, C requests 1 KB
Read all 8 Steps
- a 32 KB region divided into 4 KB pages Memory is handed out in whole 4 KB pages — eight of them here. A process can never receive part of a page, and that single rule is the entire cause of internal fragmentation.
- process A requests 8 KB — a perfect fit 8 KB needs exactly two pages. Both are completely filled, so A wastes nothing. A request that lands on a page boundary is the best case.
- process B requests 5 KB — rounded up to 8 KB 5 KB does not fit in one page, so B is granted two pages = 8 KB. Only 5 KB holds real data.
- 3 KB of B's allocation is wasted B holds 8 KB and uses 5 KB. The other 3 KB sits inside B's own block — it cannot be given to anyone else, and it stays wasted until B exits. That is internal fragmentation.
- process C requests 1 KB — a whole page anyway Even a 1 KB request consumes a full 4 KB page, wasting 3 KB. The waste does not depend on how much you ask for; it depends on how far past a page boundary you land.
- the total cost so far Three processes asked for 8 + 5 + 1 = 14 KB and were granted 8 + 8 + 4 = 20 KB. 6 KB is wasted inside their blocks. Meanwhile 12 KB is genuinely free and fully usable — the waste is not there.
- why the waste is bounded Each allocation wastes at most one page minus one byte — 3 KB here. It cannot compound: no matter how long the system runs, the waste stays proportional to the number of live allocations, averaging half a page each. External fragmentation has no such ceiling.
- smaller pages would waste less With 1 KB pages, B would receive exactly 5 KB and C exactly 1 KB, wasting nothing. But eight times as many page-table entries would be needed, and the TLB would hold a smaller share of the address space. 4 KB is the long-standing compromise.