堆学习

  在系统内部堆就是一块预定的地址空间区域。刚开始堆的大部分页面都没有调拨物理存储器。随着我们不断的从堆中分配内存,堆管理器会给堆调拨越来越多的物理存储器。这些物理存储器始终是从页交换文件中分配的。释放堆中的内存时,堆管理器会撤销已调拨的物理存储器。

  进程初始化时,系统会在进程的地址空间中创建一个堆。这个堆被称为进程的默认堆。默认情况下,这个堆的地址空间区域大小是1MB。程序员可以控制这个大小。我们可以在创建应用程序时用/HEAP连接器开关来改变默认堆的大小。由于DLL没有与之关联的堆,因此在创建DLL时,不应该使用/HEAP开关。默认堆的句柄会被保存在进程环境块_PEB的ProcessHeap字段中。

  通过.process获得_PEB的地址

  ProcessHeap字段即为进程默认堆。其上的HeapSegmentReserve是进程堆的预订(默认为1MB)大小。HeapSegmentCommit是进程堆的初始提交大小,默认值为2个内存页大小(x86内存页为4KB),NumberOfHeaps字段用来介绍堆的总数。ProcessHeaps是一个数组,用来记录每个堆的句柄。

  在windbg中可以使用!heap扩展显示堆使用信息,控制堆管理器中的断点,检测泄露的堆块,搜索堆块或者显示页堆(page heap)信息。

  !heap -h列出当前进程的所有堆

  这些都是进程分配的默认堆,除了进程的默认堆以外,程序也可以调用HeapCreate函数创建自己的堆,这样创建的堆被称为私有堆,3b0000就是私有堆。

  !heap -a 3b0000显示堆的信息

  Segment at 3b0000 to 3c0000 (00001000 bytes committed)指明堆的内存范围和提交字节数。Granularity: 8 bytes指明堆块分配粒度。在0号堆段的3b0680为空闲堆。

  从_PEB中的堆数组可以知道,进程中可以存在多个堆。在每个堆内部又可以分为多个堆段,每个堆段又可以分为多个堆块。堆管理器在创建堆时创建的第一个段,我们将其称为0号段。堆是可增长的,当一个段不能满足要求时,堆管理器会继续创建其他段。但最多可以有64个段。每个堆使用_HEAP结构来描述,用命令dt _HEAP 3b0000来查看私有堆3b0000来查看堆结构。

   _HEAP_ENTRY  用于存放堆管理数据结构的堆块结构

  VirtualMemoryThreshold : 0xfe00   以分配粒度为单位的堆块阀值,表示可以在段中分配的堆块的最大有效(即应用程序可以实际使用的)值,该值为508kB

  SegmentReserve   堆段保留大小,SegmentReserve字段的值为0x100000 = 1MB。表示我们请求创建的堆的最大大小。

  SegmentCommit   堆段提交大小,SegmentCommit为0x2000,表示仅仅提交两个页面为8KB。

  ProcessHeapsListIndex PEB中ProcessHeaps的索引

  VirtualAllocdBlocks 虚拟内存分配块链表,超过堆块阀值将在此分配。当应用程序从堆中分配的堆块的最大大小大于堆块阀值,堆管理器会直接从内存管理器中分配,并不会从从空闲链表申请。同时将此空间添加到VirtualAllocdBlocks结构所指向的链表中。

  Segments是一个数组,它记录着堆拥有的所有段。每个元素类型为_HEAP_SEGMENT结构。

  FreeLists是一个双向链表的头指针,该链表记录着所有空闲堆块的地址。链表元素为FREE_LIST结构,该链表为双向链表,每个链表中都保存着一些空闲堆块。各个链表项都指向_HEAP_FREE_ENTRY结构中的FreeList字段。当应用程序申请新的空间时,堆管理器会首先遍历这个链表,如果找到满足需要的堆块就分配出去。否则便要考虑建立新的堆块或从内存管理器申请空间。在释放时,当不满足解除提交条件时,大多数情况下也是将要释放的堆块加入到该空闲链表中。Free_list数组,该数组有128个元素,用来存储各个空闲链表的表头。空闲链表的元素为_HEAP_FREE_LIST类型。

  FrontEndHeap该字段为指针指向前端分配器

  LastSegmentIndex表示堆中最后一个段的序号,加1便是总段数。

  上面LastSegmentIndex为0说明只有一个段,在看下堆段的结构,每个段使用_HEAP_SEGMENT结构描述

  Entry字段是一个数组,存储着该段所有的堆块。由于每个堆块使用_HEAP_ENTRY结构描述,因此该数组元素类型为_HEAP_ENTRY。

  Heap字段维护该块块所属的堆的_HEAP结构的首地址。

  BaseAddress字段维护该段的基地址。

  FirstEntry表示该段中第一个堆块的地址。

  LastEntryInSegment表示最后一个堆块。

  现在执行HeapAlloc函数在3b0000申请一个堆块,再执行!heap -a 3b0000显示堆的信息

  3b0680已在使用,也是我们刚申请的堆块,3b06a8成为了新的空闲堆,再执行dt _HEAP_SEGMENT 3b0640可以看到最后一个堆块的位置变成了3b06a8

  可以看出段内部又可以分为多个堆块。堆块使用 _HEAP_ENTYR结构来描述,该结构占8 Byte。_HEAP_ENTRY结构之后就是供应用程序使用的区域。调用HeapAlloc函数将返回HEAP_ENTRY之后的地址。此地址减去8Byte便可以得到_HEAP_ENTRY结构。查看堆块结构

  Size,堆块的大小,以分配粒度为单位  。前面知道粒度为8 bytes,且UnusedBytes为0x18,所以真正的大小为5*8-0x18=0x10字节

  PreviousSize,前一个堆块的大小

  Flags,标志

  

  UnusedBytes,因为补齐而多分配的字节数

  SegmentIndex,这个堆块所在堆段的序号

 

  该结构只比_HEAP_ENTRY多了个FreeList字段,用来存储空闲链表的一个链表项,所以_HEAP_FREE_ENTRY大小为16字节。多个链表项构成一个空闲链表。因为该链表仅仅只有一个空闲堆块,因此上述_LIST_ENTRY的Flink和Blink 字段均指向空闲链表的头结点。

  HeapAlloc申请的空间:

  调用HeapFree释放后:

  可以看出释放后修改了_HEAP_ENTRY的Flags字段,后8字节添加了FreeList链表。

  FreeList链表中的3b01a0地址保存的值为3b0688,也是我们调用HeapFree释放空间的地址。

  当再次调用HeapFree释放时,FreeList会将释放的地址加入链表

  在进行堆溢出的时候,通常会覆盖_HEAP_ENTRY或者_HEAP_FREE_ENTRY结构,但一般程序中会进行多次的堆分配,所以大多都是覆盖_HEAP_ENTRY。

