引子: 對(duì)于之前分析的Mini JVM的實(shí)現(xiàn)原理, 這里再加幾篇關(guān)于源碼的分析, 目的是為了可以更形象的展現(xiàn)之前所說的內(nèi)容, 同時(shí)在分析源碼的同時(shí)如果有同學(xué)有更好的實(shí)現(xiàn)的方法也可以提出來一起交流.
1. 解析常量池
在之前的文章中已經(jīng)提到過, 常量池是整個(gè)class文件中十分重要的一部分, 其存在的意思主要是可以減少class中的重復(fù)數(shù)據(jù), 使class文件可以更加的小. 同時(shí)執(zhí)行引擎在執(zhí)行的時(shí)候也會(huì)引用到常量池中的內(nèi)容. 所以常量池的解析對(duì)于整個(gè)JVM來說是很關(guān)鍵的一步.
首先再來看一下常量池在class存在的形式
<div style="margin-left:200px">圖1-1</div>
嗯, 沒錯(cuò), 都是一些二進(jìn)制數(shù)值, 所以解析常量池的工作其實(shí)就是將這個(gè)數(shù)值根據(jù)規(guī)則轉(zhuǎn)成java中的一個(gè)一個(gè)的類.
1.1 常量池類結(jié)構(gòu)圖
之前常量池的文章中介紹過, 常量池就是由一個(gè)個(gè)的常量項(xiàng)組成的, 因此可以得出以下的類結(jié)構(gòu)圖
<div style="margin-left:200px">圖1-1 constant-pool-uml (點(diǎn)擊看大圖)</div>
這個(gè)結(jié)構(gòu)應(yīng)該很容易就可以想到, 下面來看一下代碼是如何實(shí)現(xiàn)的.
1.2 代碼實(shí)現(xiàn)
解析常量池:
傳入的參數(shù)就是class文件的字節(jié)碼數(shù)組
private int _parseConstantPool(byte[] contents) {
// 由class文件的結(jié)構(gòu)規(guī)范可知, 常量吃的長度在class文件的第8個(gè)字節(jié)到第10(不包括)個(gè)字節(jié)記節(jié)
// 所以這里的CONSTANT_POOL_LENGTH_START和CONSTANT_POOL_LENGTH_END分別是8和10
Integer constantPoolLength = byte2Int(Arrays.copyOfRange(contents, CONSTANT_POOL_LENGTH_START, CONSTANT_POOL_LENGTH_END));
// 常量池長度數(shù)據(jù)之后緊接著就是常量池的具體內(nèi)容, 所以pos的值就是10
int pos = CONSTANT_POOL_START;
List<AbstractConstant> abstractConstant = new ArrayList<>();
ConstantPool pool = new ConstantPool(abstractConstant);
// 因?yàn)槌A砍刂谐A宽?xiàng)的序號(hào)是從1開始的, 所以遍歷的時(shí)候需要是常量項(xiàng)長度-1
// 同時(shí)保留的第0項(xiàng)用于表示不引用任何的常量項(xiàng), 因此這里一開始就創(chuàng)建一個(gè)NullConstant
abstractConstant.add(new NullConstant());
for (int i = 0; i < constantPoolLength - 1; i++) {
byte tag = contents[pos];
pos = pos + 1;
// 根據(jù)tag的值來判斷接下來的常量項(xiàng)是到底是什么類型
// 不同的常量項(xiàng)的具體數(shù)據(jù)內(nèi)容不同, 需要進(jìn)行單獨(dú)的解析
// 解析完每一個(gè)常量項(xiàng)都需要更新pos的值, 其一直指向下一個(gè)常量項(xiàng)的tag位置
// 每一個(gè)常量項(xiàng)的數(shù)據(jù)格式定義可以參考o(jì)racle jvm規(guī)范
switch (tag) {
case CONSTANT_UTF8: {
int length = byte2Int(Arrays.copyOfRange(contents, pos, pos + 2));
byte[] content = Arrays.copyOfRange(contents, pos + 2, pos + 2 + length);
UTF8Constant utf8Constant = new UTF8Constant(pool, tag, length, content);
abstractConstant.add(utf8Constant);
pos += (2 + length);
break;
}
case CONSTANT_INTEGER: {
// TODO: 17/6/6 后序添加
break;
}
case CONSTANT_FLOAT: {
// TODO: 17/6/6 后序添加
break;
}
case CONSTANT_LONG: {
// TODO: 17/6/6 后序添加
break;
}
case CONSTANT_DOUBLE: {
// TODO: 17/6/6 后序添加
break;
}
case CONSTANT_CLASS: {
int nameIndex = byte2Int(Arrays.copyOfRange(contents, pos, pos + 2));
ClassConstant classConstant = new ClassConstant(pool, tag, nameIndex);
abstractConstant.add(classConstant);
pos += 2;
break;
}
case CONSTANT_STRING: {
Integer stringIndex = byte2Int(Arrays.copyOfRange(contents, pos, pos + 2));
StringConstant stringConstant = new StringConstant(pool, tag, stringIndex);
abstractConstant.add(stringConstant);
pos += 2;
break;
}
case CONSTANT_FIELD_REF: {
Integer classIndex = byte2Int(Arrays.copyOfRange(contents, pos, pos + 2));
Integer nameAndTypeIndex = byte2Int(Arrays.copyOfRange(contents, pos + 2, pos + 4));
FieldRefConstant fieldRefConstant = new FieldRefConstant(pool, tag, classIndex, nameAndTypeIndex);
abstractConstant.add(fieldRefConstant);
pos += 4;
break;
}
case CONSTANT_METHOD_REF: {
Integer classIndex = byte2Int(Arrays.copyOfRange(contents, pos, pos + 2));
Integer nameAndTypeIndex = byte2Int(Arrays.copyOfRange(contents, pos + 2, pos + 4));
MethodRefConstant methodRefConstant = new MethodRefConstant(pool, tag, classIndex, nameAndTypeIndex);
abstractConstant.add(methodRefConstant);
pos += 4;
break;
}
case CONSTANT_INTERFACE_METHOD_REF: {
// TODO: 17/6/6 后序添加
break;
}
case CONSTANT_NAME_AND_TYPE: {
Integer nameIndex = byte2Int(Arrays.copyOfRange(contents, pos, pos + 2));
Integer descriptorIndex = byte2Int(Arrays.copyOfRange(contents, pos + 2, pos + 4));
NameAndTypeConstant nameAndTypeConstant = new NameAndTypeConstant(pool, tag, nameIndex, descriptorIndex);
abstractConstant.add(nameAndTypeConstant);
pos += 4;
break;
}
case CONSTANT_METHOD_HANDLE: {
// TODO: 17/6/6 后序添加
break;
}
case CONSTANT_METHOD_TYPE: {
// TODO: 17/6/6 后序添加
break;
}
case CONSTANT_INVOKE_DYNAMIC: {
// TODO: 17/6/6 后序添加
break;
}
default:
throw new RuntimeException("class文件常量池結(jié)構(gòu)不正確");
}
}
classFile.setConstantPool(pool);
return pos;
}
2. 解析訪問標(biāo)志
由之前的class文件的結(jié)構(gòu)可知, 常量池之后緊跟著的就是類的訪問標(biāo)志, 也就是那些private
, abstract
之類的東西. class文件中使用2個(gè)字節(jié)來表示這些內(nèi)容, 具體的說是用16bit來表示這些內(nèi)容, 因?yàn)檫@兩個(gè)字節(jié)的每一位都是有意義的, 當(dāng)某一位的值為1時(shí)就表示有某個(gè)修飾符. 所以解析訪問標(biāo)志實(shí)際上就是判斷每一位是否是1.
代碼如下:
class修飾符的枚舉定義:
/**
* @author tonyhui
* @since 17/6/5
*/
public enum ClassAccessFlag {
// 每一個(gè)對(duì)于類合法的修飾符都會(huì)在這里進(jìn)行定義, code代表的就是該修飾符具體所在的bit
ACC_PUBLIC(0X0001, "PUBLIC"),
ACC_FINAL(0x0010, "FINAL"),
ACC_SUPER(0x0020, "SUPER"),
ACC_ABSTRACT(0x0400, "ABSTRACT"),
ACC_SYNTHETIC(0x1000, "SYNTHETIC"),
ACC_ANNOTATION(0x2000, "ANNOTATION"),
ACC_ENUM(0x4000, "ENUM");
private int code;
private String name;
ClassAccessFlag(int code, String name) {
this.code = code;
this.name = name;
}
public int getCode() {
return code;
}
public String getName() {
return name;
}
}
解析class的修飾符
/**
* 解析Class的修飾符
*/
private int _parseClassAccessFlag(byte[] contents, int accessFlagStart) {
int accessFlag = byte2Int(Arrays.copyOfRange(contents, accessFlagStart, accessFlagStart + 2));
// 由上面的分析可以知道, 只要將正在解析的類的修飾符的值與上面定義的枚舉進(jìn)行位運(yùn)算就可以知道該類有哪些修飾符
// 寫到這里突然發(fā)現(xiàn)這個(gè)過程可以不用一個(gè)一個(gè)if進(jìn)行判斷而是可以通過一個(gè)循環(huán)進(jìn)行實(shí)現(xiàn), 所以寫文章的好處之一就是有的時(shí)候會(huì)靈光一現(xiàn), 想到其他的更好的實(shí)現(xiàn)方法
List<ClassAccessFlag> classAccessFlags = new ArrayList<>();
if ((accessFlag & ACC_PUBLIC.getCode()) != 0) {
classAccessFlags.add(ACC_PUBLIC);
}
if ((accessFlag & ACC_FINAL.getCode()) != 0) {
classAccessFlags.add(ACC_FINAL);
}
if ((accessFlag & ACC_SUPER.getCode()) != 0) {
classAccessFlags.add(ACC_SUPER);
}
if ((accessFlag & ACC_ABSTRACT.getCode()) != 0) {
classAccessFlags.add(ACC_ABSTRACT);
}
if ((accessFlag & ACC_SYNTHETIC.getCode()) != 0) {
classAccessFlags.add(ACC_SYNTHETIC);
}
if ((accessFlag & ACC_ENUM.getCode()) != 0) {
classAccessFlags.add(ACC_ENUM);
}
classFile.setAccessFlag(classAccessFlags);
return accessFlagStart + 2;
}
3. 解析類的索引
解析類的索引可以說是很簡單的了, 其實(shí)就是找到該類和其父類在常量池中的索引項(xiàng), 也就是找到該類和其父類在常量池中的索引. 這些內(nèi)容在常量項(xiàng)中本身就存在, 這里僅僅是引用一下, 但是jvm規(guī)范單獨(dú)將這部分?jǐn)?shù)據(jù)提取出來表示我想是為了之后獲取類的信息可以更加的方便, 而不用再到常量池中一個(gè)個(gè)的找. 畢竟類的信息對(duì)于解析一個(gè)類是時(shí)常要用到的.
解析類的索引
/**
* 解析Class和其父類在常量池中的索引
*/
private int _parseClassIndex(byte[] contents, int classIndexStart) {
int thisClassIndex = byte2Int(Arrays.copyOfRange(contents, classIndexStart, classIndexStart + 2));
int superClassIndex = byte2Int(Arrays.copyOfRange(contents, classIndexStart + 2, classIndexStart + 4));
ClassIndex classIndex = new ClassIndex(thisClassIndex, superClassIndex);
classFile.setClassIndex(classIndex);
return classIndexStart + 4;
}
這段代碼沒什么好解釋的, 可以說是簡單的到不能再簡單了, thisClassIndex和superClassIndex的值一定是常量池的某個(gè)常量項(xiàng)的索引值(如果這個(gè)class是合法的).
其實(shí)在解析完class index后緊跟的是interface index, 也就是這個(gè)類實(shí)現(xiàn)的接口的索引, 但是我的這個(gè)mini jvm要解析的類沒有實(shí)現(xiàn)接口, 所以這個(gè)就不剖析了, 但是其實(shí)現(xiàn)的方式和解析class index實(shí)際上是一樣的.
4. 總結(jié)
整個(gè)mini jvm的代碼實(shí)現(xiàn)還不是很完整, 即使是已經(jīng)實(shí)現(xiàn)的功能也還有很多可以優(yōu)化的地方. 之后的計(jì)劃是一遍完善功能一遍繼續(xù)解析mini jvm的代碼, 后面解析方法和字段的代碼也是很關(guān)鍵的, 還有最后的執(zhí)行引擎的實(shí)現(xiàn)是整個(gè)mini jvm的核心. 最后如果有必要再對(duì)一些Miscellaneous的實(shí)現(xiàn)進(jìn)行解析一下.
5. 本系列其他文章
手把手教你擼一個(gè)Mini JVM系列(1)之解析Class File -- 初探
手把手教你擼一個(gè)Mini JVM系列(2)之解析Class File -- 常量池
手把手教你擼一個(gè)Mini JVM系列(3)之解析Class File -- 字段袜啃、方法、屬性
手把手教你擼一個(gè)Mini JVM系列(4)之執(zhí)行引擎
手把手教你擼一個(gè)Mini JVM系列(6)之控制流 -- 條件判斷和循環(huán)