转载

自己动手写JVM四【解析class文件(二)】

本章节开始重点学习ClassFile中各项所代表的含义,这部分可能会比较枯燥,坚持学下去,会有收获的。

魔数(magic)

很多文件格式都会规定满足该格式的文件必须以某几个固定字节开头,这几个字节主要起标识作用,叫作魔数。class文件的魔数是”0xCAFEBABE”,可以参照classpy打开的ClassFile.class文件看下:

自己动手写JVM四【解析class文件(二)】

所以,class文件以固定的4个字节(CAFEBABE)开头。在class_file.go文件中添加读取魔数的方法:

func(self *ClassFile)readAndCheckMagic(reader *ClassReader)  {
	magic := reader.readUint32()
	if magic != 0xCAFEBABE {
		/*
			Java虚拟机的实现是抛出java.lang.ClassFormatError
			目前由于才开始写虚拟机,还没法抛出异常,暂先调用panic
		 */
		panic("java.lang.ClassFormatError: magic!")
	}
}

版本号(minorVersion & majorVersion)

魔数之后是class文件的次版本号和主版本号,各占2个字节(u2类型)。下面列举几个最新的版本号:

Java版本 class文件版本号
JavaSE8 52.0
JavaSE7 51.0
JavaSE6 50.0
JavaSE5 49.0

ClassFile.class的次版本号和主版本号信息如下:

自己动手写JVM四【解析class文件(二)】 自己动手写JVM四【解析class文件(二)】

目前Oracle的实现时完全向后兼容的,比如JavaSE8支持版本号为45~52.0的class文件。如果版本号不在支持范围内,Java虚拟机实现就抛java.lang.UnsupportedClassVersionError异常。在class_file.go文件中添加读取版本号的方法:

func(self *ClassFile)readAndCheckVersion(reader *ClassReader) {
	self.minorVersion = reader.readUint16()
	self.majorVersion = reader.readUint16()
	switch  self.majorVersion {
	// 主版本号为45直接返回
	case 45:
		return
	// 主版本号为46~52时,次版本号必须为0
	case 46, 47, 48, 49, 50, 51, 52:
		if self.minorVersion == 0 {
			return
		}
	}
	panic("java.lang.UnsupportedClassVersionError!")
}

类访问标志

版本号之后是常量池,常量池比较复杂,后面再讲。常量池之后是类访问标志,是一个16位的”bitmask”,指出class文件定义的是类还是接口,访问级别是public还是private等等。下表不完全列举下这16位代表的含义:

标志名称 标志值 含义
ACC_PUBLIC 0x0001 是否为public类型
ACC_FINAL 0x0010 是否被声明为final,只有类可设置
ACC_SUPER 0x0020 是否允许使用invokespecial字节码指令的新语义,JDK1.0.2之后编译出来的这个标志都必须为真
ACC_INTERFACE 0x0200 标示这是一个接口
ACC_ABSTRACT 0x0400 是否为abstract类型,对于接口或者抽象类来说,此标志为真,其他类值为假
ACC_SYNTHETIC 0x1000 标志这个类并非由用户代码产生的
ACC_ANNOTATION 0x2000 标志这是一个注解
ACC_ENUM 0x4000 标志这是一个枚举

注:ACC_SUPER的含义可以参照 csdn的一篇博客 去理解。

ClassFile.class的类访问标志为0x0021(ACC_PUBLIC和ACC_SUPER):

自己动手写JVM四【解析class文件(二)】

在class_file.go文件中添加读取类访问标志的方法:

func(self *ClassFile)readAccessFlags(reader *ClassReader) {
	self.accessFlags = reader.readUint16()
}

类和超类索引

类访问标志之后是两个u2类型的常量池索引,分别给出类名和超类名。因为每个类都有名字,所以thisClass必须是有效的常量池索引,除了java.lang.Object之外,其他类都有超类,所以supperClass只在Object.class中是0,在其他class文件中必须是有效的常量池索引。由于常量池还没涉及,这里先不展开了。ClassFile.class的类和超类索引分别为0x0005、0x0006。

自己动手写JVM四【解析class文件(二)】 自己动手写JVM四【解析class文件(二)】

在class_file.go文件中添加读取类和超类索引的方法:

func(self *ClassFile)readThisClass(reader *ClassReader) {
	self.thisClass = reader.readUint16()
}

func(self *ClassFile)readSuperClass(reader *ClassReader) {
	self.superClass = reader.readUint16()
}

接口索引表

类和超类索引后面时接口索引表,表中存放的也是常量池索引。ClassFile.class没有实现接口,所以接口表是空的:

自己动手写JVM四【解析class文件(二)】

在class_file.go文件中添加读取接口索引表的方法:

func(self *ClassFile)readInterface(reader *ClassReader) {
	//接口是表结构,所以用的readUint16s方法读取
	self.interfaces = reader.readUint16s()
}

下节继续学习ClassFile…

原文  http://bboyjing.github.io/2017/01/05/自己动手写JVM四【解析class文件(二)】/
正文到此结束
Loading...