1 概述
本文是利用Java
實現操作系統中的四種動態內存分配方式 ,分別是:
BF
NF
WF
FF
分兩部分,第一部分是介紹四種分配方式的概念以及例子,第二部分是代碼實現以及講解。
2 四種分配方式
2.1 概念
操作系統中有一個動態分區分配的概念,內存在初始化的時候不會划分區域,而是在進程裝入的時候,根據所要裝入的進程動態地對內存空間進行划分,以提高內存空間的利用率,降低碎片的大小,主要的方法有一下四種:
- 首次適應算法(
First Fit
):從空閑分區鏈首開始查找,直到找到一個滿足其大小要求的空閑分區為止 - 循環首次適應算法(
Next Fit
):從上次找到的空閑分區的下一個開始查找 - 最佳適應算法(
Best Fit
):把空閑分區按大小遞增的方式形成分區鏈,找到第一個能滿足要求的空閑分區就進行分配 - 最壞適應算法(
Worst Fit
):與最佳適應算法相反,把空閑分區按大小遞減的方式形成分區鏈,找到第一個能滿足要求的空閑分區就進行分配
2.2 例子
假設現在有100MB
的內存空間,某一時刻先后分配了20MB
、4MB
、10MB
內存,示意圖如下:
現在需要再分配5MB
內存。
若采用FF
,因為FF
是直接按順序分配內存,從低地址開始搜索空閑分區,因此便會從第一塊空閑分區分配5MB
(地址0-5
),示意圖:
若采用NF
,NF
與FF
類似,只不過NF
是從上一次找到的空閑分區的下一塊開始查找,因為上一次分配的是10MB
,因此會從最后一塊空閑分區(地址80-100
)分配內存:
若采用BF
,BF
是遍歷所有空閑分區並找到一個能滿足要求的最小分區,也就會找到一個比5MB
大的空閑分區,且該空閑分區是所有空閑分區中最小的,也就是地址為64-70
的空閑分區:
若采用WF
,WF
與BF
相反,總是從最大的空閑分區開始分配,因此會從地址為30-60
的空閑分區進行分配:
3 代碼實現
3.1 總覽
代碼分成了四個類:
Main
:測試Print
:輸出打印Table
:表示每一個分區TableList
:對分區進行控制,包括初始化,分配,回收等
3.2 Main
Main
是測試類,代碼如下:
public class Main {
private final static TableList list = new TableList(64);
public static void main(String[] args) {
list.useWF();
// list.useBF();
// list.useNF();
// list.useFF();
list.allocate(10);
list.allocate(20);
list.free(10);
list.show();
list.allocate(8);
list.show();
list.allocate(13);
list.allocate(1);
list.show();
list.free(1);
list.allocate(9);
list.free(13);
list.show();
list.allocate(18);
list.show();
list.allocate(3);
list.allocate(4);
list.free(20);
list.free(8);
list.show();
list.allocate(8);
list.free(9);
list.show();
list.clear();
list.show();
}
}
通過TableList
對內存進行分配以及釋放,初始化分配64MB
大小內存,切換分配算法時使用前四行的其中一行即可。
3.3 Table
Table
類表示每一個分區,無論是空閑的還是已分配的,成員變量有四個,分別是:
- 起始地址
- 大小
- 是否空閑(只有兩種狀態,空閑或分配)
- 是否是上一次分配(
NF
專用)
代碼如下:
@AllArgsConstructor
public class Table {
@Getter
@Setter
private int address;
@Setter
@Getter
private int size;
private boolean free;
@Getter
@Setter
private boolean lastAllocated;
public static Table freeTable(int address,int size)
{
return new Table(address,size,true,false);
}
public static Table allocatedTable(int address,int size)
{
return new Table(address,size,false,false);
}
public boolean isFree()
{
return free;
}
public boolean isAllocated()
{
return !isFree();
}
public void setFree()
{
free = true;
}
}
只有一些Getter
和Setter
,為了方便提供了一個創建空閑分區或已分配分區的靜態方法,指定起始地址和大小即可。
3.4 TableList
TableList
是整個算法的核心類,成員變量如下:
private final List<Table> list = new ArrayList<>();
private final int totalSize;
private boolean ff = false;
private boolean nf = false;
private boolean bf = false;
private boolean wf = false;
private boolean first = true;
private final static Print print = new Print();
list
就是所有的空閑分區與已分配分區組成的數組,totalSize
是總大小,接着是四個控制算法的布爾變量,first
表示是否是第一次分配內存,因為第一次的話四種算法都是固定的從地址為0
處開始分配。
接下來就是內存分配算法以及釋放算法。
3.4.1 FF
if (ff)
{
for (int i = 0; i < list.size(); i++) {
Table table = list.get(i);
if(table.isFree() && table.getSize() >= size)
{
int address = table.getAddress();
Table allocated = Table.allocatedTable(address,size);
table.setAddress(address+size);
table.setSize(table.getSize()-size);
list.add(i,allocated);
return;
}
}
}
FF
的實現還是比較簡單的,直接遍歷列表,如果是空閑分區並滿足大小要求,直接進行分配,修改空閑分區的起始地址和大小並插入一個新的已分配分區到列表中即可。
3.4.2 NF
else if (nf)
{
int lastNFIndex = findLastAllocated();
int i = lastNFIndex;
do
{
if(i == list.size())
i = 0;
Table table = list.get(i);
if(table.isFree() && table.getSize() >= size)
{
int address = table.getAddress();
Table allocated = Table.allocatedTable(address,size);
table.setAddress(address+size);
table.setSize(table.getSize()-size);
list.get(lastNFIndex).setLastAllocated(false);
table.setLastAllocated(true);
list.add(i,allocated);
return;
}
++i;
}
while (i != lastNFIndex);
}
NF
的話需要提前記錄上一次分配的位置,通過Table
中的lastAllocated
確定上一次分配的位置,找到后從該位置開始遍歷列表,注意需要進行繞回處理,因為到末尾位置后有可能還沒有能滿足的空閑分區,此時需要將下標繞回到0
並再次遍歷直到到達上一次分配的位置。
3.4.3 BF
+WF
由於BF
與WF
都需要遍歷所有的空閑分區,只是前者是選擇最小滿足要求的,后者是選擇最大滿足要求的,因此兩者的實現差別在於一個判斷大小的符號,代碼如下:
else
{
int i;
int target = -1;
for (i = 0; i < list.size(); i++) {
Table table = list.get(i);
if(table.isFree())
{
if(table.getSize() >= size)
{
if(target == -1)
target = i;
else
{
if(bf)
{
if(list.get(target).getSize() > table.getSize())
target = i;
}
else
{
if(list.get(target).getSize() < table.getSize())
target = i;
}
}
}
}
}
if(target != -1)
{
Table table = list.get(target);
int address = table.getAddress();
table.setAddress(address+size);
table.setSize(table.getSize()-size);
list.add(target,Table.allocatedTable(address,size));
return;
}
}
首先遍歷找到符合條件的空閑分區的下標,接着通過判斷target
,也就是目標空閑分區的下標,如果為-1
表示沒有找到符合條件的空閑分區,如果不為-1
直接分配空間。
3.4.4 釋放算法
釋放算法的設計是比較復雜的,代碼如下:
public void free(int size)
{
int index = 0;
while(index < list.size())
{
if(list.get(index).isAllocated() && list.get(index).getSize() == size)
break;
++index;
}
if(index >= list.size())
{
print.freeFailed(size);
return;
}
int address = list.get(index).getAddress();
if(index == 0)
{
list.get(0).setFree();
if(index+1 < list.size())
{
Table nextTable = list.get(index+1);
if(nextTable.isFree())
{
list.get(0).setSize(nextTable.getSize()+size);
list.remove(index+1);
}
}
}
else if(index == list.size()-1)
{
list.get(index).setFree();
Table lastTable = list.get(index-1);
if(lastTable.isFree())
{
lastTable.setSize(lastTable.getSize()+size);
list.remove(index);
}
}
else
{
Table before = list.get(index-1);
Table after = list.get(index+1);
if(before.isFree() && after.isFree())
{
before.setSize(before.getSize()+size+after.getSize());
list.remove(index+1);
list.remove(index);
}
else if(before.isFree() && after.isAllocated())
{
before.setSize(before.getSize()+size);
list.remove(index);
}
else if(before.isAllocated() && after.isFree())
{
after.setSize(after.getSize()+size);
after.setAddress(address);
list.remove(index);
}
else
{
list.get(index).setFree();
}
}
}
主要考慮了六種情況(黃色代表需要釋放的空間,橙色是已分配的內存空間):
- 第一種情況就是需要釋放首部的分區,此時需要修改后面空閑分區的起始地址和大小,並刪除目標分區
- 第二種情況是釋放尾部的分區,此時需要修改前面空閑分區的大小即可,無需修改起始地址,並刪除目標分區
- 第三種情況是后面是已分配的分區,前面的空閑分區,需要修改前面空閑分區的大小,並刪除目標分區
- 第四種情況是前面是已分配的分區,后面是空閑分區,需要修改后面的空閑分區的起始地址以及大小,並刪除目標分區
- 第五種情況是前后都是已分配的分區,此時只需要修改目標分區的標志為空閑即可,無需額外操作
- 第六種情況是前后都是空閑分區,這種情況下需要進行連接操作,具體來說就是先修改前面空閑分區的大小,接着刪除目標分區以及后面的空閑分區
下面回到代碼,首先是判斷第一種情況:
if(index == 0)
{
list.get(0).setFree();
if(index+1 < list.size())
{
Table nextTable = list.get(index+1);
if(nextTable.isFree())
{
list.get(0).setSize(nextTable.getSize()+size);
list.remove(index+1);
}
}
}
也就是需要釋放首部的分區,通過setFree()
設置標志位表示空閑狀態,接着判斷是否需要修改后面空閑分區的大小,因為有可能后面是一個已分配的分區而不是空閑分區。
else if(index == list.size()-1)
{
list.get(index).setFree();
Table lastTable = list.get(index-1);
if(lastTable.isFree())
{
lastTable.setSize(lastTable.getSize()+size);
list.remove(index);
}
}
這里是判斷第二種情況,也就是釋放尾部的分區,同樣需要判斷前一個分區是已分配的分區還是空閑的分區,是空閑分區的話修改大小並移除目標分區。
else
{
Table before = list.get(index-1);
Table after = list.get(index+1);
if(before.isFree() && after.isFree())
{
before.setSize(before.getSize()+size+after.getSize());
list.remove(index+1);
list.remove(index);
}
else if(before.isFree() && after.isAllocated())
{
before.setSize(before.getSize()+size);
list.remove(index);
}
else if(before.isAllocated() && after.isFree())
{
after.setSize(after.getSize()+size);
after.setAddress(address);
list.remove(index);
}
else
{
list.get(index).setFree();
}
}
接下來是最后四種情況的判斷,首先獲取前一個以及后一個分區,接着按上面算法的思路進行判斷即可。
4 測試
以WF
為例,默認大小64MB
,測試順序如下:
- 分配
10MB
- 分配
20MB
- 釋放
10MB
- 打印結果
- 分配
8MB
- 打印結果
- 分配
13MB
- 分配
1MB
- 打印結果
- 釋放
1MB
- 分配
9MB
- 釋放
13MB
- 打印結果
- 分配
18MB
- 打印結果
- 分配
3MB
- 分配
4MB
- 釋放
20MB
- 釋放
8MB
- 打印結果
- 分配
8MB
- 釋放
9MB
- 打印結果
- 清空
- 打印結果
輸出:
Free : 0-10MB
Allocated : 10-30MB
Free : 30-64MB
----------------------------------------------------------------
Free : 0-10MB
Allocated : 10-30MB
Allocated : 30-38MB
Free : 38-64MB
----------------------------------------------------------------
Free : 0-10MB
Allocated : 10-30MB
Allocated : 30-38MB
Allocated : 38-51MB
Allocated : 51-52MB
Free : 52-64MB
----------------------------------------------------------------
Free : 0-10MB
Allocated : 10-30MB
Allocated : 30-38MB
Free : 38-51MB
Allocated : 51-60MB
Free : 60-64MB
----------------------------------------------------------------
Do nothing.
Allocated failed, out of memory
Free : 0-10MB
Allocated : 10-30MB
Allocated : 30-38MB
Free : 38-51MB
Allocated : 51-60MB
Free : 60-64MB
----------------------------------------------------------------
Allocated : 0-4MB
Free : 4-38MB
Allocated : 38-41MB
Free : 41-51MB
Allocated : 51-60MB
Free : 60-64MB
----------------------------------------------------------------
Allocated : 0-4MB
Allocated : 4-12MB
Free : 12-38MB
Allocated : 38-41MB
Free : 41-64MB
----------------------------------------------------------------
Free : 0-64MB
----------------------------------------------------------------
讀者可以自行畫圖驗證。
5 源碼
如果覺得文章好看,歡迎點贊。
同時歡迎關注微信公眾號:氷泠之路。