手绘的运行时数据区如下:
JVM 定义了在程序执行期间使用的各种运行时数据区:
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 thereturnAddress
type are pointers to the opcodes of Java Virtual Machine instructions. Unlike the numeric primitive types, thereturnAddress
type does not correspond to any Java programming language type and cannot be modified by the running program.
每个 JVM 线程都有一个私有的 JVM Stack 栈区,与该线程同时创建。JVM Stack 存储栈帧(参考 栈帧(Frames)一节)。JVM Stack 类似于常规编程语言(例如 C 语言):它保存局部变量和部分结果,并在方法调用和返回中起作用。由于 JVM Stack 的操作只有出栈和入栈,因此栈帧可能堆积。JVM Stack 的内存空间不必连续。
规范允许 JVM Stack 要么是固定大小(通过 -Xss
指定大小)、要么是根据计算的需要进行动态扩容和缩容。如果 JVM Stack 的大小固定,则在创建每个 JVM Stack 时可以独立选择其大小。
以下异常情况与 JVM Stack 相关:
StackOverflowError
。OutOfMemoryError
。栈帧用于:
每次调用方法时都会创建一个栈帧。当方法调用完毕,无论是正常还是异常结束(例如抛出了未捕获的异常),栈帧都会销毁。栈帧由 JVM 栈区创建。每个栈帧都有它自己的局部变量数组(Local Variables Array)、操作数栈(Operand Stacks)、以及对当前类当前方法的运行时常量池的引用。
局部变量数组和操作数栈的大小在编译时确定,并与栈帧关联的方法代码(参考 The Code
Attribute)一起提供。因此,栈帧数据结构的大小仅取决于 JVM 的实现,并且可以在方法调用时分配用于这些结构的内存。
在给定线程的任何时候,只有一个在执行方法中的栈帧处于活动状态。该活动栈帧称为当前帧(current frame),该方法称为当前方法(current method),定义当前方法的类称为当前类(current class)。局部变量和操作数栈上的操作引用当前帧。
如果当前方法调用了另一个方法或者该方法执行完毕,则该方法所处的帧不再是当前帧。调用方法时,将创建新的栈帧,并在控制权转移到新方法时变为当前帧。当方法返回时,当前帧将其方法调用的结果(如有)传递回前一帧并被丢弃,然后前一帧变回当前帧。
注意,由线程创建的栈帧仅线程自身可见,无法被其它线程所引用。
JVM 具有一个在所有 JVM 线程之间共享的堆区(Heap),用于分配所有类实例和数组所需的内存。
堆区在虚拟机启动时创建。堆中的对象由垃圾收集器(garbage collector)进行回收。对象永远不会显式释放。JVM 不假定任何类型的垃圾收集器,而由实现者根据系统要求自行选择实现。
堆的大小可以是固定的,也可以根据计算的需要进行扩容,如果不需要更大空间的堆,可以进行缩容。堆区的内存空间不必连续。
JVM 实现可以为用户提供堆的初始值配置。并且,如果堆可以动态扩容和缩容,还需提供堆的最大、最小值配置。
以下异常情况与堆相关:
OutOfMemoryError
。JVM 具有一个在所有 JVM 线程之间共享的方法区(Method Area)。方法区类似于常规编程语言的编译代码的存储区域。它存储每个类的结构,例如运行时常量池、字段(field)及方法(method)的数据、以及方法(methods)和构造方法(constructors)的代码,包括用于类及其实例初始化和接口初始化的特殊方法(参考 Special Methods)。
方法区在虚拟机启动时创建。尽管方法区在逻辑上是堆区的一部分,但是 JVM 实现可以选择不进行垃圾回收或压缩。JVM 规范没有规定方法区的位置或用于管理已编译代码的策略。
方法区可以是固定大小的,或者根据计算的需要进行扩容,如果无需更大空间的方法区,可以进行缩容。方法区的内存空间不必连续。
JVM 实现可以为用户提供方法区的初始值配置。在方法区大小可变的情况下,可以提供最大、最小值配置。
以下异常情况与方法区相关:
OutOfMemoryError
。运行时常量池是每个类或每个接口的 class
文件中 constant_pool
表(参考 The Constant Pool)的运行时表示。它包含多种常量,范围从编译时已知的数值型的字面值(numeric literals)到必须在运行时解析的方法和字段引用。运行时常量池的功能类似于常规编程语言的符号表,尽管它包含的数据范围比典型的符号表要大。
每个运行时常量池都由方法区分配。当 JVM 创建类或接口时(参考 Creation and Loading Class),将构造该类或接口的运行时常量池。
以下异常情况与方法区相关:
OutOfMemoryError
。java [options*] *classname [args]
java [options*] -jar *filename [args]
-jar
option.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 用于禁用。对于需要参数的选项,每个选项的确切语法有所差异:参数可以用空格、冒号(:
)或等号(=
)与选项名分开,或者参数可以直接跟在选项后面,具体参考文档。
如果需要指定字节大小,可以使用以下几种格式:
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%)。
-Xss
size
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:
The following examples set the thread stack size to 1024 KB in different units:
-Xss1m
-Xss1024k
-Xss1048576
This option is equivalent to -XX:ThreadStackSize
.
-Xms
size
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
.
-Xmx
size
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
.
-Xmn
size
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。 |
JDK 7 以前:
参数 | 描述 |
---|---|
-XX:PermSize |
Perm 的初始值 |
-XX:MaxPermSize |
Perm 的最大值 |
JVM 的永久代(PermGen)主要用于存放 Class 的 meta-data,Class 在被 Loader 加载时就会被放到 PermGen space,GC 在主程序运行期间不会对该区进行清理,默认是 64M 大小,当程序需要加载的对象比较多时,超过 64M 就会报这部分内存溢出了,需要加大内存分配。
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 的最大值 |
-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
:这种是堆内存不够,一个原因是真不够,另一个原因是程序中有死循环,例如:
如果是堆内存不足,可调整 -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
参数增加线程栈大小。
基于分代收集算法的垃圾收集器组合,总结如下图,常用于 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
命令的功能与 ps
类似,用于列出正在运行的 JVM 进程状态。
常用参数:
-q
只输出 LVMID,省略主类的名称。-l
输出主类的全名,如果进程执行的是 JAR 包,则输出 JAR 路径。-m
输出虚拟机进程启动时传递给主类 main()
函数的参数。-v
输出虚拟机进程启动时的 JVM 参数。jstat
命令用于监视当前 JVM 的各种运行状态信息。在用户体验上也许不如 JMC、VisualVM 等可视化监控工具以图表形式展示那样直观,但在实际生产环境中不一定可以使用 GUI 图形界面,因此在没有 GUI、只提供命令行界面的服务器上,仍是运行期定位虚拟机性能问题的常用工具。
命令格式:
$ jstat options vmid [interval[s|ms] [count]]
常用参数:
-class
监视类加载、卸载数量、总空间以及类加载所耗费的时间-gccapacity
查看 GC 情况和 JVM 各区的容量(字节)-gc
查看 GC 情况和 JVM 各区的容量和使用量(字节)-gcutil
查看 GC 情况和 JVM 各区的使用率(%)-compiler
输出即时编译器编译过的方法、耗时等信息-printcompilation
输出已经被即时编译的方法[protocol:][//]lvmid[@hostname[:port]/servername]
示例展示:
此示例连接到 lvmid 21891,并以 250 毫秒的间隔获取 7 个样本,每 6 行显示一次标题([-h
),并显示由 -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
命令用于生成虚拟机的内存转储快照(heap dump 文件),或查看堆内存信息。
常用参数:
-dump
生成 Java 堆转储快照。格式为 -dump:[live,]format=b,file=
,其中 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
命令用于 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 虚拟机》