【Hotspot】类生命周期(4):对象引用管理:HandleMark 和 HandleArea

1.通过HSDB观察堆栈引用信息
栈区域:
用来存放基本数据类型和引用数据类型的实例的(也就是实例对象的在堆中的首地址)还有就是堆栈是线程独享的。每一个线程都有自己的线程栈。
局部变量在栈内存中,JVM为每一个类分配一个栈帧,然后引用类型的局部变量指向堆内存中的地址),但是堆是内存中共享的区域,所以要考虑线程安全的问题。
堆区域:
用来存放程序动态生成的数据。(new 出来的对象的实例存储在堆中,但是仅仅存储的是成员变量,也就是平时所说的实例变量,成员变量的值则存储在常量池中。成员方法是此类所实现实例共享的,并不是每一次new 都会创建成员方法。成员方法被存储在方法区,并不是存储在第一个创建的对象中,因为那样的话,第一个对象被回收,后面创建的对象也就没有方法引用了。静态变量也存储在方法区中。 
方法区(非堆):
在堆中为其分配的一部分内存):里面存储的是一些。类类型加载的东西(也就是反射中的.class之后的Class),用于存储已经被虚拟机加载的类的信息、常量、静态变量等。与堆一样,是被线程共享的内存区域,要注意线程安全问题。
所有的内存区域在一起,构成了JVM运行时内存分配:
以下列Java测试为例显示堆栈引用的查看:
package tool;
public class UserService {
    public String name;
    public UserService(String name) {
        this.name = name;
    }
    public void debugMsg(){
        System.out.println("name:"+name);
    }
    public static void main(String[] args) {
        UserService user = new UserService("Job");
        // 子线程循环调用 user 函数
        Thread thread = new Thread(()->{
            while (true){
                try {
                    user.debugMsg();
                    Thread.sleep(1000);
                }
                catch (Exception e){
                    e.printStackTrace();
                }
            }
        });
        // 线程名称
        thread.setName("debugMsgThread");
        thread.start();
    }
}

