凌晨1点40分,监控系统把电话打到了我这里,一台Kylin V10服务器内存占用超过92%,业务系统已经明显卡顿。我登录进去第一件事就是看进程列表,结果发现罪魁祸首不是业务进程,而是Xorg进程,RSS已经涨到7.3GB,而且还在缓慢爬升。
这种场景我相信不少运维兄弟都遇到过。Xorg这个名字在服务器上看起来就是"图形显示服务",按道理不该吃这么多内存,但它在Kylin V10上确实能给你吃到让人怀疑人生的程度。这篇文章我尽量完整地复盘一下当时的定位过程、根因分析,以及最后采用的几种有效手段,希望能给正在被同样问题困扰的人一点参考。
1. 告警当晚的第一现场:确认Xorg不是误报
这台服务器的情况先说清楚。它是某内网项目里的业务服务器,装的是Kylin V10服务器版,32GB内存,跑了一套B/S架构的生产系统。因为现场坐席需要远程接入操作,这台机器上额外启用了图形会话和远程桌面服务,说白了就是这台服务器既要在机房跑业务,又要充当远程工作台的"画面输出端"。
当天晚上的现象很典型:内存从平时的40%左右一路爬到92%,系统开始频繁使用swap,业务响应明显变慢。我习惯先看一遍整体内存状况再下结论。
free -h结果没啥悬念,Mem几乎被吃满,Swap用了好几个G。紧接着按内存占用排序找进程:
ps aux --sort=-%mem | head -20排第一的就是Xorg,%MEM那一列轻轻松松上了40%。这里要先明确一个关键点:Xorg吃掉的是RSS常驻内存,不是缓存。Linux系统对page cache的占用是很正常的,看到内存高先分清是cache还是进程RSS,别把正常缓存当成泄漏。我当时连续观察了半小时,Xorg的RSS曲线不是波动,是单边上涨,基本可以判定这属于真实占用。
我还做了一个动作来确认它和业务流量之间的关系:用远程桌面客户端去操作一次图形界面,刷新页面、打开关闭几个窗口,然后回来观察Xorg内存。结果很明显,每次图形交互动作之后,Xorg内存就会涨一块,涨幅从十几MB到上百MB不等,但几乎不会回落。这个"只涨不跌"的特性基本排除了瞬时峰值,指向的是一个资源泄漏型的累积问题。
再补一个观察:系统内存报警的时间点,往往是远程桌面在线用户最多、交互最频繁的时段,比如白天工作高峰。到了夜间无人操作时,Xorg内存虽然涨得慢,但同样不会自动释放。这意味着问题一直在持续,只是快慢不同,如果不处理,迟早还会触发告警。
2. 从经典三连到xrestop:一步步锁定内存去向
定位Xorg内存高,光看ps不够,我当时按下面的顺序往下排,每一步都在缩小范围。
2.1 先用/proc确认虚拟内存和常驻内存的构成
找到Xorg的PID后,直接看进程内存详情:
cat /proc/<Xorg_PID>/status | grep -E 'VmSize|VmRSS|RssAnon|RssFile'我这边看到的关键数据是:
| 指标 | 数值 | 含义 |
|---|---|---|
| VmSize | 约为9GB | 进程地址空间大小,含未实际分配部分 |
| VmRSS | 7.3GB | 实际驻留在物理内存中的部分 |
| RssAnon | 6.8GB | 私有匿名内存,通常是堆上分配 |
| RssFile | 400MB左右 | 映射到文件的内存,一般是库和资源 |
重点在于RssAnon占了绝对大头。匿名内存就是Xorg自己malloc或通过驱动接口分配出来的,不属于文件映射,这种内存在IO活动结束后很难被系统回收,必须靠进程自己释放。
2.2 pmap看映射明细,找到大块anon
接下来用pmap拆Xorg的内存映射:
pmap -d <Xorg_PID> | sort -k 2 -n -r | head -30结果里最扎眼的就是一堆[ anon ]块,其中有好几个块的KByte数量级在1GB多,这通常不是正常窗体控件的体量。匿名映射块巨大,说明Xorg内部有大量图形缓冲区没有释放,或者释放机制没有被触发。
这里我要说一个容易踩坑的细节:pmap看到的[ anon ]很难直接对应到具体功能,普通人看到一堆巨型anon块就懵了。不要在这个阶段浪费时间猜,继续用工具缩小范围。
2.3 xrestop看X协议层面的资源占用
Xorg本质上是管理X客户端资源的服务端。它内存涨,往往是资源没释放,这些资源在X协议层面有对应的对象,比如pixmap、window、gc。用xrestop能直接看到每个客户端在X服务端上占了多少资源。
xrestop输出结果里,内存占用最高的几个条目对应的是远程桌面会话相关的客户端,其中Pixmap数量一直在增加。每创建一个pixmap,Xorg就要分配对应的显存/内存对象,如果客户端不释放,这些对象就一直挂在服务端。这个观察把问题从"Xorg内存高"精确到了"pixmap资源持续积累"。
2.4 重启图形会话做对照实验
为了确认是"配置/驱动问题"还是"远程客户端行为问题",我做了一次对照实验。在业务低峰期重启Xorg图形会话(等价于注销远程桌面再重新登录),重启完成后Xorg内存回落到250MB左右,一切正常。但接下来用远程桌面客户端连续操作半小时,内存又开始上涨,幅度在800MB以上。
这个结果说明两件事:
- Xorg本身启动时并不异常,进程被拉大是运行过程中被"喂"出来的。
- 远程桌面的交互动作会不断刺激Xorg分配图形资源,而释放机制没有及时工作。
到这一步,问题根因的大方向已经很清晰了:Xorg在高频图形交互场景下,pixmap等资源回收不及时,叠加图形合成/渲染路径设置不理想,最终导致内存单边上涨。
3. 服务器上的Xorg为什么这么"能吃"
既然锁定了资源方向,就要进一步问"为什么"。我把Kylin V10默认图形栈的构成梳理了一遍,发现它和传统机房服务器的需求存在明显错位。
3.1 服务器需要图形界面,但用的是桌面级方案
Kylin V10服务器版在默认装桌面组件时,集成的是完整的X Window系统栈,包含Xorg服务、图形加速模块、扩展组件。它在设计上更多考虑的是桌面场景:窗口管理、动画合成、多屏切换。服务器远程接入场景下,这些能力并不是核心需求,但组件一旦被加载,它就有各自的资源消耗路径。
最典型的就是Composite扩展。这个扩展负责窗口合成,允许每个窗口在独立的缓冲区里渲染,再统一合成输出。桌面环境开它没问题,视觉效果很好。但在服务器上,远程桌面客户端高频刷新画面,Xorg会为每个窗口、每个被覆盖的区域维护额外的pixmap和合成缓冲区。操作越多,累积越高。更麻烦的是,有些窗口状态变化后,旧缓冲区的回收并不一定及时,长时间运行就是内存黑洞。
3.2 渲染加速方式和硬件的错位
另一个方向是驱动和加速模式。Kylin V10的服务器硬件一般用的是板载显示控制器,很多还是老旧的VGA或通用显示芯片,GPU能力很弱。Xorg默认配置下会选择某个通用驱动,但加速模式如果设置不合适,渲染请求会退化成软件渲染,由CPU生成位图数据,再传到缓冲区里,这同样会造成大量匿名内存分配。
我在排查时查看了Xorg的实际日志和加载模块:
grep -i "(EE)\|(WW)" /var/log/Xorg.0.log没有明显错误,但也没看到加速模块被启用的信息。换句话说,它很可能一直在软件渲染模式下工作,内存消耗自然压不下来。这种情况在KVM/QEMU虚拟化的环境里更常见——虚拟机里的虚拟显卡如果只支持标准VGA,Xorg会退回通用框架,性能差加上内存管理机制没有优化,就会出这种"越用越肥"的问题。
3.3 X客户端连接与资源的生命周期
第三个因素是连接Xorg的客户端会话本身。远程桌面协议会建立X客户端连接,这个会话的窗口、光标、颜色映射等对象都挂在Xorg服务端。如果客户端异常断开、重连频繁,或者应用层没有正确释放图形对象,Xorg端就会遗留对象。遗留对象包含位图数据、上下文对象和协议缓冲区,累计上来不可小觑。
我当时的判断就是:多因素叠加。默认图形栈带着桌面级扩展,硬件和驱动有点"带不动",远程客户端会话操作频繁,Xorg的资源回收机制又不给力。四者合在一起,就把内存撑爆了。搞清楚了这层,后面的修复方向就清晰了,不是只靠"重启图形服务"做临时止血,而是要改配置、调驱动、加限制一起上。
4. 动手修复:四招把Xorg的"饭量"压下来
修复顺序我建议从成本低、影响小的开始,一项一项来,每改一项就观察一段时间,别一口气全上,不然出了问题都不知道是哪步改坏的。
4.1 第一招:关闭Composite扩展,减少合成缓冲区
确认Composite扩展是否开启,可以用:
xdpyinfo | grep -i composite如果输出里有Composite,说明扩展是启用的。对服务器远程场景,这个扩展基本是负资产。我在/etc/X11/xorg.conf.d/10-server-tuning.conf里新建了一段配置:
Section "Extensions" Option "Composite" "Disable" EndSection注意这里不是关掉整个Xorg的渲染,只是不允许窗口合成器使用独立缓冲层。远程桌面操作时,画面更新会直接走基本绘制路径,不再为每个窗口维护额外的合成缓存。
改完配置后重启图形会话,观察半天,内存增长速度明显放缓,从之前半小时涨800MB降到了半天涨几百MB。对坐席操作来说,视觉上基本无感,远程桌面本来就是逐帧传输,不需要本地窗口透明/阴影/动画效果。如果你的业务环境确实需要窗口特效,这一招要慎重,但对大多数服务器场景来说,关掉是对的。
4.2 第二招:调整驱动加速选项,关掉不合适的加速路径
Xorg的驱动配置在Device段落里。我实际用的配置如下:
Section "Device" Identifier "Card0" Driver "modesetting" Option "AccelMethod" "none" EndSection这里的思路是绕开不成熟的硬件加速路径,让Xorg走最朴素可靠的软件绘制模式,反而能降低缓冲区的异常堆积。对服务器来说,渲染性能不是关键指标,内存稳定才是。
注意不同的硬件平台可能有不同选择。如果你在虚拟化环境里用的是虚拟显卡驱动,以QXL虚拟显卡为例,走"modesetting"配合默认加速通常是稳妥的;如果你在物理机上发现modesetting有异常,可以检查是否有厂商驱动模块更匹配。Kylin V10里自带的驱动框架比较全,具体能让哪种驱动加载成功,要以Xorg.0.log为准。我的做法是先不指定Driver,只看默认加载了什么,再覆盖调整。
改完驱动配置后,务必再检查一次日志:
grep -i "modesetting\|GLX" /var/log/Xorg.0.log确认配置生效。GLX如果远程桌面用不上,也可以考虑禁用GLX扩展,进一步减少显存/内存映射路径,但不是必须项。
4.3 第三招:用systemd限制Xorg的内存上限
前两招是"减少浪费",第三招是"强行兜底"。Xorg一般是通过display manager启动的服务(比如lightdm),它在systemd里是一个scope。我采用的是更直接的方案:在系统服务层级上限制内存。
找到Xorg对应的服务或会话作用域后,在/etc/systemd/system/xorg-oom-guard.service里做了一个套接字包装,不对,我这里其实是直接在scope上加了限制,正规做法是找到display-manager的systemd服务:
systemctl status lightdm然后编辑/etc/systemd/system/lightdm.service.d/memory-limit.conf:
[Service] MemoryAccounting=yes MemoryLimit=3G对于较新的systemd版本,也可以配合MemoryMax使用。设置之后重启lightdm服务。
这里有一个坑必须提醒:MemoryLimit设得太低,Xorg到上限后会被systemd杀掉,导致远程桌面直接断开,正在操作的坐席会丢失画面。所以一定要结合业务实际情况来定,我当时设的是3G,足够正常操作用,但不会让它吃到失控。
如果怕systemd版本或display manager差异导致MemoryLimit不生效,可以用cgroup v2统一限制:
mkdir -p /sys/fs/cgroup/xorg-limit echo 3G > /sys/fs/cgroup/xorg-limit/memory.max echo <Xorg_PID> > /sys/fs/cgroup/xorg-limit/cgroup.procs不过这种动态设置重启后不会保留,只适合应急。长期还是建议用systemd unit drop-in的方式。
4.4 第四招:定期重置图形会话,给资源一个"洗白"入口
即使做完前三步,Xorg长时间运行后内存还是会缓慢增长,只是增长斜率慢了几个量级。针对这种"释放不干净"的老毛病,最稳妥的兜底手段是计划性重启图形会话。
我写了一个带告警和自动执行的小脚本,逻辑比较简单:检查Xorg的RSS,如果超过1.5GB并持续3分钟,就记录当前会话信息,并在业务低峰自动重启图形服务。
#!/bin/bash THRESHOLD_KB=1536000 INTERVAL=180 PID=$(pgrep -x Xorg | head -1) if [ -z "$PID" ]; then exit 0 fi RSS_KB=$(awk '/VmRSS/{print $2}' /proc/$PID/status 2>/dev/null) if [ -z "$RSS_KB" ]; then exit 0 fi if [ "$RSS_KB" -gt "$THRESHOLD_KB" ]; then sleep $INTERVAL RSS_KB2=$(awk '/VmRSS/{print $2}' /proc/$PID/status 2>/dev/null) if [ "$RSS_KB2" -gt "$THRESHOLD_KB" ]; then echo "$(date '+%F %T') Xorg RSS exceed threshold: ${RSS_KB2}KB, restarting" >> /var/log/xorg-mem-guard.log systemctl restart lightdm fi fi配合crontab每10分钟执行一次。重启动作要避开业务高峰,脚本里可以再做一层时间窗口判断,比如只在22点到6点之间执行。这个脚本虽然糙,但在真实生产环境里非常管用,等于是给Xorg加了一层"人工GC",内存泄漏再顽固,也扛不住定期重置。
5. 三十六小时实测数据:调优效果和监控预警
改完配置不是结束,验证结果才是重点。我调完方案之后跑了三十六小时,数据如下:
| 时间点 | Xorg RSS | 说明 |
|---|---|---|
| 修复前(持续) | 稳定上涨,峰值7.3GB | 每次交互涨缓不落 |
| 修复后0小时 | 约300MB | 重启会话后基线 |
| 修复后12小时 | 约520MB | 有白天远程操作 |
| 修复后24小时 | 约680MB | 操作高峰叠加 |
| 修复后36小时 | 约790MB | 缓慢增长但仍可控 |
注意,内存还是会有小幅增长,这很正常。真正有效的判断标准是"斜率",从之前半小时涨800MB变成三十六小时涨不到500MB,这个斜率已经完全在可控范围内,再配合自动限制和定期重置,就不需要半夜爬起来处理告警了。
监控告警方面,我在脚本基础上加了通知动作,当Xorg RSS超过阈值并触发重启时,通过Webhook抛出重启消息,这样第二天早上只需要看日志和通知,不用等业务侧投诉。监控项里建议把Xorg进程的RSS、整体内存使用率、Swap使用率三块放到一起看,因为一旦Xorg失控,前两个必然联动异常。
5.1 最容易踩的几个坑
第一坑:改配置不备份。我前两次改xorg.conf.d时图省事,直接覆盖,结果有一版配置导致图形服务起不来,远程桌面直接黑屏,坐席电话马上就来。后面所有配置都先在文件名后加.bak,并且每次改完都先在本地用systemctl restart lightdm验证一遍再离开。这个好习惯能帮你省掉无数麻烦。
第二坑:MemoryLimit设置过低引发循环重启。我最初为了保险把内存限制设在1G,结果Xorg在常规操作时就触顶,systemd直接杀掉又拉起,远程桌面反复掉线,业务侧体验极差。1.5G到3G是我试下来比较合理的一个区间,具体要看你们实际业务跑多少个图形客户端,在正常场景下把峰值放进去,再留30%余量就够了。
第三坑:关闭Composite后远程桌面显示异常。如果你发现关闭合成扩展之后,远程桌面画面出现拖影、残影或者区域刷新异常,别急着改回去,先看看是不是驱动也改了。我在某台硬件上同时改了Composite和AccelMethod,结果远程桌面出现刷新残影,后来单独把驱动从通用驱动切到modesetting就好了。这说明每个配置项之间是关联的,改完多测几种操作,别只看内存降了就以为完事。
第四坑:直接kill Xorg进程而不是重启display manager。Xorg被kill后,如果display manager没有自动拉起它,整台服务器的图形栈就瘫在那。规范做法永远是systemctl restart lightdm,让它把整个会话栈连同客户端的连接一起重建,这样最干净,也最不容易留下半死状态的图形进程。
5.2 我后来还发现了什么
在跟进这个问题的过程中,我顺手做了一次图形栈依赖梳理,发现Kylin V10的Xorg里还挂着一些跟字体、光标、输入法相关的模块。如果你们服务器的远程桌面主要用于特定业务系统,且这个业务系统用不到复杂的输入法或字体渲染,可以进一步精简这些模块,系统库里卸载对应软包,也能降低Xorg的启动负载。不过这属于锦上添花,不建议在问题没解决之前动它,否则排查又多了变量。
另外一个方向是如果远程桌面主要用在上层业务展示,可以考虑直接用浏览器或Web桌面方案替代传统Xorg远程,少一层X协议传输和渲染转换,内存压力会更小。但这种方式涉及客户端使用习惯和适配改造,落地成本高,适合做长期优化方向,不解决眼前问题。我当时没有动这个念头,只是记在问题单里了。
6. 最后说几句实在话
如果你遇到的Xorg内存高问题和我这边现象不完全一样,我的建议是先别急着改配置,把"涨速"和"触发条件"摸清楚。有些环境是瞬间冲高再回落,有些是无操作也在涨,还有些是特定分辨率/多屏配置导致的基础内存过高,不同表现对应的处理方向完全不同。这次我用的四招对"只涨不跌、交互加剧"的泄漏型问题特别有效,但如果你那边是瞬时峰值问题,可能要从分辨率、多显示器配置或者客户端数量入手。
还有一点是,在Kylin V10这类系统上做问题处理,一定要养成查日志的习惯。Xorg的日志、systemd的日志、display manager的日志,三条线一起看,很多看起来诡异的问题其实都写在里面。我之前因为急着看内存,忽略过Xorg日志里的驱动加载失败记录,绕了很大一圈,后来回头一看,问题早就摆在日志里了。
这次的经验如果浓缩成一句话,那就是:服务器上的图形服务,必须当做一个需要持续治理的"业务进程"来管理,而不是装完就不管。配置要精简、内存要设限、行为要监控、场景要兜底。做到这四点,Xorg再娇气也翻不出什么大浪。