内存块与内存池

 (1)在运行过程中,MemoryPool内存池可能会有多个用来满足内存申请请求的内存块,这些内存块是从进程堆中开辟的一个较大的连续内存区域,它由一个MemoryBlock结构体和多个可供分配的内存单元组成,所有内存块组成了一个内存块链表,MemoryPool的pBlock是这个链表的头。对每个内存块,都可以通过其头部的MemoryBlock结构体的pNext成员访问紧跟在其后面的那个内存块。

(2)每个内存块由两部分组成,即一个MemoryBlock结构体和多个内存分配单元。这些内存分配单元大小固定(由MemoryPool的nUnitSize表示),MemoryBlock结构体并不维护那些已经分配的单元的信息;相反,它只维护没有分配的自由分配单元的信息。它有两个成员比较重要:nFree和nFirst。nFree记录这个内存块中还有多少个自由分配单元,而nFirst则记录下一个可供分配的单元的编号。每一个自由分配单元的头两个字节(即一个USHORT型值)记录了紧跟它之后的下一个自由分配单元的编号,这样,通过利用每个自由分配单元的头两个字节,一个MemoryBlock中的所有自由分配单元被链接起来。

(3)当有新的内存请求到来时,MemoryPool会通过pBlock遍历MemoryBlock链表,直到找到某个MemoryBlock所在的内存块,其中还有自由分配单元(通过检测MemoryBlock结构体的nFree成员是否大于0)。如果找到这样的内存块,取得其MemoryBlock的nFirst值(此为该内存块中第1个可供分配的自由单元的编号)。然后根据这个编号定位到该自由分配单元的起始位置(因为所有分配单元大小固定,因此每个分配单元的起始位置都可以通过编号分配单元大小来偏移定位),这个位置就是用来满足此次内存申请请求的内存的起始地址。但在返回这个地址前,需要首先将该位置开始的头两个字节的值(这两个字节值记录其之后的下一个自由分配单元的编号)赋给本内存块的MemoryBlock的nFirst成员。这样下一次的请求就会用这个编号对应的内存单元来满足,同时将此内存块的MemoryBlock的nFree递减1,然后才将刚才定位到的内存单元的起始位置作为此次内存请求的返回地址返回给调用者。

(4)如果从现有的内存块中找不到一个自由的内存分配单元(当第1次请求内存,以及现有的所有内存块中的所有内存分配单元都已经被分配时会发生这种情形),MemoryPool就会从进程堆中申请一个内存块(这个内存块包括一个MemoryBlock结构体,及紧邻其后的多个内存分配单元,假设内存分配单元的个数为n,n可以取值MemoryPool中的nInitSize或者nGrowSize),申请完后,并不会立刻将其中的一个分配单元分配出去,而是需要首先初始化这个内存块。初始化的操作包括设置MemoryBlock的nSize为所有内存分配单元的大小(注意,并不包括MemoryBlock结构体的大小)、nFree为n-1(注意,这里是n-1而不是n,因为此次新内存块就是为了满足一次新的内存请求而申请的,马上就会分配一块自由存储单元出去,如果设为n-1,分配一个自由存储单元后无须再将n递减1),nFirst为1(已经知道nFirst为下一个可以分配的自由存储单元的编号。为1的原因与nFree为n-1相同,即立即会将编号为0的自由分配单元分配出去。现在设为1,其后不用修改nFirst的值),MemoryBlock的构造需要做更重要的事情,即将编号为0的分配单元之后的所有自由分配单元链接起来。如前所述,每个自由分配单元的头两个字节用来存储下一个自由分配单元的编号。另外,因为每个分配单元大小固定,所以可以通过其编号和单元大小(MemoryPool的nUnitSize成员)的乘积作为偏移值进行定位。现在唯一的问题是定位从哪个地址开始?答案是MemoryBlock的aData[1]成员开始。因为aData[1]实际上是属于MemoryBlock结构体的(MemoryBlock结构体的最后一个字节),所以实质上,MemoryBlock结构体的最后一个字节也用做被分配出去的分配单元的一部分。因为整个内存块由MemoryBlock结构体和整数个分配单元组成,这意味着内存块的最后一个字节会被浪费,这个字节在图6-2中用位于两个内存的最后部分的浓黑背景的小块标识。确定了分配单元的起始位置后,将自由分配单元链接起来的工作就很容易了。即从aData位置开始,每隔nUnitSize大小取其头两个字节,记录其之后的自由分配单元的编号。因为刚开始所有分配单元都是自由的,所以这个编号就是自身编号加1,即位置上紧跟其后的单元的编号。初始化后,将此内存块的第1个分配单元的起始地址返回,已经知道这个地址就是aData。

