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


氷泠
420 声望647 粉丝