引言
内存管理是操作系统和大型应用的核心问题。频繁的malloc/free或new/delete不仅带来系统调用开销,还会产生大量内存碎片。内存池技术通过预先分配大块内存并自行管理,显著提升分配效率。本文用Java实现经典的伙伴系统(Buddy System)内存分配器。
伙伴系统原理
伙伴系统将内存划分为大小为2的幂次的块。当请求大小为k的内存时,系统分配不小于k的最小2^n块。若空闲块过大,则将其对半分割(产生两个”伙伴”),直到大小合适。释放时,检查相邻伙伴是否也空闲,若是则合并为更大的块。
核心规则
- 所有内存块大小为 2^n 字节(如 16, 32, 64, 128…)
- 第 i 级链表管理大小为 2^i 的空闲块
- 块的地址与其伙伴块地址只有第 i 位不同
Java实现
import java.util.*;
/**
* 内存块:伙伴系统的基本单元
*/
class MemoryBlock {
int start; // 起始地址
int size; // 块大小(2的幂)
boolean free; // 是否空闲
MemoryBlock buddy; // 伙伴块引用
MemoryBlock(int start, int size) {
this.start = start;
this.size = size;
this.free = true;
}
int getEnd() { return start + size; }
boolean isBuddyOf(MemoryBlock other) {
// 伙伴块判定:起始地址异或 size 等于对方起始地址,且同级
return this.size == other.size &&
(this.start ^ other.start) == this.size;
}
}
/**
* 伙伴系统内存池
*/
class BuddyAllocator {
private final int minBlockSize; // 最小块大小(如 16 字节)
private final int poolSize; // 总内存池大小
private final byte[] memory; // 实际内存存储
private final List<LinkedList<MemoryBlock>> freeLists; // 分级空闲链表
private final Map<Integer, MemoryBlock> blockMap; // 地址到块映射
public BuddyAllocator(int poolSize, int minBlockSize) {
// 确保poolSize和minBlockSize都是2的幂
this.poolSize = Integer.highestOneBit(poolSize);
this.minBlockSize = Integer.highestOneBit(minBlockSize);
this.memory = new byte[this.poolSize];
int levels = log2(this.poolSize) - log2(this.minBlockSize) + 1;
this.freeLists = new ArrayList<>(levels);
for (int i = 0; i < levels; i++) {
freeLists.add(new LinkedList<>());
}
this.blockMap = new HashMap<>();
// 初始化:将整个内存池作为一个大空闲块
MemoryBlock initial = new MemoryBlock(0, this.poolSize);
blockMap.put(0, initial);
freeLists.get(log2(this.poolSize) - log2(this.minBlockSize)).add(initial);
}
private int log2(int n) {
return 31 - Integer.numberOfLeadingZeros(n);
}
/**
* 分配指定大小的内存,返回起始地址(-1表示失败)
*/
public int allocate(int requestSize) {
if (requestSize <= 0 || requestSize > poolSize) return -1;
// 计算需要分配的块大小(不小于请求的最小2的幂)
int blockSize = minBlockSize;
while (blockSize < requestSize) blockSize <<= 1;
int level = log2(blockSize) - log2(minBlockSize);
int foundLevel = -1;
// 从当前级别向上查找可用的空闲块
for (int i = level; i < freeLists.size(); i++) {
if (!freeLists.get(i).isEmpty()) {
foundLevel = i;
break;
}
}
if (foundLevel == -1) return -1; // 内存不足
// 分裂大块直到目标级别
MemoryBlock block = freeLists.get(foundLevel).removeFirst();
block.free = false;
while (foundLevel > level) {
block = split(block);
foundLevel--;
}
return block.start;
}
/**
* 分裂块为两个伙伴
*/
private MemoryBlock split(MemoryBlock block) {
int newSize = block.size / 2;
// 创建伙伴块
MemoryBlock buddy = new MemoryBlock(block.start + newSize, newSize);
blockMap.put(buddy.start, buddy);
// 更新原块
block.size = newSize;
// 原块继续分配,伙伴块放入空闲链表
int buddyLevel = log2(newSize) - log2(minBlockSize);
freeLists.get(buddyLevel).add(buddy);
// 建立双向伙伴引用
block.buddy = buddy;
buddy.buddy = block;
return block;
}
/**
* 释放指定地址的内存
*/
public void free(int start) {
MemoryBlock block = blockMap.get(start);
if (block == null || block.free) return;
block.free = true;
int level = log2(block.size) - log2(minBlockSize);
// 尝试与伙伴合并
while (block.size < poolSize) {
MemoryBlock buddy = findBuddy(block);
if (buddy == null || !buddy.free) break;
// 移除伙伴的空闲记录
freeLists.get(level).remove(buddy);
// 合并:取起始地址较小的一方
if (buddy.start < block.start) {
MemoryBlock temp = block;
block = buddy;
buddy = temp;
}
block.size *= 2;
block.buddy = null;
blockMap.remove(buddy.start);
level++;
}
freeLists.get(level).add(block);
}
/**
* 根据地址计算伙伴块
*/
private MemoryBlock findBuddy(MemoryBlock block) {
int buddyStart = block.start ^ block.size;
return blockMap.get(buddyStart);
}
/**
* 打印内存状态
*/
public void printStatus() {
System.out.println("=== 内存池状态 ===");
for (int i = 0; i < freeLists.size(); i++) {
int size = minBlockSize << i;
System.out.printf("大小 %4d: %d 个空闲块%n", size, freeLists.get(i).size());
}
System.out.println("已分配块数: " + blockMap.values().stream().filter(b -> !b.free).count());
}
public int getPoolSize() { return poolSize; }
}
/**
* 测试入口
*/
public class BuddySystemDemo {
public static void main(String[] args) {
// 创建 1024 字节内存池,最小块 16 字节
BuddyAllocator allocator = new BuddyAllocator(1024, 16);
allocator.printStatus();
System.out.println("\n--- 分配 100 字节 ---");
int p1 = allocator.allocate(100); // 分配 128 字节块
System.out.println("分配地址: " + p1);
allocator.printStatus();
System.out.println("\n--- 分配 50 字节 ---");
int p2 = allocator.allocate(50); // 分配 64 字节块
System.out.println("分配地址: " + p2);
allocator.printStatus();
System.out.println("\n--- 释放 100 字节 ---");
allocator.free(p1);
allocator.printStatus();
System.out.println("\n--- 分配 200 字节 ---");
int p3 = allocator.allocate(200); // 分配 256 字节块
System.out.println("分配地址: " + p3);
allocator.printStatus();
System.out.println("\n--- 释放全部 ---");
allocator.free(p2);
allocator.free(p3);
allocator.printStatus();
}
}
复杂度分析
| 操作 | 时间复杂度 | 说明 |
|---|---|---|
| 分配 | O(log(max/min)) | 最多分裂 log 级 |
| 释放 | O(log(max/min)) | 最多合并 log 级 |
| 空间开销 | O(n) | n 为块数量,约 poolSize/minBlockSize |
内部碎片与外部碎片
- 内部碎片:分配的块大于请求大小,浪费空间。伙伴系统中最大浪费接近50%(请求刚好比2^n大1)
- 外部碎片:伙伴系统通过强制合并相邻空闲块,理论上不存在不可合并的外部碎片
总结
伙伴系统以其简洁的分裂-合并机制和O(log n)的分配复杂度,成为Linux内核等系统的经典内存管理方案。理解其原理有助于深入掌握操作系统的内存分配策略。