Java 虚拟机总结----厂商实现总结、运行时数据区总结、垃圾收集器总结、性能监控、故障处理工具总结

厂商实现总结

jvm_impl

运行时数据区

手绘的运行时数据区如下:

jvm

JVM 定义了在程序执行期间使用的各种运行时数据区:

  • 其中一些数据区是在 JVM 启动时创建、仅在 JVM 退出时才被销毁。
  • 另外一些数据区是随每个线程创建及销毁。

PC Register

JVM 可以一次支持多个线程执行。每个 JVM 线程都有专属的 pc(程序计数器 program counter)寄存器。在任何时候,每个 JVM 线程都在执行某个方法的代码,即该线程的当前方法(参考 栈帧(Frames)一节)。

pc 寄存器的值可以为两种:

  • 如果当前执行的是非 native 方法,值为当前正在执行的 JVM 指令的地址(returnAddress)。
  • 如果是 native 方法,值为 undefined。

JVM 的 pc 寄存器长度足以保存 returnAddress 或特定平台的本地指针。

The returnAddress Type and Values :

The returnAddress type is used by the Java Virtual Machine’s jsr, ret, and jsr_w instructions (§jsr, §ret, §jsr_w). The values of the returnAddress type are pointers to the opcodes of Java Virtual Machine instructions. Unlike the numeric primitive types, the returnAddress type does not correspond to any Java programming language type and cannot be modified by the running program.

JVM Stacks

每个 JVM 线程都有一个私有的 JVM Stack 栈区,与该线程同时创建。JVM Stack 存储栈帧(参考 栈帧(Frames)一节)。JVM Stack 类似于常规编程语言(例如 C 语言):它保存局部变量和部分结果,并在方法调用和返回中起作用。由于 JVM Stack 的操作只有出栈和入栈,因此栈帧可能堆积。JVM Stack 的内存空间不必连续。

规范允许 JVM Stack 要么是固定大小(通过 -Xss 指定大小)、要么是根据计算的需要进行动态扩容和缩容。如果 JVM Stack 的大小固定,则在创建每个 JVM Stack 时可以独立选择其大小。

以下异常情况与 JVM Stack 相关:

  • 如果线程所需空间大于分配的 JVM Stack 空间,则 JVM 抛出 StackOverflowError
  • 如果 JVM Stack 能够动态扩展,并尝试扩展,但是内存不足,则 JVM 抛出 OutOfMemoryError

Frames

栈帧用于:

  • 存储数据和部分结果
  • 作为方法的返回值
  • 调度异常
  • 执行动态链接

每次调用方法时都会创建一个栈帧。当方法调用完毕,无论是正常还是异常结束(例如抛出了未捕获的异常),栈帧都会销毁。栈帧由 JVM 栈区创建。每个栈帧都有它自己的局部变量数组(Local Variables Array)、操作数栈(Operand Stacks)、以及对当前类当前方法的运行时常量池的引用。

局部变量数组和操作数栈的大小在编译时确定,并与栈帧关联的方法代码(参考 The Code Attribute)一起提供。因此,栈帧数据结构的大小仅取决于 JVM 的实现,并且可以在方法调用时分配用于这些结构的内存。

在给定线程的任何时候,只有一个在执行方法中的栈帧处于活动状态。该活动栈帧称为当前帧(current frame),该方法称为当前方法(current method),定义当前方法的类称为当前类(current class)。局部变量和操作数栈上的操作引用当前帧

如果当前方法调用了另一个方法或者该方法执行完毕,则该方法所处的帧不再是当前帧。调用方法时,将创建新的栈帧,并在控制权转移到新方法时变为当前帧。当方法返回时,当前帧将其方法调用的结果(如有)传递回前一帧并被丢弃,然后前一帧变回当前帧

注意,由线程创建的栈帧仅线程自身可见,无法被其它线程所引用。

Heap

JVM 具有一个在所有 JVM 线程之间共享的堆区(Heap),用于分配所有类实例和数组所需的内存。

