使用JDK自带工具进行JVM内存分析之旅

进行jvm内存分析可以排查存在和潜在的问题。

通过借助jdk自带的常用工具,可以分析大概可能的问题定位以及确定优化方向。

JVM内存分析有很多好处。

  • 内存泄漏排查:JVM 内存泄漏是指应用程序中的对象占用的内存无法被垃圾回收器释放,导致内存占用持续增长,最终耗尽可用内存。通过内存分析工具,可以检测到哪些对象占用了大量内存且无法被释放,进而定位到可能存在内存泄漏的代码。
  • 内存优化:合理优化 JVM 内存配置可以提高应用程序的性能和稳定性。通过分析应用程序的内存使用情况,可以调整堆内存大小、永久代(如果是旧版 Java)大小、新生代与老年代比例等参数,以减少垃圾回收频率,降低内存占用。
  • 性能调优:内存分析也有助于发现内存中的瓶颈,如频繁的 Full GC(全局垃圾回收)导致的停顿时间过长。通过调整垃圾回收器类型、GC 算法、堆内存大小等参数,可以改善应用程序的性能表现。
  • 异常分析:当应用程序出现内存相关的异常,如 OutOfMemoryError(内存溢出错误)时,通过分析内存使用情况可以找到导致异常的根本原因,例如某个模块或对象占用了过多内存。
  • 容量规划:对于大型应用程序或者需要长时间运行的系统,进行内存分析可以帮助进行容量规划,确保系统具有足够的内存资源支持应用程序的正常运行。

本文将通过一次jvm内存分析过程来说明jpsjcmdjstatjstackjmap 工具的使用方法。

本文使用到的是JDK17版本。

一次jvm内存分析之旅

当需要进行 JVM 内存分析时,结合使用 jpsjcmdjstatjstackjmap 可以提供全面的诊断信息。

下面是一般的步骤:

使用 jps 查看 Java 进程的 PID

bash 复制代码
jps -l

这将列出所有 Java 进程的 PID 和主类名。

使用 jcmd 执行一些诊断命令

bash 复制代码
jcmd <PID> VM.flags
jcmd <PID> VM.system_properties

这些命令可以显示 JVM 的启动参数和系统属性,帮助了解 JVM 的配置。

使用 jstat 监视 JVM 内存和垃圾回收情况

bash 复制代码
jstat -gc <PID> 5000 10

这将持续输出 JVM 的垃圾回收情况,包括各个堆区的使用情况、GC 时间等。

使用 jstack 生成线程堆栈信息

bash 复制代码
jstack <PID>

查看线程堆栈信息,以检查是否存在死锁或其他线程相关的问题。

使用 jmap 生成堆转储文件

bash 复制代码
jmap -dump:file=heapdump.hprof <PID>

这将生成一个名为 heapdump.hprof 的堆转储文件,可以用于进一步分析内存使用情况,查找内存泄漏等问题。

分析堆转储文件

使用工具如 Eclipse Memory Analyzer (MAT) 或者 VisualVM 来分析生成的堆转储文件,查找内存泄漏、大对象、无用对象等问题。

通过结合使用这些工具,可以全面地了解 JVM 运行时的状态,诊断性能问题,以及解决内存相关的错误。

下面将详细解释这些工具的使用方法。

jps

jps 是 JDK 提供的一个用于列出 Java 虚拟机进程的命令行工具。它通常用于查看当前系统中正在运行的 Java 进程的 PID(进程标识符)以及对应的主类名。

下面是 jps 命令的使用方法:

或者使用ps -ef|grep java 也可以搜索到对应的pid。

bash 复制代码
jps [ options ] [ hostid ]

其中,options 是一些可选的参数,hostid 是可选的主机标识符。常用的选项包括:

  • -q:仅显示进程的 PID,不显示对应的主类名。
  • -m:显示传递给主类的参数。
  • -l:显示主类的全限定名,通常用于区分具体的 Java 应用程序。
  • -v:显示传递给 JVM 的参数。

例如,要显示当前系统中所有 Java 进程的 PID 和对应的主类名,可以直接运行 jps 命令:

bash 复制代码
jps

如果要仅显示 PID,可以使用 -q 选项:

bash 复制代码
jps -q

要显示主类的全限定名,可以使用 -l 选项:

bash 复制代码
jps -l

如果要显示传递给主类的参数,可以使用 -m 选项:

bash 复制代码
jps -m

如果要显示传递给 JVM 的参数,可以使用 -v 选项:

bash 复制代码
jps -v

jcmd

jcmd :jcmd 命令是 Java 8 新增的命令,可以执行多种 JVM 监控和诊断任务。例如,可以使用 jcmd <pid> VM.flags 查看 JVM 启动参数,或者使用 jcmd <pid> Thread.print 打印线程堆栈信息。

下面是 jcmd 命令的基本使用方法:

bash 复制代码
jcmd <PID | main class> <command> [options]

其中:

  • <PID | main class>:要操作的 Java 进程的 PID(进程标识符)或者主类名。如果提供了 PID,则直接操作对应的 Java 进程;如果提供了主类名,则 jcmd 会尝试找到匹配的 Java 进程并执行相应的命令。
  • <command>:要执行的诊断命令。
  • [options]:可选的命令参数。

常用的 jcmd 命令包括:

  • help : 显示 jcmd 支持的命令列表,以及每个命令的简要描述。
  • VM.version: 显示 JVM 的版本信息。
  • VM.flags: 显示 JVM 的启动参数。
  • VM.system_properties: 显示 JVM 的系统属性。
  • Thread.print: 打印 Java 进程中所有线程的堆栈信息。
  • GC.run: 执行一次垃圾回收。
  • GC.heap_dump: 生成 Java 堆转储文件(heap dump)。

举例来说,如果要打印指定 Java 进程的线程堆栈信息,可以使用以下命令:

bash 复制代码
jcmd <PID> Thread.print

如果要执行一次垃圾回收,可以使用以下命令:

bash 复制代码
jcmd <PID> GC.run

如果要生成 Java 堆转储文件,可以使用以下命令:

bash 复制代码
jcmd <PID> GC.heap_dump <filename>

jstat

jstat :jstat 命令可以监视 JVM 内存、垃圾回收等情况。例如,可以使用 jstat -gc <pid> 查看垃圾回收统计信息,或者使用 jstat -gcutil <pid> 查看垃圾回收统计信息及内存使用情况。

下面是 jstat 的基本使用方法:

bash 复制代码
jstat [ options ] <PID> [ interval [ count ] ]

其中:

  • [ options ]:可选的命令选项,用于指定要监视的数据类型和格式。
  • <PID>:要监视的 Java 进程的 PID(进程标识符)。
  • [ interval ]:可选参数,指定输出统计信息的时间间隔(以毫秒为单位)。如果省略,则默认为每秒一次。
  • [ count ]:可选参数,指定输出统计信息的次数。如果省略,则默认为无限次输出。

常用的 jstat 命令选项包括:

  • -class: 显示类加载、卸载信息以及类加载器的状态。
  • -gc: 显示垃圾回收相关的信息,包括各个代的使用情况、GC 时间等。
  • -compiler: 显示即时编译器(JIT)的编译统计信息。
  • -gccapacity: 显示各个堆区的容量及使用情况。
  • -gcutil: 显示各个堆区的使用情况,以百分比表示。
  • -gccause: 显示导致最近一次 GC 的原因。
  • -printcompilation: 打印方法的即时编译(JIT)信息。

举例来说,要查看 Java 进程的类加载情况,可以使用以下命令:

bash 复制代码
jstat -class <PID>

如果想要每隔 5 秒输出一次类加载信息,共输出 10 次,可以使用以下命令:

bash 复制代码
jstat -class <PID> 5000 10

jstat只能查看当前的gc信息,查看gc日志更适合线上环境的做法是在启动JVM时加上-XX:+PrintGCDetails -Xloggc:/path/to/gc.log(JDK1.8以下)或者-Xlog:gc*:file=/path/to/gc.log(JDK9+)参数,将生成的gc日志文件放到GCViewer、GCeasy(需注册)进行分析。