使用 HSDB 链接目标应用后,上面五个按钮分别功能如下,我们可以清楚的了解堆栈之间的引用关系,首先查看栈信息(Show the stack memory for the current thread):
这里选择目标线程 debugMsgThread,因为栈是在内存上分配的逻辑结构,实际上分析栈实际上就是分析栈帧中结构,看每个地址上面保存的信息。 
其中最左第一列:
是栈的内存地址,按照从上到下由低到高分配。
第二列:
是栈这个内存地址上保存的值(大多数是其他对象的地址,表示栈这个内存地址上引用了其他的对象)
最右边彩色:
是 hotspot 对左侧栈内存地址上的说明。
Interpreted frame 表示一个栈帧这里共,
PSYoungGen tool/UserService 引用的一个对象,地址是 0x000000076da4ae80,这个地址可以去堆上面查找。
然后点击查看堆信息:
Tools->Object Histogram ,打开右边的Object Histogram面板,输入类路径查找后双击可以打开,会显示这个类创建了一个实例,并且地址是 0x000000076da4ae80:
2. Handler 句柄
栈帧:
栈帧(frame)在线程执行时和运行过程中用于保存线程的上下文数据,是垃圾回收中的最重要的根。
GC时通常第一步就是遍历根,java线程栈帧就是根元素之一,遍历整个栈帧的方式就是用过StackFrameStream,
其中封装了一个next指针,通过sender获得调用者的栈帧。我们将java的栈帧作为根来遍历堆,对其中的对象进行标记并收集垃圾。
在Java本地方法栈中,Java线程使用一个对象句柄存储块JNIHandleBlock来为其在本地方法中申请的临时对象创建对应的句柄。
每个JNIHandleBlock里面有一个oop数组,长度为32,如果超过数组长度则申请新的Block并通过next指针形成链表。
还有一个_pop_frame_link属性,用来保存Java线程切换方法时分配本地对象句柄的上下文从而形成调用handle的链表。
句柄
一个线程可以执行Java代码也可以执行Java的本地代码,如果Java本地代码(JVM内部的本地代码,JNI代码也算)需要引用堆里面的对象应该如何处理?
JVM并没区分本地方法栈和java栈(前面分析过Java函数调用,call_helper()函数为分界线,前面是 native栈,后面是java栈),如果通过栈进行处理必须要区分这两种情况。
因此JVM设计了另一个概念:HandleAera, 一块线程资源区,在这个区域分配句柄,并且管理所有的句柄。如果函数还在调用中,那么句柄有效,句柄关联的对象也就是活跃对象。
为了管理句柄的生命周期,引入了HandleMark,通常标记是分配在线程栈上的,在创建HandleMark的时候标记HandleArea对象有效。
当HandleMark对象析构的时候,从HandleArea中删除对象的引用。由于所有的句柄 handler 都形成了一个链表。因此通过整个链表可以获得本地代码执行中对堆对象的引用。
句柄和OOP对象关联,在HandleArea中有一个slot用于指向OOP对象。这个 HandArea 实际上就是
JNI句柄:
对于本地代码,并不归JVM直接管理,在执行JNI代码的时候,也有可能访问堆中的OOP对象。所以也需要一个机制进行管理,JVM同样引入类似的句柄机制,JNIHandle。
分为两种,全局和局部对象引用。局部对象引用最终调用了JNIHandleBlock来管理,因为JNIHandle没有设计一个JNIHandleMark的机制,所以创建时需要明确调用make_local,回收时也需要明确调用destory_local。
对于全局对象,在编译任务compilerTask中会访问method对象,需要把这些对象设置为全局的,否在可能在GC的时候被回收。
这两个部分的垃圾回收时的处理也是不同的,局部JNIhandle是通过线程,全局JNIhandle则是通过全局变量开始的
这里将 handler 直接理解为对象句柄,每个C++对象都会创建这个句柄,GC时对象的地址可能变化,那么直 接通过句柄去访问对象就不用管他的地址是否发生改变。handler的继承关系如图:
以下展示直接访问 Oop和 Klass。于通过 handler 句柄简介访问的区别:
当Oop地址发生变化时,为了避免修改直接引用时造成的多个引用更新,而直接将 handle 引用的 Oop 进行更新。Handle 类实际上就是对每个 Oop 的封装:
class Handle VALUE_OBJ_CLASS_SPEC {
private:
  oop* _handle;
protected:
  oop     obj() const                            { return _handle == NULL ? (oop)NULL : *_handle; }
  oop     non_null_obj() const                   { assert(_handle != NULL, "resolving NULL handle"); return *_handle; }
public:
  // Constructors
  Handle()                                       { _handle = NULL; }
  Handle(oop obj);
  Handle(Thread* thread, oop obj);
  // General access
  oop     operator () () const                   { return obj(); }
  oop     operator -> () const                   { return non_null_obj(); }
  bool    operator == (oop o) const              { return obj() == o; }
  bool    operator == (const Handle& h) const          { return obj() == h.obj(); }
  // Null checks
  bool    is_null() const                        { return _handle == NULL; }
  bool    not_null() const                       { return _handle != NULL; }
  // Debugging
  void    print()                                { obj()->print(); }
  // Direct interface, use very sparingly.
  // Used by JavaCalls to quickly convert handles and to create handles static data structures.
  // Constructor takes a dummy argument to prevent unintentional type conversion in C++.
  Handle(oop *handle, bool dummy)                { _handle = handle; }
  // Raw handle access. Allows easy duplication of Handles. This can be very unsafe
  // since duplicates is only valid as long as original handle is alive.
  oop* raw_value()                               { return _handle; }
  static oop raw_resolve(oop *handle)            { return handle == NULL ? (oop)NULL : *handle; }
};

3.HandlerMark + HandlerArea + Chunk 维护引用信息
而 HandlerMark 用于在一个Java函数调用时对里面涉及到的 Oop 对象引用的生命周期进行管理,会绑定当前调用目标Java函数的线程。
HandleMark的定义在hotspot/src/share/vm/runtime/handles.hpp中,主要用于记录当前线程的HandleArea的内存地址top,执行方法调用后,HandleMark实例自动销毁。
在HandleMark的析构函数中会将HandleArea的当前内存地址到方法调用前的内存地址top之间的所有分配的oop都销毁掉,然后恢复当前线程的HandleArea的内存地址top到方法调用前的状态。
class HandleMark {
private:
  Thread *_thread;              // 表示拥有此HandleMark实例的Thread
  HandleArea *_area;            // 此HandleMark实例保存的HandleArea
  Chunk *_chunk;                // 保存HandleArea的Chunk
  char *_hwm, *_max;            // 保存的HandleArea的信息
  size_t _size_in_bytes;        // 保存的HandleArea的大小
  HandleMark* _previous_handle_mark; // 指向当前线程的上一个活跃的HandleMark实例
  void initialize(Thread* thread); // 初始化,将当前线程的_area的属性保存到新的HandleMark实例中。将当前HandleMark实例同线程关联起
  void set_previous_handle_mark(HandleMark* mark) { _previous_handle_mark = mark; }    //
  HandleMark* previous_handle_mark() const        { return _previous_handle_mark; }
  size_t size_in_bytes() const { return _size_in_bytes; }
public:
  HandleMark();                            // see handles_inline.hpp
  HandleMark(Thread* thread)                      { initialize(thread); }
  ~HandleMark();
  // Functions used by HandleMarkCleaner
  // called in the constructor of HandleMarkCleaner
  void push();
  // called in the destructor of HandleMarkCleaner
  void pop_and_restore();
  // overloaded operators
  void* operator new(size_t size) throw();
  void* operator new [](size_t size) throw();
  void operator delete(void* p);
  void operator delete[](void* p);
};

