Java中,静态属性和静态方法都是属于类的,类的诸多实例共享同一个静态属性和静态方法。操作实例属性和实例方法的指令分别为:getfield
、putfield
、invokespecial
、invokevirtual
等,至于静态属性和静态方法,对应的指令为getstatic
、putstatic
、invokestatic
。
有了前面实现操作实例属性和实例方法的经验, 实现操作静态属性和静态方法也不难。
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
类似,就不多讲啦。
invokestatic
指令的实现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);
}
方法遇到到静态属性,往往会涉及
方法,该方法是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;
}
}
生成的
方法对应的字节码为:
0: bipush 15
2: putstatic #2 // Field i:I
5: ldc #4 // float 2.6f
7: putstatic #3 // Field f:F
10: return
看来要测试我们实现的getstatic
、putstatic
、invokestatic
指令,还得先实现调用
方法。
可以在之前的代码上改进:
修正runMainMethod
函数
// 在runMainMethod的 runMethod(&mainEnv) 这一行前添加如下代码:
if (clinitMethod = findClinitMethod(pclass)) {
// 如果找到了方法就执行它
runClinitMethod(&mainEnv, pclass, clinitMethod);
}
添加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
来执行的。
新建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
函数,是因为
方法是加载类时调用,而我们是按需加载类,这个不确定性使得不能在原来的runMethod
函数里执行
的指令,以免程序不能按预期执行。
然后在加载类的函数需要修正:
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 method
runClinitMethod(env, pclass, method);
}
}
storeLoadedClass(pclass);
return pclass;
}
由于jdk中类的
方法经常涉及到native
方法(由非Java编写的方法,不是由Java虚拟机执行),我们这里把jdk中类的
方法跳过,不然不好处理(已经踩了很多坑了!)。
方法的调用大概就这样。下面我们可以测试了。
测试的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
类执行。
调试输出如下:
可见我们实现的指令是正确的。
getstatic
、putstatic
、invokestatic
指令的实现倒不难,难点在于实现
方法,执行jdk类中的
方法的时候,问题蛮多的,需要查看源文件以及测试输出的字节码文件调试,由于太多复杂,暂时跳过了。