Java實現操作系統中四種動態內存分配算法:BF+NF+WF+FF


1 概述

本文是利用Java實現操作系統中的四種動態內存分配方式 ,分別是:

  • BF
  • NF
  • WF
  • FF

分兩部分,第一部分是介紹四種分配方式的概念以及例子,第二部分是代碼實現以及講解。

2 四種分配方式

2.1 概念

操作系統中有一個動態分區分配的概念,內存在初始化的時候不會划分區域,而是在進程裝入的時候,根據所要裝入的進程動態地對內存空間進行划分,以提高內存空間的利用率,降低碎片的大小,主要的方法有一下四種:

  • 首次適應算法(First Fit):從空閑分區鏈首開始查找,直到找到一個滿足其大小要求的空閑分區為止
  • 循環首次適應算法(Next Fit):從上次找到的空閑分區的下一個開始查找
  • 最佳適應算法(Best Fit):把空閑分區按大小遞增的方式形成分區鏈,找到第一個能滿足要求的空閑分區就進行分配
  • 最壞適應算法(Worst Fit):與最佳適應算法相反,把空閑分區按大小遞減的方式形成分區鏈,找到第一個能滿足要求的空閑分區就進行分配

2.2 例子

假設現在有100MB的內存空間,某一時刻先后分配了20MB4MB10MB內存,示意圖如下:

在這里插入圖片描述

現在需要再分配5MB內存。

若采用FF,因為FF是直接按順序分配內存,從低地址開始搜索空閑分區,因此便會從第一塊空閑分區分配5MB(地址0-5),示意圖:

在這里插入圖片描述

若采用NFNFFF類似,只不過NF是從上一次找到的空閑分區的下一塊開始查找,因為上一次分配的是10MB,因此會從最后一塊空閑分區(地址80-100)分配內存:

在這里插入圖片描述

若采用BFBF是遍歷所有空閑分區並找到一個能滿足要求的最小分區,也就會找到一個比5MB大的空閑分區,且該空閑分區是所有空閑分區中最小的,也就是地址為64-70的空閑分區:

在這里插入圖片描述

若采用WFWFBF相反,總是從最大的空閑分區開始分配,因此會從地址為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;
    }
}

只有一些GetterSetter,為了方便提供了一個創建空閑分區或已分配分區的靜態方法,指定起始地址和大小即可。

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

由於BFWF都需要遍歷所有的空閑分區,只是前者是選擇最小滿足要求的,后者是選擇最大滿足要求的,因此兩者的實現差別在於一個判斷大小的符號,代碼如下:

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 源碼

如果覺得文章好看,歡迎點贊。

同時歡迎關注微信公眾號:氷泠之路。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM