Linux7.3.1611默认启动后会占用50MB物理内存的测试表

一、背景

在项目中经常会遇到在没有大并发活动SQL的情况下,MySQL占用的物理内存远大于配置大小。我最初怀疑它被吃掉了,或者 MySQL 中存在内存泄漏,但后来我发现事实并非如此。是因为我对 MySQL 和 Linux 的内存管理不太了解,所以我会在这篇文章中深入讨论。有错不严谨的欢迎大家~

简单说一下MySQL内存分配的基本认识,可能会有一些认知偏差:

MySQL的内存使用主要由两部分组成,其中,哪里是全局共享缓存,哪里是所有线程的独立缓存,如下图所示:

图片[1]-Linux7.3.1611默认启动后会占用50MB物理内存的测试表-4747i站长资讯

: +

: (当前活动连接数) * ( )

其中,它是 MySQL 中占用的最大一块内存,也就是常驻内存,也就是说,除非 MySQL 进程退出,否则它不会被释放。

另一块占用更多内存的内存是线程缓存。例如,, , 等通常与连接数成正比。即连接数越高,并发性越高,总线程缓存占用率也越高。但是,这种类型的缓存通常在连接关闭时被释放,并且不驻留在内存中。

二、内存高水位线

Linux 7.3.1611 (Core) : 5.7.27-log MySQL (GPL) 我们做个小测试观察一下 MySQL 的内存使用情况,先关闭并防止缓存干扰。然后设置100M,理论上最大只占100M,可以通过show G查看。

通过创建一个100W的测试表,重启MySQL,观察MySQL目前总共占用物理内存,其中432*16K=内存被占用,那么我什至假设MySQL默认启动后会占用50MB的物理内存.

/s /sVSZ RSS %MEM .000.00 55536 0.69 ———- 池和—- ——————大池总大小

然后我们开始压测,从4个线程开始,逐渐增加4-8-16-32-64上的线程数,每次压测2分钟,最后观察MySQL总物理内存使用量的变化。

图片[2]-Linux7.3.1611默认启动后会占用50MB物理内存的测试表-4747i站长资讯

从上图可以看出,4个线程刚开始压力测试时,内存使用量猛增。主要是因为中间有大量的数据页涌入。那么当线程数增加的时候,波动不是很大,因为已经达到了100M的上限。这个内存增加的原因主要是增加了。经过最后的64线程压力测试,MySQL的总物理内存使用稳定在194MB左右,一直保持,没有释放到操作系统。

压测结束后,再次查看,可以看到Free为空,100M已满。

———————- POOL AND ————大池大小页面总数 2156

减去100M,MySQL刚启动占用的50M,主要是40MB+的内存占用。

通过这个测试,我们可以看到之前理解的线程缓存在连接关闭时是不正确的。MySQL 不会将这部分缓存返回给操作系统,而只是在 MySQL 内部释放,然后再使用。

我将这种现象称为内存高水位线,因为它与中高水位线概念非常相似。同样,在MySQL中,ibd文件被删除后,即使表已满,也不会主动释放磁盘空间并返回给操作系统mysql配置内存,而是重新使用释放的磁盘空间。现象也很一致。

PS:这里的压测是单表where使用主键索引查询,不会申请等,所以单个申请的线程缓存比较小。因此,最终的总线程缓存使用率并不是很高。如果是按复杂的SQL,内存占用应该比较高。

三、Linux进程内存分配

为了找出MySQL经常出现高内存水位现象的原因,先去查学习Linux下的相关内存调用原理,具体内容总结如下:

图片[3]-Linux7.3.1611默认启动后会占用50MB物理内存的测试表-4747i站长资讯

上图是32位用户虚拟空间内存的结构示意图,从上到下:

1. 只读段:包括代码和常量等;

2. 数据段:包括全局变量等;

3. 堆:包括动态分配的内存,从低地址向上增长;

4. 文件映射段:包括动态库、共享内存等,从高地址开始向下增长;

5. 栈:包括局部变量和函数调用的上下文等。

堆和文件映射段是我们讨论的重点,它们的内存是动态分配的。例如,使用 C 标准库的 () 或 mmap(),可以分别在堆和文件映射段中动态分配内存。

那么两者有什么区别呢?

()是C标准库提供的内存分配函数mysql配置内存,对应系统调用,有两种实现,分别是brk()和mmap()。

1. brk 方式

