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);  //进行标记和压栈
              } 
     }
     从上面来看,采用位图来区分对象引用基本上是肯定的,具体如何实现还有待于细读。

原文地址:https://www.cnblogs.com/dongxiaoguang/p/2962030.html