在HotSpot VM中定义了一个Relocation类及相关的子类,可以通过这些类操作不同的重定位数据,如在CodeCache中读写这些数据。这些类需要的内存很小,但是不同的类需要的内存大小又不一样,所以做了如下的设计:
#include <cstdlib> #include "iostream" class Relocation; class RelocationHolder { friend class Relocation; private : enum { _relocbuf_size = 5 }; // 总共分配了5 * 8 = 40 字节大 // 小的内存用来存储Relocation void* _relocbuf[ _relocbuf_size ]; public : Relocation* reloc() const { return (Relocation*) &_relocbuf[0]; } }; class Relocation { public: void *operator new(size_t size, const RelocationHolder &holder) { // 由于Relocation是RelocationHolder的友元, // 所以能访问其私有的_relocbuf数据 if (size > sizeof(holder._relocbuf)) { std::cerr << "error" << std::endl; } return holder.reloc(); } // 虚函数,子类可重写,这样能进行动态分派 virtual void pack_data_to() {} }; class DataRelocation : public Relocation { private: int _data; // 具体数据 public: DataRelocation(int data){ _data = data; } virtual void pack_data_to() { std::cout << "DataReloction::pack_data_to()" << std::endl; } }; class CallRelocation : public Relocation { private: u_char* _call_pc; // 具体数据 public: CallRelocation(u_char* call_pc) { _call_pc = call_pc; } virtual void pack_data_to() { std::cout << "CallRelocation::pack_data_to()" << std::endl; } };
其中的RelocationHolder是一个具体的Relocation的持有者。DataRelocation和CallRelocation代表了不同的重定位数据,可以调用对应的pack_data_to()函数按一定的规则将相应的数据写入CodeCache中。
下面看具体的使用,如下:
int main() { // 在栈上分配内存 RelocationHolder rh; // 使用RelocationHolder中的_relocbuf数组占用的内存为DataRelocation // 分配内存 u_char *addr = NULL; CallRelocation *dr = new(rh) CallRelocation(addr); dr->pack_data_to(); // DataRelocation操作完成后,重用RelocationHolder中_relocbuf的 // 内存 DataRelocation *cr = new(rh) DataRelocation(100); cr->pack_data_to(); return 0; }
RelocationHolder中的_relocbuf数组有固定的40字节内存,这些内存都分配在栈上,而DataRelocation或CallRelocation虽然需要的内存大小不同,但是都小于40字节,所以当CallRelocation使用完后,DataRelocation又可以重用这一部分栈内存。虽然使用new关键字创建了2个对象,但是分配的内存都在栈上,不需要释放。当函数返回时,对象会自动失效。
运行后的结果如下:
DataReloction::pack_data_to() CallRelocation::pack_data_to()
如上的方法已经能满足一部分需求,但是使用起来不方便,首先需要找一个RelocationHolder,然后还需要自己创建一个对应的Relocation实例出来。为了让程序用起来更方便,也更优雅一些,HotSpot VM又增加了一些设计,提供了工厂方法,改造后的代码如下:
class Relocation { public: static RelocationHolder newHolder() { // 调用默认的构造函数,生成一个在栈上分配内存的 // RelocationHolder类型的对象 // 注意,这里创建的对象在调用完函数后会 // 失效,返回的是一个通过拷贝构造函数 // 拷贝到栈上的一个临时对象 return RelocationHolder(); } void *operator new(size_t size, const RelocationHolder &holder) { // 由于Relocation是RelocationHolder的友元, // 所以能访问其私有的_relocbuf数据 if (size > sizeof(holder._relocbuf)) { std::cerr << "error" << std::endl; } return holder.reloc(); } virtual void pack_data_to() {} }; class DataRelocation : public Relocation { private: int _data; public: DataRelocation(int data){ _data = data; } static RelocationHolder spec(int data) { RelocationHolder rh = newHolder(); new(rh) DataRelocation(data); return rh; } virtual void pack_data_to() { std::cout << "DataReloction::pack_data_to()" << std::endl; } }; class CallRelocation : public Relocation { private: u_char* _call_pc; public: CallRelocation(u_char* call_pc) { _call_pc = call_pc; } static RelocationHolder spec(u_char* call_pc) { RelocationHolder rh = newHolder(); new(rh) CallRelocation(call_pc); return rh; } virtual void pack_data_to() { std::cout << "CallRelocation::pack_data_to()" << std::endl; } };
RelocationHolder类不需要改动,主要是为CallRelocation和DataRelocation增加了工厂方法spec(),同样使用的是栈上分配的内存,不需要释放,使用时只需要这样:
void relocate(RelocationHolder const& spec) { Relocation* reloc = spec.reloc(); // 处理重定位相关数据 reloc->pack_data_to(); }; int main() { // 收集重定位相关数据 u_char *addr = NULL; RelocationHolder r1 = CallRelocation::spec(addr); relocate(r1); RelocationHolder r2 = DataRelocation::spec(100); relocate(r2); return 0; }
这样看起来是不是要比之前更加简洁了呢?在一个函数中收集数据,在另外的函数中处理数据。
本人最近准备出一个手写Hotspot VM的课程,超级硬核,从0开始写HotSpot VM,将HotSpot VM所有核心的实现全部走一遍,如感兴趣,加我速速入群。
已加群的不要重复加。
群里可讨论虚拟机和Java性能剖析与故障诊断等话题,欢迎加入。