C++在HotSpot VM中一种巧妙的内存管理方式

在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性能剖析与故障诊断等话题,欢迎加入。

相关推荐
人道领域1 小时前
【零基础学java】(等待唤醒机制,线程池补充)
java·开发语言·jvm
小突突突1 小时前
浅谈JVM
jvm
饺子大魔王的男人2 小时前
远程调试总碰壁?局域网成 “绊脚石”?Remote JVM Debug与cpolar的合作让效率飙升
网络·jvm
天“码”行空12 小时前
java面向对象的三大特性之一多态
java·开发语言·jvm
独自破碎E18 小时前
JVM的内存区域是怎么划分的?
jvm
期待のcode19 小时前
认识Java虚拟机
java·开发语言·jvm
leaves falling1 天前
一篇文章深入理解指针
jvm
linweidong1 天前
C++ 中避免悬挂引用的企业策略有哪些?
java·jvm·c++
曹轲恒1 天前
JVM中的直接内存
jvm
BHXDML1 天前
JVM 深度理解 —— 程序的底层运行逻辑
java·开发语言·jvm