Java 调试入门工具

jps

jps是jdk提供的一个查看当前java进程的小工具, 可以看做是JavaVirtual Machine Process Status Tool的缩写。

jps常用命令

jps参数

jps原理

java程序在启动以后,会在java.io.tmpdir指定的目录下,就是临时文件夹里,生成一个类似于hsperfdata_User的文件夹,这个文件夹里(在Linux中为/tmp/hsperfdata_{userName}/),有几个文件,名字就是java进程的pid,因此列出当前运行的java进程,只是把这个目录里的文件名列一下而已。 至于系统的参数什么,就可以解析这几个文件获得。

更多请参考 jps - Java Virtual Machine Process Status Tool

jstack

jstack是jdk自带的线程堆栈分析工具,使用该命令可以查看或导出 Java 应用程序中线程堆栈信息。线程快照是当前虚拟机内每一条线程上在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、 请求外部资源导致的长时间等待等问题

注意:

  • Jstack 可以直接检测死锁;

  • Jstack 并不能直接检测死循环,但可以通过分析线程堆栈信息间接发现死循环的存在;例如

    • 线程一直处于 RUNNABLE 状态。

    • CPU 使用率异常高(可以通过 toppidstat 等工具查看)。

    • 线程的堆栈信息中会反复出现相同的函数调用。

jstack常用命令:

pid 是需要被打印配置信息的java进程id,可以用jps查询

jstack参数:

Jstack 使用

通过使用 jps 命令获取需要监控的进程的pid,然后使用 jstack pid 命令查看线程的堆栈信息。

通过 jstack 命令可以获取当前进程的所有线程信息。

每个线程堆的信息中,都可以查看到 线程ID、线程的状态(wait、sleep、running 等状态)、是否持有锁信息等。

死锁示例

下面通过一个例子,来演示 jstack 检查死锁的一个例子,代码如下:

使用 jstack -l pid 查看线程堆栈信息,发现在堆栈信息最后面检查出了一个死锁。如下图 可以清楚的看出 mythread2 等待 这个锁 “0x00000000d6eb82d0”,这个锁是由于mythread1线程持有。

mythread1线程等待这个锁“0x00000000d6eb8300”,这个锁是由mythread2线程持有。

“mythread1”线程堆栈信息如下: 可以看出当前线程持有“0x00000000d6eb82d0”锁,等待“0x00000000d6eb8300”的锁

“mythread2”线程堆栈信息如下: “mythread2”的堆栈信息中可以看出当前线程持有“0x00000000d6eb8300”锁,等待“0x00000000d6eb82d0”的锁。

jinfo

jinfo 是 JDK 自带的命令,可以用来查看正在运行的 java 应用程序的扩展参数,包括Java System属性和JVM命令行参数;也可以动态的修改正在运行的 JVM 一些参数。当系统崩溃时,jinfo可以从core文件里面知道崩溃的Java应用程序的配置信息

Javacore,也可以称为“threaddump”或是“javadump”,它是 Java 提供的一种诊断特性,能够提供一份可读的当前运行的 JVM 中线程使用情况的快照。即在某个特定时刻,JVM 中有哪些线程在运行,每个线程执行到哪一个类,哪一个方法。应用程序如果出现不可恢复的错误或是内存泄露,就会自动触发 Javacore 的生成。

jinfo常用命令:

jinfo参数:

示例一: no option

命令:jinfo pid 描述:输出当前 jvm 进程的全部参数和系统属性

示例二: -flag name

命令:jinfo -flag name pid 描述:输出对应名称的参数 使用该命令,可以查看指定的 jvm 参数的值。如:查看当前 jvm 进程是否开启打印 GC 日志。

示例三:-flag [+|-]name

命令:jinfo -flag [+|-]name pid 描述:开启或者关闭对应名称的参数

使用 jinfo 可以在不重启虚拟机的情况下,可以动态的修改 jvm 的参数。尤其在线上的环境特别有用。