时间: 2024-10-13 11:16:45

堆学习的相关文章

[可并堆学习]

happy children's day! [bzoj2809][APIO2012]dispatching dispatch:派遣,调度,快速处理的意思 题意可以简化为,找max{L[u]*可取的子数结点最大值},并且使所取结点薪水和小于M,即取最小几项. n为10w,暴力显然会T. 考虑到递归的过程中,较大的子节点不被取,以后更加不会取,所以只需将较大的子节点弹出栈即可. 关于可并堆的写法,记录它的左节点与右结点,在这道题里,是小的并到大处,千万别忘了将他们左右结点再调换一下. #includ

20172308 《程序设计与数据结构》第八周学习总结

教材学习内容总结 第 十二 章 优先队列与堆 一.堆:具有两个附加属性的一颗二叉树 它是一颗完全树 对每一结点,它小于或等于其左右孩子(或大于等于其左右孩子) 最小堆:对每一结点,它小于或等于其左右孩子 最大堆:对每一结点,它大于或等于其左右孩子 最小堆将其最小元素存储在二叉树的根处,且其根的两个孩子同样也是最小堆 最大堆将其最大元素存储在二叉树的根处,且其根的两个孩子同样也是最大堆 堆是二叉树的扩展,继承了二叉树的所有操作 addElement操作 addElcment方法将给定的元素添加到堆