对于小块内存(brk()来分配。即通过移动堆顶的方式来分配内存。内存释放后不会立即返回给系统,而是会缓存起来重用。优点和缺点: brk() 方法可以减少缺页异常的发生,提高内存访问效率。但是由于这些内存没有返回给系统,频繁的内存分配和释放会在内存繁忙时造成内存碎片。2. mmap 匿名映射方法

对于大块内存(>128K),C标准库使用mmap()进行分配,即在文件映射段中寻找一块空闲内存进行分配。mmap()分配的内存在释放的时候会直接归还给系统,所以每次mmap都会出现缺页异常。优缺点: mmap() 方法可以及时将内存返回给系统,避免OOM。但是在工作繁忙的时候,频繁的内存分配会导致大量的缺页异常,增加了内核的管理负担。这也是为什么 mmap 只用于大块内存的原因。所谓缺页异常是指进程申请内存后,只分配虚拟内存。

brk() 方法申请的堆内存在内存释放后不会返回给系统,所以下次申请内存时不需要出现缺页异常。mmap()申请的动态内存在内存释放后会直接返回给系统,所以下次申请内存时会出现缺页异常(增加内核态CPU开销)。C语言中与内存应用相关的函数主要有 、 、 等。

:根据内存申请的大小,选择在堆或文件映射段中分配连续内存,但不会初始化内存。一般这个内存会通过一个函数来初始化。: 类似,只是这个内存空间是自动初始化的,每个字节都设置为0。 : 可以调整已经申请的内存大小,新申请的内存也是未初始化的。

四、Linux 内存分配器

上面提到的是Linux进程通过C标准库中的内存分配函数向系统申请内存,但在与内核的实际交互之间其实还有一层,即内存分配管理器( )。常见的内存分配器包括:(Glibc)、()、()。MySQL 默认使用 glibc 作为内存分配器。

图片[4]-Linux7.3.1611默认启动后会占用50MB物理内存的测试表-4747i站长资讯

内存分配器采用内存池的管理方式,位于用户程序层和内核层之间。它响应用户的分配请求,向操作系统申请内存,然后返回给用户程序。

为了保持高效的分配,分配器通常会提前向操作系统申请一块内存。当用户程序申请和释放内存时,分配器会对内存进行管理,并使用一些算法策略来决定是否将其返回给操作。系统。这样做最大的好处是可以避免用户程序频繁调用系统进行内存分配,使用户程序在内存使用上更加高效快捷。

关于内存分配原理,我不是很了解。我不知道在这里做什么。有兴趣的同学可以去华亭的《glibc内存管理源码解析》【文末链接】。

关于如何选择这三个内存分配器,网上的资料大多建议放弃原生的glibc,改用它或者作为默认分配器。因为主要的问题其实是内存浪费、内存碎片、加锁带来的性能问题,最好是优化内存碎片和多线程。

图片[5]-Linux7.3.1611默认启动后会占用50MB物理内存的测试表-4747i站长资讯

目前适用于 , 并且是 Redis、等、while 等默认推荐的内存分配器。

总的来说,比较推荐 MySQL 作为内存分配器,可以有效解决内存碎片,提升整体性能。有兴趣的同学可以进一步测试,本文不再深入。

五、MySQL 内存管理

接下来,我们来看看 MySQL 的内部内存管理。查阅了很多资料,发现自己原来的理解不是很正确。之前,我曾经将 MySQL 的内存分为三类: 按架构划分内存管理是合理的。也就是层和层(),而这两块内存的管理方式不同。

该层用于内存管理,包括Thead和Thead;层主要由Free List、LRU List、FLU List等多个链表管理。

4.1.

.7开始支持动态调整大小,每一个由相同数量的chunk组成,每个chunk的内存大小为1,所以认为基本单位是动态增减的。

图片[6]-Linux7.3.1611默认启动后会占用50MB物理内存的测试表-4747i站长资讯

图片[7]-Linux7.3.1611默认启动后会占用50MB物理内存的测试表-4747i站长资讯

可以看出内存初始化是通过mmap()方法直接向操作系统申请内存。每个申请的大小为 ,最终会申请到大小为大小的文件映射段的动态内存。这部分内存空间在初始化后只是虚拟内存,实际使用时会分配物理内存。

根据之前Linux下的内存分配原则,mmap()申请的内存会在文件映射段中分配内存,释放时直接返还给系统。

仔细想想,内存的分配和使用。确实如此。初始化后,会慢慢的被数据页和索引页填满,然后一直保持size大小所占用的物​​理内存。除非在线减少或关闭MySQL,否则内存会通过()方法释放,这里释放的内存直接返回给操作系统。

内存主要通过Free List、LRU List、FLU List、Unzip LRU List等四个链表进行管理和分配。

Free List:缓存空闲页 LRU List:缓存数据页 FLU List:缓存所有 Unzip LRU List:缓存所有解压后的页 PS:源码全局遍历,只有带ddl的内存管理使用mmap()方法直接申请内存分配给操作系统而不经过内存分配器。

4.2.

MySQL 层广泛使用结构来管理内存,避免频繁调用内存操作,提高性能,统一分配和管理内存,防止内存泄漏:

图片[8]-Linux7.3.1611默认启动后会占用50MB物理内存的测试表-4747i站长资讯

MySQL首先通过一个函数来初始化一个很大的内存空间。其实它最终通过一个函数给内存分配器申请一块内存空间,然后每次调用该函数在这个内存空间中分配内存供使用。目的是多次使用。将分散的操作合并为一个大型操作以提高性能。

一开始我以为 MySQL 层完全是由一个 来管理所有层内存的,就这样。后来发现不是。不同的线程会生成不同的线程来管理自己的内存,不同的线程相互之间没有影响。

与层相比,层的内存管理要复杂得多,也更容易出现内存碎片。许多 MySQL 内存问题都来自于此。

六、总结

下面用一个简单的图来总结一下 MySQL 的内存管理:

图片[9]-Linux7.3.1611默认启动后会占用50MB物理内存的测试表-4747i站长资讯

最后,让我们看看原来的问题。为什么经常会出现MySQL的实际物理内存远高于配置而没有释放的情况?

事实上,大部分占用的内存都被内存分配器吃掉了。为了更高效的内存管理,内存分配器通常会占用大量内存而不释放;当然,另一部分原因是内存碎片,这会导致内存分配器无法重用之前申请的内存。

但是,内存分配器并不是从不释放内存,而是需要达到一定的阈值,才会将部分内存释放给操作系统。原理需要在源码中找到~

这次内存原理探索,其实一开始我只是想知道MySQL内存使用率高的原因。没想到一步步挖得越来越深,从MySQL内存管理到Linux进程内存管理,再到内存管理器。对记忆的理解。

文章来源:http://baijiahao.baidu.com/s?id=1680403636407300017&wfr=spider&for=pc

------本页内容已结束,喜欢请分享------

感谢您的来访,获取更多精彩文章请收藏本站。

© 版权声明
THE END
喜欢就支持一下吧
点赞6 分享