使用如下:

示例四:-flag name=value

命令:jinfo -flag name=value pid 描述:修改指定参数的值。

同示例三,但示例三主要是针对 boolean 值的参数设置的。 如果是设置 value值,则需要使用 name=value 的形式。

使用如下:

注意事项 :jinfo虽然可以在java程序运行时动态地修改虚拟机参数,但并不是所有的参数都支持动态修改

示例五: -flags

命令:jinfo -flags pid 描述:输出全部的参数

示例六:-sysprops

命令:jinfo -sysprops pid 描述:输出当前 jvm 进行的全部的系统属性

jmap

命令jmap是一个多功能的命令。它可以生成 java 程序的 dump 文件, 也可以查看堆内对象示例的统计信息、查看 ClassLoader 的信息以及 finalizer 队列。

两个用途

jmap参数

示例一:no option

命令:jmap pid 描述:查看进程的内存映像信息,类似 Solaris pmap 命令。

使用不带选项参数的jmap打印共享对象映射,将会打印目标虚拟机中加载的每个共享对象的起始地址、映射大小以及共享对象文件的路径全称。这与Solaris的pmap工具比较相似。

示例二:heap

命令:jmap -heap pid 描述:显示Java堆详细信息

打印一个堆的摘要信息,包括使用的GC算法、堆配置信息和各内存区域内存使用信息

示例三:histo[:live]

命令:jmap -histo:live pid 描述:显示堆中对象的统计信息

其中包括每个Java类、对象数量、内存大小(单位:字节)、完全限定的类名。打印的虚拟机内部的类名称将会带有一个’*’前缀。如果指定了live子选项,则只计算活动的对象。

示例四:clstats

命令:jmap -clstats pid 描述:打印类加载器信息

-clstats是-permstat的替代方案,在JDK8之前,-permstat用来打印类加载器的数据 打印Java堆内存的永久保存区域的类加载器的智能统计信息。对于每个类加载器而言,它的名称、活跃度、地址、父类加载器、它所加载的类的数量和大小都会被打印。此外,包含的字符串数量和大小也会被打印。

示例五:finalizerinfo

命令:jmap -finalizerinfo pid 描述:打印等待终结的对象信息 Number of objects pending for finalization: 0 说明当前F-QUEUE队列中并没有等待Fializer线程执行final

示例六:dump:<dump-options>

命令:jmap -dump:format=b,file=heapdump.phrof pid 描述:生成堆转储快照dump文件。

以hprof二进制格式转储Java堆到指定filename的文件中。live子选项是可选的。如果指定了live子选项,堆中只有活动的对象会被转储。想要浏览heap dump,你可以使用jhat(Java堆分析工具)读取生成的文件。

这个命令执行,JVM会将整个heap的信息dump写入到一个文件,heap如果比较大的话,就会导致这个过程比较耗时,并且执行的过程中为了保证dump的信息是可靠的,所以会暂停应用, 线上系统慎用。

更多请参考: jmap - Memory Map

注意

此命令会导致虚拟机暂停工作1~3秒,因此在实际生产环境中基本不使用!!!一般情况下,在出现问题时,会用以下方案

  • 服务已OOM或即将OOM:依赖预设的JVM参数 -XX:+HeapDumpOnOutOfMemoryError。这样JVM会在崩溃时自动生成Dump,无需手动干预,避免二次伤害。

  • 服务可用但性能劣化:Arthas,用它进行在线、低侵入式分析

jstat

它是 JDK 自带的工县,用于监控JVM 各种运行时信息

jstat参数众多,但是使用一个就够了

  • gc选项:显示垃圾收集信息(也可以用 gcutil,gcutil以百分比形式显示内存的使用情况,gc显示的是内存占用的字节数,以KB 的形式输出堆内存的使用情况)

  • pid:Java 进程的 PID。

  • 1000:每 1000 毫秒采样一次。

  • 10:采样 10 次。

示例输出

