内存的基本认知

125 阅读7分钟

内存的概念模型

内核为了更方便的让我们去操作物理内存,引入了虚拟地址这个概念。

如果没有虚拟地址,那我们程序直接操作的是物理地址,那每一块物理内存一次性取多少?

取完之后再放回去的时候,由于每一次取的大小不固定,产生的内存间隙又该如何去处理?

所以内核为了解决这种问题引入了虚拟地址。

实际的物理地址的分配以及物理地址的存取是由内核去完成的,用虚拟地址最后会映射成物理地址,才会去实际的从主存上面拿取数据。

关于实际上数据的存取是由内核帮助我们完成的。每个用户进程只能看见自己空间的地址。在cpu处理的时候,cpu会把这个地址通过mmu转变成物理地址。

mmu的工作流程

image.png

首先是中央处理器(cpu)在得到一个虚拟地址之后,会从一级缓存里面去寻找这个虚拟地址对应的数据在不在,不在的话,就会从存储器管理单元(mmu),mmu会有一个页表的结构,这个页表本来是在主存上面的,但是如果每次mmu都从主存上面去拉取这个页表的映射信息,肯定会比较慢了,所以mmu又引入了转址旁路缓存(tlb),它是一块硬件设备,是和mmu靠在一起的。

当mmu在寻找这个映射关系的时候,先从tlb上面去寻找这个映射关系,如果是找不到的话,那它才会在主存上面的页表上面去拉取这个映射关系,最终得到了物理地址之后,它会进到cpu的一个二级缓存里面去寻找,二级缓存里面找不到,会再从三级缓存里面去寻找数据,再找不到那就从主存里面去获取这个数据了。

页交换

当系统内存比较吃紧的时候,内核会交换一部分的内存页到磁盘上,等到需要的时候,会再从磁盘上把那个内存页读取到主存里面去,如果是内存页被交换到磁盘上之后,再从磁盘的读回来,这整个过程是一个比较消耗性能的,如果整个系统在频繁的去进行页交换,那很有可能页交换会造成瓶颈,而根本原因是内存空间不足了,得加一个内存条。

如何分析内存性能问题?

先从整体上去把握整个系统内存的情况,首先来看下内存的使用率、饱和度、错误数等。

针对于内存而言,它的一个使用率就是说它使用的内存空间的大小占总的大小的多少。

内存饱和度就是页交换的次数,如果内存频繁的去进行页交换,那可以认为内存现在是处于一种饱和的状态。

通过top命令 我们能够去看到整个系统的一个内存情况,top后输入E,切换内存换算单位,默认为字节,

image.png

  • total 总内存大小
  • free 空闲内存大小
  • used 已经使用的内存大小
  • buff/cache 文件系统cache与磁盘buffer。如果在系统内存比较吃紧的时候,空闲内存大小在不断的降低之后,内核很有可能会释放部分的buffer来供应用程序所使用。
  • Swap 交换分区的内存用于页交换

buff/cache

对文件进行操作的时候,会经由文件系统和磁盘打交道,当我们写一个字符串或写一段文本到文件里的时候,它不会立马刷到磁盘里去,进入文件系统它会先存到一个缓存里,这个缓存叫做page cache,也就是buffer/cache里的cache。会有一个进程在执行一个刷盘的操作,刷盘的话会把这个cache里面的数据真正的写入到磁盘上面去。读文件也是类似,当我们读文件的时候,先从文件系统的page cache里面去读,读不到的时候,它才会真正的从磁盘上面去读真正的文件数据。

在老的linux系统上,这两个值其实是分开的,但如果这两个值分开的话,比如说我读文件,先到buffer,再到cache,再到应用程序里面的话,这个数据就相当于被多复制了一份,在新版的linux系统,读文件时,cache已经指向了buffer,这样的话,就减少了一次数据的复制过程。

内存的饱和度

内存的饱和度是用页交换去衡量的,通过sar -W的命令可以去看系统的页交换次数,

image.png

这里会每秒刷新一次页交换的次数(换进换出的次数)。

那如果整个系统频繁的进行页交换之后,很有可能内存已经处于一种比较吃紧的状态了,需要去加内存条。

上面看了系统层面的内存使用情况,再定位到具体的进程。

通过top命令输入M,会把内存从大到小进行排序,从而找出最消耗内存的一个进程。

定位到具体的进程之后,可以去分析进程具体是什么原因导致内存消耗过大。

查看进程使用内存的情况

可以用pidstat命令去看这个进程使用的内存的情况,

pidstat -r -p pid

image.png

缺页异常的次数

缺页异常的次数也是我们平时要经常关注的一个点。majflt指的是一个主的缺页次数,主的缺页次数指的是当要读取内存页的时候,这个内存页实际是在磁盘上的,在读取的时候,它要被内核交换到内存上面来,所产生的这种缺页异常会记录到这里,而除此以外的缺页异常都会被minflt记录。如果主的这个缺页异常过多,也可以从侧面反映出这个进程在进行过多的这种页交换,而页交换所导致的问题就是系统的性能肯定是上不去的。

在golang里面如何去看具体的哪一段代码导致的内存占用问题?

官方提供的go tool pprof工具可以分析内存的profile文件,通过分析这个profile文件,我们能够找出具体是哪段代码导致的问题。通过go tool pprof --base这个命令是能够比较两个内存的profile文件,这也是排查golang程序内存泄漏经常用到的一个手段,也很方便的能够找出问题代码。在golang里可以很方便查看整个内存的使用情况从操作系统到进程再到具体的代码段整个过程。

内存泄漏问题如何排查?

内存泄漏的表现就是内存在不断的增长,而不断增长后可能整个系统就会频繁的进行换页的动作。

在golang程序里面能够通过比较两次的内存的profile文件,看下具体是哪段代码导致内存在不断增长,从而去找出内存泄漏的那段问题代码。

特别是针对开发来讲,排查下性能优化的最终原因,其实大部分都是由于你的问题代码导致的,我们得学会如何找出这段问题代码。