目录

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

本文是转载文章,点击查看原文
如有侵权,请联系 xyy@jishuzhan.net 删除
相关推荐
Aphelios38014 小时前
Java全栈面试宝典:线程协作与Spring Bean管理深度解析
java·开发语言·jvm·spring·面试·职场和发展
M malloc15 小时前
【C++奇遇记】C++中的进阶知识(继承(一))
java·jvm·c++
摘星编程15 小时前
JVM深入原理(六)(二):双亲委派机制
jvm
李小白6616 小时前
JavaEE初阶复习(JVM篇)
java·jvm·java-ee
我不想当小卡拉米2 天前
C++:继承+菱形虚拟继承的一箭双雕
开发语言·jvm·c++
时光呢2 天前
JAVA常见的 JVM 参数及其典型默认值
java·开发语言·jvm
我命由我123452 天前
Spring Boot 自定义日志打印(日志级别、logback-spring.xml 文件、自定义日志打印解读)
java·开发语言·jvm·spring boot·spring·java-ee·logback
xyliiiiiL2 天前
ZGC初步了解
java·jvm·算法
我没想到原来他们都是一堆坏人2 天前
利用vmware快速安装一个可以使用的centos7系统
linux·虚拟机
心灵Haven2 天前
JVM介绍
jvm