(5)当某个被分配的单元因为delete需要回收时,该单元并不会返回给进程堆,而是返回给MemoryPool。返回时,MemoryPool能够知道该单元的起始地址。这时,MemoryPool开始遍历其所维护的内存块链表,判断该单元的起始地址是否落在某个内存块的地址范围内。如果不在所有内存地址范围内,则这个被回收的单元不属于这个MemoryPool;如果在某个内存块的地址范围内,那么它会将这个刚刚回收的分配单元加到这个内存块的MemoryBlock所维护的自由分配单元链表的头部,同时将其nFree值递增1。回收后,考虑到资源的有效利用及后续操作的性能,内存池的操作会继续判断:如果此内存块的所有分配单元都是自由的,那么这个内存块就会从MemoryPool中被移出并作为一个整体返回给进程堆;如果该内存块中还有非自由分配单元,这时不能将此内存块返回给进程堆。但是因为刚刚有一个分配单元返回给了这个内存块,即这个内存块有自由分配单元可供下次分配,因此它会被移到MemoryPool维护的内存块的头部。这样下次的内存请求到来,MemoryPool遍历其内存块链表以寻找自由分配单元时,第1次寻找就会找到这个内存块。因为这个内存块确实有自由分配单元,这样可以减少MemoryPool的遍历次数。

综上所述,每个内存池(MemoryPool)维护一个内存块链表(单链表),每个内存块由一个维护该内存块信息的块头结构(MemoryBlock)和多个分配单元组成,块头结构MemoryBlock则进一步维护一个该内存块的所有自由分配单元组成的"链表"。这个链表不是通过"指向下一个自由分配单元的指针"链接起来的,而是通过"下一个自由分配单元的编号"链接起来,这个编号值存储在该自由分配单元的头两个字节中。另外,第1个自由分配单元的起始位置并不是MemoryBlock结构体"后面的"第1个地址位置,而是MemoryBlock结构体"内部"的最后一个字节aData(也可能不是最后一个,因为考虑到字节对齐的问题),即分配单元实际上往前面错了一位。又因为MemoryBlock结构体后面的空间刚好是分配单元的整数倍,这样依次错位下去,内存块的最后一个字节实际没有被利用。这么做的一个原因也是考虑到不同平台的移植问题,因为不同平台的对齐方式可能不尽相同。即当申请MemoryBlock大小内存时,可能会返回比其所有成员大小总和还要大一些的内存。最后的几个字节是为了"补齐",而使得aData成为第1个分配单元的起始位置,这样在对齐方式不同的各种平台上都可以工作。

#ifndef DATA_BLOCK_H_
#define DATA_BLOCK_H_#include<assert.h>
#include<memory.h>class DataBlock
{
public:DataBlock(size_t capacity){data_ = new uint8_t[capacity];capacity_ = capacity;len_ = 0;}~DataBlock(){if (data_){delete[] data_;capacity_ = 0;len_ = 0;data_ = nullptr;}}uint8_t* Data(){return data_;}size_t Length(){return len_;}size_t Capacity(){return capacity_;}void SetData(const uint8_t* data, size_t len){assert(len <= capacity_);memcpy(data_, data, len);len_ = len;}void SetLength(size_t len){assert(len <= capacity_);len_ = len;}
private:uint8_t* data_ = nullptr;size_t    capacity_ = 0;size_t  len_ = 0;
};#endif

#ifndef MEM_POOL_H_
#define MEM_POOL_H_
#include <memory>
#include <vector>
#include <mutex>
#include "data_block.h"
class MemPool
{
public:MemPool(size_t max_block_count);std::shared_ptr<DataBlock> Get(size_t capacity);
private:std::vector<std::shared_ptr<DataBlock>> pool_;std::mutex    mutex_;size_t max_block_count_;
};
#endif
#include "mem_pool.h"MemPool::MemPool(size_t max_block_count): max_block_count_(max_block_count) {}
std::shared_ptr<DataBlock> MemPool::Get(size_t capacity)
{std::lock_guard<std::mutex> guard(mutex_);int index = -1;for (size_t i = 0; i < pool_.size(); ++i){if (pool_[i].use_count() == 1 && pool_[i]->Capacity() >= capacity){if (index == -1){index = i;}else if (pool_[i]->Capacity() < pool_[index]->Capacity()){index = i;}}}if (index == -1){auto new_block = std::make_shared<DataBlock>(capacity);if (pool_.size() < max_block_count_){pool_.push_back(new_block);}return new_block;}else{return pool_[index];}
}

