Linux内存闲谈

Linux内存闲谈

内存映射

Linux 内核给每个进程都提供了一个独立的虚拟地址空间,并且这个地址空间是连续的,所以进程可以方便地访问虚拟内存。虚拟地址空间的内部又被分为内核空间和用户空间两部分,不同字长(也就是单个 CPU 指令可以处理数据的最大长度)的处理器,地址空间的范围也不同。进程在用户态时,只能访问用户空间内存;只有进入内核态后,才可以访问内核空间内存。进程在用户态时,只能访问用户空间内存;只有进入内核态后,才可以访问内核空间内存。
在这里插入图片描述

虚拟内存的大小可能远大于物理内存大小,因此,并不是每个虚拟内存都在物理内存中有对应,而是只有使用时才会为其分配内存,物理内存和虚拟内存之间的关系,通过内存映射完成。物理内存和虚拟内存都被分成一页一页的,MMU以页为单位管理内存。
在这里插入图片描述

页表中记录了虚拟内存到物理内存的映射关系,当然也有一些附加信息,包括页是否修改、数据是否有效、是否在物理内存上等诸多信息。实际上还有一个TLB用来加速地址映射速度,可以把TLB当作一个缓存即可,TLB命中的话就不需要再去访问页表,否则需要再次访问页表。

Linux通过多级页表机制实现内存映射,多级页表大大减少了页表的大小,节省了内存空间。

程序的虚拟内存空间分布

在这里插入图片描述

  • 只读段,包括代码和常量等
  • 数据段,包括全局变量等
  • 堆,包括动态分配的内存,从低地址开始向上增长,malloc或者new进行heap空间的分配
  • 文件映射段,包括动态库、共享内存等,从高地址开始向下增长
  • 栈,包括局部变量和函数调用的上下文等
  • 栈的大小是固定的,一般是 8 MB

一个进程占用的空间该怎样算?一堆进程占用的空间该怎么算?

一个进程占用的空间,有一个有意思细节:malloc或者全局申请的变量,没有进行初始化,那么,这个变量实际上是不占用真实地址(物理地址)的,只是占用了虚拟地址,只有当真正写的时候才会占用真实的地址。因此一个程序占用的虚拟地址通常来说是要大于这个程序占用的物理地址。

计算一堆进程占用空间时,也有一个有意思的细节:一堆进程之间会存在一些共享库,也包括一些copy-on-write的内存,但是直接在shell中查看proc的内存时会算上这个,因此假设这样一个场景:系统出现问题,然后直接将所有进程的信息list出来,如果直接将每个proc的内存加起来当作总的内存和,这就会产生一个比较大的问题,因为共享的区域会加起来很多次。可能会产生一个误解,即明明proc的占用内存可能并不高,但是因为重复计算太多可能导致评估proc占用的总内存爆了,这就会产生一个误判,但是直接free展现的话就会发现系统还有内存。因此计算一堆proc占用的总内存就需要注意这里。

Linux主要通过使用伙伴系统来管理内存分配,以页为单位来管理内存,并且会通过相邻页的合并,减少内存碎片化。Linux 则通过 slab 分配器来管理小内存。

当内存不够用时,系统需要回收内存:

  • 回收缓存,比如使用 LRU(Least Recently Used)算法,回收最近使用最少的内存页面
  • 回收不常访问的内存,把不常用的内存通过交换分区直接写到磁盘中 【会用到交换分区Swap。Swap 把一块磁盘空间当成内存来用。它可以把进程暂时不用的数据存储到磁盘中(这个过程称为换出),当进程访问这些内存时,再从磁盘读取这些数据到内存中。通常只在内存不足时,才会发生 Swap 交换。并且由于磁盘读写的速度远比内存慢,Swap 会导致严重的内存性能问题。因此如果系统的Swap较大,通常说明内存紧张】
  • 杀死进程,内存紧张时系统还会通过 OOM(Out of Memory),直接杀掉占用大量内存的进程
buffer和cache

通过对proc文件系统查询,可以知道Linux上的buffer和cache的定义:

  • Buffers 是对原始磁盘块的临时存储,也就是用来缓存磁盘的数据,通常不会特别大(20MB 左右)。这样,内核就可以把分散的写集中起来,统一优化磁盘的写入,比如可以把多次小的写合并成单次大的写等等
  • Cached 是从磁盘读取文件的页缓存,也就是用来缓存从文件读取的数据。下次访问这些文件数据时,就可以直接从内存中快速获取,而不需要再次访问缓慢的磁盘。
  • SReclaimable 是 Slab 的一部分。Slab 包括两部分,其中的可回收部分,用 SReclaimable 记录;而不可回收部分,用 SUnreclaim 记录

