自己动手写JVM——(4)类与对象机制 NEW指令的实现

类与对象

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对类与对象的实现机制

首先是LDC指令

LDC

    int x = 32768;                       // ldc

ldc系列指令从运行时常量池中加载常量值,并把它推入操作数栈

ldc系列指令属于常量类指令,共3条。

  1. 其中ldc和ldc_w指令用于加载int、float和字符串常量,java.lang.Class实例或者MethodType和MethodHandle实例。
  2. ldc2_w指令用于加载long和double常量。
  3. ldc 和ldc_w指令的区别仅在于操作数的宽度。
// Push item from run-time constant pool
type LDC struct{ base.Index8Instruction }

func (self *LDC) Execute(frame *rtda.Frame) {
	_ldc(frame, self.Index)
}

func _ldc(frame *rtda.Frame, index uint) {
	stack := frame.OperandStack()
	class := frame.Method().Class()
	c := class.ConstantPool().GetConstant(index)

	switch c.(type) {
	case int32:
		stack.PushInt(c.(int32))
	case float32:
		stack.PushFloat(c.(float32))
	case string:
		internedStr := heap.JString(class.Loader(), c.(string))
		stack.PushRef(internedStr)
	case *heap.ClassRef:
		classRef := c.(*heap.ClassRef) // 常量池的常量是类引用
		classObj := classRef.ResolvedClass().JClass() // 解析类引用
		stack.PushRef(classObj) // 将类对象入栈
	// case MethodType, MethodHandle
	default:
		panic("todo: ldc!")
	}
}

首先来回忆一下我们的JVM 运行时的结构:

/*
JVM
   Thread
      pc
      Stack
        Frame
           LocalVars
           OperandStack
  **/

其中,stack是一个抽象的栈,栈的元素是Frame,也就是函数栈帧

type Frame struct {
	lower *Frame
	localVars LocalVars
	operandStack *OperandStack
	thread *Thread
	method *heap.Method
	nextPC int // the next instruction after the call
}

函数执行的必要资源都在frame中。

Method

函数栈帧绑定的函数对象

type Method struct {
	ClassMember
	maxStack uint // 最大栈深度
	maxLocals uint // 最大局部变量数
	code []byte // 代码源码
	exceptionTable ExceptionTable // 异常处理表
	lineNumberTable *classfile.LineNumberTableAttribute // 行号表
    // ...
	argSlotCount uint // 函数参数槽量
}

其中ClassMember是为了与Field的部分字段复用

type ClassMember struct {
	accessFlags    uint16 // 访问级别
	name           string // 类名
	descriptor     string // 描述符
    // ...
	class          *Class // 类指针
}

这时,再来看LDC指令的实现,获取了常量池的数据,然后压入操作数栈。

    // 从函数的类指针中获取与运行时常量池指针,并获取常量池中保存的数据: 数值或引用
	class := frame.Method().Class()
	c := class.ConstantPool().GetConstant(index)

OperandStack

    // 获取操作数栈
	stack := frame.OperandStack()
    // ...
		stack.PushInt(c.(int32))
// 一个用数组实现的栈
type OperandStack struct {
	size uint
	slots []Slot
}

NEW

    MyObject myObj = new MyObject();     // new

NEW的实现:

// Create new object
type NEW struct {
	base.Index16Instruction
}

func (self *NEW) Execute(frame *rtda.Frame) {
	cp := frame.Method().Class().ConstantPool()
	classRef := cp.GetConstant(self.Index).(*heap.ClassRef)
	class := classRef.ResolvedClass() // 用符号引用加载整个类信息
	// new指令触发构建类实例 但类还没有初始化 终止指令执行
	if !class.InitStarted() {
		frame.RevertNextPC() // 回置PC
		base.InitClass(frame.Thread(), class)
		return
	}

	// interface and abstract class can be instantced
	if class.IsInterface() || class.IsAbstract() {
		panic("java.lang.InstantiationError")
	}
	ref := class.NewObject() // 实例化
	frame.OperandStack().PushRef(ref)
}

	cp := frame.Method().Class().ConstantPool()
	classRef := cp.GetConstant(self.Index).(*heap.ClassRef)
	class := classRef.ResolvedClass() // 用符号引用加载整个类信息

首先,上面这段代码从方法所属的类的运行时常量池中获取myObj引用,接下来进行解引用。那么什么是引用呢?

type ClassRef struct {
	SymRef
}

// 1. 类符号引用
// 2. 字段符号引用
// 3. 方法符号引用
// 4. 接口方法符号引用
type SymRef struct {
	cp *ConstantPool // 符号引用所在的运行时常量池指针
	className string
	class *Class
}

类引用包含了一些可以索引到对应类的必要信息,通过这些必要信息我们可以获取该类的具体信息,这个过程就是解引用。

func (self *SymRef) ResolvedClass() *Class {
	if self.class == nil {
		self.resolveClassRef()
	}
	return self.class
}

// 如果类D通过符号引用类C的话
// 要解析N 先用D的类加载器加载C 然后检查D是否有权限访问C
func (self *SymRef) resolveClassRef() {
	d := self.cp.class 
	c := d.loader.LoadClass(self.className)
	if !c.isAccessibleTo(d) {
		panic("java.lang.IllegalAccessError")
	}
	self.class = c
}

可以发现,我们在解引用的时候进行了对类的加载。简单叙述一下就是从classpath中找到对应的class文件,读取并解析其中的内容,但类的初始化并不是这里进行的,而是在NEW指令中

还有一点,就是d := self.cp.class c := d.loader.LoadClass(self.className) 这两句中出现了两个类,他们有什么区别呢?

是这样的,为了支持多态,java允许Super obj = new Sub() 这样的实现,也就是说,classRef.className为SubclassRef.cp.class.Super,也就是说JVM使用了一个Super的常量池指针保存了一个Sub的类引进。

c 为 Sub 实际的类,d为Super 保存该引用的类,可能是引用类本身 ,也可能是引用类的父类。这就解释了为什么后面我们需要检查d是否可以访问到c,只有Super的引用才能访问Sub的实例

	// new指令触发构建类实例 但类还没有初始化 终止指令执行
	if !class.InitStarted() {
		frame.RevertNextPC() // 回置PC
		base.InitClass(frame.Thread(), class)
		return
	}

上面这段代码的意思大致就是:检查类是否已经初始化,如果没有就重新先去初始化,然后重新执行一次NEW指令。


	ref := class.NewObject() // 实例化
	frame.OperandStack().PushRef(ref)

上面这段代码是NEW指令的最后一部分,实例化该类的一个对象,然后压入函数栈帧的操作数栈。

func (self *Class) NewObject() *Object {
	return newObject(self)
}

type Object struct {
	class *Class
	data interface{}
	extra interface{} // 记录Object结构体实例的额外信息
}

func newObject(class *Class) *Object {
	return &Object{
		class: class,
		data: newSlots(class.instanceSlotCount), // 分配类实例内存
	}
}

这样,一个有类信息,分配了内存的类实例就创建好了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值