1.問題概述
通過這次試驗,加深對內存管理的認識,進一步掌握內存的分配、回收算法的思想。
設計程序模擬內存的動態分區法存儲管理。內存空閒區使用自由鏈管理,採用最壞適應算法從自由鏈中尋找空閒區進行分配
假定系統的內存共640K,初始狀態爲操作系統本身佔用64K。在t1時間之後,有作業A、B、C、D分別請求8K、16K、64K、124K的內存空間:在t2時間之後,作業C完成;在t3時間之後,作業E請求5K的內存空間;在t4時間之後,作業D完成。要求編程序分別輸出t1、t2、t3、t4(時刻內存的空閒區的狀態。
2.算法簡介
最差適應算法(worst-fit):它從全部空閒區中找出能滿足作業要求的、且大小最大的空閒分區,從而使鏈表中的節點大小趨於均勻。
具體參照:https://blog.csdn.net/u011070169/article/details/53177987?fps=1&locationNum=5
3.思路分析
最早出現的固定分區分配方式比較死板,易造成較多的內部碎片,太浪費資源,因此進一步的發展後出現了動態分區分配的方式,這種方式解決了前一種的主要弊端。接下來就是如何實現的問題了,主要是解決如何動態的分配內存以及回收。
本次實驗的分配方式採用最壞適應算法。該實驗基本思想雖然容易理解,但實現起來依舊是困難重重。首先要做的是確定數據的存儲結構,用了兩個鏈表分別存儲了已分配的和空閒的內存,然後初始化等等。接下來是實現分配算法,寫一個比較器,每次分配完內存後(將鏈表中第一個空閒塊分配出部分空間),都必須將空閒塊按從大到小的順序重新排列。這點並不難,難點在回收內存後如何合併新產生的空閒內存塊。這裏存在四種情況,新產生的空閒內存塊可能與它上面一塊空閒塊相鄰,也可能與它下面一塊空閒塊相鄰,還可能不與任何一塊相鄰,或者是同時與上下兩塊相鄰。
前三種情況也不是大問題,畫個圖也就出來了,但是最後一種情況就很讓我頭疼了。因爲鏈表中所有的空閒塊並不是物理意義上的有序排列,而是離散的,任何位置都是有可能的。一開始我的想法是把空閒鏈表從頭到尾掃描一次,通過比較首地址,很容易就能解決前三種情況,但是上下相鄰的情況就無從下手了,思路也因此陷入了僵局。後來我想到其實上下相鄰的情況必然是先經歷了與上相鄰和與下相鄰兩種情況。這樣一來,我先用兩個值分別記錄下前兩種情況下的空閒塊的位置,如果檢測到了前兩種情況發生,就接着執行第四種情況。
大方向定下了,接下來是一些細節的問題,比如說不與上下相鄰的情況不能放在循環體內,否則一旦執行就會不停添加新的空閒塊,造成死循環。還有在Java中,遍歷鏈表用迭代器會比容器內部下標遍歷效率高的多,但是用了迭代器就無法用容器自身的方法,這個細節讓我程序一直報錯,找了很久。
4.代碼實現
package AllocationAlg;
public class Busy {
String name;//進程名
int address;//分區起始地址
int len;//分區長度
public Busy(String name, int address, int len) {
super();
this.name = name;
this.address = address;
this.len = len;
}
public Busy() {
// TODO Auto-generated constructor stub
}
}
package AllocationAlg;
public class Free {
int address;//分區起始地址
int len;//分區長度
public Free(int address, int len) {
super();
this.address = address;
this.len = len;
}
}
package AllocationAlg;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;
public class Partition {
LinkedList<Free> freeLink;//空閒區隊列
LinkedList<Busy> busyLink;//已分配區隊列
Free free;
Busy busy;
public Partition() {
super();
start();
}
void start() {
freeLink=new LinkedList<Free>();
busyLink=new LinkedList<Busy>();
Busy os=new Busy("OS",0,64);//系統佔用區64K
busyLink.add(os);//加入已分配隊列
free=new Free(64,300);//初始的內存空閒區就一塊,大小300K,首地址64K
freeLink.add(free);//加入空閒區隊列
}
void requierMemo(String name,int require) {//模擬內存分配
if(require<=freeLink.get(0).len) {//可劃分內存給所請求分配的大小
int address=freeLink.get(0).address;//獲得該最大空閒區的首地址,從這開始分配require長度內存
freeLink.get(0).address=address+require;//更新該空閒區的首地址
freeLink.get(0).len=freeLink.get(0).len-require;//更新該空閒區的長度
busy=new Busy(name, address, require);//生成分配區間
busyLink.add(busy);//將該分區分配給請求者,加入已分配隊列
Collections.sort(freeLink, new compatorFree());//因爲原來最大的空閒區被劃分走一塊後可能不是最大了,必須重新排序
System.out.println("爲"+busy.name+"分配內存成功!");
}
else
System.out.println("當前無法找到足夠內存分配,請求失敗,請等待!");
}
void freeMemo(String name) {//模擬內存回收
Busy recycle=new Busy();//暫時存放要回收的內存的信息
/*如果該回收區分別與上下兩塊空閒區鄰接(第三種情況),下面算法必會先會依次執行一二兩種情況,導致回收區被重複計算,要消除該影響
就得先記錄下這兩塊已經完成操作的空閒區間位置,再把這兩塊合併即可(因爲兩者有重疊區域)*/
int freeUp=-1;//記錄上面相鄰接塊的位置,-1代表不鄰接
int freeDown=-1;//記錄下面相鄰接塊的位置,-1代表不鄰接
for(int i=0;i<busyLink.size();++i) {
busy=busyLink.get(i);
if(busy.name==name) {
recycle=busy;//先找到要回收的內存,保留信息
busyLink.remove(recycle);//如果用了迭代器,必須用它的方法增刪改查,容器的方法會失效
}
}
// for(ListIterator<Free> iter=freeLink.listIterator();iter.hasNext();){//迭代器效率O(1)遠高於隨機訪問O(N^2)
for(int i=0;i<freeLink.size();++i) {
free=freeLink.get(i);//獲得鏈表中指針指向的對象
/*第一種情況回收區與上面一個空閒區間相鄰接*/
if(recycle.address==(free.address+free.len)) {
freeUp=freeLink.indexOf(free);//記錄下該空閒區間的位置
free.len=free.len+recycle.len;//更新該空閒區間長度
freeLink.set(freeUp, free);
}
/*第二情況回收區與下面一個空閒區間相鄰接*/
if((recycle.address+recycle.len)==free.address) {
freeDown=freeLink.indexOf(free);//記錄下該空閒區間的位置
free.address=recycle.address;//更新該空閒區間首地址
free.len=recycle.len+free.len;//更新該空閒區間長度
freeLink.set(freeDown, free);
}
/*第三情況回收區分別與上下面空閒區間相鄰接*/
if(freeUp!=-1&&freeDown!=-1) {
Free freeUpObj=freeLink.get(freeUp);
Free freeDownObj=freeLink.get(freeDown);
/*不用多此一舉,前面已經決定了freeUp的首地址必然小於freeDown的首地址
if(freeUpObj.address<freeDownObj.address) {//確保低地址在前,高地址在後
Free temp;
temp=freeUpObj;freeDownObj=freeUpObj;freeUpObj=temp;
}*/
freeUpObj.len=freeUpObj.len+freeDownObj.len-recycle.len;//更新兩塊合併後的長度
freeLink.set(freeUp, freeUpObj);//修改鏈表中三塊合一後的最終空閒塊
freeLink.remove(freeDown);//下面這塊已經被上面那塊合併,把它從鏈表中刪除
}
}
/*第四情況回收區與空閒區間不相鄰接(注意:這情況不能放在上面的for循環內,否則會造成隊列不斷變大,死循環)*/
if(freeUp==-1&&freeDown==-1) {
Free addFree=new Free(recycle.address, recycle.len);
freeLink.addLast(addFree);
}
System.out.println("回收"+recycle.name+"的內存成功!");
Collections.sort(freeLink,new compatorFree());//長度改變需要重排
}
void printLink() {//輸出內存情況
System.out.println("********************************");
System.out.println("內存已分配區間情況:");
for(Iterator<Busy> iter=busyLink.iterator();iter.hasNext();) {
busy=(Busy)iter.next();
System.out.print(busy.name+":"+busy.address+"~"+(busy.address+busy.len)+" ");
}
System.out.println('\n'+"內存空閒區間信息,按從大到小排列:");
for(Iterator<Free> iter=freeLink.iterator();iter.hasNext();){//迭代器效率O(1)遠高於隨機訪問O(N^2)
free=(Free)iter.next();//獲得鏈表中下一個元素
System.out.print(free.address+"~"+(free.address+free.len)+" ");//空閒區間
}
System.out.println('\n'+"********************************");
}
}
class compatorFree implements Comparator<Free>{//按空閒區間從大到小排列
@Override
public int compare(Free o1, Free o2) {
return o1.len<o2.len?1:-1;
}
}
5.實驗結果
package AllocationAlg;
public class TestPartition {
public static void main(String[] args) {
Partition pa=new Partition();
pa.requierMemo("A", 20);pa.requierMemo("B",16);
pa.requierMemo("C", 64);pa.requierMemo("D", 124);
pa.printLink();
pa.freeMemo("C");pa.printLink();
pa.requierMemo("E", 50);pa.printLink();
pa.freeMemo("D");pa.printLink();
}
}