簡易JVM的實現思路:C++實現

編譯器前端:

1.詞法分析:由Scanner.cc將char流轉換成一個個token給Parser.cc消費,Scanner轉換的過程中可以甄別出非法的token類型,將其打上tag=error的標籤。

2.語法分析:Parser.cc收到一個個的token進行語法進行語法分析,將其轉換成AST : List<Statmt>。設置兩個緩衝區存儲token流,Que<Token>tokenbuffer1,Que<Token>tokenbuffer2,大小不超過10.

      getToken時,先判斷tokenbuffer2中有沒有token,如果有,則優先拿tokenbuffer2中的token。當tokenbuffer1大小超過10時,從tokenbuffer1的前端pop出一個值。這樣保證緩存區中的大小爲10;

      ungetToken時,tokenbuffer1中肯定是有token的,如果沒有,則報錯。

      爲什麼要設置兩個buffer呢?一個buffer不就可以了嗎?

不可以,設置兩個緩衝區的目的是爲了同步。當我unget兩個token的時候,Scanner的指針已經在兩個token之後的位置了,但是此時Scanner指針是不能回退的,設置buffer2的目的是爲了保存unget的結果,同時可以讓Scanner一直往後移動,不回頭。

Scanner::Token Parser::getToken() {
  if (tokenBuffer2.size() == 0) {
    auto token = scanner.nextToken();
    tokenBuffer1.push_back(token);
    if (tokenBuffer1.size() > 10)
      tokenBuffer1.pop_front();
  } else {
    auto token = tokenBuffer2.front();
    tokenBuffer1.push_back(token);
    tokenBuffer2.pop_front();
  }
  return tokenBuffer1.back();
}

Scanner::Token Parser::ungetToken() {
  assert(tokenBuffer1.size() > 0);
  auto token = tokenBuffer1.back();
  tokenBuffer2.push_front(token);
  tokenBuffer1.pop_back();
  return tokenBuffer1.back();
}

3.採用Visitor模式遍歷AST,進行變量的引用消解,將變量的引用定位的局部/全局變量表的具體位置,並進行類型的檢查,比如將int型賦值給String變量,即保證“=”兩邊的類型要一致。當然,也可以不用Visitor模式,直接給每個Statmt打上標籤,就可以判斷出Statmt是屬於哪個Stat類型,比如說賦值Statmt,函數調用Statmt。

4.將語法分析好的AST樹交給後端處理。

編譯器後端:

1.後端接到AST後,將節點中的信息分配到虛擬機的不同的區域。

#include <stdio.h>
#define STACK_MAX 256
typedef char BYTE;
//無操作數指令
typedef enum{
    ireturn
    
}singleOp;

typedef enum{
    iput
} doubleOp;

typedef struct {
    union{
        singleOp sinOp;
        
        struct{
            doubleOp douOp;
            int arg1;
        };
    };
}Instruction;

typedef struct {
    int returnVar;//返回值
    BYTE hasReturnVar;//標誌位
    int localVar[STACK_MAX];
    int operandStack[STACK_MAX];
    int* ConstantPoolRef;
    int returnAdr;//方法返回地址
}Frame;
//方法區裏面有多個ClassData
typedef struct {
    int ConstantPool[STACK_MAX];
    Instruction OPList[STACK_MAX];
}ClassData;

typedef struct {
            int gcMark;
            ClassData *ClassRef;
            BYTE  commonVarTable[1];//存放普通類型對象
            BYTE  arrayVarTable[1];//數組類型對象,存放數組的長度,如果h長度爲0;則不是數組變量
}Heap;

typedef struct {
    int start;
    int end;
}ObjSite;

typedef struct {
    int stackSize;
    Heap *heap;
    Instruction insList[STACK_MAX];

    ObjSite objSiteTable[STACK_MAX];
    
    int numObjects;
    
    int maxObjects;
    int PC;
    Frame FrameStack[STACK_MAX];
} VM;

jvm內存佈局 (圖片轉自:http://blog.jamesdbloom.com/JVMInternals.html

2.解析opcode指令。 

從Main類的main函數開始執行。

3.堆內存分配,如何實現堆中分配內存?

直接動態擴展數組:

BYTE  commonVarTable[1];//存放普通類型對象
BYTE  arrayVarTable[1];//數組類型對象,存放數組的長度,如果h長度爲0;則不是數組變量

 

4.GC回收

採用標記-整理算法。

4.1什麼時候觸發GC?

當堆內存已滿或者下一次分配對象時堆剩餘空間無法滿足分配時,就觸發GC。

4.2 怎樣標記存活的對象?即怎樣區分哪些對象是垃圾?什麼樣的代碼會觸發JVM的GC?

遍歷Frame中的對象,與這些對象有關聯的對象都是不能回收的。

比如,我在一個函數裏面申明瞭一個局部變量對象,那麼函數結束時,是要將該局部變量清理掉的。那不可能沒次彈出一個Frame,JVM就去釋放掉對應的內存,這樣太費時間了,並不是每個函數都申明瞭局部變量對象,那麼JVM可以等到堆內存空間滿時再用GCRoot算法,找出要釋放的堆中要釋放的對象。在JDK6之後,採用棧對象,將函數創建的局部對象變量分配在棧中。

1.public class A {

       A a = new A();

      A b = new B();

         a = null;   -----1

         a = b; ------2

    }

}

當給一個對象引用賦值爲null或者指向其他對象,堆中的那個對象就無法訪問

2.當在函數裏面創建了局部對象變量,函數結束時,導致無法訪問

4.3C++內存泄漏的場景分析,即C++什麼時候用delete?

在C++中new和delete一定要成對使用。參考:https://blog.csdn.net/xxpresent/article/details/53024555

測試文件,test.java

class C{//類的大寫檢查
      int = 0;
      C(int i ){
        i ++;
      }
 }

Class B{
	int i = 0;
       C c = new C(3);
        Int fun(int i, int j, int k){
            int m = 3;  //函數局部變量
          Return i + j + k + m;  //前端處理表達式
        }
}
class A{
const m = 3;
Const B n = new B();//默認構造函數
int i = 0;
char a = ‘a’;
B p;                     //並沒有在堆中開闢內存
 B b = new B();   //成員變量
 b.fun(1,2,3);
 b = null;       //GC
 C c = new C();
int arr[3] = {1,2,3};
 for(int i = 0; i < 3;i++){ 
      i  = i + 1;
      j =  i + j; //前端類型檢查
      for(int k = 0; k < 3; k++){
          print(‘*’);
      }
 }
}

參考列表:

1.參考用C++寫的JVM,實現了類加載和前端。

https://github.com/raylrnd?after=Y3Vyc29yOnYyOpK5MjAxNi0xMC0yN1QxMjowNjozNiswODowMM4EPdGu&tab=stars

2.可以參考https://github.com/raylrnd/wgtcc寫個前端(C++)。

3.參考https://github.com/airtrack/luna,他仿照lua編譯器寫的,實現類StringPool。

4.https://github.com/Xiang1993/jack-compiler/blob/master/jackc/

5.《自制編譯器》

6.http://blog.jamesdbloom.com/JVMInternals.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章