类与对象
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
}
}