字段含义:

  • S0C(Survivor Space 0 Capacity):第一个 Survivor 区域的容量(字节数).

  • S1C(Survivor Space 1 Capacity):第二个 Survivor 区域的容量(字节数)。

  • S0U(Survivor Space 0 Utilization):第一个 Survivor 区域的使用量(字节数)

  • S1U(Survivor Space 1 Utilization):第二个 Survivor 区域的使用量(字节数)。

  • EC(Eden Space Capacity): Eden 区域的容量(字节数)。

  • EU(Eden Space Utilization): Eden 区域的使用量(字节数)

  • OC(Old Generation Capacity): 老年代的容量(字节数)

  • OU(Old Generation Utilization): 老年代的使用量(字节数)

  • MC(Metaspace Capacity):方法区(Metaspace)的容量(字节数)

  • MU (Metaspace Utilization):方法区的使用量(字节数)。

  • CCSC(Compressed Class Space Capacity): 压缩类空间的容量(字节数)

  • CCSU(Compressed Class Space Utilization): 压缩类空间的使用量(字节数)

  • YGC (Young Generation GC Count):年轻代垃圾回收的次数

  • YGCT (Young Generation GC Time):年轻代垃圾回收的总时间(秒)。

  • FGC (Full GC Count): full gc 的次数。

  • FGCT(Full GC Time): full gc 的总时间(秒)。

  • GCT(Garbage Collection Time): 总的垃圾回收时间(秒)。

注意:如果 FGC 变化频率很高,则说明系统性能和吞吐量将下降,或者可能出现内存溢出。

jdb

jdb可以用来预发debug,假设你预发的java_home是/opt/java/,远程调试端口是8000.那么

出现以上代表jdb启动成功。后续可以进行设置断点进行调试。

具体参数可见oracle官方说明jdb - The Java Debugger

CHLSDB

CHLSDB感觉很多情况下可以看到更好玩的东西,不详细叙述了。 查询资料听说jstack和jmap等工具就是基于它的。

更详细的可见R大此贴

Java 调试进阶工具

btrace

首当其冲的要说的是btrace。真是生产环境&预发的排查问题大杀器。 简介什么的就不说了。直接上代码干

l 查看当前谁调用了ArrayList的add方法,同时只打印当前ArrayList的size大于500的线程调用栈

  • 监控当前服务方法被调用时返回的值以及请求的参数

btrace 具体可以参考这里:https://github.com/btraceio/btrace

注意:

  • 经过观察,1.3.9的release输出不稳定,要多触发几次才能看到正确的结果

  • 正则表达式匹配trace类时范围一定要控制,否则极有可能出现跑满CPU导致应用卡死的情况

  • 由于是字节码注入的原理,想要应用恢复到正常情况,需要重启应用。

Greys

Greys是@杜琨的大作吧。说几个挺棒的功能(部分功能和btrace重合):

  • sc -df xxx: 输出当前类的详情,包括源码位置和classloader结构

  • trace class method: 打印出当前方法调用的耗时情况,细分到每个方法, 对排查方法性能时很有帮助。

Arthas

Arthas是基于Greys。

输入 dashboard 命令,按回车 enter,会展示当前进程的信息,按 ctrl+c 可以中断执行

字段含义:

heap: 堆内存的使用情况:

  • used 32M: 当前堆内存使用 32MB

  • total 155M: 堆内存总量为 155MB.

  • max1820M:堆内存最大量为1820MB.

  • usage 1.77%: 堆内存使用百分比为 1.77%

ps_eden_space: 年轻代 Eden 区域的使用情况

  • used 14M: 当前 Eden 区域使用 14MB

  • total 65M: Eden 区域总量为 65MB.

  • max 672M: Eden 区域最大量为 672MB.

  • usage 2.21%: Eden 区域使用百分比为 2.21%。

ps_survivor_space: 年轻代 Survivor 区域的使用情况。

  • used 4M: 当前 Survivor 区域使用 4MB.

  • total 5M: Survivor 区域总量为 5MB。

  • max 5M: Survivor 区域最大量为 5MB。