堆区在虚拟机启动时创建。堆中的对象由垃圾收集器(garbage collector)进行回收。对象永远不会显式释放。JVM 不假定任何类型的垃圾收集器,而由实现者根据系统要求自行选择实现。

堆的大小可以是固定的,也可以根据计算的需要进行扩容,如果不需要更大空间的堆,可以进行缩容。堆区的内存空间不必连续。

JVM 实现可以为用户提供堆的初始值配置。并且,如果堆可以动态扩容和缩容,还需提供堆的最大、最小值配置。

以下异常情况与堆相关:

  • 如果所需的堆空间大于能够分配的堆空间,则 JVM 抛出 OutOfMemoryError

Method Area

JVM 具有一个在所有 JVM 线程之间共享的方法区(Method Area)。方法区类似于常规编程语言的编译代码的存储区域。它存储每个类的结构,例如运行时常量池、字段(field)及方法(method)的数据、以及方法(methods)和构造方法(constructors)的代码,包括用于类及其实例初始化和接口初始化的特殊方法(参考 Special Methods)。

方法区在虚拟机启动时创建。尽管方法区在逻辑上是堆区的一部分,但是 JVM 实现可以选择不进行垃圾回收或压缩。JVM 规范没有规定方法区的位置或用于管理已编译代码的策略。

方法区可以是固定大小的,或者根据计算的需要进行扩容,如果无需更大空间的方法区,可以进行缩容。方法区的内存空间不必连续。

JVM 实现可以为用户提供方法区的初始值配置。在方法区大小可变的情况下,可以提供最大、最小值配置。

以下异常情况与方法区相关:

  • 如果方法区的内存空间无法满足分配请求,则 JVM 抛出 OutOfMemoryError

Run-Time Constant Pool

运行时常量池是每个类或每个接口的 class 文件中 constant_pool 表(参考 The Constant Pool)的运行时表示。它包含多种常量,范围从编译时已知的数值型的字面值(numeric literals)到必须在运行时解析的方法和字段引用。运行时常量池的功能类似于常规编程语言的符号表,尽管它包含的数据范围比典型的符号表要大。

每个运行时常量池都由方法区分配。当 JVM 创建类或接口时(参考 Creation and Loading Class),将构造该类或接口的运行时常量池。

以下异常情况与方法区相关:

  • 创建类或接口时,如果运行时常量池的构造所需的内存超过 JVM 的方法区中可用的内存,则 JVM 抛出 OutOfMemoryError

Native Method Stacks

java 命令

java [options*] *classname [args]

java [options*] -jar *filename [args]

  • options: Command-line options separated by spaces.
  • classname: The name of the class to be launched.
  • filename: The name of the Java Archive (JAR) file to be called. Used only with the -jar option.
  • args: The arguments passed to the main() method separated by spaces.

java 命令用于启动 Java 应用程序。它通过启动 JRE,加载指定类并调用其 main() 方法来实现启动。main() 方法声明如下:

public static void main(String[] args)

java 命令支持以下几类选项:

所有 JVM 实现都需要保证支持标准选项。标准选项用于执行常见操作,例如检查 JRE 版本、设置类路径、启用详细输出等。

非标准选项是针对 Java HotSpot VM 的通用选项,因此不能保证所有 JVM 实现都能支持,并且随时可能改变。非标组选项以 -X 开头。

高级选项不建议随意使用。这些是开发人员用于调整 Java HotSpot VM 特定区域的选项。这些区域通常具有特定的系统要求,并且可能需要对系统配置参数的访问权限。这些选项也不能保证所有 JVM 实现都能支持,并且随时可能改变。高级选项以 -XX 开头。

此处参考已废弃与已移除的选项(JDK 8)。

布尔类型的选项用于启用默认情况下禁用的功能,或者禁用默认情况下启用的功能。此类选项无需参数,格式如下:

  • -XX:+OptionName 用于启用;
  • -XX:-OptionName 用于禁用。

对于需要参数的选项,每个选项的确切语法有所差异:参数可以用空格、冒号(:)或等号(=)与选项名分开,或者参数可以直接跟在选项后面,具体参考文档。

