杰瑞科技汇

Linux Java进程内存占用高怎么办?

Java 内存 vs. 物理内存

必须理解一个关键点:Java 进程的内存占用不等于我们在 Linux 命令中看到的 RESRSS 内存

Linux Java进程内存占用高怎么办?-图1
(图片来源网络,侵删)
  1. Java 堆内存:这是 Java 应用程序最核心的内存区域,用于存放对象实例和数组,我们通常说的 -Xmx (最大堆内存) 和 -Xms (初始堆内存) 就是指这里。
  2. 非堆内存:这部分包含了 JVM 自身运行所需的内存,以及应用程序代码本身(如类信息、JIT 编译后的代码)等,主要包括:
    • 方法区:存储类信息、常量、静态变量等。
    • 虚拟机栈:每个线程私有的,存储局部变量、操作数栈等。
    • 本地方法栈:为 Native 方法服务。
    • JIT Code Cache:存放 JIT (Just-In-Time) 编译器编译后的本地代码。
    • Direct Memory (直接内存):NIO 使用,不受 JVM 堆大小限制,但受系统总内存限制。
    • Thread Stacks:每个 Java 线程的栈空间。

当我们在 Linux 中查看一个 Java 进程时,RES (常驻内存) 代表的是该进程当前实际物理内存占用,它包括了:

  • Java 堆
  • 非堆内存
  • JIT 编译的代码
  • 加载的共享库(如 libjvm.so
  • 线程栈
  • 其他一些进程开销

RES 内存是 Java 内部所有内存使用情况在操作系统层面的一个“总和”体现。


查看和分析 Java 内存的方法

我们将从简单到深入,介绍几种主流方法。

使用 jcmd (JDK 内置工具,首选)

jcmd 是 JDK 自带的强大工具,无需安装,功能比 jmap 更丰富,是首选的诊断工具。

Linux Java进程内存占用高怎么办?-图2
(图片来源网络,侵删)

列出所有 Java 进程

jcmd

输出示例:

12345 sun.tools.jcmd.JCmd
67890 com.example.MyApplication
...

这里的 1234567890 Java 进程的 PID。

查看内存摘要

对目标 PID (67890) 执行 GC.heap_info

jcmd 67890 GC.heap_info

输出示例:

GC Heap Information:
PSYoungGen      total 9216K, used 5120K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
  eden space 8192K, 62% used [0x00000000ff600000, 0x00000000ffad8000, 0x00000000ffec0000)
  from space 1024K, 0% used [0x00000000ffec0000, 0x00000000ffec0000, 0x00000000fff00000)
  to   space 1024K, 0% used [0x00000000fff00000, 0x00000000fff00000, 0x0000000100000000)
ParOldGen       total 20480K, used 10240K [0x00000000fc000000, 0x00000000fe600000, 0x00000000ff600000)
  object space 20480K, 50% used [0x00000000fc000000, 0x00000000fd300000, 0x00000000fe600000)
Metaspace       used 2560K, capacity 4480K, committed 4864K, reserved 1056768K
  class space    used 288K, capacity 384K, committed 512K, reserved 1048576K
  • PSYoungGen: 新生代,包含 Eden 和两个 Survivor 区。
  • ParOldGen: 老年代。
  • Metaspace: 元空间,替代了之前的永久代。

查看详细的内存使用情况

jcmdGC.class_histogram 命令类似于 jmap -histo,可以查看类实例的内存占用。

jcmd 67890 GC.class_histogram

输出会按类名和实例数量、内存占用大小排序,对定位内存中的大对象非常有帮助。

生成堆转储文件

这是分析内存泄漏最关键的一步。

jcmd 67890 GC.heap_dump /path/to/heapdump.hprof

这会生成一个 .hprof 文件,你可以使用 Eclipse MATVisualVM 等工具打开它,进行深入分析,比如查找泄漏对象、GC Roots 等。


使用 jmap (JDK 内置工具)

jmap 是一个传统的工具,但 jcmd 在很多场景下已经取代了它。

查看内存映射

jmap -heap <PID>

输出示例:

Attaching to process ID 67890, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.314-b01
using thread-local object allocation.
Parallel GC with 4 thread(s)
Heap Configuration:
   MinHeapFreeRatio         = 0
   MaxHeapFreeRatio         = 100
   MaxHeapSize              = 2147483648 (2048.0MB)  // -Xmx
   NewSize                  = 9437184 (9.0MB)
   MaxNewSize               = 2147483648 (2048.0MB)
   OldSize                  = 13631456 (13.0MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415MB
   G1HeapRegionSize         = 0 (0.0MB)
Heap Usage:
PS Young Generation
Eden Space:
   capacity = 8388608 (8.0MB)
   used     = 4194304 (4.0MB)
   free     = 4194304 (4.0MB)
   50.0% used
From Space:
   capacity = 1048576 (1.0MB)
   used     = 0 (0.0MB)
   free     = 1048576 (1.0MB)
   0.0% used
To Space:
   capacity = 1048576 (1.0MB)
   used     = 0 (0.0MB)
   free     = 1048576 (1.0MB)
   0.0% used
PS Old Generation
   capacity = 142606336 (136.0MB)
   used     = 71680000 (68.375MB)
   free     = 70926336 (67.625MB)
   50.2750222962963% used
...

这个输出清晰地展示了 JVM 的堆配置和当前使用情况。

生成类直方图

jmap -histo <PID>

注意:这个命令会暂停目标 JVM,在生产环境慎用。jcmd GC.class_histogram 是更好的选择,因为它通常不会导致长时间停顿。


使用 pstop (Linux 原生命令)

这些命令不能看到 Java 内部的堆/非堆划分,但可以快速查看进程总的内存占用。

ps 命令

ps -o pid,ppid,cmd,%mem,%cpu --sort=-%mem -C java
  • %mem: 进程的物理内存占用百分比。
  • --sort=-%mem: 按内存使用率降序排列。

top 命令

top -p <PID>

top 界面中,按 f 键,然后选择 代表 RES (常驻内存) 和 S (Command/SWAP) 列,按 Enter 确认,这样你就可以实时看到 Java 进总的内存消耗和命令行参数(其中可能包含 -Xmx 等)。


使用 pmap (Linux 原生命令)

pmap 可以查看一个进程的内存映射详情,非常有助于理解 JVM 的内存是如何分布的。

pmap -x <PID>

输出示例:

67890:   /usr/lib/jvm/java-11-openjdk-amd64/bin/java
0000000000400000      4K r-x-- /usr/lib/jvm/java-11-openjdk-amd64/bin/java
0000000000800000      4K r---- /usr/lib/jvm/java-11-openjdk-amd64/bin/java
0000000000801000      4K rw--- /usr/lib/jvm/java-11-openjdk-amd64/bin/java
00007f8a1a8b8000    132K rw---   [ anon ]
00007f8a1a8c2000    8608K rw---   [ heap ]  // <-- 这就是 Java 堆内存
00007f8a1b0c3000     88K rw---   [ anon ]
...
  • [ heap ]: 这明确指出了 JVM 堆在进程虚拟地址空间中的位置和大小。
  • [ anon ]: 匿名内存,通常对应非堆内存,如元空间、线程栈等。
  • libjvm.so: JVM 自身的动态链接库。

使用 jstat (JDK 内置工具)

jstat 用于监控 JVM 的运行时行为,包括 GC 活动和堆内存使用情况,非常适合动态观察。

# 每秒一次,输出 GC 统计信息
jstat -gcutil <PID> 1s

输出示例:

  S0C    S1C    S0U    S1U      EC       EU       OC       OU      MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT    LGCE    LGCT    GC
 512.0   512.0    0.0    0.0    4096.0    512.0   10240.0   5120.0   5120.0   512.0   512.0   512.0      3    0.150     0      0.000    0.150      0    0.000    0.150
 512.0   512.0    0.0    0.0    4096.0   1024.0   10240.0   5120.0   5120.0   512.0   512.0   512.0      3    0.150     0      0.000    0.150      0    0.000    0.150
...
  • S0C, S1C: Survivor 0/1 区容量。
  • S0U, S1U: Survivor 0/1 区使用量。
  • EC, EU: Eden 区容量和使用量。
  • OC, OU: Old 区容量和使用量。
  • YGC: Young GC 次数。
  • FGC: Full GC 次数。

实战分析流程

假设你怀疑一个 Java 应用有内存泄漏,可以按以下步骤操作:

  1. 初步观察:使用 topps 发现 Java 进程的 RES 内存持续增长,并且没有下降的趋势。
  2. 确认趋势:使用 jstat -gcutil <PID> 5s 持续观察。OU (Old Used) 持续增长,FGC (Full GC) 后内存没有回收回来,这是典型的内存泄漏迹象。
  3. 定位对象:在内存泄漏发生期间,执行 jcmd <PID> GC.class_histogram,记录下占用内存最多的类,多次对比,看是哪些类的实例数量在持续增加。
  4. 生成快照:在内存达到一个峰值时,执行 jcmd <PID> GC.heap_dump /tmp/leak.hprof 生成堆转储文件。
  5. 深入分析:使用 Eclipse MAT 打开 .hprof 文件。
    • 使用 Leak Suspects Report 快速定位可疑的泄漏点。
    • 使用 Dominators Tree 分析对象的支配关系,找到“根”对象。
    • 使用 Path to GC Roots 查看为什么这些对象无法被 GC 回收。
  6. 代码定位:根据分析结果,找到代码中持有这些对象引用的地方,修复代码。
工具/命令 主要用途 优点 缺点
jcmd 首选,内存摘要、类直方图、生成堆转储 功能全面,无需停止应用,是现代 JDK 的标准 命令相对较多
jmap 查看堆配置、生成堆转储、类直方图 传统工具,功能稳定 生成堆转储和应用直方图可能引起应用停顿
ps / top 快速查看进程总内存占用 简单快速,系统自带 无法区分 Java 堆/非堆内存
pmap 查看进程内存映射详情 能清晰看到 [ heap ] 等内存区域划分 信息量较大,需要一定解读能力
jstat 动态监控 GC 和堆内存使用情况 实时性强,适合观察趋势 不提供详细的内存构成分析
jconsole / VisualVM 图形化监控和管理 直观,可远程连接,集成多种工具 需要图形界面或开启 JMX 端口

对于日常开发和运维,jcmdjstat 是最常用、最强大的组合。pmaptop 用于快速概览,而 jmap 和堆转储分析则是解决内存泄漏问题的终极武器。

分享:
扫描分享到社交APP
上一篇
下一篇