精华内容
下载资源
问答
  • jemalloc 内存分配器 是什么
    万次阅读
    2021-08-04 13:39:40

    jemalloc 内存分配器 是什么?

    内存池
    所谓内存池,是指应用程序向操作系统(或 JVM)申请一块内存,自己管理这一块内存,对象的创建和销毁都从这块内存中分配和回收,这么一块内存就可以称作内存池,

    对应地,管理这块内存的工具就称作内存分配器。
    同时,对于申请对象的不同又可以分为堆内存池和直接内存池,
    1 如果是向 JVM 申请的内存,那就是堆内存池,
    2 如果是向操作系统申请的内存,那就是直接内存池。

    那么,有哪些内存分配器呢?

    业界比较著名的有三个内存分配器:
    1 ptmalloc,Doug Lea 编写的分配器,支持每个线程(per-thread,简称 pt)的 arena,glibc 的默认分配器。
    Doug Lea 大神还有个分配器叫作 dlmalloc,dl 即其名之缩写。
    2 tcmalloc,Google 的分配器,它加入了线程缓存(thread cache,简称 tc),Google 声称其比 ptmalloc 快 6 倍。

    3 jemalloc,Jason Evans 的分配器,je 即其名之缩写,借鉴了很多 tcmalloc 的优秀设计,声称比 tcmalloc 更快,且 CPU 核数越多优势越大,当然,算法也更复杂。

    目前,jemalloc 已经广泛运用在 facebook、Mozilla、FreeBSD 等公司的产品上,那么,它有怎样的优势呢?

    简单总结一下,主要有三大优势:
    1 快速分配和回收
    2 内存碎片少
    3 支持性能分析

    当然了,以上说的都是原生的 jemalloc,我们今天要讲的是 Netty 中的 jemalloc,它是原生 jemalloc 在 Java 中的一种实现方式,并根据 Java 语言自身的特点做了一些删减和优化。

    在这里插入图片描述
    我们先从宏观方面对 Netty 中的内存池有个全面的了解,在 Netty 中,主要包含上面这些组件:

    PoolArena
    PoolChunkList
    PoolChunk
    PoolSubpage
    PoolThreadCache

    PoolArena
    根据内存方式的不同,PoolArena 分成 HeapArena 和 DirectArena 两个子类,在创建 PooledByteBufAllocator 的时候会分别初始化这两种类型的 PoolArena 数组,数组默认大小为核数的 2 倍,同时也会根据可以使用的内存大小动态调整。
    public class PooledByteBufAllocator extends AbstractByteBufAllocator implements ByteBufAllocatorMetricProvider {
    private final PoolArena<byte[]>[] heapArenas;
    private final PoolArena[] directArenas;
    }

    核数也可以通过 JVM 启动参数 io.netty.availableProcessors 配置,因为如果使用低版本的 JDK 且部署在 docker 容器中,获取的是主机的核数,而不是 docker 容器分配的核数。

    PoolArena 中存储着 2 种类型的数据结构,分别为 2 个 PoolSubPage [] 数组和 6 个 PoolChunkList:

    abstract class PoolArena<T> implements PoolArenaMetric {
    	private final PoolSubpage<T>[] tinySubpagePools;
        private final PoolSubpage<T>[] smallSubpagePools;
        private final PoolChunkList<T> q050;
        private final PoolChunkList<T> q025;
        private final PoolChunkList<T> q000;
        private final PoolChunkList<T> qInit;
        private final PoolChunkList<T> q075;
        private final PoolChunkList<T> q100;
    }
    

    为什么这么复杂呢?一切都是为了更好地利用内存。

    实际上,所有的数据都存储在叫作 PoolChunk 的对象中,默认每个 PoolChunk 可以存储 16MB 的数据(chunkSize),每个 PoolChunk 内部又使用伙伴算法将这 16MB 拆分成 2048 个 Page,每个 Page 的大小(pageSize)为 16MB/2048=8KB。

    如果分配的内存(规范化后的内存)小于 8KB,则把 Page 拆分成更小的内存块,并使用 PoolSubpage 管理这些更小的内存,每个 Page 的拆分标准根据这个 Page 首次被分配时的请求的大小决定:

    1 如果小于 512B,则按照 16B 规范化,比如请求的大小为 30B,则规范化到 32B,然后 PoolSubpage 中的元素大小就是 32B,那么,这个 Page 就被拆分成了 8KB/32B=256 个更小的内存块。

    2 如果大于等于 512B,则按照 512B*(2^n) 规范化,比如请求的大小为 996B,那就规范化到 1024B,也就是 1KB,然后这个 Page 就被拆分成了 8KB/1KB=8 个更小的内存块。

    如果分配的内存大于等于 8KB,且小于等于 16MB,则按照 Page 的大小,也就是 8KB,进行规范化,然后再根据伙伴算法的规则进行内存的分配,什么是伙伴算法呢?我们待会讲。
    如果分配的内存大于 16MB,则按照非池化的方式分配内存。
    所以,为了区分以上几种情况,Netty 中定义了一个 SizeClass 类型的枚举,把这几种情况分别叫作 Tiny、Small、Normal、Huge,其中 Huge 不在这个枚举中。
    在这里插入图片描述
    对于 Tiny 和 Small 类型,Netty 为了快速定位,定义了两个数组放在 PoolArena 中,分别是 tinySubpagePools 和 smallSubpagePools,它们的大小分别为 32 和 4,如果这两个数组对应的位置有值,说明之前出现过相同大小的内存块,那就快速定位到那个 PoolSubpage,使用它直接分配内存,而不用再从头查找,加快分配内存的速度。

    前面我们说了,实际上,所有的数据都位于 PoolChunk 中,为了更好地管理这些 PoolChunk,Netty 将它们以双向链表的形式存储在 PoolChunkList 中,同时 PoolChunkList 本身也以双向链表的形式呈现。

    在 PoolArena 中,定义了 6 个 PoolChunkList,分别是 qInit、q000、q025、q050、q075、q100,Netty 根据 PoolChunk 的使用率将它们放到不同类型的 PoolChunkList 中,它们代表的使用率分别为:

    qInit,内存使用率为 Integer.MIN_VALUE ~ 25%,当然不可能有负的使用率,所以最小应该是 0
    q000,内存使用率为 0 ~ 50%
    q025,内存使用率为 25% ~ 75%
    q050,内存使用率为 50% ~ 100%
    q075,内存使用率为 75% ~ 100%
    q100,内存使用率为 100% ~ Integer.MAX_VALUE,当然不可能有超过 100% 的使用率,所以最大应该是 100%

    举个例子来说明,比如一个 Chunk 首次分配了大小为 512B 的内存,那么它的内存使用率就是 512B/16MB 不足 1%,向上取整为 1%,初始时放在 qInit 中,当其分配的总内存超过了 4MB 的时候,也就是达到 25% 了,这个 PoolChunk 就被移动到 q000 中,同样地,当其分配的内存超过 8MB 的时候,就移动到了 q025 中。反过来也是一样,当有对象释放内存时,这部分内存又会被回收到 PoolChunk 中待分配,这时候内存使用会降低,当降低到 4MB 时,也就是 q025 的下限,则会将这个 PoolChunk 移动到 q000 中。

    PoolChunkList

    正如前面所说,PoolChunkList 就是相近内存使用率的 PoolChunk 的集合,这些 PoolChunk 以双链表的形式存储在 PoolChunkList 中,而 PoolChunkList 本身也以双向链表的形式连在一起,为什么要以双向链表的形式存在呢?
    在这里插入图片描述
    其实,这包含两个问题:
    1 PoolChunk 以双向链表的形式存在,是为了删除元素(移动 PoolChunk)的时候更快,比如,要删除 chunk2,只要把它的 prev 和 next 连一起就行了,时间复杂度更低;
    2 PoolChunkList 以双向链表的形式存在,是为了让 PoolChunk 在 PoolChunkList 之间移动更快,比如,一个 PoolChunk 不管是从 q025 到 q050,还是从 q050 回到 q025,都很快,时间复杂度都很低;

    另外,在 Netty 中,当分配内存时,优先从 q050 中寻找合适的 PoolChunk 来分配内存,为什么先从 q050 开始呢?

    private void allocateNormal(PooledByteBuf<T> buf, int reqCapacity, int normCapacity) {
        if (q050.allocate(buf, reqCapacity, normCapacity) 
            || q025.allocate(buf, reqCapacity, normCapacity) 
            || q000.allocate(buf, reqCapacity, normCapacity) 
            || qInit.allocate(buf, reqCapacity, normCapacity) 
            || q075.allocate(buf, reqCapacity, normCapacity)) {
            return;
        }
        // 省略其它代码
    }
    

    因为 q050 中的 PoolChunk 的内存使用率都比 50% 多一点,这样更容易找到符合条件的 PoolChunk,又不至于使 PoolChunk 的利用率偏低。

    因为 q050 中的 PoolChunk 的内存使用率都比 50% 多一点,这样更容易找到符合条件的 PoolChunk,又不至于使 PoolChunk 的利用率偏低。

    我们举个例子,假如从 q075 中先寻找,如果要分配 4M 以上的内存就无法找到合适的 PoolChunk;假如从 q025 中先寻找,可能正好有内存使用率在 25% 以上的 PoolChunk,这时候就直接使用了,那么 q050 中的 PoolChunk 就很难被利用起来,也就是 q050 中的 PoolChunk 的剩余空间很难被利用起来,进而导致整体的利用率偏低,也就是内存碎片会变高。

    那么,如果先从 q050 寻找合适的 PoolChunk 呢?这时 q025 和 q075 中的 PoolChunk 可能永远都不会被使用到,不过没关系,对于 q025 中的 PoolChunk 的内存使用率变为 0 的时候,它们自然就被释放了,而 q075 中的 PoolChunk 本身内存使用率就已经很高了,不用到它们反而更好,等它们的内存使用率降低的时候就又回到 q050 中了,此时就又来很容易地被利用起来。

    因此,从 q050 开始寻找,能很大程度上增大整体的内存使用率,降低内存碎片的存在。

    PoolChunk
    前面我们说了,默认地,一个 PoolChunk 可以存储 16MB 的数据,PoolChunk 是真正存储数据的地方,何以见得?

    final class PoolChunk<T> implements PoolChunkMetric {
        // 数据存储的地方
        final T memory;
        // 满二叉树对应节点是否被分配,数组大小为4096
        private final byte[] memoryMap;
        // 满二叉树原始节点高度,数组大小为4096
        private final byte[] depthMap;
        // 管理更小的内存,数组大小为2048
        private final PoolSubpage<T>[] subpages;
        // 剩余的内存
        private int freeBytes;
        PoolChunk<T> prev;
        PoolChunk<T> next;
    }
    

    PoolChunk 本身是一个泛型类型,内部保存了一个叫作 memory 的变量,这个 memory 会根据分配的是堆内存还是直接内存而变换类型:
    对于堆内存,memory 的类型为 byte []
    对于直接内存,memory 的类型为 ByteBuffer,实际上为 DirectByteBuffer。

    所有的数据都存储在 memory 中,至于更小粒度的划分,比如 PoolSubpage,它们使用各种偏移量对 memory 进行分段处理,数据本身并不会复制到这些细粒度的类中。

    Netty 中,并没有 PoolPage 或者 Page 这个类,Page 是一种抽象的说法,
    它表示的是 PoolChunk 中每 8KB 的数据块,它同样使用 PoolSubpage 来表示。
    

    默认地,Netty 使用伙伴算法将 PoolChunk 分成 2048 个 Page,这些 Page 又向上形成一颗满二叉树:
    在这里插入图片描述
    结合上图,我们先来简单介绍一下 PoolChunk 中的几个变量:

    depthMap,保存着满二叉树原始的高度信息,比如 depthMap [1024]=10

    memoryMap,初始值等于 depthMap,随着节点的被分配,它的值会不断变化,更新子节点的值时,会同时更新其父节点的值,其父节点的值等于两个子节点值中的最小者。

    subpages,对应于上图中的 Page0、Page1、…、Page2047,在 Netty 中并没有 Page 的具体代码实现,它同样使用 PoolSubpage 来表示。只有分配的内存小于 8KB,才会使用 PoolSubpage 进行管理,在 PoolSubpage 创建之后,会加入到 PoolArena 中 tinySubpagePools [] 或 smallSubpagePools [] 对应位置的链表中,同时,在 PoolSubpage 代表的内存被分配完之后,会从对应的链表中删除,也就是说,在同一时刻,head 最多只会与一个 PoolSubpage 形成双向链表。

    freeBytes,PoolChunk 中剩余的内存,即可被使用的内存。

    如果分配的内存大于等于 8KB,由 PoolChunk 自己管理。

    为了更好地理解伙伴分配算法,我们来假想一种分配内存的情况,如果分配内存的顺序分别为 8KB、16KB、8KB,则会按以下顺序进行:

    8KB,符合一个 Page 大小,所以从第 11 层(12-8KB/8KB)寻找节点,这里找到了 2048 这个节点,发现其 memoryMap [2048]=11=depthMap [2048],可以被分配,然后到其对应的 Page [0] 中分配内存,分配之后将其 memoryMap [2048]=12,memoryMap [1024]=11=(2048 和 2049 中的最小者 11),memoryMap [512]=10=(1024 和 1025 中的最小者 10),…,memoryMap [1]=1;

    16KB,符合两个 Page 大小,所以从第 10 层寻找节点(12-16KB/8KB),找到 1024 节点,发现其 memoryMap [1024]=11!=depthMap [1024],不符合条件,继续寻找到 1025 节点,发现其 memoryMap [1025]=10=depthMap [1025],符合条件,所以,到其对应的叶子节点 2050/2051 对应的 Page [2]/Page [3] 中分配内存,分配之后 memoryMap [2050]=12,memoryMap [2051]=12,memoryMap [1025]=12=(2050 和 2051 中的最小值 12),memoryMap [512]=11=(1024 和 1025 中的最小者 11),…,memoryMap [1]=1;

    8KB,符合一个 Page 大小,所以从第 11 层(12-8KB/8KB)寻找节点,2048 已经不符合条件了,所以找到了 2049 这个节点,到其对应的 Page [1] 中分配内存,然后更新 memoryMap [2049]=12,memoryMap [1024]=12=(2048 和 2049 中的最小者 12),memoryMap [512]=12=(1024 和 1025 中的最小者 12),…,memoryMap [1]=1;

    至此,三次内存都分配完毕,总共分配了 Page0~Page3 共 4 个 Page,从分配结果也可以看出,使用伙伴分配算法,能极大地保证分配连续的内存空间,并减少内存碎片的诞生。

    PoolSubpage
    前面我们说过,只有当分配的内存小于一个 Page 大小,即 8KB 时,才会使用 PoolSubpage 来进行管理,那么它是怎么管理的呢?

    让我们先来看看它的几个关键字段:

    final class PoolSubpage<T> implements PoolSubpageMetric {
        // 对应满二叉树中的哪个节点
        private final int memoryMapIdx;
        // 在PoolChunk的memory中的偏移量
        private final int runOffset;
        // 表示每个小块的状态
        private final long[] bitmap;
        // 每个小块(元素)的大小
        int elemSize;
        // 最大的元素个数=8KB/elemSize
        private int maxNumElems;
        // 需要使用到几个long
        private int bitmapLength;
        // 可用的元素个数
        private int numAvail;
        // 双向链表的指针
        // 与PoolArena中的tinySubpagePoos或smallSubpagePools中的元素形成双向链表
        PoolSubpage<T> prev;
        PoolSubpage<T> next;
    }
    

    elemSize 表示每个元素的大小,这个大小是根据这个 Page 接收到的第一个请求的大小决定的。
    比如,首次分配 30B 的内存,则会经历以下几个步骤:

    1判断小于 512B,按 16B 向上规范化到 32B;
    2 在满二叉树的第 11 层寻找一个可用的节点,假如是 2049,即 memoryMapIdx=2049,它代表的是 Page1,Page1 这个节点在 PoolChunk 中对应到 memory 上的偏移量就是 8192(前面有个 Page0),所以,runOffset=8192;
    3 此时,会把 Page0 按 32B 分成(8KB/32B=256)个小块,所以,elemSize=32B,maxNumElems=256,numAvail=256;
    4 同时,这 256 个小块就需要 256 个 bit(位)来表示其每个小块的状态,也就是需要(256/64=4)个 long 类型来表示,所以,bitmapLength=4;
    5 然后,把这个 PoolSubpage 与 PoolArena 的 tinySubpagePools [1](相当于 head)形成双向链表,因为 tinySubpagePools [0] 代表的是 16B 的内存,tinySubpagePools [1] 代表的是 32B 的内存;
    6 当分配完这 32B 之后,可用节点数减一,所以,numAvail=255;

    当再次分配规范为 32B 内存的时候,就看 PoolArena 的 tinySubpagePools [1] 的 next 中有没有值,有值,就直接使用其分配内存了,而不用再重新走一遍上面的过程,从而加快分配内存的速度。

    PoolThreadCache
    前面讲了这么多,分配内存的速度已经足够快了,但是,还可以更快,那就是加入线程缓存 PoolThreadCache,那么,PoolThreadCache 在何时使用呢?

    其实,这要结合回收内存一起使用,当回收内存时,先不还给 PoolChunk,而是使用本地线程缓存起来,当下一次再分配同样大小(规范化后的大小)的内存时,先尝试从本地线程缓存里面取,如果取到了就可以直接使用了。

    那么,PoolThreadCache 可以缓存哪些类型的缓存呢?

    在 Netty 中,除了 Huge,其它类型的内存都可以缓存,即 Tiny、Small、Normal,当然,根据堆内存和直接内存的不同,PoolThreadCache 中又分成了两大类:

    final class PoolThreadCache {
        // 堆内存的缓存
        private final MemoryRegionCache<byte[]>[] tinySubPageHeapCaches;
        private final MemoryRegionCache<byte[]>[] smallSubPageHeapCaches;
        private final MemoryRegionCache<byte[]>[] normalHeapCaches;
        // 直接内存的缓存
        private final MemoryRegionCache<ByteBuffer>[] tinySubPageDirectCaches;
        private final MemoryRegionCache<ByteBuffer>[] smallSubPageDirectCaches;
        private final MemoryRegionCache<ByteBuffer>[] normalDirectCaches;
    }
    

    PoolThreadCache 中使用了一个叫作 MemoryRegionCache 的类来做缓存,它内部维护了一个队列,当回收内存时,这块内存进入到这个队列中,当下次再分配同样大小(规范化后的大小)的内存时,从这个队列中取,关于 PoolThreadCache 的使用,我们下一节结合代码一起学习。

    更多相关内容
  • malloc 是如何分配内存的?

    千次阅读 多人点赞 2022-04-07 09:29:06
    最近想多写一些内存管理的文章,这次我们就以 malloc 动态内存分配为切入点,我在文中也做了小实验: malloc 是如何分配内存的? malloc 分配的是物理内存吗? malloc(1) 会分配多大的内存? free 释放内存,会归还...

    作者:小林coding
    图解计算机基础网站:https://xiaolincoding.com/

    大家好,我是小林。

    很早之前写了一篇图解虚拟内存的文章:真棒!20 张图揭开内存管理的迷雾,瞬间豁然开朗

    最近想多写一些内存管理的文章,这次我们就以 malloc 动态内存分配为切入点,我在文中也做了小实验:

    • malloc 是如何分配内存的?
    • malloc 分配的是物理内存吗?
    • malloc(1) 会分配多大的内存?
    • free 释放内存,会归还给操作系统吗?
    • free() 函数只传入一个内存地址,为什么能知道要释放多大的内存?

    发车!

    Linux 进程的内存分布长什么样?

    在 Linux 操作系统中,虚拟地址空间的内部又被分为内核空间和用户空间两部分,不同位数的系统,地址空间的范围也不同。比如最常见的 32 位和 64 位系统,如下所示:

    图片

    通过这里可以看出:

    • 32 位系统的内核空间占用 1G,位于最高处,剩下的 3G 是用户空间;
    • 64 位系统的内核空间和用户空间都是 128T,分别占据整个内存空间的最高和最低处,剩下的中间部分是未定义的。

    再来说说,内核空间与用户空间的区别:

    • 进程在用户态时,只能访问用户空间内存;
    • 只有进入内核态后,才可以访问内核空间的内存;

    虽然每个进程都各自有独立的虚拟内存,但是每个虚拟内存中的内核地址,其实关联的都是相同的物理内存。这样,进程切换到内核态后,就可以很方便地访问内核空间内存。

    图片

    接下来,进一步了解虚拟空间的划分情况,用户空间和内核空间划分的方式是不同的,内核空间的分布情况就不多说了。

    我们看看用户空间分布的情况,以 32 位系统为例,我画了一张图来表示它们的关系:

    通过这张图你可以看到,用户空间内存从低到高分别是 6 种不同的内存段:

    图片

    • 程序文件段,包括二进制可执行代码;
    • 已初始化数据段,包括静态常量;
    • 未初始化数据段,包括未初始化的静态变量;
    • 堆段,包括动态分配的内存,从低地址开始向上增长;
    • 文件映射段,包括动态库、共享内存等,从低地址开始向上增长(跟硬件和内核版本有关 );
    • 栈段,包括局部变量和函数调用的上下文等。栈的大小是固定的,一般是 8 MB。当然系统也提供了参数,以便我们自定义大小;

    在这 6 个内存段中,堆和文件映射段的内存是动态分配的。比如说,使用 C 标准库的 malloc() 或者 mmap() ,就可以分别在堆和文件映射段动态分配内存。

    malloc 是如何分配内存的?

    实际上,malloc() 并不是系统调用,而是 C 库里的函数,用于动态分配内存。

    malloc 申请内存的时候,会有两种方式向操作系统申请堆内存。

    • 方式一:通过 brk() 系统调用从堆分配内存
    • 方式二:通过 mmap() 系统调用在文件映射区域分配内存;

    方式一实现的方式很简单,就是通过 brk() 函数将「堆顶」指针向高地址移动,获得新的内存空间。如下图:

    图片

    方式二通过 mmap() 系统调用中「私有匿名映射」的方式,在文件映射区分配一块内存,也就是从文件映射区“偷”了一块内存。如下图:

    图片

    什么场景下 malloc() 会通过 brk() 分配内存?又是什么场景下通过 mmap() 分配内存?

    malloc() 源码里默认定义了一个阈值:

    • 如果用户分配的内存小于 128 KB,则通过 brk() 申请内存;
    • 如果用户分配的内存大于 128 KB,则通过 mmap() 申请内存;

    malloc() 分配的是物理内存吗?

    不是的,malloc() 分配的是虚拟内存

    如果分配后的虚拟内存没有被访问的话,是不会将虚拟内存不会映射到物理内存,这样就不会占用物理内存了。

    只有在访问已分配的虚拟地址空间的时候,操作系统通过查找页表,发现虚拟内存对应的页没有在物理内存中,就会触发缺页中断,然后操作系统会建立虚拟内存和物理内存之间的映射关系。

    malloc(1) 会分配多大的虚拟内存?

    malloc() 在分配内存的时候,并不是老老实实按用户预期申请的字节数来分配内存空间大小,而是会预分配更大的空间作为内存池

    具体会预分配多大的空间,跟 malloc 使用的内存管理器有关系,我们就以 malloc 默认的内存管理器(Ptmalloc2)来分析。

    接下里,我们做个实验,用下面这个代码,通过 malloc 申请 1字节的内存时,看看操作系统实际分配了多大的内存空间。

    #include <stdio.h>
    #include <malloc.h>
    
    int main() {
      printf("使用cat /proc/%d/maps查看内存分配\n",getpid());
      
      //申请1字节的内存
      void *addr = malloc(1);
      printf("此1字节的内存起始地址:%x\n", addr);
      printf("使用cat /proc/%d/maps查看内存分配\n",getpid());
     
      //将程序阻塞,当输入任意字符时才往下执行
      getchar();
    
      //释放内存
      free(addr);
      printf("释放了1字节的内存,但heap堆并不会释放\n");
      
      getchar();
      return 0;
    }
    

    执行代码:

    图片

    我们可以通过 /proc//maps 文件查看进程的内存分布情况。我在 maps 文件通过此 1 字节的内存起始地址过滤出了内存地址的范围。

    [root@xiaolin ~]# cat /proc/3191/maps | grep d730
    00d73000-00d94000 rw-p 00000000 00:00 0                                  [heap]
    

    这个例子分配的内存小于 128 KB,所以是通过 brk() 系统调用向堆空间申请的内存,因此可以看到最右边有 [heap] 的标识。

    可以看到,堆空间的内存地址范围是 00d73000-00d94000,这个范围大小是 132KB,也就说明了 malloc(1) 实际上预分配 132K 字节的内存

    可能有的同学注意到了,程序里打印的内存起始地址是 d73010,而 maps 文件显示堆内存空间的起始地址是 d73000,为什么会多出来 0x10 (16字节)呢?这个问题,我们先放着,后面会说。

    free 释放内存,会归还给操作系统吗?

    我们在上面的进程往下执行,看看通过 free() 函数释放内存后,堆内存还在吗?

    图片

    从下图可以看到,通过 free 释放内存后,堆内存还是存在的,并没有归还给操作系统。

    图片

    这是因为与其把这 1 字节释放给操作系统,不如先缓存着放进 malloc 的内存池里,当进程再次申请 1 字节的内存时就可以直接复用,这样速度快了很多。

    当然,当进程退出后,操作系统就会回收进程的所有资源。

    上面说的 free 内存后堆内存还存在,是针对 malloc 通过 brk() 方式申请的内存的情况。

    如果 malloc 通过 mmap 方式申请的内存,free 释放内存后就会归归还给操作系统。

    我们做个实验验证下, 通过 malloc 申请 128 KB 字节的内存,来使得 malloc 通过 mmap 方式来分配内存。

    #include <stdio.h>
    #include <malloc.h>
    
    int main() {
      //申请1字节的内存
      void *addr = malloc(128*1024);
      printf("此128KB字节的内存起始地址:%x\n", addr);
      printf("使用cat /proc/%d/maps查看内存分配\n",getpid());
    
      //将程序阻塞,当输入任意字符时才往下执行
      getchar();
    
      //释放内存
      free(addr);
      printf("释放了128KB字节的内存,内存也归还给了操作系统\n");
    
      getchar();
      return 0;
    }
    

    执行代码:

    图片

    查看进程的内存的分布情况,可以发现最右边没有 [head] 标志,说明是通过 mmap 以匿名映射的方式从文件映射区分配的匿名内存。

    图片

    然后我们释放掉这个内存看看:

    图片

    再次查看该 128 KB 内存的起始地址,可以发现已经不存在了,说明归还给了操作系统。

    图片

    对于 「malloc 申请的内存,free 释放内存会归还给操作系统吗?」这个问题,我们可以做个总结了:

    • malloc 通过 brk() 方式申请的内存,free 释放内存的时候,并不会把内存归还给操作系统,而是缓存在 malloc 的内存池中,待下次使用
    • malloc 通过 mmap() 方式申请的内存,free 释放内存的时候,会把内存归还给操作系统,内存得到真正的释放

    为什么不全部使用 mmap 来分配内存?

    因为向操作系统申请内存,是要通过系统调用的,执行系统调用是要进入内核态的,然后在回到用户态,运行态的切换会耗费不少时间。

    所以,申请内存的操作应该避免频繁的系统调用,如果都用 mmap 来分配内存,等于每次都要执行系统调用。

    另外,因为 mmap 分配的内存每次释放的时候,都会归还给操作系统,于是每次 mmap 分配的虚拟地址都是缺页状态的,然后在第一次访问该虚拟地址的时候,就会触发缺页中断。

    也就是说,频繁通过 mmap 分配的内存话,不仅每次都会发生运行态的切换,还会发生缺页中断(在第一次访问虚拟地址后),这样会导致 CPU 消耗较大

    为了改进这两个问题,malloc 通过 brk() 系统调用在堆空间申请内存的时候,由于堆空间是连续的,所以直接预分配更大的内存来作为内存池,当内存释放的时候,就缓存在内存池中。

    等下次在申请内存的时候,就直接从内存池取出对应的内存块就行了,而且可能这个内存块的虚拟地址与物理地址的映射关系还存在,这样不仅减少了系统调用的次数,也减少了缺页中断的次数,这将大大降低 CPU 的消耗

    既然 brk 那么牛逼,为什么不全部使用 brk 来分配?

    前面我们提到通过 brk 从堆空间分配的内存,并不会归还给操作系统,那么我们那考虑这样一个场景。

    如果我们连续申请了 10k,20k,30k 这三片内存,如果 10k 和 20k 这两片释放了,变为了空闲内存空间,如果下次申请的内存小于 30k,那么就可以重用这个空闲内存空间。

    图片

    但是如果下次申请的内存大于 30k,没有可用的空闲内存空间,必须向 OS 申请,实际使用内存继续增大。

    因此,随着系统频繁地 malloc 和 free ,尤其对于小块内存,堆内将产生越来越多不可用的碎片,导致“内存泄露”。而这种“泄露”现象使用 valgrind 是无法检测出来的。

    所以,malloc 实现中,充分考虑了 sbrk 和 mmap 行为上的差异及优缺点,默认分配大块内存 (128KB) 才使用 mmap 分配内存空间。

    free() 函数只传入一个内存地址,为什么能知道要释放多大的内存?

    还记得,我前面提到, malloc 返回给用户态的内存起始地址比进程的堆空间起始地址多了 16 字节吗?

    这个多出来的 16 字节就是保存了该内存块的描述信息,比如有该内存块的大小。

    图片

    这样当执行 free() 函数时,free 会对传入进来的内存地址向左偏移 16 字节,然后从这个 16 字节的分析出当前的内存块的大小,自然就知道要释放多大的内存了。

    展开全文
  • 【C语言】动态内存的分配

    千次阅读 多人点赞 2022-01-28 17:20:06
    动态内存的分配就那么回事儿!

    目录

    🍋🍋前言

    🍋🍋动态内存分配的定义

    🍋🍋动态内存的优势

    🍋🍋<1>  可以控制内存的大小

    🍋🍋<2> 可以多次利用这部分空间

    🍋🍋<3>不占用栈区的内存

    🍋🍋malloc calloc realloc和free函数的介绍

    🍋🍋​​​动态空间的申请与释放

             🍋🍋为单个对象分配空间

    🍋🍋 为数组分配空间

    🍋🍋改变申请的动态内存(realloc的使用)

    🍋🍋扩容可能有三种情况


    👀前言

    给数组分配多大的空间?

    你是否和初学C时的我一样,有过这样的疑问。

    这一期博客就来聊一聊动态内存的分配

    读完这篇文章,你可能对内存的分配有一个更好的理解

    📢动态内存分配的定义

    首先我们要搞清楚什么是动态内存的分配

    平常我们定义的数组,都是在栈区分配的空间,都是分配的空间都是固定的大小

    这种分配固定大小的内存分配方法称之为静态内存分配

    与静态内存相对的,就是可以控制内存的分配的动态内存分配

    📢注意:这里动态内存分配的空间是在堆区申请的,不是在栈区申请的

    📌这里要讲一下什么是栈区,什么是堆区

    内存的空间并不是都是一样的,在学习C语言时,提到的区域大致上分为栈区,堆区,和静态区。就比如说在一个车间一样,不同的区域做着不同的事,就有不同的功能,但是这些不同的功能又不是毫不相关的,他们彼此联系,相互构成整个内存空间.

    📢动态内存的优势

    <1>  可以控制内存的大小

    在很多时候,我们申请的空间是未知的

    就比如说通讯录,在刚刚开始用的时候很小的空间就足够了,但是在未来你不知道你需要存下多少个号码,这时候就存在一个问题,你定的空间需要多少个字节,当申请的太少,就会出现存不下去的情况,如果存的空间过大,有会造成一定的浪费。 

    在动态内存分配就可以避免这个问题,你可以运用 reallac 控制大小,当内存达到申请的空间时,就会主动扩容,也就是再次向内存申请空间。

    <2> 可以多次利用这部分空间

    静态内存分配利用的空间,整个程序结束才会释放给系统

    而动态内存分配的空间,只能在函数运行结束后由系统自动释放,需要用户主动去释放,可以通过利用完(就比如说打印元素,打印完),用户再通过 free函数释放 这块申请的空间,当再次用动态内存申请空间时,就可以再次利用这块空间,这样也能在一定程度上,可以节省一定的空间。

    <3>不占用栈区的内存

    假设栈区定义了变量

    而每个变量分配内存时,之间又有一定的间隙

    当定义的变量足够多时,空隙也会很多

    这时候向系统申请一个比较大且连续的空间时,虽然有足够的空间,但是缺少了连续的空间

    就无法申请到这部分空间

    所以动态内存在堆区申请,就完全不必担心栈区的空间不够的问题


    说到这里,你是不是有一个疑惑,为什么空间的内存存在栈区和堆区之分 

    如果感兴趣,可以参考这个回答——为什么存在栈区堆区

    📢malloc calloc realloc和free函数的介绍

    在动态内存的分配中,离不开malloc与calloc,这两个函数都是向内存申请空间

                              calloc
    头文件               #include <stdlib.h> 
    格式                   void *calloc(size_t num, size_t size);
    功能                   为num个大小为size字节的对象分配存储空间,该空间内的所有位都会初始化为。
    返回值                若分配成功,则返回一个指向已分配的空间开头的指针;若分配失败,则返回空指针
                              malloc
    头文件               #include <stdlib.h>
    格式                    void *malloc(size_t size);
    功能                    为大小为size字节的对象分配存储空间,此存储空间中的初始值不确定
    返回值                 若分配成功,则返回一个指向已分配的空间开头的指针;若分配失败,则返回空指针

    这两个函数都是向系统申请动态内存空间,他们的头文件,返回值和功能大致都是相同的

    不同的是calloc函数开辟的空间,就会将空间的内容全部初始话为零

    而,malloc函数向系统申请的空间,空间的值都是随机的

                    realloc

    头文件     #include <stdlib.h>
    格式         void *realloc(void *mem_address, unsigned int newsize);
    功能         先判断当前的指针是否有足够的连续空间,如果有,扩大mem_address指向的地址,并且将                            mem_address返回,如果空间不够,先按照newsize指定的大小分配空间,将原有数据从头到尾拷贝                  到新分配的内存区域,而后释放原来mem_address所指内存区域(注意:原来指针是自动释放,不                    需要使用free),同时返回新分配的内存区域的首地址。即重新分配存储器块的地址。
    返回值      如果重新分配成功则返回指向被分配内存的指针,否则返回空指针NULL。
                             free
    头文件             #include <stdlib.h>
    格式                 void free(void *ptr);
    功能                 释放ptr指向的空间,让这部分空间能继续用于之后的动态分配。当ptr为空指针时,不执行任何                          操作。除此之外,当实际参数与之前通过calloc函数、mal1oc函数或realloc函数返回的指针不                          一致时,或者ptr指向的空间已经通过调用 free或real1oc被释放时,则作未定义处理
    返回值             无

    📢动态空间的申请与释放

    讲完动态内存申请的相关函数,那具体的代码实现是什么呢

    <1>    double *x;

    <2>    x=calloc(1,sizeof(double))或者x=malloc(sizeof(double));

    <3>    free;

     下面动态分配的内存赋值并显示

    📢为单个对象分配空间

    #include<stdio.h>
    #include<stdlib.h>
    int main()//动态内存的赋值与显示
    {
    	int* a;
    	a = malloc(sizeof(int));    //分配动态内存
    	if (a == NULL)              //是否成功分配了储存空间,否则返回分配失败
    		printf("分配失败");
    	else
    	{
    		*a = 20;
    		printf("*a=%d\n", *a);
    		free(a);                //释放
    	}
    	return 0;
    }

    📢 为数组分配空间

    #include<stdio.h>
    #include<stdlib.h>
    int main()//动态内存的赋值与显示
    {
    	int n = 0;  int* a; int i = 0;
    	printf("输入分配空间元素的个数:>");
    	scanf_s("%d", &n);
    	a =(int *) calloc(n,sizeof(int));
    	if (a == NULL)
    		printf("分配失败");
    	else
    	{
    		for (i = 0; i < n; i++)
    		{
    			*(a + i) = i;
    			printf("*a=%d\n", *(a+i));
    		}
    		free(a);
    	}
    	return 0;
    }

    这里其实没有“为数组开辟的空间”这一说

    因为动态申请的空间都是一个一个的“

    不难发现,calloc与malloc的差别并不大,只有第一个参数不同

    在这两行代码中,存在着一个小细节 

    a = malloc(sizeof(int));

    a =(int *) calloc(n,sizeof(int));

    这两者的差别不仅仅是函数的不同,其中后者有强制类型转换,而前者没有

    实际上在C语言的标准上,有无强制类型转换都是行得通的(当然在c++必须将强制类型转换)

    因为无论是calloc还是malloc,他们的返回值都是void* ,这里的void*实际上可以转换为int*类型或者其他类型,换句话说,就是返回的指针是兼容所有类型的万能指针。

    📢即指向void型的指针可以指向任意类型的对象,是一种特殊类型的指针。

    指向void型的指针的值可以赋给指向任意类型的指针,反之亦可。

    📢改变申请的动态内存(realloc的使用)

    #include<stdlib.h>
    #include<stdio.h>
    int main()
    {
    	int* a; int i = 0;
    	a=(int *)calloc(10,sizeof(int));
    	if (a == NULL)
    		printf("分配失败");
         //使用
    	else
    	{
    		for (i = 0; i < 10; i++)
    		{
    			*(a + i) = i;
    			printf("*a=%d\n", *(a + i));
    		}
    	//需要扩容
    		int* ret = realloc(a, 80);
    		if (ret != NULL)
    		{
    			a = ret;
    		}
    		free(a);
            a=NULL;
    	}
    }

    📢扩容不能直接就a=realloc(a, 80),需要中间引一个中间变量*ret

    📢扩容可能有三种情况

    情况一(在a的地址处,有空余的空间来扩容)

    情况二 (在a的地址处,没有空余的空间来扩容,但是有其他的空间可存储扩容后的空间)

     情况三(reallo调整空间失败)

     在这三种情况中,第一种的地址不变

    第二种会在一个新的地方申请足够大的地方,此时的地址不在是a原先的地址

    第三种就扩容失败,就会导致扩容前申请的空间,也发生了改变,所以不能直接用a来重新赋值,


     欢迎点赞收藏加关注,如若有问题可以提出来😁😁😁😁 

    展开全文
  • 高速PCB基础-电源分配网络

    千次阅读 2021-11-19 16:11:21
    电源分配网络 电源分配网络又称为电源配送网络 (PDN) ,包含从稳压模块 (VRM) 到芯片的焊盘,再到裸芯片内分配本地电压和返回电流的片上金属层在内的所有互连。其中有稳压模块、体去耦电容器、过孔、互连、电路板上...

    电源分配网络

    电源分配网络又称为电源配送网络 (PDN) ,包含从稳压模块 (VRM) 到芯片的焊盘,再到裸芯片内分配本地电压和返回电流的片上金属层在内的所有互连。其中有稳压模块、体去耦电容器、过孔、互连、电路板上的平面、板外附加电容器、封装的焊球或引脚、装在电路板上的封装中的互连、键合线或 C4 焊球、芯片上的内部互连等。
    电源分配网络的首要和基本要求:保持芯片焊盘间的供电电压恒定,并使它能够维持在一个很小的容差范围内通常在 5% 以内。
    电源分配网络的作用有3个:保持芯片焊盘问的供电电压恒定,使地弹最小化,使电磁干扰问题最小化。

    在大多数设计中,用于供应电力的电源、分配网络互连也总是用于运送信号线的返回电流。这些电源分配网络互连的第二个作用是提供一个低阻抗的信号返回路径
    提供低阻抗路径的最简单方法是使互连足够宽从而使返回电流尽可能地分布开,并且让信号线保持分离,使得它们的返回电流不会相互重叠。若不满足这些条件,则返回电流将会聚
    集,不同信号的返回电流将会互相重叠。其结果就是产生地弹,也称为同时开关躁声 (SSN)开关噪声

    问题的根源

    电压出现下沉,不能使用稳定度更高的芯片来解决,因为电压下沉来源于互连的阻抗。
    在这里插入图片描述
    从稳压模块经电源分配网络连到芯片焊盘。电压分配网络的阻抗造成了压降。
    从片上焊盘看过去的电源分配网络阻抗,通常是一个与频率相关的阻抗,记为 Z ( f ) Z(f) Z(f)
    当具有一定频谱宽度的电流 I ( f ) I(f) I(f)流过该电源分配网络时,电源分配网络上将产生电压降:
    V ( f ) = I ( f ) × Z ( f ) V(f)=I(f)\times Z(f) V(f)=I(f)×Z(f)
    电源分配网络上的这一压降表明稳压器输出的恒定电压是芯片得不到的,在进入芯片前已被改变。芯片焊盘上的电压变化必须在给定的电流波动下小于某一电压噪声容差,就是通常所说的纹波。这就要求电源分配网络阻抗必须低于某一最大容许值,即目标阻抗:
    V r i p p l e > V P D N = I ( f ) × Z P D N ( f ) V_{ripple}>V_{PDN}=I(f)\times Z_{PDN}(f) Vripple>VPDN=I(f)×ZPDN(f)
    Z P D N ( f ) < V r i p p l e I ( f ) = Z t a r g e t ( f ) Z_{PDN}(f)<\frac{V_{ripple}}{I(f)}=Z_{target}(f) ZPDN(f)<I(f)Vripple=Ztarget(f)
    其中 V r i p p l e V_{ripple} Vripple芯片的电压噪声容差(V);
    V P D N V_{PDN} VPDN电源分配网络互连上的噪声压降(V);
    I ( f ) I(f) I(f)芯片消耗电流的频谱(A);
    Z P D N ( f ) Z_{PDN}(f) ZPDN(f)芯片焊盘看过去的电源分配网络阻抗曲线(Ω);
    Z t a r g e t ( f ) Z_{target}(f) Ztarget(f)电源分配网络所容许的最大阻抗(Ω)
    电源分配网络导体上的轨道塌陷或电压噪声的根本原因在于流过电源分配网络阻抗的芯片电流导致电源分配网络互连上产生了电压降。

    假定芯片中的电流产生了波动,如果要保持芯片焊盘之间的电压稳定,就需要保持电源分配网络阻抗低于目标阻抗值。这是电源分配网络设计中最根本的指导准则。

    电源分配网络稳定时,电压恒定,如果电流改变(即出现电流),那么互连阻抗会分去一部分电压,导致电压下降。

    电源分配网络的设计准则

    1、.让电源和地平面成为相邻的平面层,平面之间的介质要尽量薄,并且还要让平面尽可能靠近电路板层叠结构的表面层;
    2、在去耦电容器焊盘和连往内层电源/地平面腔的过孔之间,使用尽可能短而宽的表层走线,并在具有最低回路电感的位置放置电容器;
    3、使用 SPICE 选择最佳的电容器容值及其个数,以使阻抗曲线低于目标阻抗。

    如何确定目标阻抗

    电源分配网络的最大阻扰,即目标阻扰,就是形成小于可接受纹波压降的最大阻抗。可以由下式得到:
    Z P D N × I t r a n s i e n t = V n o i s e < V D D × r i p p l e Z_{PDN}\times I_{transient}=V_{noise}<V_{DD}\times ripple ZPDN×Itransient=Vnoise<VDD×ripple
    即:
    Z t a r g e t < V D D × r i p p l e I t r a n s i e n t Z_{target}<\frac{V_{DD}\times ripple}{I_{transient}} Ztarget<ItransientVDD×ripple
    I t r a n s i e n t I_{transient} Itransient表示最坏情况下的瞬间电流,
    Z t a r g e t Z_{target} Ztarget表示目标阻抗
    ripple表示可容许的波纹
    V D D V_{DD} VDD表示特定轨道的供电电压
    最佳的电源分配网络阻抗值应低于但不应远低于目标阻抗值。
    芯片手册中没有提供最坏的瞬间电流,但是一般给出每个电压轨道的峰值电流,可以根据经验法则粗略的估计瞬时电流。即:
    I t r a n s i e n t = 0.5 I m a x I_{transient}=0.5I_{max} Itransient=0.5Imax
    I m a x I_{max} Imax表示芯片最大的总电流。
    同时也可以用最坏的功耗情况来估计,此时有: Z t a r g e t ( f ) < 2 V d d 2 × r i p p l e P m a x Z_{target}(f)<2\frac{V_{dd}^2\times ripple}{P_{max}} Ztarget(f)<2PmaxVdd2×ripple
    一些FPGA公司,如Altera公司会提供计算工具,下表时Altera公司的Stratix II GX FPGA器件的分析结果:
    在这里插入图片描述
    下图是一个成功设计的阻抗曲线

    在这里插入图片描述
    当电源分配网络的阻抗曲线(下图)低于目标阻抗时,最坏情况下的电压噪声(上图)就低于对纹波的要求。其中方波信号是芯片消耗电流而平滑的曲线是供电轨道上的电压。

    但是如果阻抗曲线上有一个峰值超过了目标阻值的指标要求,并且最坏情况下的电流的峰值刚好落在该阻抗峰值的位置,波纹就有可能超标。

    在这里插入图片描述
    当电源、分配网络的阻抗曲线超过目标阻抗的指标要求(下图) ,并且电流的峰值频率与阻抗峰值重叠时,就会产生过冲(上图)。)。上图中的方波是流过芯片的消耗电流。振铃波形是供电轨道上的电压。内嵌的图形是在该电源分配网络中测得的电压噪声,显示了电源分配网络阻抗曲线峰值引起的典型振铃响应

    不能盲目的将一种产品的电源分配网络盲目的移到其他产品的电源分配网络中。

    电源分配网络工程化建模

    电源分配网络可以在频域里划分5个成简单区段。
    在这里插入图片描述
    在低于10kHz的频率范围内,稳压模块的性能对电源分配网络阻抗起决定性的作用。
    在10~100kHz的范围,体去耦电容器对电源分配网络的阻抗起决定作用,通常是一些在稳压模块作用频率范围上提供低阻抗的电解电容及钽电容。
    最高频率时的阻抗取决于片上电容,这一容性阻抗是芯片在GHz所看到的电源分配网络的唯一特性。
    任何芯片与电路板的接口都存在一些寄生电感。这通常取决于封装、过孔,以及过孔到电源/地平面连接处的扩散电感。
    板级电源分配网络设计的频率范围约从 100 kHz 到100 MHz 。这正是电路板平面和多层陶瓷贴片电容器 (MLCC) 发挥作用的频率范围。

    稳压模块

    稳压模块 (VRM) 决定了电源分配网络的低频阻抗。下图给出了一个所测典型稳压模块的阻抗曲线。此外也给出了一个简单双电容模型的阻抗曲线。
    在这里插入图片描述
    在10Hz到40MHz 的频率范围内用Ultimetrix 阻抗分析仪测量一个典型稳压模块得到的阻抗曲线。图中显示了稳压模块在开启和关闭时的测量阻抗以及稳压模块基于双电容器模型的仿真阻抗

    可以看出在大约4kHz,稳压模块的输出阻抗由无源电容器决定,有源稳压器对阻抗不起作用,无论稳压器是开或关,输出阻抗都相同。

    片上电容

    片上去耦电容决定了最高频率时的电源分配网络阻抗。片上电容有3个成因:电源和地轨道金属层之间的电容,所有的 p/n 管的栅极电容,以及各种寄生电容。
    高频时片上电容为电源分配网络提供了低阻抗。
    如果目标阻抗为 10 mΩ 那么片上电容将会在高于 100 MHz 的频率时起到显著的去耦作用。

    封装屏障

    封装引脚中存在串联的电感。其阻抗为 Z = 2 π f L Z=2\pi fL Z=2πfL,这将导致无论板级电源分配网络怎么设计,都不可能将芯片看到的电源分配网络阻抗减小到比封装引脚更小。
    一般情况下,封装与目标阻抗一起把板级阻抗的有效频率限制在 100 MHz 以下。除非有其他反面的佐证信息,这就是板级电源分配网络设计目标通常设置为不高于 100 MHz 频率的原因。

    展开全文
  • 一、密钥分配 、 二、密钥分配中心 、 三、对称密钥分配 、 四、对称密钥分配说明 、 五、密钥分配协议 、 六、Kerberos 协议工作流程 、 七、Kerberos 协议要求
  • 动态内存分配 (详解版)

    千次阅读 多人点赞 2021-03-18 20:31:27
    动态内存分配 (详解版) malloc和free C++语言允许使用C语言标准库函数中malloc和free申请和释放内存,保留这两个函数主要有以下3点考虑: C++程序经常要调用写好的C函数,而在C语言中,只能使用malloc和free; ...
  • 动态内存分配(malloc)详解

    万次阅读 多人点赞 2020-09-11 10:18:05
    也就是说,malloc()分配了内存,但没有为它指定名字。 然而,它却可以返回那块内存第一个字节的地址。 因此,可以把那个地址赋值给一个指针变量,并使用该指针来访问那块内存。 因为char代表一个字节,所以传统上...
  • 数组动态分配与静态分配的区别

    千次阅读 2020-04-23 21:02:43
    所谓动态内存分配就是指在程序执行的过程中动态地分配或者回收存储空间的分配内存的方法。动态内存分配不象数组等静态内存分配方法那样需要预先分配存储空间,而是由系统根据程序的需要即时分配,且分配的大小就是...
  • 本文通过简化了社会财富分配的过程,使用Python进行模拟计算,得出了几个有趣的结论。
  • C++ 在栈上分配内存

    千次阅读 2020-01-02 09:44:09
    malloc与alloc的区别 malloc()与 alloc() C语言跟内存分配方式 (1) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的...栈内存分配运算内置于处理器的指令集中,效率很高,但是分配...
  • 文章目录一、连续内存分配1、内存碎片的问题(1)外部碎片(2)内部碎片2、连续内存分配算法(1)首次适配(2)最优适配(3)最差适配3、碎片整理方法4、连续内存分配的缺点二、非连续内存分配1、非连续分配的优点2...
  • JVM 内存分配策略

    万次阅读 2022-04-05 13:49:33
    内存分配通常在java堆上分配(随着虚拟机优化技术的诞生,某些场景也会在栈上分配),对象主要分配在新生代的Eden区,如果开启了本地线程缓冲,将会按照线程优先在TLAB 上分配,少数情况也会在老年代上分配,总的来...
  • 动态分配和静态分配

    千次阅读 2019-06-28 21:06:11
    所谓动态内存分配就是指在程序执行的过程中动态地分配或者回收存储空间的分配内存的方法。动态内存分配不象数组等静态内存分配方法那样需要预先分配存储空间,而是由系统根据程序的需要即时分配,且分配的大小就是...
  • C++ 分配器 allocator

    千次阅读 2019-03-28 11:11:52
    在C++编程中,分配器(英语:allocator)是C++标准库的重要组成部分。C++的库中定义了多种被统称为“容器”的数据结构(如链表、集合等),这些容器的共同特征之一,就是其大小可以在程序的运行时改变;为了实现这...
  • 动态分区分配又称为可变分区分配,他是根据进程的实际需要,动态地为之分配内存空间。在实现动态分区分配时,将涉及到分区分配中所有的数据结构、分区算法和分区的分配与回收操作这三方面的问题。 在本实验中运用了...
  • 数据挖掘与工资分配

    千次阅读 多人点赞 2020-09-01 15:01:51
    工资总额分配是与企业人力资源战略紧密联系的管理要素。企业的工资总额对一个企业的未来发展至关重要,本文以2018年26个省市分公司年运营的统计数据作为研究对象,在合理假设的基础上,综合考虑国企对各省市分公司...
  • 动态分区分配方式的模拟实现 编程模拟实现动态分区分配方式中内存的分配与回收 要求分别采用首次适应算法,循环首次适应算法,最佳适应算法和最坏适应算法实现. 代码; #include <stdio.h> //#include...
  • 磁盘显示未分配怎么办?

    千次阅读 2021-07-11 01:40:43
    本文详细介绍如何妥善处理磁盘的未分配空间,包括,如何从未分配的磁盘进行数据恢复,如何在未分配的磁盘空间上建立分区,以及如何将未分配的磁盘空间合并给现有分区。关于磁盘未分配问题什么是磁盘未分配呢?打开...
  • 在动态分区分配存储管理方式的内存分配中,当有作业请求装入时,根据作业的需要内存空间的大小查询内存各个空闲区,从中找到一个大于或等于该作业大小的内存空闲区,然后按作业需求量划出一个分区装入该作业。...
  • 连续内存分配与非连续内存分配

    万次阅读 2018-09-22 08:45:03
    连续内存分配 首次适配:空闲分区以地址递增的次序链接。分配内存时顺序查找,找到大小能满足要求的第一个空闲分区。 最优适配:空闲分区按容量递增形成分区链,找到第一个能满足要求的空闲分区。 最坏适配:空闲...
  • 资源分配与调度

    千次阅读 2019-12-31 19:07:29
    4、防止由资源分配不当而引起的死锁。 对资源的管理应包括以下几个方面: 1、资源管理的描述--数据结构 2、确定资源的分配原则和调度原则 3、执行资源分配(实施) 4、存取控制和安全保护 1.2 资源的几种分类方法 ...
  • 按金额平均分配任务量算法实例需求背景思路分析代码实现 需求背景 需要批量分配订单给人员,尽量保证分配到每个人的订单金额相近。 思路分析 比如100个任务,每个任务ID和金额及号为(T100,100)…(t1,1)。第一轮...
  • 图解Golang的内存分配

    万次阅读 2019-06-09 20:11:07
    一般程序的内存分配 在讲Golang的内存分配之前,让我们先来看看一般程序的内存分布情况: 以上是程序内存的逻辑分类情况。 我们再来看看一般程序的内存的真实(真实逻辑)图: Go的内存分配核心思想 Go是内置运行时的...
  • 资源分配问题

    千次阅读 多人点赞 2020-07-20 15:14:18
    某工业生产部门根据国家计划的安排,拟将某种高效率的5台机器,分配给所属的3个工厂A,B,C, 各工厂在获得这种机器后,可以为国家盈利的情况如表1所示。问:这5台机器如何分配给各工厂,才能使得国家利益最大? ...
  • 磁盘情况: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qFblQrWf-1634110105898)(C:\Users\FLY\AppData\Roaming\Typora\typora-user-images\image-20211013152606256.png)] ...
  • 操作系统:实验三 动态分区分配算法

    千次阅读 多人点赞 2019-11-15 15:05:41
    用高级语言编写和调试一个内存连续分配中动态分区分配模拟程序,以加深对进程的概念及进程调度算法的理解. 二、实验指导 设计程序模拟内存动态分区分配流程,要求实现三项功能:分配内存、回收内存、显示内存使用...
  • 情况说明:Virtualbox虚拟分配空间扩展,适用于动态分配存储的情况,静态分配存储的情况可能会报错。 步骤: 1.进入VitualBox所在的安装目录: cd C:\Program Files\Oracle\VirtualBox 2.查看虚拟磁盘空间...
  • 资源分配之最大最小公平分配算法

    千次阅读 2021-01-23 11:40:06
    在实际生产过程中,我们经常面临给多个用户分配一组资源的场景,他们都有等价的权力来共享这一段资源,那么我们应该如何来分配资源呢?下面的最大最小公平分配算法是实际中广泛运用的一种算法 最大最小公平分配算法...
  • #include&lt;stdio.h&gt; /** 在C中动态分配内存的基本步骤有: ... realloc : 在之前分配的内存块的基础上,将内存重新分配为更大或者更小的部分  calloc: 从堆上分配内存并清零  fre...
  • Java内存划分和分配

    万次阅读 2018-10-18 15:03:41
    在了解Java每个内存区域的功能之后,进一步分析Java对象如何的创建和对象的内存分配,以及如何访问对象中的内存。最后学习一下Java堆内存的分代划分和内存分配。 Java内存区域划分 首先通过一张图来看一下Java虚拟机...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,035,638
精华内容 1,214,255
关键字:

分配

友情链接: HikLoad.zip