前言

在使用虚拟机跑PHP脚本,惊讶发现 top 命令看到的进程使用的内存总和竟然大于虚拟机可使用的最大物理内存。其实是分析的方向有些小问题,是因为通常大多数人关注的是 RSS 内存。

通常在 Linux 操作系统中对内存有着 VSS、RSS、PSS、USS等分类。不管你是用上面提到的 top、还是 ps aux 、或者查看 /proc/process_num(进程号)/status | grep RSS,往往大家下意识观察的都是 RSS。

往往这也是不太对的…

分类

上面提到在 Linux 中有着VSS、RSS、PSS、USS等指标,通常情况下它们有着占用大小 VSS >= RSS >= PSS >= USS 的规律。先看下图:
在这里插入图片描述

VSS - Virtual Set Size 虚拟耗用内存

对应上图 - 区域1。

通常不用于判断进程占用的内存大小,因为它不仅包含共享库占用的全部内存,而且包含分配但未使用内存。

对于不了解虚拟内存的同学可以先看看博文《Linux - 内存管理》,了解操作系统是怎么做内存管理的。

RSS - Resident Set Size 实际使用物理内存

对应上图 - 区域2 + 3 + 4。

其实不建议大家使用 RSS 去观察进程内存的占用情况。因为它包含了共享库占用的内存,所以会给大家造成误解。

共享库是什么?举个例子:比如我们使用 redis 扩展,在编译完成后会有一个 redis.io 的文件,可以理解这就是共享库。当有10个进程都使用了 redis(假如使用1M的内存),这10个进程的 RSS 占用中都包含了 1M 的 redis 共享库内存占用,所以就可能出现前言中提到的现象。所以使用 RSS 查看进程的实际占用是不合适的!

USS - Unique Set Size 进程独自占用的物理内存

对应上图 - 区域2。

通常建议大家使用 USS 来观察进程实际占用的大小。因为不包含共享库占用的内存,是描述进程真正意义上的内存占用情况。比如你申请了一个数组、new了一个对象,USS 反映了这些内存占用。

当一个进程被销毁释放了,这部分内存是归还给操作系统的。比如你在验证一个进程是否发生内存泄漏, USS 是你最好的观察指标。

可以使用 smem 命令查看,下面小节也会举一个实际的例子帮助大家理解。

PSS - Proportional Set Size 实际使用的物理内存

对应上图 - 区域2 + 区域4(按进程数等分区域3)。

比如有2个进程,那一个进程内存占用 PSS 就等RSS + (共享内存 / 2),也是比较好的进程内存占用的分析指标。当一个进程被销毁释放之后,其占用的共享库那部分比例的PSS 将会再次按比例等分分配给其他使用该共享库的进程。

实例

上面描诉了内存指标的种类和对其详细的说明。但是毕竟是文字,还是比较空洞的,接下来我用一个实际的例子生动的展示。

最近在了解 swoole, 下面用 swoole table 共享内存的方式向大家解释为什么使用 RSS 分析内存占用是不恰当的。先看代码:

<?php

// 创建一个 swoole_table 
$table = new swoole_table(1024);
$table->column('data', swoole_table::TYPE_STRING, 1024);
$table->create();

$serv = new swoole_server('0.0.0.0', 9500, SWOOLE_PROCESS);

// worker_num 开启两个进程
// dispatch_mode 开启轮循模式,收到会轮循分配给每一个worker进程。
$serv->set([
   'worker_num'     => 2, 
   'dispatch_mode'  => 1
]);
$serv->table = $table;

$serv->on('receive', function ($serv, $fd, $reactorId, $data) &#123;

    $key = 'smem';
    $cmd = trim($data);

    switch ($cmd) &#123;

        // 读取 用于验证共享内存
        case 'get' :
            $data = $serv->table->get($key);
            $serv->send($fd, json_encode($data));
            break;
        
        // 设置 swoole_table 内容
        case 'set':
            $serv->table->set($key, [
                'data'  => str_repeat("smem", 512)
            ]);
            $serv->send($fd, "OK\r\n");
            break;
        default:
            break;
    &#125;
&#125;);

$serv->start();

第一步:在终端下执行上面代码,可以看到如下的进程。
在这里插入图片描述
可以看到 1331 和 1332 是主进程和管理进程,不在我们此次讨论范围内。主要看 1335 和1336 两个work进程,可以看到这个时候两个进程的 RSS 都是一样的 10140。

第二步:使用 telnet 请求一个进程,让它往 swoole table 中增加点东西。如下图:
在这里插入图片描述
忽略我忘记怎么退出,输了几个乱码…

第三步:现在在看两个work进程的 RSS情况,如下图:
在这里插入图片描述
可以明显看到进程 1335 明显变大了,但是 1336还是没变。

第四步:在使用 telnet 让 1336 使用 swoole table 的共享内存,因为开启轮循模式,所以这次请求是 1336 来承接。
在这里插入图片描述

第五步:再来观察两个 work 进程的 RSS情况,见下图:
在这里插入图片描述
是不是 1336 也涨了,所以得出 RSS 并不能反映内存占用的实际情况。所以我们需要使用 USS 来分析,这时候可以使用 smem 这个工具来查看,请自行安装。

第六步:使用 smem 分析进程内存占用。
在这里插入图片描述
很直观的看到进程实际使用的内存占用 USS 相比 RSS 是比较少的,也是最能反映内存占用的真实情况。

总结

本文分析了内存占用的指标种类,分别是VSS、RSS、PSS、USS 并对其进行了详细的描述。文中也用一个实际的例子得到一个结论:RSS 并不能直观反映进程占用的真实情况。

需要反映进程占用的真实情况需要观察 USS 指标,这时候需要借助工具 smem 的使用。