自制Java虚拟机(六)静态属性和静态方法(getstatic, putstatic, invokestatic, <clinit>)

Java中,静态属性和静态方法都是属于类的,类的诸多实例共享同一个静态属性和静态方法。操作实例属性和实例方法的指令分别为:getfieldputfieldinvokespecialinvokevirtual等,至于静态属性和静态方法,对应的指令为getstaticputstaticinvokestatic

有了前面实现操作实例属性和实例方法的经验, 实现操作静态属性和静态方法也不难。

一、静态属性的存取

typedef struct _ClassFile{
    ...
    ushort static_field_size; // 静态成员数量
    char *static_fields; // 这里以一维数组的方式存放静态成员
} ClassFile;

typedef ClassFile Class;

与对象的实例属性类似,我们可以把类的所有静态成员都保存在一个一维数组里,通过下标/索引来访问,只不过由于静态成员属于类,所以把这个数组保存在Class这个结构中,字段名为static_fields,类型为char*,因为我们只加载一次类,所以可以保证静态成员只有一份,不会重复创建。

在加载类的解析属性的函数中,可以这样处理静态属性:

void parseFields(FILE *fp, Class *pclass)
{
    ...
    ushort static_last_index = 0;
    while (index < fcount) {
        ...
        if (NOT_ACC_STATIC(tmp_field->access_flags)) {
            ...
        } else {
            // 设置该静态属性的索引/下标
            tmp_field->findex = static_last_index;
            if (ftype == 'J' || ftype == 'D') {
              // long和double需要占据连续的两个单元
              static_last_index+=2;
            } else {
              // 其它类型占据一个单元
              static_last_index+=1;
            }
        }
        ...
    }
    pclass->static_field_size = static_last_index;
    // 给静态属性分配空间
    pclass->static_fields = (char*)malloc(sizeof(char) * ((static_last_index+1)<<2));
}

getstatic操作:

#define GET_STATIC_FIELD(pclass, findex, ftype) *(ftype*)(pclass->static_fields+(findex<<2))
#define OP_GET_STATIC_FIELDI(pclass, findex, ftype) PUSH_STACK(env->current_stack, GET_STATIC_FIELD(pclass, findex, ftype), int);
#define OP_GET_STATIC_FIELDF(pclass, findex, ftype) PUSH_STACK(env->current_stack, GET_STATIC_FIELD(pclass, findex, ftype), float);
#define OP_GET_STATIC_FIELDL(pclass, findex, ftype) PUSH_STACKL(env->current_stack, GET_STATIC_FIELD(pclass, findex, ftype), ftype);
#define OP_GET_STATIC_FIELDR(pclass, findex, ftype) PUSH_STACK(env->current_stack, GET_STATIC_FIELD(pclass, findex, ftype), ftype);

根据偏移从static_fields数组中加载相应的静态属性的值,push到操作数栈上。针对4字节int,4字节float,8字节long、double和引用类型分别定义了一个宏,基于GET_STATIC_FIELD这个宏。

putstatic操作:

#define PUT_STATIC_FIELD(pclass, findex, fvalue, ftype) *(ftype*)(pclass->static_fields+(findex<<2))=fvalue
#define OP_PUT_STATIC_FIELDI(pclass, findex, ftype) PUT_STATIC_FIELD(pclass, findex, PICK_STACK(env->current_stack, ftype), ftype);\
    SP_DOWN(env->current_stack)
#define OP_PUT_STATIC_FIELDF(pclass, findex, ftype) PUT_STATIC_FIELD(pclass, findex, PICK_STACK(env->current_stack, ftype), ftype);\
    SP_DOWN(env->current_stack)
#define OP_PUT_STATIC_FIELDL(pclass, findex, ftype) PUT_STATIC_FIELD(pclass, findex, PICK_STACKL(env->current_stack, ftype), ftype);\
    SP_DOWNL(env->current_stack)
#define OP_PUT_STATIC_FIELDR(pclass, findex, ftype) PUT_STATIC_FIELD(pclass, findex, PICK_STACK(env->current_stack, ftype), ftype);\
    SP_DOWN(env->current_stack)

getstatic类似,不同之处在于这是一个赋值的过程,从栈顶取值,保存到static_fields这个数组中。

二、getstatic指令的实现:

getstatic指令格式与操作如下:

getstatic(一个字节) #n(两个字节)
操作数栈(前):...,
操作数栈(后):..., value

getstatic opcode后面跟着的是两个字节组成的无符号整数,指向常量池中的Fieldref类型。

实现方法如下:

Opreturn do_getstatic(OPENV *env)
{
    CONSTANT_Fieldref_info *fieldref;
    Class *pclass;
    cp_info cp;
    short index = TO_SHORT(env->pc); // 获取对应在常量池中的索引
    PRINTSD(TO_SHORT(env->pc));
    cp = env->current_class->constant_pool;
    fieldref = (CONSTANT_Fieldref_info*)(cp[index]);
    // 如果还没有解析过
    if (0 == fieldref->ftype) {
        // 解析静态属性
        resolveClassStaticField(env->current_class, &fieldref);
    }

    pclass = ((CONSTANT_Class_info*)(cp[fieldref->class_index]))->pclass;

    switch (fieldref->ftype) {
        ...
        case 'S': // short
            OP_GET_STATIC_FIELDI(pclass, fieldref->findex, short);
            break;
        case 'Z': // boolean
            OP_GET_STATIC_FIELDI(pclass, fieldref->findex, int);
            break;
        case 'I': // integer
            OP_GET_STATIC_FIELDI(pclass, fieldref->findex, int);
            break;
        ...
        case 'L': // reference
            OP_GET_STATIC_FIELDR(pclass, fieldref->findex, Reference);
             break;
        case 'J': // long
            OP_GET_STATIC_FIELDL(pclass, fieldref->findex, long);
            break;
        case 'D': // double
            OP_GET_STATIC_FIELDL(pclass, fieldref->findex, double);
            break;
        default:
            printf("Error: getfield, ftype=%d\n", fieldref->ftype);
            exit(1);
            break;
    }

    INC2_PC(env->pc);
}

其中,resolveClassStaticField函数的代码与之前解析实例属性的函数(resolveClassInstanceField)类似,不同的只是把其中的NOT_ACC_STATIC换成了IS_ACC_STATIC,只从静态属性中查找。

putstatic指令的实现与getstatic类似,就不多讲啦。

三、static方法、invokestatic指令的实现

static方法与实例方法的不同之处在于:

  • 调用static方法无需创建对象,因此传递参数的时候,无需传递this(当前对象的引用)。
  • 实例方法因为需要传递this,调用的时候需要在原先方法参数长度的基础上加4,然后从调用者的操作数栈上复制实参到新建栈帧的局部变量数组中。

invokestatic指令实现:

Opreturn do_invokestatic(OPENV *env)
{
    PRINTSD(TO_SHORT(env->pc));
    short mindex = TO_SHORT(env->pc); // 对应常量池中Methodref类型的索引
    INC2_PC(env->pc);
    // 调用静态方法
    callStaticClassMethod(env, mindex);
}

callStaticClassMethod函数如下:

void callStaticClassMethod(OPENV* current_env, int mindex)
{
    Class* current_class = current_env->current_class;
    CONSTANT_Methodref_info* method_ref = (CONSTANT_Methodref_info*)(current_class->constant_pool[mindex]);
    // 如果还没有解析过就去解析
    if (NULL == method_ref->ref_addr) {
        resolveStaticClassMethod(current_class, &method_ref, current_env);
    }
    // 调用解析过后的静态方法
    callResolvedStaticClassMethod(current_env, method_ref);
}

其中,resolveStaticClassMethod函数与之前用到的的resolveClassSpecialMethod类似。只是查找条件稍微不同而已。

callResolvedStaticClassMethod与之前用到的callResolveClassSpecialMethod也类似,就是复制参数有点不同:

// callResolvedClassSpecialMethod
real_args_len = method->args_len + SZ_REF;
last_stack->sp -= real_args_len;
memcpy(stf->localvars, last_stack->sp, real_args_len);
// callResolvedStaticClassMethod
real_args_len = method->args_len;
if (real_args_len > 0) {
    last_stack->sp -= real_args_len;
    memcpy(stf->localvars, last_stack->sp, real_args_len);
}

四、<clinit>方法

遇到到静态属性,往往会涉及<clinit>方法,该方法是Java编译器生成的,加载类时执行,只执行一次,用于初始化静态属性和执行static块。

如,这个类:

package test;

class HelloStatic
{
    private static int i= 15 ;
    private static float f = 2.6f;

