答案是:free命令的“used”包含应用程序内存和系统缓存,而ps的rss仅统计进程实际占用的物理内存,统计范围不同导致数值差异。

别光盯着 free 里那个“used”数字,也别只用 ps 看个 %MEM 就下结论。Java 进程的内存占用分好几块:JVM 堆、非堆(Metaspace、CodeCache、直接内存)、本地线程栈、还有内核缓存。要真正看清分布,得把 free 和 ps 的输出交叉比对,再结合进程内部视角。
free -h 看清系统级真实可用内存
free -h 是起点,但关键字段不是 used,而是 available。这个值代表系统当前能立即分配给新进程的物理内存总量,已自动扣除了内核缓存可回收部分。如果 available 还剩 1G 以上,说明物理内存并不紧张;如果持续低于 200MB,才真该警惕。同时注意 buff/cache 大小——它高是常态,Linux 会主动利用空闲内存做文件缓存,不等于被“占死”。swap usage 如果非零且持续增长,说明物理内存确实不够用了,内核开始换页。
ps aux 找出 Java 进程的 RSS 和 VSZ
运行 ps aux --sort=-%mem | head -10,重点关注 Java 进程的两列:
- RSS(Resident Set Size):进程当前实际占用的物理内存 KB 数,包含 JVM 堆、非堆、线程栈、JIT 编译代码等所有驻留内存。这是最贴近“真实吃掉多少 RAM”的指标。
- VSZ(Virtual Memory Size):进程申请的全部虚拟地址空间大小,含 mmap 映射的 jar 包、共享库、未分配的堆预留空间等。它通常远大于 RSS,不能直接反映物理内存压力。
比如一个 Java 进程 RSS 是 1800MB,而你给 JVM 设置了 -Xmx2g,说明堆基本跑满了;但如果 RSS 达到 3.5G,就很可能有堆外内存泄漏(如 DirectByteBuffer、JNI 调用、Log4j2 的异步队列)或线程数暴增。
Java JDK 25 来自 OpenJDK 官方归档,版本为 JDK 25,本条下载地址已指向官方 Windows x64 zip 安装包直链,适合调试旧项目或兼容旧版 Java 运行环境。
ps -p PID -o 查看单个进程的精细内存项
拿到具体 Java 进程 PID 后,用 ps -p $PID -o pid,rss,vsz,%mem,cmd 定向查看。更进一步,读取 /proc/$PID/status 文件里的几个关键行:
- VmRSS:和 ps 的 RSS 一致,单位 KB
- VmSize:和 ps 的 VSZ 一致
- VmData:进程数据段大小(含 JVM 堆的 committed 部分)
- VmStk:所有线程栈总和,线程数 × 栈大小(-Xss 默认 1M)
若 VmStk 占比异常高(比如超 500MB),检查是否创建了过多线程;若 VmData 远小于 RSS,说明大量内存来自 DirectMemory 或 native code。
结合 jstat 和 jmap 定位 JVM 内部来源
仅靠系统命令只能看到“外面”,要进 JVM 里看:
- 用
jstat -gcutil $PID 1000 3观察老年代使用率和 GC 频次。如果老年代使用率长期 >75% 且频繁 Full GC,堆内存就是瓶颈。 - 用
jmap -histo:live $PID | sort -k3 -nr | head -20查看存活对象实例数和占用字节数,快速定位大对象或集合类泄漏。 - 用
jcmd $PID VM.native_memory summary scale=MB查看 Metaspace、Compressed Class Space、Direct Buffer 等非堆区域使用量,确认是否是它们撑大了 RSS。
这样一层层剥开,就能区分清楚:是堆内存真爆了?还是 DirectByteBuffer 没释放?或是线程栈吃光了内存?又或者只是 Linux 缓存占了显示空间?
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










