自己动手写JVM——(5)类与对象机制 操作静态数据

类与对象

public class MyObject {
public static int staticVar;
public int instanceVar;

public static void main(String[] args) {
    int x = 32768;                       // ldc
    MyObject myObj = new MyObject();     // new
    MyObject.staticVar = x;              // putstatic
    x = MyObject.staticVar;              // getstatic
    myObj.instanceVar = x;               // putfield
    x = myObj.instanceVar;               // getfield
    Object obj = myObj;
    if (obj instanceof MyObject) {       // instanceof
        myObj = (MyObject) obj;          // checkcast
        System.out.println(myObj.instanceVar);
    }
}

以上面这段为例,我们继续说一下JVM对类与对象的实现机制

PUTSTATIC

    MyObject.staticVar = x;              // putstatic

staticVar是MyObject类的静态字段,不需要实例化就可以访问,这是如何实现的呢?

// putstatic指令给类的某个静态变量赋值
type PUT_STATIC struct {
	base.Index16Instruction
}

func (self *PUT_STATIC) Execute(frame *rtda.Frame) {
	currentMethod := frame.Method()
	currentClass := currentMethod.Class()
	cp := currentClass.ConstantPool()
	// 通过这个索引可以从当前类的运行时常量池中找到一个字段符号引用
	fieldRef := cp.GetConstant(self.Index).(*heap.FieldRef)
	field := fieldRef.ResolvedField()
	class := field.Class()
	// init class
	if !class.InitStarted() {
		frame.RevertNextPC()
		base.InitClass(frame.Thread(), class)
		return
	}

	if !field.IsStatic() {
		panic("java.lang.IncompatiableClassChangeError")
	}
	// 如果是final字段,则实际操作的是静态常量,只能在类初始化方法中给它赋值
	// 类初始化方法由编译器生成,名字是<clinit>
	if field.IsFinal() {
		if currentClass != class || currentMethod.Name() != "<clinit>" {
			panic("java.lang.IllegalAccessError")
		}
	}

	descriptor := field.Descriptor()
	slotId := field.SlotId()
	slots := class.StaticVars()
	stack := frame.OperandStack()

	switch descriptor[0] {
	case 'Z', 'B', 'C', 'S', 'I':
		slots.SetInt(slotId, stack.PopInt())
	case 'F':
		slots.SetFloat(slotId, stack.PopFloat())
	case 'J':
		slots.SetLong(slotId, stack.PopLong())
	case 'D':
		slots.SetDouble(slotId, stack.PopDouble())
	case 'L', '[':
		slots.SetRef(slotId, stack.PopRef())
	default:
		// TODO
	}
}

我们来一段一段的看:

	currentMethod := frame.Method()
	currentClass := currentMethod.Class()
	cp := currentClass.ConstantPool()
	// 通过这个索引可以从当前类的运行时常量池中找到一个字段符号引用
	fieldRef := cp.GetConstant(self.Index).(*heap.FieldRef)

以上代码通过函数栈帧,获取函数,进而获取函数的类,再用类的常量池指针找到指定的Field引用。

