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

相关推荐
李长渊哦2 小时前
常用的 JVM 参数:配置与优化指南
java·jvm
数巨小码人5 小时前
QT SQL框架及QSqlDatabase类
jvm·sql·qt
martian66515 小时前
【Java高级篇】——第16篇:高性能Java应用优化与调优
java·开发语言·jvm
李长渊哦1 天前
Java 虚拟机(JVM)方法区详解
java·开发语言·jvm
二十七剑1 天前
jvm中各个参数的理解
java·jvm
七禾页话1 天前
垃圾回收知识点
java·开发语言·jvm
至少零下七度2 天前
Mac book Air M2 用VMware安装 Ubuntu22.04
linux·ubuntu·vmware·虚拟机
小梁不秃捏2 天前
深入浅出Java虚拟机(JVM)核心原理
java·开发语言·jvm
xiaolingting2 天前
JVM层面的JAVA类和实例(Klass-OOP)
java·jvm·oop·klass·instanceklass·class对象
神仙别闹2 天前
基于Python+Sqlite实现的选课系统
jvm·python·sqlite