专注虚拟机与编译器研究

第2.8篇-句柄Handle的释放

本篇首先介绍几个与句柄分配与释放密切相关的类,然后重点介绍句柄的释放。

1、HandleArea、Area与Chunk

句柄都是在HandleArea中分配并存储的。

HandleArea继承自CHeap,所以是通过malloc()与free()函数在本地内存中分配内存。另外还有ResourceArea,2个都在Thread类中定义,如下:

// Thread local resource area for temporary allocation within the VM
ResourceArea* _resource_area;

// Thread local handle area for allocation of handles within the VM
HandleArea* _handle_area;

在创建Thread实例时,在构造函数中创建HandleArea与ResourceArea实例,如下:

Thread::Thread() {
  ...
  set_resource_area(new (mtThread)ResourceArea()); // 初始化_resource_area属性
  set_handle_area(new (mtThread) HandleArea(NULL));// 初始化_handle_area属性
  ...
}

调用重写的new重载运算符为ResourceArea与HandleArea分配内存,如下:

void* Arena::operator new(size_t size, MEMFLAGS flags) throw() {
  return (void *) AllocateHeap(size, flags|otArena, CALLER_PC);
}

// 调用malloc()函数分配内存,如果没有足够内存,分配将失败
inline char* AllocateHeap(
 size_t          size,
 MEMFLAGS        flags,
 address         pc = 0,
 AllocFailType   alloc_failmode = AllocFailStrategy::EXIT_OOM
){
  char* p = (char*) os::malloc(size, flags, pc);
  return p;
}

现在来看HandleArea类的定义,如下:

源代码位置:openjdk/hotspot/src/share/vm/runtime/handles.hpp

class HandleArea: public Arena {
  ...
  HandleArea* _prev;  // HandleArea通过_prev连接成单链表
 public:
  HandleArea(HandleArea* prev) : Arena(Chunk::tiny_size) {
    _prev = prev;
  }
  
 private:
  oop* real_allocate_handle(oop obj) { // 分配内存并存储obj
    oop* handle = (oop*) Amalloc_4(oopSize);
    *handle = obj;
    return handle;
  } 
  ... 
};

real_allocate_handle()函数在HandleArea中分配内存并存储obj,此函数调用父类Arena中定义的Amalloc_4()函数分配内存。Arena类的定义如下:

 

源代码位置:openjdk/hotspot/src/share/vm/memory/allocation.hpp

class Arena: public public CHeapObj<mtNone|otArena>{
protected:
  ...
  Chunk *_first;                // 单链表的第1个Chunk
  Chunk *_chunk;                // 当前正在使用的Chunk
  char  *_hwm, *_max;            
public:
  ...
  void *Amalloc_4(size_t x) {
    if (_hwm + x > _max) {
      // 分配新的Chunk块,在新的Chunk块中分配内存
      return grow(x); 
    } else {
      char *old = _hwm;
      _hwm += x;
      return old;
    }
  }  
  ... 
}; 

Amalloc_4()函数会在当前的Chunk块中分配内存,如果当前块的内存不够,调用grow()函数分配新的Chunk块,然后在新的Chunk块中分配内存。

Arena类通过_first_chunk等属性管理着一个连接成单链表的Chunk,其中_first指向单链表的第一个Chunk,而_chunk指向的是当前可提供内存分配的Chunk,通常为单链表的最后一个块Chunk_hwm_max指示当前可分配内存的Chunk的一些分配信息。

Chunk类的定义如下:

源代码位置:openjdk/hotspot/src/share/vm/memory/allocation.hpp

class Chunk: CHeapObj<mtChunk> {
 public:
  ...
  Chunk*       _next;           // 单链表的下一个Chunk
  size_t       _len;            // 当前Chunk的大小  
    
  char* bottom() const { 
    return ((char*) this) + sizeof(Chunk);  
  }
  char* top()    const { 
    return bottom() + _len; 
  }
};

HandleAreaChunk类之间的关系如图2-17所示。

上图已经清楚的展示了HandleAreaChunk的关系,灰色部分表示在Chunk中已经分配了的内存,那么新的内存分配就可以从_hwm开始。现在看Amalloc_4()函数的逻辑就非常容易理解了,这个函数还会调用grow()函数分配新的Chunk块,如下:

源代码位置:openjdk/hotspot/src/share/vm/runtime/handles.hpp

void* Arena::grow( size_t x ) {
  size_t len = max(x, Chunk::size);

  register Chunk *k = _chunk;   // Get filled-up chunk address
  _chunk = new (len) Chunk(len);

  if( k )
	  k->_next = _chunk;    
  else
	  _first = _chunk;

  _hwm  = _chunk->bottom();     
  _max =  _chunk->top();
  set_size_in_bytes(size_in_bytes() + len);
  void* result = _hwm;
  _hwm += x;
  return result;
}

分配新的Chunk块后加入单链表,然后在新的Chunk块中分配x大小的内存。

2、HandleMark

