java栈帧中的对象引用
openjdk中的java栈帧是如何布置的呢,在java栈中如果确定变量是一个引用呢,先复习《深入java虚拟机第二版》有关栈帧的内容。
“栈帧由三部分组成:局部变量区、操作数栈和栈数据区。局部变量区和操作数栈要视对应的方法而定,他们是按字长计算的。编译器在编译时就确定了这些值并放在class文件中,而栈数据区的大小依赖于具体实现。
当虚拟机调用一个java方法时,它从对应的类的类型信息得到局部变量区和操作数栈的大小,并据此分配栈帧内存,并压入java栈中。
局部变量区 java栈帧的局部变量区被组织成一个以字长为单位、从0开始计数的数组。字节码指令通过以0开始的索引来使用其中的数据。类型为int、float、refence和returnAdress的值在数组内只占据一项。而类型为byte、short、char的值在存入数组时都先转换为int值,因此同样只占据一项。而类型为long和double的值在数组中却占据了连续两项。
局部变量区包含了对应的方法参数和局部变量。”
现在的问题是在垃圾回收算法遍历java线程堆栈时候,它是如何确定局部变量是基本类型还是对象引用呢?
openjdk对局部变量区解释有两种方法,通过TaggedStackInterpreter这个设置来区分。从英文意思上面来看应该是一个通过在栈中打上标志,另外一个则是在其他地方做标志。在栈上打标志,则应该是每个变量多压入一个标志头(为一个字长),猜测是否正确呢,看看解释器将引用压栈的代码。
void BytecodeInterpreter::astore(intptr_t* tos, int stack_offset,
intptr_t* locals, int locals_offset) {
if (TaggedStackInterpreter) {
frame::Tag t = (frame::Tag) tos[Interpreter::expr_tag_index_at(-stack_offset)];
locals[Interpreter::local_tag_index_at(-locals_offset)] = (intptr_t)t;
}
intptr_t value = tos[Interpreter::expr_index_at(-stack_offset)];
locals[Interpreter::local_index_at(-locals_offset)] = value;
}
local_index_at代码
{
return stackElementWords() * i + (value_offset_in_bytes()/wordSize);
}
stackElementWords() 代码
{
return TaggedStackInterpreter ? 2 : 1; //如果是在栈打标志,字长为2,否则是1,在这可以肯定每个变量前都会多压入一个字长的标志头,虚拟机会通过这个标志头来确定是否是对象引用
}
上面分析对象在栈中打标志来区分基本类型和引用类型,这种方法每个变量起码多了一个字的内存空间,空间浪费严重。如果能用位图表示岂不更好?再看另外一种方案。
从上面的压栈代码看不到另外的操作,openjdk是如何做的呢,还是从上篇gc代码中看看。
void frame::oops_interpreted_do(OopClosure* f, const RegisterMap* map, bool query_oop_map_cache) {
........
jint bci = interpreter_frame_bci();
methodHandle m (thread, interpreter_frame_method());
InterpreterFrameClosure blk(this, max_locals, m->max_stack(), f);
InterpreterOopMap mask;
if (query_oop_map_cache) { //传入true
m->mask_for(bci, &mask); //从oopMapCache中填充mask变量
} else {
OopMapCache::compute_one_oop_map(m, bci, &mask);
}
mask.iterate_oop(&blk);
}
void InterpreterOopMap::iterate_oop(OffsetClosure* oop_closure) {
int n = number_of_entries();
int word_index = 0;
uintptr_t value = 0;
uintptr_t mask = 0;
// 遍历每项
for (int i = 0; i < n; i++, mask <<= bits_per_entry) {
if (mask == 0) {
value = bit_mask()[word_index++];
mask = 1;
}
// 判断是否是对象引用?
if ((value & (mask << oop_bit_number)) != 0) oop_closure->offset_do(i);
}
}
void InterpreterFrameClosure::offset_do(int offset) {
oop* addr;
if (offset < _max_locals) {
addr = (oop*) _fr->interpreter_frame_local_at(offset);
assert((intptr_t*)addr >= _fr->sp(), "must be inside the frame");
_f->do_oop(addr); //进行标记和压栈
}
}
从上面来看,采用位图来区分对象引用基本上是肯定的,具体如何实现还有待于细读。