一. 上节回顾
1. PHP的问题定位
2. CPU的三个主要案例:
(1) mysql
(2) java
(3) PHP
二. 内存
1. 物理内存
生活中,我们知道内存有4G和8G,这里提到的是内存容量,其实指的是物理内存
物理内存:也称为主存,大多数计算机的主存都是动态随机访问内存(DRAM)
2. 虚拟内存
只有内核才能直接访问物理内存,那么,应用进程要访问内存时,怎么办?
Linux内核给每个进程都提供了一个独立的虚拟地址空间,并且这个空间是连续的,这样进程就可以很方便的访问内存,更确切的说是访问虚拟内存
虚拟地址空间的内部又被分为内核空间和用户空间两部分,不同字长(也就是单个CPU指令可以处理数据的最大长度)的处理器,地址空间的范围也不同,比如64位系统,32位系统
我们都知道,进程在用户态时,只能访问用户空间内存,只有进入内核态后,才可以访问内核空间内存,虽然每个进程的地址空间都包含了内核空间,但这些内核空间,其实关联的都是相同的物理内存,这样,进程切换到内核态后,就可以很方便的访问内核空间内存
3. 内存映射
每个进程都有一个这么大的地址空间,那么所有进程的虚拟内存加起来,自然要比实际的物理内存大的多,所以并不是所有的虚拟内存都会分配物理内存,只有那些实际使用的虚拟内存才会分配物理内存,并且分配后的物理内存,是通过内存映射来管理的
内存映射:就是将虚拟内存地址映射到物理内存地址,为了完成内存映射,内核为每个进程都维护了一张页表,记录虚拟地址和物理地址的映射关系
页表实际上存储在CPU的内存管理单元MMU中,这样,正常情况下,处理器就可以直接通过硬件,找出要访问的内存
4. 内存使用
通过上面的页表映射,进程就可以通过虚拟地址来访问物理内存了,那么具体一个Linux进程中这些内存又是怎么使用的呢?
以32位系统为例,从低到高有五种不同的内存段:
只读段:包括代码和常量等
数据段:包括全局变量等
堆:包括动态分配的内存,从低地址开始向上增长
文件映射:包括动态库,共享内存,从高地址开始向下增长
栈:包括局部变量和函数调用的上下文,栈的大小都是固定的,一般是8MB
在这五个内存段中,堆和文件映射的内存是动态分配的
5. 内存分配与回收
malloc()是C标准库提供的内存分配函数,对应到调用系统上,有两种实现方式:brk()和mmap()
对于存储小于128K,C标准库使用brk()来分配,也就是通过移动堆顶的位置来分配内存,这些内存释放之后并不会立即归还系统,而是被缓存起来,这样就可以重复使用
对于大块内存大于128K的,直接使用内存映射mmap()来分配,就是在文件映射段中找一块内存分配出去
优缺点:
brk()方式的缓存:可以减少缺页异常的发生,提高内存访问效率,不过由于这些内存没有归还系统,在内存工作繁忙时,频繁的内存分配和释放会造成内存碎片
mmap()方式分配的内存:会在释放时直接归还系统,所以每次mmap都会发生缺页异常,在内存工作繁忙时,频繁的内存分配会导致大量的缺页异常,使内核的管理负担增大,这也是malloc只对大块内存使用mmap的原因
对内存来说,如果只分配而不释放,就会造成内存泄漏,甚至会耗尽系统内存,所以在应用程序用完内存后,还需要调用free()或unmap(),来释放内存
当然,系统也不会任由某种进程用完所有内存,在发现内存紧张时,系统会通过一系列的机制来回收内存,如下三种:
第一种:回收缓存,使用LRU(Least Recently used)算法,回收最近使用最少的内存页面
第二种:回收不常用的内存,把不常用的内存通过交换分区直接写到磁盘中
第三种:杀死进程,内存紧张时系统会跳过OOM(OutOfMemory),直接杀掉占用大量内存的进程
其中第二种回收不常用的内存时,就会用到交换分区(swap),swap其实就是把一块磁盘空间当做内存来使用,它可以把进程暂时不用的数据存储到磁盘中(这个过程称为换出),当进程访问这些内存时,再从磁盘读取这些数据到内存中(这个过程称为换入)
所以,发现swap把系统的可用内存变大了。但要注意,通常只有在内存不足时,才会发送swap交换,并且由于磁盘读写的速度远比内存慢,swap会导致严重的内存性能问题
第三种方式OOM,是内核的一种保护机制,它监控进程的内存使用情况,并且使用oom_score为每个进程的内存使用进行评分:
一个进程消耗的内存越大,oom_score越大
一个进程消耗的内存越小,oom_score越小
这样,进程的oom_score越大,代表消耗的内存越大, 也就很容易被OOM掉,从而可以更好的保护系统
三. 查看内存使用情况的工具
1. 第一个查看内存的性能工具:free
free
free -h
free -m
total:总的内存大小
used:已使用内存的大小
free:未使用内存的大小
shared:共享内存的大小
buff/cache:缓存和缓冲区的大小
available:新进程可用内存的大小
注意:available不仅包含未使用的内存,还包括了可回收的缓存
2. top
上面可以看出free显示的是整个系统的内存使用情况,想要查看进程的内存使用情况,可以使用top或者ps等工具
这些输出,包含了进程最重要的几个内存使用情况
VIRT:进程虚拟内存的大小,只要是进程申请过的内存,即便还没有真正分配物理内存,也会计算在内
RES:常驻内存的大小,也就是进程实际使用的物理内存大小,但不包括swap和共享内存
SHR:共享内存的大小,比如与其他进程共同使用的共享内存,加载的动态链接库以及程序的代码段
%MEM:进程使用物理内存占系统总内存的百分比
需要注意:
(1) 虚拟内存通常并不会全部分配物理内存,从上面的输出,可以发现每个进程的虚拟内存都比常驻内存大得多
(2) 共享内存SHR也不一定是共享的,比如说,程序的代码段,非共享的动态链接库,也都算在SHR里面。当然,SHR也包括了进程间真正共享的内存,在计算多个进程的内存使用时,不要把所有进程的SHR直接相加得出结果
buff和cache:从字面上来说,buff是缓冲区,而cache是缓存,两者都是数据在内存中的临时存储,这两种有什么区别?
buff:是对原始磁盘块的临时存储,也就是用来缓存磁盘的数据,通常不会特别大(20MB左右)
cache:是从磁盘读取文件的页缓存,也就是用来缓存从文件读取的数据
两个问题:
(1) buff是磁盘读数据还是写数据的缓存?
(2) cache是对文件读数据的缓存,那么是不是也会缓存写文件的数据?
四. 场景案例
1. 场景一:磁盘和文件写案例
准备工作:先安装sysstat
(1) 先清理缓存,清理文件页,目录项,inodes等各种缓存
echo 3 > /proc/sys/vm/drop_caches #通过proc文件系统修改内核行为,写入3表示清理文件,目录项,inodes等各种缓存
(2) 在第一个终端输入:vmstat 1
在输出界面中,内存部分的buff和cache,io的bi、bo是需要重点关注的,bi和bo分别表示块设备读取和写入的大小
(3) 在第二个终端执行dd命令,读取随机设备,生成一个200MB大小的文件。在第一个终端,观察buff和cache的变化情况
dd if=/dev/urandom of=/data/file bs=200M count=500
观察vmstat的输出,发现在dd命令运行时,cache在不停的增长,而buff没有变化
进一步观察I/O的情况,会看到在cache刚开始增长时,bi只出现了16KB/s,bo刚开始也很小,但是一下子会出现大量的快设备写入,bo达到40000以上
当dd命令结束后,cache不会增长,但块设备写入还会持续一段时间,并且多次I/O写的结果加起来,才是dd要写的500 x 200M的数据
通过案例,发现写文件时也会用到cache缓存数据,而写磁盘会用到buff来缓存数据