每一个Java线程都有一个私有的句柄区_handle_area来存储其运行过程中的句柄信息,这个句柄区会随着Java线程的栈帧变化。Java线程每调用一个Java方法就会创建一个对应的HandleMark保存创建的对象句柄,然后等调用返回后释放这些对象句柄,此时释放的仅是调用当前方法创建出来的句柄,所以HandleMark只需要恢复到调用方法之前的状态即可。

HandleMark主要用于记录当前线程的HandleArea的内存地址top,当相关的作用域执行完成后,当前作用域之内的HandleMark实例自动销毁,在HandleMark的析构函数中会将HandleArea的当前内存地址到方法调用前的内存地址top之间的所有分配的地址中存储的内容都销毁掉,然后恢复当前线程的HandleArea的内存地址top到方法调用前的状态。

C++的析构函数专门用来释放内存,这绝对是一个需要好好学习的知识点。

HandleMark一般情况下直接在线程栈内存上分配,应该继承自StackObj,但是部分情况下HandleMark也需要在堆内存上分配,所以没有继承自StackObj,并且为了支持在堆内存上分配,重载了newdelete运算符

类的定义如下:

源代码位置:openjdk/hotspot/src/share/vm/runtime/handles.hpp

class HandleMark {
 private:
  Thread     *_thread;          // 拥有当前HandleMark实例的线程
  HandleArea *_area;            
  Chunk      *_chunk;           // Chunk和Area配合,获得准确的内存地址
  char       *_hwm, *_max;     
// 这个值用来保存的HandleArea的总大小,也就是所有的Chunk块的大小的总和 size_t _size_in_bytes; // 通过如下属性让HandleMark形成单链表 HandleMark* _previous_handle_mark; void initialize(Thread* thread); 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(); HandleMark(Thread* thread) { initialize(thread); } ~HandleMark(); ... };

handleMark也会通过_previous_handle_mark属性形成一条单链表。

HandleMark的构造函数中会调用initialize()函数,函数的实现如下: 

源代码位置:openjdk/hotspot/src/share/vm/runtime/handles.cpp

void HandleMark::initialize(Thread* thread) {
  _thread = thread;
  _area  = thread->handle_area();
  _chunk = _area->_chunk;
  _hwm   = _area->_hwm;
  _max   = _area->_max;
  _size_in_bytes = _area->_size_in_bytes;
  
  // 通过当前的HandleMark的_previous_handle_mark属性保存
  // 前一个HandleMark实例
  HandleMark* hm = thread->last_handle_mark();
  set_previous_handle_mark(hm);

 // 将当前HandleMark实例同线程关联起来
  // 注意,线程中的_last_handle_mark属性保存HandleMark实例
  thread->set_last_handle_mark(this); 
}

主要初始化一些属性。Thread中定义的_last_handle_mark属性的定义如下:

源代码位置:openjdk/hotspot/src/share/vm/runtime/thread.hpp

// 线程中的此属性永远指定最后一个HandleMark实例
HandleMark* _last_handle_mark;

handleMark的析构函数如下: 

源代码位置:openjdk/hotspot/src/share/vm/runtime/handles.cpp

HandleMark::~HandleMark() {
  HandleArea* area = _area;  
  
  // 如果Chunk的_next属性不为空,说明当前的HandleMark实例创建过新的Chunk块,
  if( _chunk->next() ) {
    assert(area->size_in_bytes() > size_in_bytes(), "Sanity check");
    // 恢复之前的大小
    area->set_size_in_bytes(size_in_bytes());
    // 删除当前Chunk以后的所有Chunk,即在当前HandleMark生命周期过程中新创建的Chunk
    _chunk->next_chop();
  } else {
    // 如果没有下一个Chunk,说明未分配新的Chunk,则area的大小应该保持不变
    assert(area->size_in_bytes() == size_in_bytes(), "Sanity check");
  }

  // 恢复HandleArea类中相关的属性到HandleMark构造时的状态
  area->_chunk = _chunk;
  area->_hwm = _hwm;
  area->_max = _max;
  
  // 解除当前HandleMark实例跟i当前线程的关联
  _thread->set_last_handle_mark(previous_handle_mark());
}

创建一个新的HandleMark以后,新的HandleMark保存当前线程的area的当前chunk_hwm _max等属性,代码执行期间新创建的Handle实例是在当前线程的area中分配内存,这会导致当前线程的area的当前chunk_hwm _max等属性发生变更,因此代码执行完成后需要将这些属性恢复成之前的状态,并把代码执行过程中新创建的Handle实例的内存给释放掉。 

下面看一下对HandleMark的具体使用,LinkResolver::resolve_field()函数中有如下代码片段:

HandleMark hm(THREAD);
Handle ref_loader (THREAD, InstanceKlass::cast(current_klass())->class_loader());
Handle sel_loader (THREAD, InstanceKlass::cast(sel_klass())->class_loader());

由于要通过Handle操作ClassLoader对象,所以使用了HandleMark,这个HandleMark的声明应该尽量在第一次使用Handle的地方,这样可尽量延迟使用或避免使用HandleMark,另外还要尽量使用块作用域来缩短其生命周期。

 

 

  

 

posted on 2020-07-15 07:27  鸠摩(马智)  阅读(2450)  评论(0编辑  收藏  举报

导航