jstack

jstack :jstack 命令用于生成 Java 线程转储快照,可以用于分析线程状态、死锁等问题。例如,可以使用 jstack <pid> 打印线程堆栈信息,或者使用 jstack -l <pid> 打印线程堆栈信息及锁信息。

下面是 jstack 命令的基本使用方法:

bash 复制代码
jstack [ options ] <PID>

其中:

  • [ options ]:可选的命令选项,用于指定输出的格式等。
  • <PID>:要生成线程堆栈信息的 Java 进程的 PID(进程标识符)。

常用的 jstack 命令选项包括:

  • -l: 长列表格式,显示关于锁的附加信息,如拥有者和等待队列。
  • -F: 当正常输出的 jstack 命令不起作用时,强制生成线程堆栈信息。这在 Java 进程没有响应时可能会很有用,但可能会导致进程暂停一段时间。
  • -m: 显示 Java 和本地方法的堆栈跟踪,而不仅仅是 Java 堆栈跟踪。
  • -h: 显示帮助信息。

举例来说,要生成指定 Java 进程的线程堆栈信息,可以使用以下命令:

bash 复制代码
jstack <PID>

如果想要输出长列表格式的线程堆栈信息,可以使用 -l 选项:

bash 复制代码
jstack -l <PID>

如果 Java 进程没有响应,可以使用 -F 选项强制生成线程堆栈信息:

bash 复制代码
jstack -F <PID>

jmap

异常没有发生定位异常代码,需要通过jmap生成dump文件。

然后将其导入到 MAT 中进行分析。以下是生成堆转储文件的步骤:

  • 确定 Java 进程 ID :首先,需要确定正在运行的 Java 进程的进程 ID(PID)。可以使用 jps 命令查看正在运行的 Java 进程及其 PID。
  • 生成堆转储文件 :使用 jmap 命令生成堆转储文件。命令格式如下:
shell 复制代码
jmap -dump:file=<文件路径> <PID>

例如,要生成名为 heapdump.hprof 的堆转储文件,可以执行以下命令:

shell 复制代码
jmap -dump:file=heapdump.hprof <PID>

这将在当前工作目录下生成一个名为 heapdump.hprof 的堆转储文件。

  • 导入堆转储文件到 MAT :将生成的堆转储文件导入到 MAT 中进行分析。打开 MAT,然后选择 File -> Open Heap Dump,然后选择生成的堆转储文件。
  • 执行内存分析:一旦堆转储文件被导入到 MAT 中,就可以执行内存分析,按照前面提到的步骤来查找内存问题。

通过这些步骤可以手动生成堆转储文件并使用 MAT 进行分析,即使没有在 OutOfMemoryError 发生时自动生成堆转储文件也可以找到问题所在。

更适合线上环境的做法是在启动JVM时加上-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heapdump.hprof参数,这样当发生OutOfMemoryError时,JVM会自动生成堆转储文件。

关于作者

来自一线全栈程序员nine的探索与实践,持续迭代中。

相关推荐
NEFU AB-IN6 小时前
Prompt Gen Desktop 管理和迭代你的 Prompt!
java·jvm·prompt
唐古乌梁海12 小时前
【Java】JVM 内存区域划分
java·开发语言·jvm
众俗13 小时前
JVM整理
jvm
echoyu.13 小时前
java源代码、字节码、jvm、jit、aot的关系
java·开发语言·jvm·八股
代码栈上的思考1 天前
JVM中内存管理的策略
java·jvm
thginWalker1 天前
深入浅出 Java 虚拟机之进阶部分
jvm
沐浴露z1 天前
【JVM】详解 线程与协程
java·jvm
thginWalker1 天前
深入浅出 Java 虚拟机之实战部分
jvm
程序员卷卷狗3 天前
JVM 调优实战:从线上问题复盘到精细化内存治理
java·开发语言·jvm
Sincerelyplz3 天前
【JDK新特性】分代ZGC到底做了哪些优化?
java·jvm·后端