Java实现操作系统中四种动态内存分配算法:BF+NF+WF+FF

Wesley13
• 阅读 595

1 概述

本文是利用Java实现操作系统中的四种动态内存分配方式 ,分别是:

  • BF
  • NF
  • WF
  • FF

分两部分,第一部分是介绍四种分配方式的概念以及例子,第二部分是代码实现以及讲解。

2 四种分配方式

2.1 概念

操作系统中有一个动态分区分配的概念,内存在初始化的时候不会划分区域,而是在进程装入的时候,根据所要装入的进程动态地对内存空间进行划分,以提高内存空间的利用率,降低碎片的大小,主要的方法有一下四种:

  • 首次适应算法(First Fit):从空闲分区链首开始查找,直到找到一个满足其大小要求的空闲分区为止
  • 循环首次适应算法(Next Fit):从上次找到的空闲分区的下一个开始查找
  • 最佳适应算法(Best Fit):把空闲分区按大小递增的方式形成分区链,找到第一个能满足要求的空闲分区就进行分配
  • 最坏适应算法(Worst Fit):与最佳适应算法相反,把空闲分区按大小递减的方式形成分区链,找到第一个能满足要求的空闲分区就进行分配

2.2 例子

假设现在有100MB的内存空间,某一时刻先后分配了20MB4MB10MB内存,示意图如下:

Java实现操作系统中四种动态内存分配算法:BF+NF+WF+FF

现在需要再分配5MB内存。

若采用FF,因为FF是直接按顺序分配内存,从低地址开始搜索空闲分区,因此便会从第一块空闲分区分配5MB(地址0-5),示意图:

Java实现操作系统中四种动态内存分配算法:BF+NF+WF+FF

若采用NFNFFF类似,只不过NF是从上一次找到的空闲分区的下一块开始查找,因为上一次分配的是10MB,因此会从最后一块空闲分区(地址80-100)分配内存:

Java实现操作系统中四种动态内存分配算法:BF+NF+WF+FF

若采用BFBF是遍历所有空闲分区并找到一个能满足要求的最小分区,也就会找到一个比5MB大的空闲分区,且该空闲分区是所有空闲分区中最小的,也就是地址为64-70的空闲分区:

Java实现操作系统中四种动态内存分配算法:BF+NF+WF+FF

若采用WFWFBF相反,总是从最大的空闲分区开始分配,因此会从地址为30-60的空闲分区进行分配:

Java实现操作系统中四种动态内存分配算法:BF+NF+WF+FF

3 代码实现

3.1 总览

代码分成了四个类:

Java实现操作系统中四种动态内存分配算法:BF+NF+WF+FF

  • 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();
        }
    }
}

主要考虑了六种情况(黄色代表需要释放的空间,橙色是已分配的内存空间):

Java实现操作系统中四种动态内存分配算法:BF+NF+WF+FF

  • 第一种情况就是需要释放首部的分区,此时需要修改后面空闲分区的起始地址和大小,并删除目标分区
  • 第二种情况是释放尾部的分区,此时需要修改前面空闲分区的大小即可,无需修改起始地址,并删除目标分区
  • 第三种情况是后面是已分配的分区,前面的空闲分区,需要修改前面空闲分区的大小,并删除目标分区
  • 第四种情况是前面是已分配的分区,后面是空闲分区,需要修改后面的空闲分区的起始地址以及大小,并删除目标分区
  • 第五种情况是前后都是已分配的分区,此时只需要修改目标分区的标志为空闲即可,无需额外操作
  • 第六种情况是前后都是空闲分区,这种情况下需要进行连接操作,具体来说就是先修改前面空闲分区的大小,接着删除目标分区以及后面的空闲分区

下面回到代码,首先是判断第一种情况:

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

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
3年前
Java日期时间API系列31
  时间戳是指格林威治时间1970年01月01日00时00分00秒起至现在的总毫秒数,是所有时间的基础,其他时间可以通过时间戳转换得到。Java中本来已经有相关获取时间戳的方法,Java8后增加新的类Instant等专用于处理时间戳问题。 1获取时间戳的方法和性能对比1.1获取时间戳方法Java8以前
Stella981 Stella981
3年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这