当前位置: 首页 > article >正文

《windows堆内存剖析(一)》

     相信很多同学对内存的认识依然停留在一些很浅显的概念上,比如32位应用程序堆内存的地址范围是怎样?操作系统如何帮我们寻找一片连续的内存空间?内存碎片是如何产生的?我们应用层需要自己手动实现一个内存池出来吗?假如你对这些问题还是停留在一知半解的状态,笔者认为还是有必要学习这些底层的知识点,夯实下自己的基本功。

     堆是一种内存管理器,程序可以通过堆来动态地分配和释放内存,进程可以通过多种堆去获取内存,具体的堆有:默认的进程堆、CRT堆、应用程序特定堆。而这些堆都由操作系统的堆管理器(HTDLL)去管理,如果这些堆都没法满足内存要求,那么堆管理器再去调用虚拟内存管理器,申请更大的连续内存空间以供上层使用。那以现有的堆管理器为切入点,分别剖析这三种堆的区别。

     默认的进程堆:进程在启动时,会利用默认进程堆申请一段内存。

     CRT堆:应用程序主动通过new、malloc等接口去申请内存时,会使用到CRT堆。

    应用程序特定堆:应用程序通过操作系统接口HeapCreate申请一段与其它内存空间隔离的堆内存时,便会使用到应用程序特定堆。

    结合上文的阐述,笔者给出一张windows内存管理架构图,方便读者们理解。

图片

     现在对堆管理器做进一步的细分讨论,堆管理器又分为前端分配器后端分配器, 前端分配器又细分成旁视列表前端分配器低碎片前端分配器,目前主流的操作系统默认使用旁视列表前端分配器,下图给出旁视列表前端分配器的简图。

图片

旁视列表前端分配器

        旁视列表其实就是一张表,这张表包含了128项,每个项对应一个单向链表,每个单项链表包含了多个固定大小的堆块,从索引0开始。堆块的起始大小就是8字节(实际存储数据的大小只有0字节,每个堆块需要8字节用来存储元数据),随着索引值的递增,每个索引项对应的链表中的堆块大小呈现出等差递增的趋势。这些堆块就是用来存储应用层程序数据、指令、地址等信息的。那么假设应用层发出指令需要24字节的内存空间时,旁视列表前端分配器该如何处理?24字节的内存请求,加上元数据占用的8字节,一共是32字节,根据寻找堆块索引的算法: 32 / 8 -1 = 3,此时应该从旁视列表的索引3对应的单向链表中寻找空闲堆块,如果有,那么堆管理器会把空闲的堆块从此链表中移除,并把当前堆块返回给上层应用程序。假如索引3对应的链表为空,那么前端分配器将无法满足内存分配的要求,进而把内存分配的请求转发给后端分配器。
      后端分配器和前端分配器一样,也是一张列表,列表中每一项对应的是一个双向链表,这是和前端分配器的区别,索引0对应的是一个大小单向递增的空闲链表,该链表中每个堆块的大小大于127*8字节,小于虚拟内存分配的阈值(0x7FFF0)。具体可以参考如下图。

图片

后端分配器

    虽然前后端分配器都是空闲列表,但是后端分配器在内存堆块查找的效率上会比前端分配器效率更高,具体体现在哪几点呢?比如:

    1、后端分配器会维护一个位图数组,该位图数组每个索引对应的比特值表示空闲列表中指定索引对应的链表中是否包含了空闲堆块,1表示包含了空闲堆块,0表示没有。

图片

    假如上层需要申请24字节的内存空间,那么先计算索引值:24 + 8 / 8 = 4,那么后端分配器便会检查位图数组索引为4的比特值是多少?如果值为1,此时便会从空闲列表索引为4对应的双向链表中寻找一个堆块,并把它从链表中移除出去,随后返回给上层应用程序。如果链表中的堆块被移除光了,变成了空链表,此时会更新位图数组中指定索引对应的比特值为0。假如上层应用程序的内存分配请求刚好指定到一个空链表,那么后端分配器该如何处理?那么后端分配器的“块分割”技术就要派上用场了。

      2、块分割也算是一种较为高效的内存分配方式吧,假设应用层需要16字节的内存,而此时24字节(16字节 + 8字节的元数据)的堆块已经耗光了,那么后端分配器将继续往下遍历空闲列表,如果存在48字节的堆块,此时后端分配器将48字节的堆块一分为2,一份24字节的堆块返回给应用层,另外一份24字节的堆块会插到24字节堆块对应的链表中去。同时更新位图数组中表示“24字节堆块链表”的比特值为1。

      好,这是后端分配器的整体工作流程,顺便提下,空闲列表中索引为0对应的双向链表的堆块空间的上限值为0X7FFF0,假设上层申请的内存空间大于此处的上限值,那么去哪里寻找一片如此大的连续内存空间给上层呢?此时虚拟内存管理器就要出场了。虚拟内存管理器会分配一大块的内存空间给堆管理器,这一大块内存空间将会被分割成不同尺寸的小块给堆管理器使用。

        虚拟内存管理器申请到的内存为堆段Heap Segment,假设一个堆段的内存空间被耗光了,此时堆管理器将请求虚拟内存管理器创建一个新的堆段出来,而且大小是之前的两倍。说到这点,笔者想到了一个面试题“vector数组初始化的内存空间被耗尽时,操作系统是如何去寻找两倍大小的内存空间,并把原始的数据拷贝到新的内存空间来的?” 。看完本文,我相信你们应该也知道个大概了吧。


http://www.kler.cn/a/418331.html

相关文章:

  • model.zero_grad() 和 self.optimizer.zero_grad() 区别
  • 【Android】组件化嘻嘻嘻gradle耶耶耶
  • 基于rpcapd与wireshark的远程实时抓包的方法
  • MySQL 核心基础 | Pandaer杂货铺
  • 开源 - Ideal库 - Excel帮助类,TableHelper实现(三)
  • k8s--pod创建、销毁流程
  • ChromeBook11 HP G7EE 刷入Ubuntu的记录
  • 鲲鹏麒麟安装离线版MySQL5.7
  • 吉客云数据集成技巧:智能实现MySQL物料信息查询
  • 栈-数组描述(C++)
  • mysql查询语句执行全流程
  • 10x 性能提升,ProtonBase 为教育行业提供统一的数据库和数仓体验
  • 【C#设计模式(16)——解释器模式(Interpreter Pattern)】
  • 搭建业务的性能优化指南
  • [C/C++]排序算法1、冒泡排序
  • 汽车座舱系统名词
  • 【开源免费】基于Vue和SpringBoot的校园资料分享平台(附论文)
  • 七牛智能CDN视频优化方案,展现企业长期价值
  • android shader gl_Position是几个分量
  • 【竞技宝】CS2-上海major:MongoLZ成为亚洲之光
  • C# 中的事件:对象间通信的利器
  • macos下brew安装redis
  • 每日十题八股-2024年11月30日
  • 依托 SpringBoot 的新冠密接者跟踪系统:技术创新驱动疫情防控效能提升
  • wordpress仿社交软件SOUL 动态标签星球- 为你的博客注入灵魂
  • <项目代码>YOLOv8 红绿灯识别<目标检测>