类与对象
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条。
- 其中ldc和ldc_w指令用于加载int、float和字符串常量,java.lang.Class实例或者MethodType和MethodHandle实例。
- ldc2_w指令用于加载long和double常量。
- 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为Sub
但classRef.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), // 分配类实例内存
}
}
这样,一个有类信息,分配了内存的类实例就创建好了。