如果需要指定字节大小,可以使用以下几种格式:

  • no suffix
  • k or K for kilobytes (KB)
  • m or M for megabytes (MB)
  • g or G for gigabytes (GB)

例如,大小为 8 GB,参数可以设为 8g, 8192m, 8388608k, 8589934592。如果需要指定百分比,使用 0 到 1 之间的数字(例如, 0.25 for 25%)。

JVM Stack

-Xsssize

Sets the thread stack size (in bytes). Append the letter k or K to indicate KB, m or M to indicate MB, g or G to indicate GB. The default value depends on the platform:

  • Linux/ARM (32-bit): 320 KB
  • Linux/i386 (32-bit): 320 KB
  • Linux/x64 (64-bit): 1024 KB
  • OS X (64-bit): 1024 KB
  • Oracle Solaris/i386 (32-bit): 320 KB
  • Oracle Solaris/x64 (64-bit): 1024 KB

The following examples set the thread stack size to 1024 KB in different units:

-Xss1m
-Xss1024k
-Xss1048576

This option is equivalent to -XX:ThreadStackSize.

Heap

-Xmssize

Sets the initial size (in bytes) of the heap. This value must be a multiple of 1024 and greater than 1 MB. Append the letter k or K to indicate kilobytes, m or M to indicate megabytes, g or G to indicate gigabytes.

The following examples show how to set the size of allocated memory to 6 MB using various units:

-Xms6291456
-Xms6144k
-Xms6m

If you do not set this option, then the initial size will be set as the sum of the sizes allocated for the old generation and the young generation.

The -Xms option is equivalent to -XX:InitialHeapSize.

-Xmxsize

Specifies the maximum size (in bytes) of the memory allocation pool in bytes. This value must be a multiple of 1024 and greater than 2 MB. Append the letter k or K to indicate kilobytes, m or M to indicate megabytes, g or G to indicate gigabytes. The default value is chosen at runtime based on system configuration. For server deployments, -Xms and -Xmx are often set to the same value. See the section “Ergonomics” in Java SE HotSpot Virtual Machine Garbage Collection Tuning Guide at http://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/index.html.

The following examples show how to set the maximum allowed size of allocated memory to 80 MB using various units:

-Xmx83886080
-Xmx81920k
-Xmx80m

The -Xmx option is equivalent to -XX:MaxHeapSize.

-Xmnsize

Sets the initial and maximum size (in bytes) of the heap for the young generation (nursery). Append the letter k or K to indicate kilobytes, m or M to indicate megabytes, g or G to indicate gigabytes.

The young generation region of the heap is used for new objects. GC is performed in this region more often than in other regions. If the size for the young generation is too small, then a lot of minor garbage collections will be performed. If the size is too large, then only full garbage collections will be performed, which can take a long time to complete. Oracle recommends that you keep the size for the young generation between a half and a quarter of the overall heap size.

The following examples show how to set the initial and maximum size of young generation to 256 MB using various units:

-Xmn256m
-Xmn262144k
-Xmn268435456

Instead of the -Xmn option to set both the initial and maximum size of the heap for the young generation, you can use -XX:NewSize to set the initial size and -XX:MaxNewSize to set the maximum size.

参数 描述
-Xms-XX:InitialHeapSize -Xmx-XX:MaxHeapSize 设置 Heap 堆区的初始值和最大值,Server 端 JVM 建议将 -Xms-Xmx 设为相同值。
参数 描述
-Xmn -XX:NewSize -XX:MaxNewSize 设置 Heap 堆内 Young Generation,而 Old Generation 等于:堆区减去 -Xmn。 设置 -Xmn 等同于设置了相同的初始值 -XX:NewSize 和最大值 -XX:MaxNewSize
参数 描述
-XX:NewRatio 设置 Young Generation 和 Old Generation 的比值,例如该值为 3,则表示 Young Generation 和 Old Generation 比值为1:3。
-XX:SurvivorRatio 设置 Young Generation 中 E 区和 S 区的比例, 即 -XX:SurvivorRatio=eden/s0=eden/s1。