HandlerMark 中封装了 HandlerArea(这两个一起得到一组,他们是相互引用的),同样位于hotspot/src/share/vm/runtime/handles.hpp中,表示一片专门用于分配Handle的内存区域。也就是说 handler 都记录在一个 HandlerArea 中。
HandlerArea 自己定义了 _prev 将栈帧中的 HandlerArea 进行链接。另外三个函数就是用来保存使用到的所有对象 Oop 对应的 handler 信息。
* allocate_handle():用于分配保存oop的内存
* oops_do():垃圾回收时遍历对应引用
* used():获取已经使用的内存大小
HandlerArea 定义如下:
class HandleArea: public Arena {
  friend class HandleMark;
  friend class NoHandleMark;
  friend class ResetNoHandleMark;
#ifdef ASSERT
  int _handle_mark_nesting;
  int _no_handle_mark_nesting;
#endif
  HandleArea* _prev;          // link to outer (older) area
public:
  // Constructor
  HandleArea(HandleArea* prev) : Arena(mtThread, Chunk::tiny_size) {
    debug_only(_handle_mark_nesting    = 0);
    debug_only(_no_handle_mark_nesting = 0);
    _prev = prev;
  }
  // Handle allocation
private:
  oop* real_allocate_handle(oop obj) {
#ifdef ASSERT
    oop* handle = (oop*) (UseMallocOnly ? internal_malloc_4(oopSize) : Amalloc_4(oopSize));
#else
    oop* handle = (oop*) Amalloc_4(oopSize);
#endif
    *handle = obj;
    return handle;
  }
public:
#ifdef ASSERT
  oop* allocate_handle(oop obj);
#else
  oop* allocate_handle(oop obj) { return real_allocate_handle(obj); }
#endif
  // Garbage collection support
  void oops_do(OopClosure* f);
  // Number of handles in use
  size_t used() const     { return Arena::used() / oopSize; }
  debug_only(bool no_handle_mark_active() { return _no_handle_mark_nesting > 0; })
};

也就是说 HandlerMark  + HandlerArea + _chunk 这三个形成一组信息,保存当前线程对所有对象引用信息,其中Chunk是真正用来存储东西的区域,在堆中。
Chunk自己只有两个属性,_next记录下一个Chunk,_len记录长度,剩下一大片空白是额外分配来真真正正存储东西的区域。
_first 记录了第一个chunk的起始地址,_chunk记录了当前chunk的起始地址
_max记录了当前chunk的可用极限地址,_hwm是已经使用了的部分的顶部也就是水位线。
注意 Chunk 只是记录了一块一块内存分配的最小单位,但是实际上每个 Oop(Handler)真正使用了多大内存这个信息是在内存分配时才通过 chunk 维护的。 代码体现在  HandleArea 的两个函数中:
HandleArea  real_allocate_handle(oop obj)    =>    这个函数就是传入一个 oop 对象,将 oop 封装成 handler 后返回,并调用 Amalloc_4  分配内存并记录 chunk 使用信息,分配从 hwm 值位置开始。
HandleArea  allocate_handle(oop obj)    =>    传入一个 oop 对象。返回一个封装好的 handler,调用上面的函数进行分配。
oop* real_allocate_handle(oop obj) {
    #ifdef ASSERT
    oop* handle = (oop*) (UseMallocOnly ? internal_malloc_4(oopSize) : Amalloc_4(oopSize));
    #else
    oop* handle = (oop*) Amalloc_4(oopSize);
    #endif
    *handle = obj;
    return handle;
  }
oop* allocate_handle(oop obj) { 
    return real_allocate_handle(obj); 
}