	field := fieldRef.ResolvedField()
	class := field.Class()
	// init class
	if !class.InitStarted() {
		frame.RevertNextPC()
		base.InitClass(frame.Thread(), class)
		return
	}

以上代码通过对Field引用的解引用获取该Field的具体信息,进而获取到它对应的类。字段的解引用实现如下:

// 对字段引用进行解引用
func (self *FieldRef) ResolvedField() *Field {
	if self.field == nil {
		self.resolveFieldRef()
	}
	return self.field
}

// 如果类D想通过字段符号引用访问类C的某个字段,首先要解析符号引用得到类C,
// 然后根据字段名和描述符查找字段。
// 如果字段查找失败,则虚拟机抛出NoSuchFieldError异常。
// 如果查找成功,但D没有足够的权限访问该字段,
// 则虚拟机抛出IllegalAccessError异常。
func (self *FieldRef) resolveFieldRef() {
	d := self.cp.class
	c := self.ResolvedClass()
	field := lookupField(c, self.name, self.descriptor)

	if field == nil {
		panic("java.lang.NoSuchFieldError")
	}
	if !field.isAccessibleTo(d) {
		panic("java.lang.IllegalAccessError")
	}
	self.field = field
}

func lookupField(c *Class, name, descriptor string) *Field {
	// check fields
	for _, field := range c.fields {
		if field.name == name && field.descriptor == descriptor {
			return field
		}
	}
	// check interfaces
	for _, iface := range c.interfaces {
		if field := lookupField(iface, name, descriptor); field != nil {
			return field
		}
	}
	// check super class
	if c.superClass != nil {
		return lookupField(c.superClass, name, descriptor)
	}
	return nil
}

这段解引用的代码比较长,但如果你看过我们上一篇文章就会发现其实很简单,总结一下就是FieldRef从创建之初就绑定了一个运行时常量池指针,这个指针所在的类可能是FieldRef的类,也可能是它的父类,在对FieldRef解引用后获取实际对应的类。再从这个类中寻找是否包含该Field,包括类本身、类实现的接口以及它的所有父类。

在通过FieldRef找到实际的Field后,通过Field尝试去初始化类。

	if !field.IsStatic() {
		panic("java.lang.IncompatiableClassChangeError")
	}
	// 如果是final字段,则实际操作的是静态常量,只能在类初始化方法中给它赋值
	// 类初始化方法由编译器生成,名字是<clinit>
	if field.IsFinal() {
		if currentClass != class || currentMethod.Name() != "<clinit>" {
			panic("java.lang.IllegalAccessError")
		}
	}

上面的代码会检查Field是否是Static以及Final,并进行对应的处理。

	descriptor := field.Descriptor()
	slotId := field.SlotId()
	slots := class.StaticVars()
	stack := frame.OperandStack()

	switch descriptor[0] {
	case 'Z', 'B', 'C', 'S', 'I':
		slots.SetInt(slotId, stack.PopInt())
	case 'F':
		slots.SetFloat(slotId, stack.PopFloat())
	case 'J':
		slots.SetLong(slotId, stack.PopLong())
	case 'D':
		slots.SetDouble(slotId, stack.PopDouble())
	case 'L', '[':
		slots.SetRef(slotId, stack.PopRef())
	default:
		// TODO
	}

以上代码就是实际上给静态变量赋值的过程了,找到该Field对应的SlotId,找到Field的类的Slots,并在对应的Slot设置从操作数栈顶弹出的数据。

GETSTATIC

    x = MyObject.staticVar;              // getstatic

GETSTATIC的实现与PUTSTATIC高度相似,唯一不同的地方就是,这次是在找到FieldRef对应的Field后,将其类中对应Slot保存的数据压入操作数栈。

// Get static field from class
type GET_STATIC struct{ base.Index16Instruction }

func (self *GET_STATIC) Execute(frame *rtda.Frame) {
	cp := frame.Method().Class().ConstantPool()
	fieldRef := cp.GetConstant(self.Index).(*heap.FieldRef)
	field := fieldRef.ResolvedField()
    
    // ...

	descriptor := field.Descriptor()
	slotId := field.SlotId()
	slots := class.StaticVars()
	stack := frame.OperandStack()

	switch descriptor[0] {
	case 'Z', 'B', 'C', 'S', 'I':
		stack.PushInt(slots.GetInt(slotId))
	case 'F':
		stack.PushFloat(slots.GetFloat(slotId))
	case 'J':
		stack.PushLong(slots.GetLong(slotId))
	case 'D':
		stack.PushDouble(slots.GetDouble(slotId))
	case 'L', '[':
		stack.PushRef(slots.GetRef(slotId))
	default:
		// TODO
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值