如何查看 Java 内存占用?(top, ps, jstat 等)
当发现系统内存紧张时,第一步是定位是哪个 Java 进程占用了内存。

使用 top 或 htop (快速概览)
top 是最常用的系统监控工具。
# 按内存使用率排序 (M) top -M
或者使用更友好的 htop (如果已安装):
htop # 然后按 F6 选择 "MEM %" 列进行排序
你会看到类似下面的输出:
PID USER PRI NI VIRT RES SHR S CPU% MEM% TIME+ COMMAND
12345 java 20 0 4.5g 1.8g 256m R 10.2 18.5 120:34 java
这里的关键列是:

- VIRT (Virtual Memory - 虚拟内存): 进程总共能访问的虚拟内存大小,它包括了实际使用的内存 (
RES)、已分配但未使用的内存、共享库、代码段、数据段等。这个值通常比实际物理占用大很多,不能直接用来判断内存压力。 - RES (Resident Memory - 常驻内存): 这是进程当前实际占用的物理内存大小,这是衡量一个进程对物理内存消耗最直接的指标。我们通常关心的是这个值。
- SHR (Shared Memory - 共享内存): 进程与其他进程共享的内存大小(共享的代码段)。
在 top/htop 中,关注 RES 列,它代表了 Java 进程正在“吃掉”你多少物理内存。
使用 ps (获取精确信息)
ps 命令可以提供更精确的内存信息。
# -o 指定输出格式 # rss: Resident Set Size, 以 KB 为单位 # vsz: Virtual Memory Size, 以 KB 为单位 ps -o pid,ppid,user,cmd,rss,vsz -C java
使用 jstat (JVM 内部视图) - 最关键的工具
jstat 是 JDK 自带的工具,可以实时监控 JVM 内部的内存分配情况,这是理解 Java 内存占用的核心。
# jstat -gc <pid> <interval_in_ms> <count> # 示例:每 1 秒查看一次 PID 为 12345 的 JVM 的 GC 情况,共 10 次 jstat -gc 12345 1000 10
输出示例:

S0C S1C ... EC OC ... M CCSMSC ... YGC FGC FGCT GCT ...
512.0 512.0 ... 4096.0 8192.0 ... 45.59 4.1968 ... 150 2 0.150 1.200
512.0 512.0 ... 4096.0 8192.0 ... 45.59 4.1968 ... 150 2 0.150 1.200
...
关键列解释:
- 堆内存区域:
S0C/S1C: Survivor 0 / 1 区的 容量。EC: Eden 区的 容量。OC: Old 区的 容量。OU: Old 区的 已使用量。这个值如果持续接近OC,说明 Old 区即将满,可能会触发 Full GC。
- 元空间:
MC: Metaspace 的 容量。MU: Metaspace 的 已使用量。
- GC 统计:
YGC: Young GC 次数。FGC: Full GC 次数。GCT: GC 总耗时。FGCT: Full GC 总耗时。FGCT时间持续增长,说明 Full GC 频繁发生,这是性能的杀手。
使用 jmap (生成堆转储)
当怀疑内存泄漏时,jmap 是你的利器,它可以生成 Java 堆的快照文件(.hprof),然后用工具(如 Eclipse MAT, VisualVM)分析,找出是哪些对象占用了内存。
# 生成堆转储文件 jmap -dump:format=b,file=heapdump.hprof <pid>
注意: 生成堆转dump是一个重量级操作,会使 JVM 停顿,请谨慎在生产环境使用,最好在业务低峰期执行。
为什么 Java 内存占用高?
理解了如何查看,接下来就是分析原因,Java 内存占用高通常由以下几个原因造成:
正常的内存使用(最常见)
- 应用负载高: 服务器处理的请求多了,需要创建更多的对象来处理请求,内存自然就高了,这是最正常的情况。
- 缓存: 很多应用会使用缓存(如 Guava Cache, Caffeine, Redis 客户端缓存)来提升性能,缓存的数据会占用大量堆内存,如果缓存配置过大,就会导致内存占用高。
JVM 自身开销
- 元空间: 在 Java 8 及以后,元空间取代了永久代,元空间使用的是本地内存,而不是堆内存,如果加载的类非常多(使用了大量反射、动态代理、或大量第三方库),元空间可能会占用很大。
- 检查: 使用
jstat -gcutil <pid>查看MU(Metaspace Used) 和MC(Metaspace Capacity)。
- 检查: 使用
- 线程栈: 每个线程都有一个独立的栈内存,默认情况下,每个线程栈大小为 1MB (
-Xss1m),如果你的应用线程数很多(几百个),这部分加起来也会很可观。- 计算: 线程数 * 线程栈大小。
内存泄漏(严重问题)
内存泄漏指程序中已不再使用的对象,由于仍然被其他对象(例如静态集合、线程等)引用,导致 GC 无法回收它们,这些对象会一直堆积在内存中,最终导致 OutOfMemoryError。
- 常见场景:
- 静态集合: 最经典的泄漏,一个静态的
Map或List被用来缓存数据,但忘记在不需要的时候清理 (map.clear())。 - 未关闭的资源: 数据库连接、文件流、网络连接等,使用后没有调用
close()方法,可能导致无法回收。 - 监听器/回调: 注册了监听器或回调,但在不需要时没有取消注册。
- ThreadLocal:
ThreadLocal变量在线程池中使用,且没有调用remove()方法,那么线程在销毁后,其持有的ThreadLocal值仍然无法被回收,造成内存泄漏。
- 静态集合: 最经典的泄漏,一个静态的
JVM 配置不当
- 堆内存过大:
-Xmx(最大堆内存) 设置得过大,会导致 JVM 在启动时就吃掉大量物理内存,给操作系统其他程序带来压力,过大的堆也会导致 GC 暂停时间变长。 - 堆内存过小:
-Xms(初始堆内存) 和-Xmx(最大堆内存) 设置得过小,应用在高峰期会频繁触发 GC,甚至因为内存不足而崩溃。
如何优化 Java 内存占用?
优化策略需要根据原因来定。
优化应用代码(治本)
- 修复内存泄漏:
- 使用分析工具: 这是最有效的方法,使用
jmap生成堆转储,然后用 Eclipse MAT 或 VisualVM 分析。 - MAT 分析: MAT 会生成一个 "Leak Suspects Report",通常能直接指出可疑的内存泄漏点,它会告诉你哪些对象占用了最多内存,并展示它们的引用链。
- 代码审查: 检查是否有静态集合未清理、资源未关闭、
ThreadLocal未移除等情况。
- 使用分析工具: 这是最有效的方法,使用
- 优化缓存:
- 设置合理大小: 为缓存设置最大容量 (
maximumSize)。 - 设置过期时间: 设置写入后或最后访问后的过期时间 (
expireAfterWrite,expireAfterAccess)。 - 使用弱引用: 对于可以随时被回收的缓存对象,考虑使用
WeakReference。
- 设置合理大小: 为缓存设置最大容量 (
- 减少对象创建: 重用对象,使用对象池(谨慎使用,避免引入新问题),避免在循环中创建不必要的临时对象。
优化 JVM 参数(治标)
- 合理设置堆内存:
- 经验法则: 将
-Xmx设置为可用物理内存的 50% ~ 75%,但要留足内存给操作系统和其他应用程序。 - 设置
-Xms = -Xmx: 避免堆在运行时动态扩容带来的性能抖动。 - 示例: 服务器有 16GB 内存,其他程序预计用 4GB,可以设置
-Xms8g -Xmx8g。
- 经验法则: 将
- 调整 GC 策略:
- 低延迟应用: 使用 G1 GC (
-XX:+UseG1GC),它是 JDK 9+ 的默认 GC,在吞吐量和延迟之间取得了很好的平衡。 - 高吞吐量应用: 使用 Parallel GC (
-XX:+UseParallelGC),它侧重于最大化应用吞吐量,但 GC 暂停时间可能较长。 - 避免 Full GC: Full GC 是导致长暂停的主要原因,通过合理调整 G1 GC 的参数(如
-XX:InitiatingHeapOccupancyPercent)来尽可能避免它。
- 低延迟应用: 使用 G1 GC (
- 调整元空间:
- 如果发现元空间频繁触发 GC 或不足,可以适当增加
-XX:MaxMetaspaceSize的值。 - 如果元空间占用一直很低,可以适当减小
-XX:MaxMetaspaceSize以节省内存。
- 如果发现元空间频繁触发 GC 或不足,可以适当增加
- 调整线程栈:
- 如果线程数非常多,可以适当减小
-Xss的值(-Xss256k),但要确保不会导致StackOverflowError。
- 如果线程数非常多,可以适当减小
监控和告警
- 建立监控体系: 使用 Prometheus + Grafana 或其他 APM 工具(如 SkyWalking, Pinpoint)对 JVM 的各项指标(堆内存使用率、GC 次数/耗时、元空间使用率、线程数)进行持续监控。
- 设置告警: 当内存使用率超过某个阈值(如 85%),或 Full GC 频率突然增加时,触发告警,以便及时介入处理。
总结排查流程
- 发现:
top/htop发现 Java 进程RES内存很高。 - 定位:
ps确认进程信息。 - 分析:
- 使用
jstat -gc <pid>持续观察,是OU(Old 区) 快满了?还是MU(元空间) 高?还是 GC 频繁? - GC 不频繁,内存只是持续缓慢增长,且没有下降,高度怀疑内存泄漏。
- GC 非常频繁且耗时,可能是配置不当或应用短时间创建了海量对象。
- 使用
- 诊断:
- 怀疑内存泄漏: 立即使用
jmap生成堆转储,用 MAT 分析。 - 怀疑配置不当: 检查
-Xmx,-Xms, GC 策略等参数是否合理。
- 怀疑内存泄漏: 立即使用
- 优化与验证:
- 根据分析结果,修改代码或调整 JVM 参数。
- 部署到测试环境验证效果。
- 部署到生产环境,并持续监控。
希望这份详细的指南能帮助你有效地排查和解决 Linux 系统下 Java 内存占用的问题!
