Nginx内存池实现以及分析

路由侠
• 阅读 1762

Nginx内存池实现以及分析

开源案例

  • tcmalloc
  • jemalloc
  • nginx

为什么需要内存池

  • 为了解决在高并发下,需要不断申请内存和效率,造成了性能消耗,且造成了内存碎片化,不易统一管理。

内存池结构

struct mp_large_node
{
    struct mp_large_node *next; //链表
    void *data;                 // 指向大块存储空间的指针
};

struct mp_small_node
{
    struct mp_small_node *next; //指向下一个small_node

    unsigned char *last; //数据存储区起始的地址
    unsigned char *end;  //数据存储区最后的地址
    size_t failed;       //该块分配内存失败的次数
};

struct mp_pool
{
    size_t max;

    struct mp_small_node *current; // small_node 链表
    struct mp_large_node *large;   // large_node 链表
    struct mp_small_node head[0];  //柔性数组 实现可变结构体
};

图解

Nginx内存池实现以及分析

Nginx内存池实现以及分析

上图mp_node_s即为mp_small_node

  • pool指向一个包含了mp_pool,mp_small_node以及data大小和的内存空间。
  • small_node 指向一个包含了small_node以及data大小和的内存空间,通过next字段不断连接。
  • large_node 包含指向下一个large_node指针和指向具体数据的指针.

nginx内存池设计

  • 当我们分配较小内存片段时,通过small_node 管理。
  • 当我们分配较大内存片段室,通过large_node 管理。
  • 做到了内存复用,统一管理,不需要不断申请和销毁内存。
  • 内存池中分配的内存,最后统一销毁。

nginx内存池分配内存流程

Nginx内存池实现以及分析

内存池创建

struct mp_pool *mp_pool_create(size_t size)
{
    struct mp_pool *pool;
    int ret = posix_memalign((void **)&pool, MP_ALIGNMENT, size + sizeof(struct mp_small_node) + sizeof(struct mp_pool));
    if (ret)
    {
        printf("create error\n");
        return NULL;
    }

    pool->max = (size < MP_MAX_ALLOC_FROM_POOL) ? size : MP_MAX_ALLOC_FROM_POOL;
    pool->large = NULL;

    pool->current = pool->head;
    pool->head->last = (unsigned char *)pool + sizeof(struct mp_small_node) + sizeof(struct mp_pool);// 内存池中第一个内存段包含了pool+smallnode+data的大小
    pool->head->end = pool->head->last + size;
    pool->head->failed = 0;

    return pool;
}

内存池销毁

void mp_pool_destroy(struct mp_pool *pool)
{
    struct mp_large_node *large = pool->large;
    struct mp_small_node *nxt, *node;
    for (large = pool->large; large; large->next) //销毁所有large_data指向的数据
    {
        if (large->data)
        {
            free(large->data);
        }
    }
    node = pool->head->next;
    while (node) //销毁 所有small_node
    {
        nxt = node->next;
        free(node);
        node = nxt;
    }
    free(pool); //销毁内存池,pool指向了pool+small_node_data的数据大小
}

内存池分配内存

void *mp_malloc(struct mp_pool *pool, size_t size)
{
    if (size <= pool->max) //属于小内存
    {
        struct mp_small_node *n = pool->current;
        while (n)
        {
            if ((size_t)(n->end - n->last) >= size) //在其中一个小的内存片段中能够塞下需要的新内存大小
            {
                void *pos = n->last;
                n->last += size;
                return pos;
            }
            n = n->next;
        }
        return mp_malloc_small(pool, size); // 现有内存片段不够存储,搞一个新的内存片段
    }
    return mp_malloc_large(pool, size); //分配大内存
}

创建small_node

static void *mp_malloc_small(struct mp_pool *pool, size_t size)
{
    unsigned char *m;
    struct mp_small_node *head = pool->head; //pool中small_node的起始地址
    size_t psize = (size_t)(head->end - (unsigned char *)head); 
    int ret = posix_memalign((void **)&m, MP_ALIGNMENT, psize); //分配一个small_node 大小
    if (ret)
    {
        return NULL;
    }
    struct mp_small_node *newnode, *current, *p;
    newnode = (struct mp_small_node *)m;
    current = pool->current;
    p = current;
    while (p->next)
    {
        p = p->next;
    }//找到最后一个small_node
    p->next = newnode;
    newnode->next = NULL;
    newnode->failed = 0;
    newnode->last = m + size;
    newnode->end = m + psize;

    return m;
}