Method Area

PermGen

JDK 7 以前:

参数 描述
-XX:PermSize Perm 的初始值
-XX:MaxPermSize Perm 的最大值

JVM 的永久代(PermGen)主要用于存放 Class 的 meta-data,Class 在被 Loader 加载时就会被放到 PermGen space,GC 在主程序运行期间不会对该区进行清理,默认是 64M 大小,当程序需要加载的对象比较多时,超过 64M 就会报这部分内存溢出了,需要加大内存分配。

Metaspace

JDK 8 及以后,永久代(PermGen)的概念被废弃掉了,参考 JEP 122: Remove the Permanent Generation

The proposed implementation will allocate class meta-data in native memory and move interned Strings and class static variables to the Java heap.

Hotspot will explicitly allocate and free the native memory for the class meta-data. Allocation of new class meta-data would be limited by the amount of available native memory rather than fixed by the value of -XX:MaxPermSize, whether the default or specified on the command line.

取而代之的是一个称为 Metaspace 的存储空间。Metaspace 使用的是本地内存,而不是堆内存,也就是说在默认情况下 Metaspace 的大小只与本地内存大小有关。可以通过以下的几个参数对 Metaspace 进行控制:

参数 描述
-XX:MetaspaceSize Metaspace 的初始值
-XX:MaxMetaspaceSize Metaspace 的最大值

Direct Memory

-XX:MaxDirectMemorySize=size

Sets the maximum total size (in bytes) of the New I/O (the java.nio package) direct-buffer allocations. Append the letter k or K to indicate kilobytes, m or M to indicate megabytes, g or G to indicate gigabytes. By default, the size is set to 0, meaning that the JVM chooses the size for NIO direct-buffer allocations automatically.

The following examples illustrate how to set the NIO size to 1024 KB in different units:

-XX:MaxDirectMemorySize=1m
-XX:MaxDirectMemorySize=1024k
-XX:MaxDirectMemorySize=1048576

内存溢出

情况一

java.lang.OutOfMemoryError: Java heap space:这种是堆内存不够,一个原因是真不够,另一个原因是程序中有死循环,例如:

OutOfMemory

如果是堆内存不足,可调整 -Xms-Xmx,或者新老生代的比例。

情况二

java.lang.OutOfMemoryError: PermGen space:这种是P区内存不够,可调整:-XX:PermSize-XX:MaxPermSize

情况三