(6)C++动态内存分配
C语言:malloc()/free()
C++中:new/delete 运算符
new运算符用于动态内存的分配,delete运算符用于动态内存的释放。
C语言:
      int *p = (int*)malloc(sizeof(int));
      *p = 100;
      free(p);
C++:
      int *p = new int(200);
      //*p = 200;
      delete  p;
 
     int* pa = new int[10];
     pa[0] = 10;
     pa[1] = 20;
     ....
     delete[] pa;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/686620.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java学习笔记------static

static 创建Javabean类 public class student {private int age;private String name;private String gender;public student() {}public student(int age, String name, String gender) {this.age age;this.name name;this.gender gender;}/*** 获取* return age*/public…

MySQL的索引类型

目录 1. 主键索引 (PRIMARY KEY) 2. 唯一索引 (UNIQUE) 3. 普通索引 (INDEX) 4. 全文索引 (FULLTEXT) 5. 空间索引 (SPATIAL) 6. 组合索引 (COMPOSITE INDEX) 7. 前缀索引 (PREFIX INDEX) 8. 覆盖索引 (COVERING INDEX) 1. 主键索引 (PRIMARY KEY) 描述&#xff1a;表…

使用Python编写脚本-根据端口号杀掉进程

我的GitHub&#xff1a;Powerveil - GitHub 我的Gitee&#xff1a;Powercs12 - Gitee 皮卡丘每天学Java 从前段开始遇到一个问题&#xff0c;服务在启动的时候总是端口被占用&#xff0c;发现还是Java程序&#xff0c;但是当时并没有启动Java程序&#xff0c;电脑出问题了。 一…

请解释Java中的字节码是什么,它与源代码和机器码有什么关系?

请解释Java中的字节码是什么&#xff0c;它与源代码和机器码有什么关系&#xff1f; 在Java编程语言中&#xff0c;源代码&#xff08;即.java文件&#xff09;经过编译器编译后会生成字节码&#xff08;即.class文件&#xff09;&#xff0c;字节码是一种中间代码&#xff0c…

AndroidFrameWork切换帧率

文章目录 参考资料 简述 一. SurfaceFlinger接受帧率变化1.1 SurfaceFlinger.setDesiredActiveConfig1.2 SurfaceFlinger.repaintEverythingForHWC1.3 Scheduler.resyncToHardwareVsync1.3.1 Scheduler.setVsyncPeriod1.3.2 VSyncReactor.setPeriod1.3.3 VSyncReactor.startPe…

【Linux】Framebuffer 应用

# 前置知识 LCD 操作原理 在 Linux 系统中通过 Framebuffer 驱动程序来控制 LCD。 Frame 是帧的意思&#xff0c; buffer 是缓冲的意思&#xff0c;这意味着 Framebuffer 就是一块内存&#xff0c;里面保存着一帧图像。 Framebuffer 中保存着一帧图像的每一个像素颜色值&…

Tomcat要点总结

一、Tomcat 服务中部署 WEB 应用 1.什么是Web应用 &#xff08;1&#xff09; WEB 应用是多个 web 资源的集合。简单的说&#xff0c;可以把 web 应用理解为硬盘上的一个目录&#xff0c; 这个目录用于管理多个 web 资源。 &#xff08;2&#xff09;Web 应用通常也称之为…

每日一个shell脚本之一键部署Agent提高工作效率!

每日一个shell脚本之一键部署Agent提高工作效率&#xff01; 源码参上 #!/usr/bin/bash # **************************************# CSDN: M乔木 # qq邮箱: 2776617348qq.com # 解释器: 这是一个shell脚本 # **…

七、ActiveMQ的传输协议

ActiveMQ的传输协议 一、是什么二、协议1.TCP(默认)2.NIO3.AMQP4.STOMP5.SSL6.MQTT7 WS 三、NIO配置案例1.修改activemq.xml2.重启3.生产者/消费者4.性能提升4.1 配置4.2 生产者/消费者 一、是什么 官网地址&#xff1a;http://activemq.apache.org/configuring-version-5-tra…

C语言——oj刷题——找单身狗2

题目名称&#xff1a; 寻找只出现一次的两个数字 题目内容&#xff1a; 在一个数组中&#xff0c;只有两个数字出现了一次&#xff0c;其他所有数字都出现了两次。本篇博客将介绍如何编写一个函数来找出这两个只出现一次的数字。 解题思路&#xff1a; 要解决这个问题&#…

WordPress Nginx 报错 502 Bad Gateway

之前租了一台服务器&#xff0c;部署了Nginx&#xff0c;WordPress。 最近机器重启了一次&#xff0c;访问WordPress就发现报错502 Bad Gateway。 然后查询/home/wwwlogs/nginx_error.log发现如下错误 2024/02/17 21:07:57 [crit] 5551#0: *19 connect() to unix:/tmp/php-cgi…

C++单例模式的实现

单例模式就是在整个程序运行期都只有一个实例。在代码实现方面&#xff0c;我们要限制new出多于一个对象这种情况的发生。而不是仅仅依靠无保障的约定。 目前大多数的编程语言的做法都是私有化构造函数&#xff0c;对外提供一个获取实例的接口。这样做的目的使实例的创建不能在…

Mysql知识点汇总

Mysql知识点汇总 1. Mysql基本场景的简单语句。2. Mysql的增删改查&#xff0c;统计表中的成绩最好的两个同学的名字&#xff0c;年级等。3&#xff1a;请使用多种方法查询每个学生的每门课分数>80的学生姓名4、order by&#xff0c;group by&#xff0c;子查询4.1、having和…

抖店需要怎么做?具体流程来了,建议收藏!

我是电商珠珠 很多人在创业的时候&#xff0c;都热衷于选择线上&#xff0c;特别是对于做店没有任何经验的新手来说&#xff0c;不敢轻易将自己十几年来的血汗钱做实体店而付之东流。 抖店作为一个发展了三年的电商平台来说&#xff0c;其市场份额达到了35%&#xff0c;热度一…

Apache Httpd 常见漏洞解析(全)

一、Apache HTTPD 换行解析漏洞 漏洞编号&#xff1a;CVE-2017-15715 Apache HTTPD是一款HTTP服务器&#xff0c;它可以通过mod_php来运行PHP网页。 其2.4.0~2.4.29版本中存在一个解析漏洞。 在解析PHP时&#xff0c;1.php\x0A将被按照PHP后缀进行解析&#xff0c;导致绕过…

XSS数据接收平台

一.使用xss数据接收平台的好处&#xff1a; 正常执行反射型xss和存储型xss&#xff0c;反射型xss在执行poc时&#xff0c;会直接在页面弹出执行注入的poc代码&#xff1b;存储型则是&#xff0c;在将poc代码注入用户的系统中后&#xff0c;用户访问有存储型xss的地方&#xff…

代码随想录算法训练营|day33

第八章 贪心算法 1005.K次取反后最大化的数组和134.加油站135.分发糖果代码随想录文章详解 1005.K次取反后最大化的数组和 贪心&#xff1a; 先对数组排序&#xff0c;若数组存在负数&#xff0c;且k > 0&#xff0c;遍历数组将其变为正数&#xff1b;若将数组整个变为正数…

SpringCloud-Nacos集群搭建

本文详细介绍了如何在SpringCloud环境中搭建Nacos集群&#xff0c;为读者提供了一份清晰而详尽的指南。通过逐步演示每个关键步骤&#xff0c;包括安装、配置以及Nginx的负载均衡设置&#xff0c;读者能够轻松理解并操作整个搭建过程。 一、Nacos集群示意图 Nacos&#xff0…

机器学习西瓜书之决策树

目录 算法原理剪枝处理连续值处理缺失值处理多变量决策树 算法原理 从逻辑角度&#xff1a;通过一系列if-else语句进行多重判断&#xff0c;比如白富美的判断条件&#xff08;“白”“富”“美”&#xff09;。 从几何角度&#xff1a;根据定义的标准进行样本空间的划分。 以二…

MySQL数据库基础(六):DDL数据库操作

文章目录 DDL数据库操作 一、MySQL的组成结构 二、数据库的基本操作 1、创建数据库 2、查询数据库 3、删除数据库 4、选择数据库 三、总结 DDL数据库操作 一、MySQL的组成结构 注&#xff1a;我们平常说的MySQL&#xff0c;其实主要指的是MySQL数据库管理软件。 一个M…