创建large_node

static void *mp_malloc_large(struct mp_pool *pool, size_t size)
{
    void *data = malloc(size);
    if (data == NULL)
        return NULL;
    struct mp_large_node *large = pool->large;
    while (large)
    {
        if (large->data == NULL) //如果指针为空,则指向新分配的内存空间
        {
            large->data = data;
            return data;
        }
    }
    struct mp_large_node *newnode = mp_malloc(pool, sizeof(struct mp_large_node)); //如果所有large_node 都占用了 就分配一新的
    if (newnode == NULL)
    {
        free(data);
        return NULL;
    }
    newnode->data = data;
    newnode->next = pool->large;
    pool->large = newnode; //头插法插入

    return data;
}
点赞
收藏
评论区
推荐文章
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
3年前
java设计思想
 https://blog.csdn.net/qq\_16038125/article/details/80180941池:同一类对象集合连接池的作用 1.资源重用 由于数据库连接得到重用,避免了频繁创建、释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增进了系统运行环境的平稳性(减少内存碎片以及数据库临时进程/线程的数量
Wesley13 Wesley13
3年前
java四大线程池
一、为什么需要使用线程池  1、减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。2、可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。Java中创建和销毁一个线程是比较
Stella981 Stella981
3年前
Netty内存池及命中缓存的分配
内存池的内存规格:在前面的源码分析过程中,关于内存规格大小我们应该还有些印象。其实在Netty内存池中主要设置了四种规格大小的内存:tiny是指0512Byte之间的规格大小,small是指512Byte8KB之间的规格大小,normal是指8KB16MB之间的规格大小,huge是指16MB以上。为什么Netty会选择
Stella981 Stella981
3年前
Netty 的内存池源码
_/\__\Copyright2013TheNettyProject__\__\TheNettyProjectlicensesthisfiletoyouundertheApacheLicense,__\version2.0(the"License");youmaynotusethisf
Stella981 Stella981
3年前
Netty 中的内存分配浅析
Netty出发点作为一款高性能的RPC框架必然涉及到频繁的内存分配销毁操作,如果是在堆上分配内存空间将会触发频繁的GC,JDK在1.4之后提供的NIO也已经提供了直接直接分配堆外内存空间的能力,但是也仅仅是提供了基本的能力,创建、回收相关的功能和效率都很简陋。基于此,在堆外内存使用方面,Netty自己实现了一套创建、回收堆外内存池的相关功能。基
Wesley13 Wesley13
3年前
Java中的OutOfMemoryError的各种情况及解决和JVM内存结构
在JVM中内存一共有3种:Heap(堆内存),NonHeap(非堆内存)\3\和Native(本地内存)。\1\堆内存是运行时分配所有类实例和数组的一块内存区域。非堆内存包含方法区和JVM内部处理或优化所需的内存,存放有类结构(如运行时常量池、字段及方法结构,以及方法和构造函数代码)。本地内存是由操作系统管理的虚拟内存。当一个应用内存不足时
Stella981 Stella981
3年前
Netty如何监控内存泄露
Netty如何监控内存泄露\TOC\前言一般而言,在Netty程序中都会采用池化的ByteBuf,也就是PooledByteBuf以提高程序性能。但是PooledByteBuf需要在使用完毕后手工释放,否则就会因为PooledByteBuf申请的内存空间没有归还进而造成内存泄露,最终OOM。而一旦
Stella981 Stella981
3年前
Redis源码剖析 内存
Redis通过自己的方法管理内存,主要方法有zmalloc(),zrealloc(),zcalloc()和zfree(),分别对应C中的malloc(),realloc(),calloc()和free().redis自己管理内存的好处主要有两个:1、可以利用内存池等手段提高内存分配的性能;2、’可以掌握更多的内存信息,以便于redi
Easter79 Easter79
3年前
ThreadLocal的内存泄露的原因分析以及如何避免
前言在分析ThreadLocal导致的内存泄露前,需要普及了解一下内存泄露、强引用与弱引用以及GC回收机制,这样才能更好的分析为什么ThreadLocal会导致内存泄露呢?更重要的是知道该如何避免这样情况发生,增强系统的健壮性。内存泄露内存泄露为程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果