《JVM G1源码分析和调优》 —2.6.2 句柄

举报
华章计算机 发表于 2019/12/20 13:27:43 2019/12/20
【摘要】 本节书摘来自华章计算机《JVM G1源码分析和调优》 一书中第2章,第2.6.2节,作者是彭成寒。

2.6.2 句柄

实际上线程既可以支持Java代码的执行也可以执行本地代码,如果本地代码(这里的本地代码指的是JVM里面的本地代码,而不是用户自定义的本地代码)引用了堆里面的对象该如何处理?是不是也是通过栈?理论上是可行的,实际上JVM并没有区分Java栈和本地方法栈,如果通过栈进行处理则必须要区分这两种情况。JVM设计了另一个概念,handleArea,这是一块线程的资源区,在这个区域分配句柄(handle),并且管理所有的句柄,如果函数还在调用中,那么句柄有效,句柄关联的对象也就是活跃对象。为了管理句柄的生命周期,引入了HandleMark,通常HandleMark分配在栈上,在创建HandleMark的时候标记handleArea对象有效,在HandleMark对象析构的时候,从HandleArea中删除对象的引用。由于所有句柄都形成了一个链表,那么访问这个句柄链表就可以获得本地代码执行中对堆对象的引用。

句柄和OOP对象关联,在HandleArea中有一个slot用于指向OOP对象。

本节源码都在下面两个文件中,为了便于阅读和减少篇幅,我们对其中的类代码进行了重组,代码如下所示:

hotspot/src/share/vm/runtime/handles.cpp

hotspot/src/share/vm/runtime/handles.hpp

 

class Handle VALUE_OBJ_CLASS_SPEC {

  private:

    oop* _handle;

 

  ……

 

        inline Handle(oop obj) {

        if (obj == NULL) {

          _handle = NULL;

        } else {

          _handle = Thread::current()->handle_area()->allocate_handle(obj);

        }

      }

 

    ……

 

  }

在HandleMark中标记Chunk的地址,这个就是找到当前本地方法代码中活跃的句柄,因此也就可以找到对应的活跃的OOP对象。下面是HandleMark的构造函数和析构函数,它们的主要工作就是构建句柄链表,代码如下所示:

class HandleMark {

  private:

    Thread *_thread;            // 这个HandleMark归属的线程

    HandleArea *_area;       // 保存句柄的区域

    Chunk *_chunk;             // Chunk 和Area配合,获得准确的内存地址

    char *_hwm, *_max;             // 句柄区域的属性

    size_t _size_in_bytes;    // 句柄区域的大小

    // HandleMark形成链表的字段

    HandleMark* _previous_handle_mark;

 

    HanldeMark(THread* thread)

      _thread = thread;

    // 获得句柄区域

    _area  = thread->handle_area();

    // 获取handleArea的信息,用于标记句柄分配的状态

    _chunk = _area->_chunk;

    _hwm   = _area->_hwm;

    _max   = _area->_max;

    _size_in_bytes = _area->_size_in_bytes;

 

    // 形成链表,注意HandleMark是通过线程访问,所以这里会关联到线程中

    set_previous_handle_mark(thread->last_handle_mark());

    thread->set_last_handle_mark(this);

}

 

    HandleMark::~HandleMark() {

    HandleArea* area = _area;  // 用于编译优化别名分析

 

    // 删除最后加入的chuanks

    if( _chunk->next() ) {

    // 恢复缓存的信息

    _chunk->next_chop();

  }

  // 恢复handleArea的信息

  area->_chunk = _chunk;

  area->_hwm = _hwm;

  area->_max = _max;

 

  // 删除handlemark

  _thread->set_last_handle_mark(previous_handle_mark());

}

 

……

};

在这里我们提到了Chunk,Chunk的回收是通过前面我们提到的周期性线程Watcher

Thread完成的。

还需要提到一点,就是JVM中的本地代码指的是JVM内部的代码,除了JVM内部的本地代码,还有JNI代码也是本地代码。对于本地代码,并不归JVM直接管理,在执行JNI代码的时候,也有可能访问堆中的OOP对象。所以也需要一个机制进行管理,JVM引入了类似的句柄机制,称为JNIHandle。JNIHandle分为两种,全局和局部对象引用,大部分对象的引用属于局部对象引用,最终还是调用了JNIHandleBlock来管理,因为JNIHandle没有设计一个JNIHandleMark的机制,所以在创建时需要明确调用make_local,在回收时也需要明确调用destory_local。对于全局对象,比如在编译任务compilerTask中会访问Method对象,这时候就需要把这些对象设置为全局的(否则在GC时可能会被回收的)。这两部分在垃圾回收时的处理是不同的,局部JNIhandle是通过线程,全局JNIhandle则是通过全局变量开始。


【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。