4.HandleMarkCleaner 清理
首先看Java普通方法最终调用的函数 call_helper:
JavaCalls::call_helper 整个代码如下:
void JavaCalls::call_helper(JavaValue* result, methodHandle* m, JavaCallArguments* args, TRAPS) {
  // 省略
  // 检查目标Java方法是否是开启了首次执行必须被编译,是的话调用JIT编译器去编译目标方法
  // 如果配置了-Xint选项就是解释模式执行,也就是需要运行时才进行编译那么就不会走这里
  // 如果配置了编译执行则会走这里
  assert(!thread->is_Compiler_thread(), "cannot compile from the compiler");
  if (CompilationPolicy::must_be_compiled(method)) {
    CompileBroker::compile_method(method, InvocationEntryBci,
                                  CompilationPolicy::policy()->initial_compile_level(),
                                  methodHandle(), 0, "must_be_compiled", CHECK);
  }
  // 获取保存在 address 属性上的 entry_point
  // 每种 Java方法对应的 entry_point 会在 generate_normal_entry() 中进行创建,初始化三个东西 1.局部变量表 2.方法栈信息 3.操作数栈信息
  // 创建的这些 entry_point 通过 _entry_table 进行缓存用于快速查找
  // 在方法连接时 Method::link_method() 会调用 Interpreter::entry_for_method() 获取 Java 方法入口 entry_point
  // 然后得到方法入口 entry_point 后调用 set_interpreter_entry() 进行保存,表存在属性 address 上
  // 而这里就是将这个 entry_point 取出来,直接从 address 属性上获取
  address entry_point = method->from_interpreted_entry();
  if (JvmtiExport::can_post_interpreter_events() && thread->is_interp_only_mode()) {
    entry_point = method->interpreter_entry();
  }
  BasicType result_type = runtime_type_from(result);
  bool oop_result_flag = (result->get_type() == T_OBJECT || result->get_type() == T_ARRAY);
  intptr_t* result_val_address = (intptr_t*)(result->get_value_addr());
  Handle receiver = (!method->is_static()) ? args->receiver() : Handle();
  if (thread->stack_yellow_zone_disabled()) {
    thread->reguard_stack();
  }
  // to Java
  if (!os::stack_shadow_pages_available(THREAD, method)) {
    Exceptions::throw_stack_overflow_exception(THREAD, __FILE__, __LINE__, method);
    return;
  } else {
    os::bang_stack_shadow_pages();
  }
  // 调用 Java Method
  { JavaCallWrapper link(method, receiver, result, CHECK);
    { HandleMark hm(thread);  // HandleMark used by HandleMarkCleaner
      // StubRoutines::call_stub()函数返回一个函数指针,通过指针来调用这个函数
      // Linux X86架构下的C/C++函数调用约定,在这个约定下,以下寄存器用于传递参数,六个参数以内用寄存器传递,操作六个则使用调用栈来传递额外的参数
      // 第1个参数:rdi c_rarg0
      // 第2个参数:rsi c_rarg1
      // 第3个参数:rdx c_rarg2
      // 第4个参数:rcx c_rarg3
      // 第5个参数:r8 c_rarg4
      // 第6个参数:r9 c_rarg5
      StubRoutines::call_stub()(
        (address)&link, // 类型为JavaCallWrapper
        // (intptr_t*)&(result->_value),
        result_val_address, // 函数返回值地址
        result_type,
        method(),  // 当前要执行的 Java Method,这里获取出来是元数据信息,里面包含字节码信息
        entry_point, // 用于后续帧开辟, entry_point 会从 method() 中获取出 Java Method 的第一个字节码命令(Opcode),也就是整个 Java Method 的调用入口
        args->parameters(), // 就是 Java Method 的函数参数
        args->size_of_parameters(), // 用函数调用Caller栈来传递,这个就是Java Method 的函数参数大小 size,占用内存大小(字节)
        CHECK // 用函数调用Caller栈来传递,CHECK是宏,定义的线程对象 thread
      );
      result = link.result();  // circumvent MS C++ 5.0 compiler bug (result is clobbered across call)
      if (oop_result_flag) {
        thread->set_vm_result((oop) result->get_jobject());
      }
    }
  }
  if (oop_result_flag) {
    result->set_jobject((jobject)thread->vm_result());
    thread->set_vm_result(NULL);
  }
}

在调用  StubRoutines::call_stub() 前会先构建一个JavaCallWrapper实例,然后再构造一个HandleMark实例。查看注释: HandleMark used by HandleMarkCleaner,可以知道最终的 HandlerMarkCleaner 会负责 HandlerMark 中被标记了的 handler 引用。
HandleMarkCleaner的角色类似于JavaCallWrapper,通过HandleMarkCleaner的构造和析构方法在每次进入虚拟机前执行某些动作,是HandleMark的强化版,它依赖于JavaCalls::call_helper方法中在执行方法调用前会创建一个新的HandleMark。其定义在hotspot/src/share/vm/runtime/interfaceSurpport.hpp中,只有一个私有属性Thread* _thread,表示关联的线程。
定义如下:
class HandleMarkCleaner: public StackObj {
private:
  Thread* _thread;
public:
  HandleMarkCleaner(Thread* thread) {
    _thread = thread;
    _thread->last_handle_mark()->push();
  }
  ~HandleMarkCleaner() {
    _thread->last_handle_mark()->pop_and_restore();
  }
private:
  inline void* operator new(size_t size, void* ptr) throw() {
    return ptr;
  }
};

其中 HandleMarkCleaner构造时调用 HandlerMark 的 push(),在销毁时调用 HandlerMark 的 pop_and_restore()。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

0x13

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值