    public static int getI()
    {
        return i;
    }

    public static float getF()
    {
        return f;
    }
}

生成的<clinit>方法对应的字节码为:

 0: bipush    15
 2: putstatic #2   // Field i:I
 5: ldc       #4   // float 2.6f
 7: putstatic #3   // Field f:F
10: return

看来要测试我们实现的getstaticputstaticinvokestatic指令,还得先实现调用<clinit>方法。

可以在之前的代码上改进:

  1. 修正runMainMethod函数

    // 在runMainMethod的 runMethod(&mainEnv) 这一行前添加如下代码:
    if (clinitMethod = findClinitMethod(pclass)) {
       // 如果找到了<clinit>方法就执行它
        runClinitMethod(&mainEnv, pclass, clinitMethod);
    }
  2. 添加runClinitMethod函数

    void runClinitMethod(OPENV *current_env, Class *clinit_class, method_info* method)
    {
       OPENV clinitEnv;
       StackFrame* stf;
       Code_attribute* code_attr;
    
       if (clinit_class->clinit_runned) {
           return;
       }
    
       // 1. create new stack frame
       code_attr = (Code_attribute*)(method->code_attribute_addr);
       stf = newStackFrame(NULL, code_attr);
    
       // 2. set new environment
       clinitEnv.pc = clinitEnv.pc_start = code_attr->code;
       clinitEnv.pc_end = code_attr->code + code_attr->code_length;
       clinitEnv.current_stack = stf;
       clinitEnv.current_class = clinit_class;
       clinitEnv.method = method;
       clinitEnv.is_clinit = 1;
       clinitEnv.call_depth = 0;
       stf->method = method;
    
       // 3. really run
       internalRunClinitMethod(&clinitEnv);
       clinit_class->clinit_runned = 1;
    }

    这里其实我们是新创建一个OPENV来执行的。

  3. 新建internalRunClinitMethod函数

    void internalRunClinitMethod(OPENV *env)
    {
       uchar op;
       Instruction instruction;
       do {
           op = *(env->pc);
           instruction = jvm_instructions[op];
           env->pc=env->pc+1;
           instruction.action(env);
       } while(env->pc <= env->pc_end && env->pc!=NULL);
    }

    之所以新建一个函数,而不是之前那个死循环的runMethod函数,是因为<clinit>方法是加载类时调用,而我们是按需加载类,这个不确定性使得不能在原来的runMethod函数里执行<clinit>的指令,以免程序不能按预期执行。

  4. 然后在加载类的函数需要修正:

    Class* loadClassFromDiskRecursive(OPENV* env, const char* class_name)
    {
    ...
       if (NULL != env && NULL != (method = findClinitMethod(pclass))) {
           if (strncmp(class_name, "java", 4) != 0) {
               // only run our <clinit> method
               runClinitMethod(env, pclass, method);
           }
       }
    
       storeLoadedClass(pclass);
       return pclass;
    }

    由于jdk中类的<clinit>方法经常涉及到native方法(由非Java编写的方法,不是由Java虚拟机执行),我们这里把jdk中类的<clinit>方法跳过,不然不好处理(已经踩了很多坑了!)。

    <clinit>方法的调用大概就这样。下面我们可以测试了。

五、测试

测试的Java代码如下:

HelloStatic.java

package test;

class HelloStatic
{
    private static int i= 15 ;
    private static float f = 2.6f;

    public static int getI()
    {
        return i;
    }

    public static float getF()
    {
        return f;
    }
}

TestStatic.java

package test;

class TestStatic
{
    static int i = 3 ;
    static float f = 10.25f;

    public static void main(String[] args)
    {
        int x = TestStatic.i + HelloStatic.getI();
        float y = TestStatic.f + HelloStatic.getF();
    }
}

测试静态方法、静态属性能否正常调用。

将以上文件编译成class文件:

javac TestHello.java TestStatic.java

然后在我们的main函数中加载test/TestStatic类执行。

调试输出如下:

这里写图片描述

这里写图片描述

可见我们实现的指令是正确的。

六、总结

getstaticputstaticinvokestatic指令的实现倒不难,难点在于实现<clinit>方法,执行jdk类中的<clinit>方法的时候,问题蛮多的,需要查看源文件以及测试输出的字节码文件调试,由于太多复杂,暂时跳过了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值