ps_old_gen: 老年代的使用情况,

  • used 12M: 当前老年代使用12MB

  • total 85M: 老年代总量为 85MB。

  • max 1365M: 老年代最大量为1365MB.

  • usage 0.91%: 老年代使用百分比为 0.91%

nonheap: 非堆内存的使用情况。

  • used 20M: 当前非堆内存使用 20MB.

  • total 23M: 非堆内存总量为 23MB。

code cache: 代码缓存区的使用情况

  • used 3M: 当前代码缓存区使用 3MB.

  • total 5M: 代码缓存区总量为 5MB。

  • max 240M: 代码缓存区最大量为 240MB。

  • usage 1.32%: 代码缓存区使用百分比为 1.32%。

javOSize

就说一个功能:

  • classes:通过修改了字节码,改变了类的内容,即时生效。 所以可以做到快速的在某个地方打个日志看看输出,缺点是对代码的侵入性太大。但是如果自己知道自己在干嘛,的确是不错的玩意儿。

其他功能Greys和btrace都能很轻易做的到,不说了。

更多请参考:官网

JProfiler

之前判断许多问题要通过JProfiler,但是现在Greys和btrace基本都能搞定了。再加上出问题的基本上都是生产环境(网络隔离),所以基本不怎么使用了,但是还是要标记一下。

更多请参考:官网

其它工具

dmesg

如果发现自己的java进程悄无声息的消失了,几乎没有留下任何线索,那么dmesg一发,很有可能有你想要的。

sudo dmesg|grep -i kill|less 去找关键字oom_killer。找到的结果类似如下:

以上表明,对应的java进程被系统的OOM Killer给干掉了,得分为854. 解释一下OOM killer(Out-Of-Memory killer),该机制会监控机器的内存资源消耗。当机器内存耗尽前,该机制会扫描所有的进程(按照一定规则计算,内存占用,时间等),挑选出得分最高的进程,然后杀死,从而保护机器。

dmesg日志时间转换公式: log实际时间=格林威治1970-01-01+(当前时间秒数-系统启动至今的秒数+dmesg打印的log时间)秒数:

date -d "1970-01-01 UTC echo "$(date +%s)-$(cat /proc/uptime|cut -f 1 -d' ')+12288812.926194"|bc seconds" 剩下的,就是看看为什么内存这么大,触发了OOM-Killer了。

JVM可视化工具

JConsole

Jconsole (Java Monitoring and Management Console),JDK自带的基于JMX的可视化监视、管理工具。 官方文档可以参考这里在新窗口打开

  • 找到jconsole工具

-l 打开jconsole

选择

  • 查看概述、内存、线程、类、VM概要、MBean

概述 内存 线程 VM概要 MBean

Visual VM

VisualVM 是一款免费的,集成了多个 JDK 命令行工具的可视化工具,它能为您提供强大的分析能力,对 Java 应用程序做性能分析和调优。这些功能包括生成和分析海量数据、跟踪内存泄漏、监控垃圾回收器、执行内存和 CPU 分析,同时它还支持在 MBeans 上进行浏览和操作。

Overview Monitor 线程 Sampler

Visual GC

visual gc 是 visualvm 中的图形化查看 gc 状况的插件。官方文档可以参考这里

比如我在IDEA中使用visual GC 插件来看GC状况。

JProfile

JProfiler 是一个商业的主要用于检查和跟踪系统(限于Java开发的)的性能的工具。JProfiler可以通过时时的监控系统的内存使用情况,随时监视垃圾回收,线程运行状况等手段,从而很好的监视JVM运行情况及其性能。

