【项目日记(二)】开胃菜--定长池的实现

简介: 【项目日记(二)】开胃菜--定长池的实现


1. 前言

在真正开始实现并发内存池前

我们先做一个子项目-定长池.

掌握了定长池对后面学习并发内存池

有很大的帮助,并且定长池本身也是

并发内存池的一个组件!

本章重点:

本篇文章着重讲解定长池的模拟实现
,其中涉及到自由链表的链接问题,如何
彻底放弃malloc的问题以及如何处理
32位和64位下指针大小的区分的问题


2. 前期基础知识铺垫

我们还需要补充两个知识的内容:

  1. 如何彻底舍弃malloc?
  2. 如何将free掉的资源再次利用?

windows/Linux下如何直接申请内存:

Windows下的申请方式

Linux下的申请方式

由于博主使用的开发环境是Windows

所以我这里直接使用此函数了:

inline static void* SystemAlloc(size_t kpage)
{
#ifdef _WIN32
  void* ptr = VirtualAlloc(0, kpage << 13, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#else
  // linux下使用brk mmap等
#endif
  if (ptr == nullptr)
    throw std::bad_alloc();
  return ptr;
}

如何重复利用已经释放掉的内存:

采用自由链表的方式将所有释放

掉的空间全部连接在一起

有一个问题:既然自由链表中链接的是
已经释放掉的内存,那么就代表这块内
存已经没用了,所以在32/64位机器下,
我们直接让这份空间的头4/8个字节指
向下一份释放的资源,就不用定义next
指针了,并且,当申请空间的大小小于
4/8时我们将它扩成4/8来存储后面的地址


3. 定长池基础框架

//定长内存池
template<class T>
class ObjectPool
{
public:
  inline static void* SystemAlloc(size_t kpage)//向系统申请内存的函数
  {
#ifdef _WIN32
    void* ptr = VirtualAlloc(0, kpage << 13, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#else
    // linux下brk mmap等
#endif
    if (ptr == nullptr)
      throw std::bad_alloc();
    return ptr;
  }
  T* New()
  {}
  void Delete(T* obj)
  {}
private:
  char* _memory = nullptr;     //向系统申请的一大块内存,申请了多少字节就从起始地址++多少次,所以定义为char*
  void* _freeList = nullptr;   //还回来的空间的过程中,链接的自由链表的头指针
  size_t _surplusBytes = 0;    //定长池剩余的空间大小(字节为单位)
};

关于代码的解释都在注释中,如果对架构
有不明白的地方欢迎私信.所以我们主要
是要实现两个函数,一个是new申请空间
一个是delete释放空间,并且需要注意的是,
这个定长池是一个类,在使用它时,同一类
型的变量申请空间会去同一个对象中申请


4. 定长池New的具体实现

在实现new函数之前我们要先想到
三个点:

  1. 当自由链表中有空间时,应该优先
    去自由链表中拿,而不是给它切分内存

  2. 当定长池剩余的空间不足时,需要扩容
  3. 若对象的大小小于4/8个字节,那么它
    就无法存储下一个位置的地址,此时需
    要将它直接扩为4/8字节来使用

T* New()
{
  T* obj = nullptr;
  //若有还回来的内存,优先使用它而不是去切割大内存
  if (_freeList != nullptr)
  {
    void* next = *(void**)_freeList;//先把头内存的后面一个内存找到,再把头内存头删了给外界用
    obj = (T*)_freeList;
    _freeList = next;
  }
  else
  {
    if (_surplusBytes < sizeof(T))//当剩余的空间不足以分配给T类型时,也要扩容
    {
      //_memory = malloc(128 * 1024);//128kbKB
      _surplusBytes = 128 * 1024;
      _memory = (char*)SystemAlloc(_surplusBytes);
      if (_memory == nullptr)
        throw std::bad_alloc();
    }
    obj = (T*)_memory;
    //若此对象的大小小于的指针的大小(4/8),则它释放后无法存储下一个内存的地址,要做特殊处理
    size_t objSize = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T);
    _memory += objSize;
    _surplusBytes -= objSize;
  }
  //定位new,显示调用T的构造函数初始化
  new(obj)T;
  return obj;
}

5. 对代码的解释

对代码的解释:

  1. 自由链表部分

void* next = * (int**)_freeList;这段代码的意思就是将自由链表的头节点的下一个节点提取出来,由于使用的机器是32还是64位是不确定的,所以将freelist强转为int**后,再解引用就是int* 类型的变量,假如你是32位那么int* 就占四位,假如你是64位那么int* 就占八位,完全省略了去判断电脑是多少位的必要.并且将自由链表中的数据返还给外界使用是用的头删的方法,因为效率更快!

  1. 直接切分内存部分

假如在64位机器下,一个指针的大小是8字节,但是用户申请了4字节的空间,这时我们需要将空间扩为八字节再返回给用户,因为一旦这份空间被返回,四字节是无法当成指针使用存储下一个节点地址的!并且切分内存后,_memory要向后移动,避免将同一份内存切分给不同的变量.在申请完内存后,需要使用定位new来对已经开辟好的空间做初始化.

  1. 定长内存池的物理结剖析

将已经释放的空间挂在自由链表上是逻辑的结构,但是实际的物理结构是不管自由链表悬挂了多少个内存块,它们实际上都是连在一起的在定长池中的,只不过已经被使用了的空间在指针_memory前面,而未分配内存的空间在_memory后面!


6. 定长池delete的具体实现

定长池的删除比较简单,不需要将空间

free掉,只需要将空间挂到自由链表上即可

并且使用头插的方式

具体代码如下:

void Delete(T* obj)
{
  //显示调用析构函数
  obj->~T();
  //使用**强转,不管是32位还是64位都没问题
  //取obj对象头四个字节来存储nullptr或下一个被Delete的对象的空间的地址
  //使用头插,不用每次都去找尾
  *(void**)obj = _freeList;
  _freeList = obj;
}

7. 总结以及小tips

定长池的实现是在为后面的并发内存

池打基础,请同学们耐心掌握这篇文章

的所有内容,后面会有大用处!

小tips:

我们在向系统申请内存时,一次性申请了256K也就是256*1024个字节的空间,也就是说我们的定长池最多只能256K


🔎 下期预告:高并发内存池总体框架🔍


相关文章
|
3月前
|
人工智能 自然语言处理 算法
数字永生源码独立部署怎么做?
数字人源码,数字人永生,数字人源码独立部署,数字人
数字永生源码独立部署怎么做?
|
5月前
|
算法 大数据 程序员
|
6月前
|
SQL 网络协议 Java
5面收割字节35k offer,只因一份热乎的Alibaba内部32W字面试手册
Java的知识体系十分庞大,多且杂,这就使得我们不仅要掌握Java基本语言,还要掌握很多相关技术。这样就导致Java程序员的面试题范围极广,必须做好充分的面试准备。
|
10月前
|
SpringCloudAlibaba 算法 前端开发
【Log/Java项目】一个自己写的供情侣们记录生活和表达关心与爱意的小网站
【Log/Java项目】一个自己写的供情侣们记录生活和表达关心与爱意的小网站
153 0
|
12月前
|
前端开发
给大家科普一泛二级程序前端几十套模板随机切换
​ 今天给大家分享几个小旋风蜘蛛池的泛二级程序网站站群模板,是无备案 新域名都可以用 老域名备案域名效果更好, 文章自动配图 关键词自动配图 泛二级程序模板是一款专门为了要从事相关工程方面工作的
93 0
|
运维 监控 数据可视化
【高效编码】简单全面JDK的监控命令,看这一篇就够了!!日拱一卒
您好,我是码农飞哥,感谢您阅读本文!如果此文对您有所帮助,请毫不犹豫的一键三连吧。小伙伴们有啥想看的,想问的,欢迎积极留言告诉我喔。 上一篇文章我们介绍了JDK中一些基础的常用的命令,BUT,这还远远不够!!SO,这篇文章我们将继续来介绍JDK中监控相关的命令。话不多说,让我们直接进入主题。
596 0
【高效编码】简单全面JDK的监控命令,看这一篇就够了!!日拱一卒
|
缓存 Java
全网最硬核 JVM TLAB 分析(单篇版不包含额外加菜)(下)
全网最硬核 JVM TLAB 分析(单篇版不包含额外加菜)(下)
|
存储 缓存 监控
全网最硬核 JVM TLAB 分析(单篇版不包含额外加菜)(上)
全网最硬核 JVM TLAB 分析(单篇版不包含额外加菜)(上)
全网最硬核 JVM TLAB 分析(单篇版不包含额外加菜)(上)
|
缓存 算法 Java
全网最硬核 JVM TLAB 分析(单篇版不包含额外加菜)(中)
全网最硬核 JVM TLAB 分析(单篇版不包含额外加菜)(中)
全网最硬核 JVM TLAB 分析(单篇版不包含额外加菜)(中)
|
存储 前端开发 数据安全/隐私保护
JavaWeb综合项目——快递e栈(后台部分)(三)
JavaWeb综合项目——快递e栈(后台部分)(三)
JavaWeb综合项目——快递e栈(后台部分)(三)