java.lang.StackOverflowError:线程栈溢出,要么是方法调用层次过多(比如存在无限递归调用):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kQuKreLW-1612059836137)(https://qidawu.github.io/img/java/jvm/SOF.png)]

要么是线程栈太小,可调整 -Xss 参数增加线程栈大小。

垃圾收集器总结

gc_summary

基于分代收集算法的垃圾收集器组合,总结如下图,常用于 JDK 8 及之前的版本:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8SHP4Qxu-1612059836140)(https://qidawu.github.io/img/java/jvm/gc_combination.png)]

性能监控、故障处理工具总结

分类

命令行工具

命令 全称 作用 备注
jps 虚拟机进程状况工具(JVM Process Status Tool), 显示正在运行的所有 HotSpot VM 进程。
jstat 虚拟机统计信息监控工具(JVM Statistics Monitoring Tool) 用于监视本地或远程 HotSpot VM 各方面的运行数据,例如类加载/卸载、运行时数据区、GC、JIT。
jinfo Java 配置信息工具(Configuration Info for Java) 实时显示或修改虚拟机配置信息。例如 jinfo -flag MetaspaceSize VMID 在 JDK 9 中已集成到 JHSDB
jmap Memory Map for Java 用于实时生成虚拟机的堆内存转储快照(heap dump/hprof 文件),或查看堆内存信息。其它转储方法: -XX:+HeapDumpOnOutOfMemoryError -XX:+HeapDumpOnCtrlBreak 在 JDK 9 中已集成到 JHSDB
jhat JVM Heap Dump Browser 用于分析 heap dump 文件,它会建立一个 HTTP/HTML 服务器,让用户可以在浏览器上查看分析结果。分析结果默认以包为单位进行分组显示,分析内存泄漏问题主要会使用到其中的 Heap Histogram(与 jmap -histo 功能一样)与 OQL 页签功能,前者可以找到内存中总容量最大的对象,后者是标准的对象查询语言,使用类似 SQL 的语法堆内存中的对象进行查询统计。 在 JDK 9 中已被 JHSDB 替代
jstack Stack Trace for Java 显示虚拟机当前时刻的线程快照(thread dump/javacore 文件)。线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合,生成堆栈快照的目的通常是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间挂起等,都是导致线程长时间停顿的常见原因。 在 JDK 9 中已集成到 JHSDB

可视化工具

命令 名称 基于 作用 备注
jconsole Java Console JMX 一款基于 JMX (Java Management Extensions) 的可视化监视、管理工具。它的主要功能是通过 JMX 的 MBean (Managed Bean) 对系统进行信息收集和参数动态调整。 JDK 5 起免费提供
jvisualvm Java VisualVM 无需特殊 Agent 拥有丰富的插件扩展。目前已经从 Oracle JDK 中分离出来,成为一个独立发展的开源项目:http://visualvm.github.io/ JDK 6 起免费提供
jmc Java Mission Control Java Flight Recorder, JFR 曾经是 BEA 公司的图形化诊断工具,随着 BEA 公司被 Oracle 收购而融合进 Oracle JDK。从 JDK 11 开始已被移除出 JDK。2018 年开源并交付给 Open JDK 组织管理。需要与 HotSpot 内部的 JFR 配合才能工作。 JDK 7 起付费提供
jhsdb Java HotSpot Debugger Serviceability Agent 一个基于 Serviceability Agent 的 HotSpot 进程调试器。 JDK 9 起免费提供

反汇编工具

大多数情况下,通过诸如javap等反编译工具来查看源码的字节码已经能够满足我们的日常需求,但是不排除在有些特定场景下,我们需要通过反汇编来查看相应的汇编指令。本文我们就来介绍两个很好用的工具——HSDIS、JITWatch

工具 描述
HSDIS (HotSpot disassembler) 一款 HotSpot 虚拟机 JIT 编译代码的反汇编插件。
JITWatch 用于可视化分析。

https://zhuanlan.zhihu.com/p/158168592?from_voters_page=true

命令行工具

jps

jps 命令的功能与 ps 类似,用于列出正在运行的 JVM 进程状态。

常用参数:

  • -q 只输出 LVMID,省略主类的名称。
  • -l 输出主类的全名,如果进程执行的是 JAR 包,则输出 JAR 路径。
  • -m 输出虚拟机进程启动时传递给主类 main() 函数的参数。
  • -v 输出虚拟机进程启动时的 JVM 参数。

jstat

jstat 命令用于监视当前 JVM 的各种运行状态信息。在用户体验上也许不如 JMC、VisualVM 等可视化监控工具以图表形式展示那样直观,但在实际生产环境中不一定可以使用 GUI 图形界面,因此在没有 GUI、只提供命令行界面的服务器上,仍是运行期定位虚拟机性能问题的常用工具。

命令格式:

$ jstat options vmid [interval[s|ms] [count]]

常用参数:

  • options,要查询的虚拟机信息,主要分为三类:
    • 类加载
      • -class 监视类加载、卸载数量、总空间以及类加载所耗费的时间
    • 运行时数据区、GC
      • -gccapacity 查看 GC 情况和 JVM 各区的容量(字节)
      • -gc 查看 GC 情况和 JVM 各区的容量使用量(字节)
      • -gcutil 查看 GC 情况和 JVM 各区的使用率(%)
    • JIT
      • -compiler 输出即时编译器编译过的方法、耗时等信息
      • -printcompilation 输出已经被即时编译的方法
  • vmid,如果是本地虚拟机进程,VMID 与 LVMID 一致;如果是远程虚拟机进程,则 VMID 的格式为:[protocol:][//]lvmid[@hostname[:port]/servername]
  • interval,间隔时间,单位为秒或者毫秒
  • count,打印次数,如果缺省则打印无数次

示例展示:

此示例连接到 lvmid 21891,并以 250 毫秒的间隔获取 7 个样本,每 6 行显示一次标题([-h<lines>]),并显示由 -gcutil 选项指定的输出:

$ jstat -gcutil -h6 21891 250 7

  S0     S1     E      O      P     YGC    YGCT    FGC    FGCT     GCT

 12.44   0.00  27.20   9.49  96.70    78    0.176     5    0.495    0.672

 12.44   0.00  62.16   9.49  96.70    78    0.176     5    0.495    0.672

 12.44   0.00  83.97   9.49  96.70    78    0.176     5    0.495    0.672

  0.00   7.74   0.00   9.51  96.70    79    0.177     5    0.495    0.673

  0.00   7.74  23.37   9.51  96.70    79    0.177     5    0.495    0.673

  0.00   7.74  43.82   9.51  96.70    79    0.177     5    0.495    0.673

  S0     S1     E      O      P     YGC    YGCT    FGC    FGCT     GCT

  0.00   7.74  58.11   9.51  96.71    79    0.177     5    0.495    0.673

该示例结果显示,对象首先都在 Eden 区中创建,在第 3 和第 4 个样本之间由于 Eden 区装满,发生了 Young GC, gc 耗时 0.001 秒,并将对象从 Eden 区(E)提升到 Old 区(O),导致 Old 区的使用率从 9.49% 增加到 9.51%。

-gcutil 选项每列说明:

列名 描述
S0 Heap 上的 Survivor space 0 区(单位**%**)
S1 Heap 上的 Survivor space 1 区(单位**%**)
E Heap 上的 Eden space 区(单位**%**)
O Heap 上的 Old space 区(单位**%**)
P Perm space 区(单位**%**)
列名 描述
YGC 从应用程序启动到采样时发生 Young GC 的次数,E 区满后触发
YGCT 从应用程序启动到采样时 Young GC 所用的时间(单位秒)
FGC 从应用程序启动到采样时发生 Full GC 的次数, O 区满后触发
FGCT 从应用程序启动到采样时 Full GC 所用的时间(单位秒)
GCT 从应用程序启动到采样时用于垃圾回收的总时间(单位秒)

jmap

jmap 命令用于生成虚拟机的内存转储快照(heap dump 文件),或查看堆内存信息。

常用参数:

  • -dump 生成 Java 堆转储快照。格式为 -dump:[live,]format=b,file=<filename>,其中 live 子参数表示是否只 dump 出存活的对象。
  • -histo 显示堆中对象统计信息,包括类、实例数量、合计容量。
  • -heap 查看当前堆内存的详细信息,如配置信息 Heap Configuration、使用情况 Heap Usage
$ jmap -heap 21090
Attaching to process ID 21090, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 24.79-b02

using thread-local object allocation.
Parallel GC with 4 thread(s)

Heap Configuration:
   MinHeapFreeRatio = 0
   MaxHeapFreeRatio = 100
   MaxHeapSize      = 3145728000 (3000.0MB)
   NewSize          = 2097152000 (2000.0MB)
   MaxNewSize       = 2097152000 (2000.0MB)
   OldSize          = 5439488 (5.1875MB)
   NewRatio         = 2
   SurvivorRatio    = 8
   PermSize         = 268435456 (256.0MB)  // JDK8+ MetaspaceSize
   MaxPermSize      = 268435456 (256.0MB)  // JDK8+ MaxMetaspaceSize
   G1HeapRegionSize = 0 (0.0MB)

Heap Usage:
PS Young Generation
Eden Space:
   capacity = 1762656256 (1681.0MB)
   used     = 1420607552 (1354.7969360351562MB)
   free     = 342048704 (326.20306396484375MB)
   80.59470172725499% used
From Space:
   capacity = 138412032 (132.0MB)
   used     = 0 (0.0MB)
   free     = 138412032 (132.0MB)
   0.0% used
To Space:
   capacity = 138412032 (132.0MB)
   used     = 0 (0.0MB)
   free     = 138412032 (132.0MB)
   0.0% used
PS Old Generation
   capacity = 1048576000 (1000.0MB)
   used     = 1048403072 (999.8350830078125MB)
   free     = 172928 (0.1649169921875MB)
   99.98350830078125% used
PS Perm Generation  // JDK8+ 没有该区域
   capacity = 268435456 (256.0MB)
   used     = 67917928 (64.7715835571289MB)
   free     = 200517528 (191.2284164428711MB)
   25.30139982700348% used

注意,由于此例中使用的 JDK 7 版本,因此 Heap 中包含 Perm Generation。如果使用的 JDK 8 以上版本,则 Heap 不再包含此区域,取而代之的是在 Heap 之外有一块 Metaspace。

例如上述例子通过 jmap -heap pid 命令发现了某个服务 O 区内存被占满的问题:Old Generation 达到 99.98350830078125% used,O 区内存被占满,可以通过 jstack 继续排查 JVM 内存的动态使用情况

jstack

jstack 命令用于 dump 出当前线程堆栈快照,根据堆栈信息我们可以定位到具体代码,所以它在 JVM 性能调优中使用得非常多。

$ jstack 21090 > /tmp/threaddump
$ less /tmp/localfile

Full thread dump Java HotSpot(TM) 64-Bit Server VM (24.79-b02 mixed mode):

"Attach Listener" daemon prio=10 tid=0x00007f67e03b4800 nid=0x7bb9 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"catalina-exec-8000" daemon prio=10 tid=0x00007f67ba4a0000 nid=0x795a waiting on condition [0x00007f6558c0a000]
   java.lang.Thread.State: WAITING (parking)
	at sun.misc.Unsafe.park(Native Method)
	- parking to wait for  <0x00000007886ab360> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
	at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2043)
	at org.apache.http.pool.PoolEntryFuture.await(PoolEntryFuture.java:139)
	at org.apache.http.pool.AbstractConnPool.getPoolEntryBlocking(AbstractConnPool.java:307)
	at org.apache.http.pool.AbstractConnPool.access$000(AbstractConnPool.java:65)
	at org.apache.http.pool.AbstractConnPool$2.getPoolEntry(AbstractConnPool.java:193)
	at org.apache.http.pool.AbstractConnPool$2.getPoolEntry(AbstractConnPool.java:186)
	at org.apache.http.pool.PoolEntryFuture.get(PoolEntryFuture.java:108)
	at org.apache.http.impl.conn.PoolingClientConnectionManager.leaseConnection(PoolingClientConnectionManager.java:212)
	at org.apache.http.impl.conn.PoolingClientConnectionManager$1.getConnection(PoolingClientConnectionManager.java:199)
	at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:424)
	at org.apache.http.impl.client.AbstractHttpClient.doExecute(AbstractHttpClient.java:884)
	at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82)
	at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:107)
	at com.xxx.xxx.xxx.HttpClientService.doPost(HttpClientService.java:103)
	......

由于导出的 threaddump 文件非常大,可以先统计下所有线程、或关注的线程分别处于什么状态:

$ grep /tmp/threaddump | awk '{print $2$3$4$5}' | sort | uniq -c | sort

39  RUNNABLE
21  TIMED_WAITING (onobjectmonitor)
6   TIMED_WAITING (parking)
51  TIMED_WAITING (sleeping)
3   WAITING (onobjectmonitor)
305 WAITING (parking)

发现有大量 WAITING (parking) 状态的线程。重新打开 threaddump 文件排查,根据堆栈可以定位到具体的问题代码,可以初步判断是 HTTP 连接耗尽资源导致的问题。

参考

《深入理解 Java 虚拟机》

猜你喜欢

转载自blog.csdn.net/younow22/article/details/113458789