JProfiler 是一个全功能的Java剖析工具(profiler),专用于分析J2SE和J2EE应用程序。它把CPU、执行绪和内存的剖析组合在一个强大的应用中。 JProfiler可提供许多IDE整合和应用服务器整合用途。JProfiler直觉式的GUI让你可以找到效能瓶颈、抓出内存漏失(memory leaks)、并解决执行绪的问题。它让你得以对heap walker作资源回收器的root analysis,可以轻易找出内存漏失;heap快照(snapshot)模式让未被参照(reference)的对象、稍微被参照的对象、或在终结(finalization)队列的对象都会被移除;整合精灵以便剖析浏览器的Java外挂功能。

核心组件

JProfiler 包含用于采集目标 JVM 分析数据的 JProfiler agent、用于可视化分析数据的 JProfiler UI、提供各种功能的命令行工具,它们之间的关系如下图所示。

  • JProfiler agent:

JProfiler agent 是一个本地库,它可以在 JVM 启动时通过参数-agentpath:<path to native library>进行加载或者在程序运行时通过JVM Attach 机制进行加载。Agent 被成功加载后,会设置 JVMTI 环境,监听虚拟机产生的事件,如类加载、线程创建等。例如,当它监听到类加载事件后,会给这些类注入用于执行度量操作的字节码。

  • JProfiler UI:JProfiler UI 是一个可独立部署的组件,它通过 socket 和 agent 建立连接。这意味着不论目标 JVM 运行在本地还是远端,JProfiler UI 和 agent 间的通信机制都是一样的。

JProfiler UI 的主要功能是展示通过 agent 采集上来的分析数据,此外还可以通过它控制 agent 的采集行为,将快照保存至磁盘,展示保存的快照。

  • 命令行工具:JProfiler 提供了一系列命令行工具以实现不同的功能。
  1. jpcontroller - 用于控制 agent 的采集行为。它通过 agent 注册的 JProfiler MBean 向 agent 传递命令。

  2. jpenable - 用于将 agent 加载到一个正在运行的 JVM 上。

  3. jpdump - 用于获取正在运行的 JVM 的堆快照。

  4. jpexport & jpcompare - 用于从保存的快照中提取数据并创建 HTML 报告。

运行测试

运行一个SpringBoot测试工程,选择attach到JVM 选择指定的进程 设置数据采集模式

JProfier 提供两种数据采集模式 Sampling 和 Instrumentation。

  • Sampling - 适合于不要求数据完全精确的场景。优点是对系统性能的影响较小,缺点是某些特性不支持(如方法级别的统计信息)。

  • Instrumentation - 完整功能模式,统计信息也是精确的。缺点是如果需要分析的类比较多,对应用性能影响较大。为了降低影响,往往需要和 Filter 一起使用。

由于我们需要获取方法级别的统计信息,这里选择了 Instrumentation 模式。 概览 内存 实时内存分布(类对象) dump 堆内存 dump完会直接打开显示 线程存储 导出HTML报告 CPU 调用树 线程历史 JEE & 探针 MBeans

Eclipse Memory Analyzer (MAT)

MAT 是一种快速且功能丰富的 Java 堆分析器,可帮助你发现内存泄漏并减少内存消耗。 MAT在的堆内存分析问题使用极为广泛,需要重点掌握。

可以在这里下载, 官方文档可以看这里

  • Overview:包含内存分布,以及潜在的问题推测

  • Histogram:可以列出内存中的对象,对象的个数以及大小。

具体需要重点理解如下两个概念,可参考官网文档的解释

  1. Shallow Heap :一个对象内存的消耗大小,不包含对其他对象的引用

  2. Retained Heap :是shallow Heap的总和,也就是该对象被GC之后所能回收到内存的总和

  • Dominator Tree:可以列出那个线程,以及线程下面的那些对象占用的空间。

  • Top consumers:通过图形列出最大的object。

  • Leak Suspects:自动分析潜在可能的泄漏。

GCeasy

GCeasy,它是一个分析 GC 日志文件的在线网站,能根据上传的 GC 日志,以图表形式分析 GC 情况:

直接在主页上传堆转储文件即可,可以得到 GC的分析结果