jmap——java内存映射工具
jdk安装后会自带一些小工具,jmap命令(Memory Map for Java)是其中之一。主要用于打印指定Java进程(或核心文件、远程调试服务器)的共享对象内存映射或堆内存细节。
jmap命令可以获得运行中的jvm的堆的快照,从而可以离线分析堆,以检查内存泄漏,检查一些严重影响性能的大对象的创建,检查系统中什么对象最多,各种对象所占内存的大小等等。可以使用jmap生成Heap Dump。
如果不想使用jmap命令,要想获取Java堆转储快照还有一些比较“暴力”的手段:譬如在前面用过的 -XX:+HeapDumpOnOutOfMemoryError参数,可以让虚拟机在OOM异常出现之后自动生成dump文件,通过-XX:+HeapDumpOnCtrlBreak参数可以使用[ctrl]+[Break]键让虚拟机生成dump文件,又或者在Linux系统下通过Kill -3 命令发送进程退出信息“恐吓”一下虚拟机,也能拿到dump文件。
jmap的作用并不仅仅是为了获取dump文件,他还可以查询finalize执行队列,java堆和永久代的详细信息,如空间使用率、当前用的是哪种收集器等。
jmap命令格式
[root@ady01 ~]# jmap
Usage:
jmap [option]
(to connect to running process)
jmap [option]
(to connect to a core file)
jmap [option] [server_id@]
(to connect to remote debug server)
where
主要选项:
选项 作用
-dump 生成java堆转储快照,格式为:-dump:[live,]format=b,file=
-finalizerinfo 显示在F-Queue中等待Finalizer线程执行finalize方法的对象,只在linux/solaris平台下有效
-heap 显示堆详细信息,如使用哪种回收期、参数配置、分带状况等,只在linux/solaris平台下有效
-histo 显示堆中对象统计信息,包括类、实例数量和合计容量
-permstat 以ClassLoader为统计口径显示永久代内存状况,只在linux/solaris平台下有效
-F 当虚拟机进程对-dump选项没有响应时,可以使用这个选项强制生成dump快照,只在linux/solaris平台下有效
jmap -dump:生成java堆转储快照
生成java对转存快照,格式:jmap -dump:[live,]format=b,file=文件名
C:\Users\Think>jmap -dump:live,format=b,file=D:/dumptest.hprof 13984
Dumping heap to D:\dumptest.hprof ...
Heap dump file created
可以使用jdk提供的jvisualvm.exe查看hprof文件
jmap -heap:显示堆详细信息
显示堆详细信息。
注意:使用时报错排查原因是由于机器上安装了多个jdk导致的。所以使用时要指定路径。
E:\java8\jdk\bin\jmap -heap 13984
命令格式:jmap -heap
[root@ady01 ~]# jmap -heap 25867
Attaching to process ID 25867, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.181-b13
using thread-local object allocation.
Parallel GC with 2 thread(s)
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 4164943872 (3972.0MB)
NewSize = 87031808 (83.0MB)
MaxNewSize = 1388314624 (1324.0MB)
OldSize = 175112192 (167.0MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
PS Young Generation
Eden Space:
capacity = 47710208 (45.5MB)
used = 2632072 (2.5101394653320312MB)
free = 45078136 (42.98986053466797MB)
5.516790033696772% used
From Space:
capacity = 1048576 (1.0MB)
used = 770128 (0.7344512939453125MB)
free = 278448 (0.2655487060546875MB)
73.44512939453125% used
To Space:
capacity = 524288 (0.5MB)
used = 0 (0.0MB)
free = 524288 (0.5MB)
0.0% used
PS Old Generation
capacity = 220200960 (210.0MB)
used = 98595728 (94.02821350097656MB)
free = 121605232 (115.97178649902344MB)
44.77533976236979% used
38803 interned Strings occupying 4463232 bytes.
jmap -histo:显示堆中对象统计信息
显示堆中对象统计信息,包括类、实例数量和合计容量
命令格式:jmap -histo[:live]
C:\Users\Think>jmap -histo 28252
num #instances #bytes class name
----------------------------------------------
1: 309006 16963968 [C
2: 1081 7275840 [I
3: 41164 3156952 [B
4: 90125 2163000 java.lang.String
5: 21000 672000 java.util.UUID
6: 21000 336000 com.jvm.test8.Test8$User
7: 21000 336000 com.jvm.test8.Test8$User$UserBuilder
8: 799 300072 [Ljava.lang.Object;
9: 7557 181368 java.lang.StringBuilder
10: 772 87704 java.lang.Class
11: 1026 65664 sun.nio.fs.WindowsFileAttributes
12: 1026 49248 sun.nio.fs.WindowsPath$WindowsPathWithAttributes
13: 837 33480 java.util.TreeMap$Entry
14: 1032 33024 java.lang.ref.WeakReference
15: 775 31000 sun.nio.fs.WindowsPath
16: 1028 24672 sun.nio.fs.WindowsPathParser$Result
17: 424 13568 java.io.File
18: 168 12096 java.lang.reflect.Field
19: 299 11960 java.util.LinkedHashMap$Entry
20: 323 11200 [Ljava.lang.String;
21: 173 11072 java.net.URL
22: 341 10912 sun.misc.FDBigInteger
23: 312 9984 java.util.Hashtable$Entry
24: 66 8992 [Ljava.util.HashMap$Node;
25: 267 8544 java.util.HashMap$Node
26: 195 7800 java.lang.ref.Finalizer
27: 264 6336 java.lang.StringBuffer
28: 121 4840 java.lang.ref.SoftReference
29: 29 4816 [Ljava.util.Hashtable$Entry;
30: 50 4800 java.util.jar.JarFile$JarFileEntry
31: 105 4552 [[C
32: 53 4240 [Ljava.util.WeakHashMap$Entry;
33: 74 4144 sun.misc.URLClassPath$JarLoader
34: 258 4128 java.lang.Integer
35: 50 4000 java.util.zip.ZipEntry
36: 79 3792 java.net.NetworkInterface
37: 150 3600 java.net.Parts
38: 111 3552 java.util.concurrent.ConcurrentHashMap$Node
39: 134 3216 java.security.Provider$ServiceKey
40: 50 3200 java.util.jar.JarFile
41: 55 3080 sun.nio.cs.UTF_8$Encoder
42: 8 3008 java.lang.Thread
43: 62 2976 java.util.HashMap
44: 51 2856 java.util.zip.ZipFile$ZipFileInputStream
45: 114 2736 java.io.ExpiringCache$Entry
46: 53 2544 java.util.WeakHashMap
47: 30 2400 java.lang.reflect.Constructor
48: 56 2240 java.util.WeakHashMap$Entry
49: 39 2184 java.util.zip.ZipFile$ZipFileInflaterInputStream
疑问:
Q: 如何dump堆快照,如何使用jvisualvm.exe查看java进程上dump下来的hprof文件?
A: 首选,我们dump堆快照可以使用jmap命令手动dump下想要获取的堆快照。格式如下:
jmap -dump:[live,]format=b,file=文件名
jmap -dump:live,format=b,file=D:/1.hprof 24956
其次,如果不想使用jmap命令,要想获取Java堆转储快照还有一些比较“暴力”的手段:譬如在前面用过的 -XX:+HeapDumpOnOutOfMemoryError参数,可以让虚拟机在OOM异常出现之后自动生成dump文件,通过-XX:+HeapDumpOnCtrlBreak参数可以使用[ctrl]+[Break]键让虚拟机生成dump文件,又或者在Linux系统下通过Kill -3 命令发送进程退出信息“恐吓”一下虚拟机,也能拿到dump文件。
windows使用jvisualvm.exe查看java进程上dump操作如下:
1、进入jdk按照的bin目录打开jvisualvm.exe。
E:\java8\jdk\bin
2、点击文件装入取选取我们dump下的文件位置。注意要更改装入的文件类型为.hprof文件。
3、切换到类选型就可以查看当前dump文件中存活的类占比较大的是什么对象。从而进一步分析内存溢出的原因。
jhat——虚拟机堆转储快照分析工具——一般很少用这个,而是用集成工具
jhat也是jdk内置的工具之一。主要是用来分析java堆的命令,可以将堆中的对象以html的形式显示出来,包括对象的数量,大小等等,并支持对象查询语言。
使用jmap等方法生成java的堆文件后,使用其进行分析
示例:
1.运行代码:
package com.jvm.test8;
import lombok.*;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class Test8 {
@Getter
@Setter
@ToString
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class User {
private String name;
}
public static void main(String[] args) throws InterruptedException {
List list = new ArrayList<>();
for (int i = 0; i < 3000; i++) {
for (int j = 0; j < 1000; j++) {
list.add(User.builder().name(UUID.randomUUID().toString()).build());
}
TimeUnit.SECONDS.sleep(1);
}
}
}
2.导出程序执行的堆信息——jmap
F:\fcargitnew\hellospringboot>jps -l
13984
12468 sun.tools.jps.Jps
6324 org.jetbrains.jps.cmdline.Launcher
6908 com.self.test.Test2
8908 org.jetbrains.idea.maven.server.RemoteMavenServer
F:\fcargitnew\hellospringboot>jmap -dump:live,format=b,file=D:/dump.hprof 6908
Dumping heap to D:\dump.hprof ...
Heap dump file created
3.使用jhat分析堆文件
F:\fcargitnew\hellospringboot>jmap -dump:live,format=b,file=D:/dump.hprof 6908
Dumping heap to D:\dump.hprof ...
Heap dump file created
F:\fcargitnew\hellospringboot>jhat D:/dump.hprof
Reading from D:/dump.hprof...
Dump file created Mon Oct 19 11:21:10 CST 2020
Snapshot read, resolving...
Resolving 179681 objects...
Chasing references, expect 35 dots...................................
Eliminating duplicate references...................................
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.
4.查看html
- 访问:http://localhost:7000/
分析内存泄露问题主要会用到“Show heap histogram”“”和“OQL”,前者可以找到内存中总容量最大的对象,后者是标准的对象查询语言,使用类似于SQL的语法对内存对象进行查询统计。
- 显示出堆中所包含的所有的类
- 从根集能引用到的对象
- 显示所有类(包括平台)的实例计数
- 堆实例的分布表
- 执行对象查询语句
输入内容如:
查询长度大于100的字符串
select s from java.lang.String s where s.count > 100
详细的OQL可点击上图的“OQL help”
jhat中的OQL(对象查询语言) ,文档可以查看:http://localhost:7000/oqlhelp/
如果需要根据某些条件来过滤或查询堆的对象,这是可能的,可以在jhat的html页面中执行OQL,来查询符合条件的对象
基本语法:
select
[from [instanceof] ]
[where ]
解释:
(1)class name是java类的完全限定名,如:java.lang.String, java.util.ArrayList, [C是char数组, [Ljava.io.File是java.io.File[]
(2)类的完全限定名不足以唯一的辨识一个类,因为不同的ClassLoader载入的相同的类,它们在jvm中是不同类型的
(3)instanceof表示也查询某一个类的子类,如果不明确instanceof,则只精确查询class name指定的类
(4)from和where子句都是可选的
(5)java域表示:obj.field_name;java数组表示:array[index]
举例:
(1)查询长度大于100的字符串
select s from java.lang.String s where s.count > 100
(2)查询长度大于256的数组
select a from [I a where a.length > 256
(3)显示匹配某一正则表达式的字符串
select a.value.toString() from java.lang.String s where /java/(s.value.toString())
(4)显示所有文件对象的文件路径
select file.path.value.toString() from java.io.File file
(5)显示所有ClassLoader的类名
select classof(cl).name from instanceof java.lang.ClassLoader cl
(6)通过引用查询对象
select o from instanceof 0xd404d404 o
built-in对象 -- heap
(1)heap.findClass(class name) -- 找到类
select heap.findClass("java.lang.String").superclass
(2)heap.findObject(object id) -- 找到对象
select heap.findObject("0xd404d404")
(3)heap.classes -- 所有类的枚举
select heap.classes
(4)heap.objects -- 所有对象的枚举
select heap.objects("java.lang.String")
(5)heap.finalizables -- 等待垃圾收集的java对象的枚举
(6)heap.livepaths -- 某一对象存活路径
select heaplivepaths(s) from java.lang.String s
(7)heap.roots -- 堆根集的枚举
辨识对象的函数
(1)classof(class name) -- 返回java对象的类对象
select classof(cl).name from instanceof java.lang.ClassLoader cl
(2)identical(object1,object2) -- 返回是否两个对象是同一个实例
select identical(heap.findClass("java.lang.String").name, heap.findClass("java.lang.String").name)
(3)objectid(object) -- 返回对象的id
select objectid(s) from java.lang.String s
(4)reachables -- 返回可从对象可到达的对象
select reachables(p) from java.util.Properties p -- 查询从Properties对象可到达的对象
select reachables(u, "java.net.URL.handler") from java.net.URL u -- 查询从URL对象可到达的对象,但不包括从URL.handler可到达的对象
(5)referrers(object) -- 返回引用某一对象的对象
select referrers(s) from java.lang.String s where s.count > 100
(6)referees(object) -- 返回某一对象引用的对象
select referees(s) from java.lang.String s where s.count > 100
(7)refers(object1,object2) -- 返回是否第一个对象引用第二个对象
select refers(heap.findObject("0xd4d4d4d4"),heap.findObject("0xe4e4e4e4"))
(8)root(object) -- 返回是否对象是根集的成员
select root(heap.findObject("0xd4d4d4d4"))
(9)sizeof(object) -- 返回对象的大小
select sizeof(o) from [I o
(10)toHtml(object) -- 返回对象的html格式
select "" + toHtml(o) + "" from java.lang.Object o
(11)选择多值
select {name:t.name?t.name.toString():"null",thread:t} from instanceof java.lang.Thread t
数组、迭代器等函数
(1)concat(enumeration1,enumeration2) -- 将数组或枚举进行连接
select concat(referrers(p),referrers(p)) from java.util.Properties p
(2)contains(array, expression) -- 数组中元素是否满足某表达式
select p from java.util.Properties where contains(referres(p), "classof(it).name == 'java.lang.Class'")
返回由java.lang.Class引用的java.util.Properties对象
built-in变量
it -- 当前的迭代元素
index -- 当前迭代元素的索引
array -- 被迭代的数组
(3)count(array, expression) -- 满足某一条件的元素的数量
select count(heap.classes(), "/java.io./(it.name)")
(4)filter(array, expression) -- 过滤出满足某一条件的元素
select filter(heap.classes(), "/java.io./(it.name)")
(5)length(array) -- 返回数组长度
select length(heap.classes())
(6)map(array,expression) -- 根据表达式对数组中的元素进行转换映射
select map(heap.classes(),"index + '-->' + toHtml(it)")
(7)max(array,expression) -- 最大值, min(array,expression)
select max(heap.objects("java.lang.String"),"lhs.count>rhs.count")
built-in变量
lhs -- 左边元素
rhs -- 右边元素
(8)sort(array,expression) -- 排序
select sort(heap.objects('[C'),'sizeof(lhs)-sizeof(rhs)')
(9)sum(array,expression) -- 求和
select sum(heap.objects('[C'),'sizeof(it)')
(10)toArray(array) -- 返回数组
(11)unique(array) -- 唯一化数组
jstack——java栈跟踪工具
jstack介绍
jstack(stack trace for java)是java虚拟机自带的一种堆栈跟踪工具。jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息,如果是在64位机器上,需要指定选项"-J-d64",Windows的jstack使用方式只支持以下的这种方式:
jstack [-l] pid
主要分为两个功能:
- 针对活着的进程做本地的或远程的线程dump
- 针对core文件做线程dump
jstack用于生成java虚拟机当前时刻的线程快照。
线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。
线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。
如果java程序崩溃生成core文件,jstack工具可以用来获得core文件的java stack和native stack的信息,从而可以轻松地知道java程序是如何崩溃和在程序何处发生问题。
另外,jstack工具还可以附属到正在运行的java程序中,看到当时运行的java程序的java stack和native stack的信息, 如果现在运行的java程序呈现hung的状态,jstack是非常有用的。
So,jstack命令主要用来查看Java线程的调用堆栈的,可以用来分析线程问题(如死锁)。
线程状态
想要通过jstack命令来分析线程的情况的话,首先要知道线程都有哪些状态,下面这些状态是我们使用jstack命令查看线程堆栈信息时可能会看到的线程的6种状态:
- NEW:未启动的。不会出现在Dump中。
- RUNNABLE:在虚拟机内执行的。运行中状态,可能里面还能看到locked字样,表明它获得了某把锁。
- BLOCKED:受阻塞并等待监视器锁。被某个锁(synchronizers)給block住了。
- WATING:无限期等待另一个线程执行特定操作。等待某个condition或monitor发生,一般停留在park(), wait(),sleep(),join() 等语句里。
- TIMED_WATING:有时限的等待另一个线程的特定操作。和WAITING的区别是wait() 等语句加上了时间限制 wait(timeout)。
- TERMINATED:已退出的。
关于线程状态,具体也可以查看:java.lang.Thread.State类。
Monitor(监视器)
在多线程的 JAVA程序中,实现线程之间的同步,就要说说 Monitor。 Monitor是 Java中用以实现线程之间的互斥与协作的主要手段,它可以看成是对象或者 Class的锁。每一个对象都有,也仅有一个 monitor。下面这个图,描述了线程和 Monitor之间关系,以 及线程的状态转换图:
- 进入区(Entrt Set):表示线程通过synchronized要求获取对象的锁。如果对象未被锁住(即获得到锁),则进入拥有者;否则则在进入区等待。一旦对象锁被其他线程释放,立即参与竞争。
- 拥有者(The Owner):表示某一线程成功竞争到对象锁。
- 等待区(Wait Set):表示线程通过对象的wait方法,释放对象的锁,并在等待区等待被唤醒。
从图中可以看出,一个 Monitor在某个时刻,只能被一个线程拥有,该线程就是 “Active Thread”,而其它线程都是 “Waiting Thread”,分别在两个队列 “ Entry Set”和 “Wait Set”里面等候。在 “Entry Set”中等待的线程状态是 “Waiting for monitor entry”,而在“Wait Set”中等待的线程状态是 “in Object.wait()”。 先看 “Entry Set”里面的线程。我们称被 synchronized保护起来的代码段为临界区。当一个线程申请进入临界区时,它就进入了 “Entry Set”队列。对应的 code就像:
synchronized(obj) {
//.........
}
调用修饰
表示线程在方法调用时,额外的重要的操作。线程Dump分析的重要信息。修饰上方的方法调用。
locked <地址> 目标:使用synchronized申请对象锁成功,监视器的拥有者。
waiting to lock <地址> 目标:使用synchronized申请对象锁未成功,在进入区等待。
waiting on <地址> 目标:使用synchronized申请对象锁成功后,释放锁并在等待区等待。
parking to wait for <地址> 目标:park是基本的线程阻塞原语,不通过监视器在对象上阻塞。随concurrent包会出现的新的机制,不synchronized体系不同。
locked
at oracle.jdbc.driver.PhysicalConnection.prepareStatement
- locked <0x00002aab63bf7f58> (a oracle.jdbc.driver.T4CConnection)
at oracle.jdbc.driver.PhysicalConnection.prepareStatement
- locked <0x00002aab63bf7f58> (a oracle.jdbc.driver.T4CConnection)
at com.jiuqi.dna.core.internal.db.datasource.PooledConnection.prepareStatement
通过synchronized关键字,成功获取到了对象的锁,成为监视器的拥有者,在临界区内操作。对象锁是可以线程重入的。
waiting to lock
at com.jiuqi.dna.core.impl.CacheHolder.isVisibleIn(CacheHolder.java:165)
- waiting to lock <0x0000000097ba9aa8> (a CacheHolder)
at com.jiuqi.dna.core.impl.CacheGroup$Index.findHolder
at com.jiuqi.dna.core.impl.ContextImpl.find
at com.jiuqi.dna.bap.basedata.common.util.BaseDataCenter.findInfo
通过synchronized关键字,没有获取到了对象的锁,线程在监视器的进入区等待。在调用栈顶出现,线程状态为Blocked。
waiting on
at java.lang.Object.wait(Native Method)
- waiting on <0x00000000da2defb0> (a WorkingThread)
at com.jiuqi.dna.core.impl.WorkingManager.getWorkToDo
- locked <0x00000000da2defb0> (a WorkingThread)
at com.jiuqi.dna.core.impl.WorkingThread.run
通过synchronized关键字,成功获取到了对象的锁后,调用了wait方法,进入对象的等待区等待。在调用栈顶出现,线程状态为WAITING或TIMED_WATING。
parking to wait for
park是基本的线程阻塞原语,不通过监视器在对象上阻塞。随concurrent包会出现的新的机制,与synchronized体系不同。
线程动作
线程状态产生的原因:
- runnable:状态一般为RUNNABLE。
- in Object.wait():等待区等待,状态为WAITING或TIMED_WAITING。
- waiting for monitor entry:进入区等待,状态为BLOCKED。
- waiting on condition:等待区等待、被park。
- sleeping:休眠的线程,调用了Thread.sleep()。
Wait on condition 该状态出现在线程等待某个条件的发生。具体是什么原因,可以结合 stack trace来分析。 最常见的情况就是线程处于sleep状态,等待被唤醒。 常见的情况还有等待网络IO:在java引入nio之前,对于每个网络连接,都有一个对应的线程来处理网络的读写操作,即使没有可读写的数据,线程仍然阻塞在读写操作上,这样有可能造成资源浪费,而且给操作系统的线程调度也带来压力。在 NIO里采用了新的机制,编写的服务器程序的性能和可扩展性都得到提高。 正等待网络读写,这可能是一个网络瓶颈的征兆。因为网络阻塞导致线程无法执行。一种情况是网络非常忙,几乎消耗了所有的带宽,仍然有大量数据等待网络读 写;另一种情况也可能是网络空闲,但由于路由等问题,导致包无法正常的到达。所以要结合系统的一些性能观察工具来综合分析,比如 netstat统计单位时间的发送包的数目,如果很明显超过了所在网络带宽的限制 ; 观察 cpu的利用率,如果系统态的CPU时间,相对于用户态的 CPU时间比例较高;如果程序运行在 Solaris 10平台上,可以用 dtrace工具看系统调用的情况,如果观察到 read/write的系统调用的次数或者运行时间遥遥领先;这些都指向由于网络带宽所限导致的网络瓶颈。
jstack命令格式
jstack [ option ] pid
jstack [ option ] executable core
jstack [ option ] [server-id@]remote-hostname-or-IP
常用参数说明
1)options:
executable Java executable from which the core dump was produced.(可能是产生core dump的java可执行程序)
core : 将被打印信息的core dump文件
remote-hostname-or-IP :远程debug服务的主机名或ip
server-id :唯一id,假如一台主机上多个远程debug服务
2)基本参数:
- -F :当’jstack [-l] pid’没有响应的时候,强制打印线程堆栈信息,一般情况不需要使用
- -l :长列表. 打印关于锁的附加信息,例如属于java.util.concurrent的ownable synchronizers列表,会使得JVM停顿得长久得多(可能会差很多倍,比如普通的jstack可能几毫秒和一次GC没区别,加了-l 就是近一秒的时间),-l 建议不要用,一般情况不需要使用
- -m : 打印java和native c/c++框架的所有栈信息.可以打印JVM的堆栈,显示上Native的栈帧,一般应用排查不需要使用
- -h | -help :打印帮助信息
- pid :需要被打印配置信息的java进程id,可以用jps查询
使用示例
jstack pid
~$ jps -ml
org.apache.catalina.startup.Bootstrap
~$ jstack 5661
2013-04-16 21:09:27
Full thread dump Java HotSpot(TM) Server VM (20.10-b01 mixed mode):
"Attach Listener" daemon prio=10 tid=0x70e95400 nid=0x2265 waiting on condition [0x00000000]
java.lang.Thread.State: RUNNABLE
"http-bio-8080-exec-20" daemon prio=10 tid=0x08a35800 nid=0x1d42 waiting on condition [0x70997000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x766a27b8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:156)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:1987)
at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:399)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:104)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:32)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:947)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:907)
at java.lang.Thread.run(Thread.java:662)
........
死循环
-
写个死循环代码
package com.jvm.jstack;/** * Java干货铺子,只生产干货,公众号:javacode2018 */ public class Demo1 { public static void main(String[] args) { while (true) { } } }
运行代码
-
cmd中执行jps查看程序进程id
F:\fcargitnew\hellospringboot>jps
13984
11060 Test2
12916 Launcher
396 Jps
8908 RemoteMavenServer进程id为 11060
-
输入jstack 11060命令,找到跟我们自己代码相关的线程,如下为main线程,处于runnable状态,在main方法的第8行,也就是我们死循环的位置.
jstack 11060"main" #1 prio=5 os_prio=0 tid=0x0000000002a37000 nid=0x2c50 runnable [0x000000000282f000] java.lang.Thread.State: RUNNABLE at com.self.test.Test2.main(Test2.java:46)
Object.wait()情况
执行下列代码:
package com.jvm.jstack;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Java干货铺子,只生产干货,公众号:javacode2018
*/
public class Demo2 {
static class TestTask implements Runnable {
@Override
public void run() {
synchronized (this) {
try {
//等待被唤醒
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
ExecutorService ex = Executors.newFixedThreadPool(1);
ex.execute(new TestTask());
}
}
"From DemoThreadFactory's 订单创建组-Worker-1" #11 prio=5 os_prio=0 tid=0x000000001e476000 nid=0x13f0 in Object.wait() [0x000000001f12e000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076b798240> (a com.self.test.Test3$TestTask)
at java.lang.Object.wait(Object.java:502)
at com.self.test.Test3$TestTask.run(Test3.java:28)
- locked <0x000000076b798240> (a com.self.test.Test3$TestTask)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
死锁情况
-
写个死锁的例子
public class TestDeadLock {private static Object obj1 = new Object(); private static Object obj2 = new Object(); public static void main(String[] args) { //自定义饱和策略 ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10, 600, TimeUnit.SECONDS, new LinkedBlockingQueue<>(5), new DemoThreadFactory("订单创建组"), new ThreadPoolExecutor.AbortPolicy()); // 起10个线程 for (int i = 0; i < 10; i++) { int order = i % 2 == 0 ? 1 : 0; executor.execute(new MyRunnable(order)); } } static class MyRunnable implements Runnable{ private int order; public MyRunnable( int order) { this.order = order; } public void test1() throws InterruptedException { synchronized (obj1) { synchronized (obj2) { System.out.println("test1。。。"); } } } public void test2() throws InterruptedException { synchronized (obj2) { synchronized (obj1) { System.out.println("test2。。。"); } } } @Override public void run() { while (true) { try { if (this.order == 1) { this.test1(); } else { this.test2(); } } catch (InterruptedException e) { e.printStackTrace(); } } } } }
运行上面代码产生死锁.
-
我们先通过jsp查找到程序的进程,然后通过jstack查看线程堆栈,很快就可以发现死锁
Found one Java-level deadlock:
=============================
"From DemoThreadFactory's 订单创建组-Worker-10":
waiting to lock monitor 0x000000001c46dfa8 (object 0x000000076b77cf68, a java.lang.Object),
which is held by "From DemoThreadFactory's 订单???建组-Worker-4"
"From DemoThreadFactory's 订单创建组-Worker-4":
waiting to lock monitor 0x000000001c46f448 (object 0x000000076b77cf58, a java.lang.Object),
which is held by "From DemoThreadFactory's 订单创建组-Worker-5"
"From DemoThreadFactory's 订单创建组-Worker-5":
waiting to lock monitor 0x000000001c46dfa8 (object 0x000000076b77cf68, a java.lang.Object),
which is held by "From DemoThreadFactory's 订单创建组-Worker-4"Java stack information for the threads listed above: =================================================== "From DemoThreadFactory's 订单创建组-Worker-10": at com.self.test.TestDeadLock$MyRunnable.test2(TestDeadLock.java:55) - waiting to lock <0x000000076b77cf68> (a java.lang.Object) at com.self.test.TestDeadLock$MyRunnable.run(TestDeadLock.java:68) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) "From DemoThreadFactory's 订单创建组-Worker-4": at com.self.test.TestDeadLock$MyRunnable.test2(TestDeadLock.java:56) - waiting to lock <0x000000076b77cf58> (a java.lang.Object) - locked <0x000000076b77cf68> (a java.lang.Object) at com.self.test.TestDeadLock$MyRunnable.run(TestDeadLock.java:68) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) "From DemoThreadFactory's 订单创建组-Worker-5": at com.self.test.TestDeadLock$MyRunnable.test1(TestDeadLock.java:48) - waiting to lock <0x000000076b77cf68> (a java.lang.Object) - locked <0x000000076b77cf58> (a java.lang.Object) at com.self.test.TestDeadLock$MyRunnable.run(TestDeadLock.java:66) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) Found 1 deadlock.
等待io
运行代码
public class TestIO {
public static void main(String[] args) throws IOException {
InputStream is = System.in;
int i = is.read();
System.out.println("exit。");
}
}-
和上面一样,jps获取进程,jstack获取线程堆栈信息
//长列表. 打印关于锁的附加信息
jstack -l 9168"main" #1 prio=5 os_prio=0 tid=0x00000000029b7000 nid=0xee0 runnable [0x000000000281f000] java.lang.Thread.State: RUNNABLE at java.io.FileInputStream.readBytes(Native Method) at java.io.FileInputStream.read(FileInputStream.java:255) at java.io.BufferedInputStream.fill(BufferedInputStream.java:246) at java.io.BufferedInputStream.read(BufferedInputStream.java:265) - locked <0x000000076b4614f8> (a java.io.BufferedInputStream) at com.self.test.TestIO.main(TestIO.java:19) Locked ownable synchronizers: - None
疑问:
Q: Wait on condition 该状态出现在线程等待某个条件的发生。具体是什么原因,可以结合 stack trace来分析。 最常见的情况就是线程处于sleep状态,等待被唤醒。 这应该是wait状态,等待被唤醒,而不是sleep状态吧?