buffer通常是解决速度差异的问题,为了避免速度快的一方受限于速度慢的一方,用一个buffer进行协调。cache通常是用来加快访问,将常用的内容放到cache里加快访问。

通过一个案例来看buffer和cache。

先清理系统缓存避免影响 :

 echo 3 > /proc/sys/vm/drop_caches

用vmstat看一下当前的buffer和cache情况:

[root@jessy /]# vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 1  0      0 7720256   5548 105876    0    0     0     6    4    2  0  0 100  0  0
 0  0      0 7719900   5548 105908    0    0     0    24  671 1107  0  0 100  0  0
 0  0      0 7721276   5548 105908    0    0     0     0  204  242  0  0 100  0  0
 0  0      0 7721452   5548 105912    0    0     0     0  395  762  0  0 100  0  0

基本情况下空闲时buffer和cache是几乎不变的,或者说变化甚微。

在一个终端通过dd命令随机读取设备生成一个500MB的文件:

[root@jessy /]# dd if=/dev/urandom of=/tmp/file bs=1M count=500
500+0 records in
500+0 records out
524288000 bytes (524 MB) copied, 3.37052 s, 156 MB/s

另一个终端用vmstat一直监视:

[root@jessy ~]# vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 1  0      0 7625712   7512 194584    0    0     0     6    4    2  0  0 100  0  0
 0  0      0 7625264   7512 194588    0    0     0     0  283  313  0  0 100  0  0
 0  0      0 7624564   7528 194576    0    0     0    84  219  289  0  0 100  0  0
 0  0      0 7623772   7528 194592    0    0     0     0  369  419  0  1 99  0  0
 0  0      0 7623292   7536 194588    0    0     8     0  678 1149  0  0 100  0  0
 1  0      0 7498880   7536 318348    0    0    76     0 2057  851  0 21 79  0  0
 1  0      0 7343648   7536 474560    0    0     0     4 3902 1497  0 25 74  0  0
 1  0      0 7188836   7544 629716    0    0     0    92 4131  943  0 26 74  0  0
 0  0      0 7103560   7544 716164    0    0     0     8 2376  656  0 14 86  0  0

发现一个很奇怪的现象,那就是buffer没有明显变化,但是cache缺增长非常明显。

再进行一个场景,对磁盘进行写入:

dd if=/dev/sda1 of=/dev/null bs=1M count=1024

另一个终端监控:

procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0  0      0 7225880   2716 608184    0    0     0     0   48  159  0  0 100  0  0
 0  1      0 7199420  28644 608228    0    0 25928     0   60  252  0  1 65 35  0
 0  1      0 7167092  60900 608312    0    0 32256     0   54  269  0  1 50 49  0
 0  1      0 7134416  93572 608376    0    0 32672     0   53  253  0  0 51 49  0
 0  1      0 7101484 126320 608480    0    0 32748     0   80  414  0  1 50 49  0

可以看到buffer增长非常迅速,cache无明显变化。

因此Linux上的cache和buffer基本可以这样理解,Buffer 既可以用作“将要写入磁盘数据的缓存”,也可以用作“从磁盘读取数据的缓存“,Cache 既可以用作“从文件读取数据的页缓存”,也可以用作“写文件的页缓存”。Buffer 是对磁盘数据的缓存,而 Cache 是文件数据的缓存,它们既会用在读请求中,也会用在写请求中

缓存的效果取决于其命中率:缓存的命中率,是指直接通过缓存获取数据的请求次数,占所有数据请求次数的百分比。缓存命中率高,应用程序的性能就越好。

主要原理是把经常访问的数据,提前放入到内存中,在下次访问时就可以直接从内存读取数据,而不需要经过硬盘,从而加快应用程序的响应速度。

内存泄漏

对普通进程来说,所谓分配的内存实际上是虚拟内存,用malloc申请虚拟内存后,系统不会立即分配物理内存,而是在首次缺页异常陷入内核中分配内存。

内存泄漏会发生了哪些内存区?

  • 栈内存由系统自动分配和管理。一旦程序运行超出了这个局部变量的作用域,栈内存就会被系统自动回收。故不会产生内存泄漏的问题
  • 堆内存由应用程序自己来分配和管理。除非程序退出,这些堆内存并不会被系统自动释放,而是需要应用程序明确调用库函数 free() 来释放它们。如果正确释放则会造成内存泄漏
  • 只读段,包括程序的代码和常量,由于是只读的,不会再去分配新的内存,故不会内存泄漏
  • 数据段,包括全局变量和静态变量,定义时就已经确定了大小,不会产生内存泄漏
  • 内存映射段,包括动态链接库和共享内存,其中共享内存由程序动态分配和管理。所以,如果程序在分配后忘了回收,会产生内存泄漏
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值