转载:堆和栈的学习

堆和栈的区别一.预备知识—程序的内存分配一个由C/C++编译的程序占用的内存分为以下几个部分1.栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等.其操作方式类似于数据结构中的栈.2.堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 .注意它与数据结构中的堆是两回事,分配方式倒是类似于链表.3.全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初

栈、堆内存到底是如何申请的,方法是如何入栈出栈的——内存结构理解学习

Lee出品,转载请注明出处http://blog.csdn.net/hnulwt/article/details/42934365 对于软件开发者而言,理解和熟悉计算机内存知识是很基础的.今天我就来翻翻旧账,回顾看看有哪些点遗漏了,在此共同学习. 提起内存,我们常常想到三个区域: 1,静态区,静态变量 static variables / constant  ,常量,静态变量就存储在静态区域,这个区域比较简单,只需要知道怎么通过地址访问他就行了. 2,堆,也叫堆栈(这里要注意堆栈 和 栈 是不同

数据结构学习——堆

1 基本介绍 堆数据结构是一种数组对象,它可以被视为一颗完全二叉树.堆的访问可以通过三个函数来进行即, parent(i) return floor(i/2); left(i) return 2i; right(i) return 2i + 1; left操作可以通过一步左移操作完成,right操作可以通过左移并在地位+1实现,parent操作则可以通过把i右移一位得到.在实现中通常会使用宏或者内联函数来实现这三个操作. 二叉堆有两种,最大堆和最小堆.对于最大堆有 A[i] >= A[left(

[数据结构学习备忘录]堆及其堆排序

[数据结构学习备忘录] 堆 一种数据结构,物理存储方式:数组 逻辑存储方式:近似于完全二叉树,假定i为堆元素的序数[Index],那么i/2就是该元素的左子树,(i/2 + 1)就是该元素的右子树,分为两种堆:大根堆.小根堆:这两种堆的区别是:大根堆的根节点元素的值比左右子树的值都要大,小根堆则相反. 可用这种数据结构进行排序,称为堆排序. 与该数据结构相关的关键算法: ①   MaxHeaplfy 当堆的元素顺序出现错误时的调整函数 ②   BulidMax[min]Heap 建立大[小]根堆

python基础教程_学习笔记19:标准库:一些最爱——集合、堆和双端队列

标准库:一些最爱 集合.堆和双端队列 集合 集合Set类位于sets模块中. >>> range(10) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> set(range(10)) set([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) 集合是由序列(或其他可迭代的对象)构建的.主要用于检查成员资格,因此,副本是被忽略的: >>> range(10)*2 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9

算法学习 - 优先队列的二叉堆实现

PriorityQuenue 优先队列就是作业调度类的ADT,这里用二叉堆来实现. 优先队列最少有两个操作:插入(Insert)和删除最小者(DeleteMin). 插入操作图解: 图片来源:www.educity.cn 删除操作图解: 图片来源:www.cfanz.cn 代码实现: // // main.cpp // binaryHeap // // Created by Alps on 14-8-17. // Copyright (c) 2014年 chen. All rights rese

大数据技术之_30_JVM学习_01_JVM 位置+JVM 体系结构概览+堆体系结构概述+堆参数调优入门+JVM 的配置和优化+Tomcat 的配置和优化

1.JVM 位置2.JVM 体系结构概览3.堆体系结构概述4.堆参数调优入门5.JVM 的配置和优化6.Tomcat 的配置和优化 熟悉 JVM 架构与 GC 垃圾回收机制以及相应的 JVM 调优,有过在 Linux 系统下的调优经验. 淘宝的周志明<深入理解 Java 虚拟机>中说 JVM 的优化,其中 99% 优化的是堆,1% 优化的是方法区. 内地女歌手照片--李嘉欣,贴在桌面上. 1.JVM 位置 JVM 是运行在操作系统之上的,它与硬件没有直接的交互 2.JVM 体系结构概览 详解如