① 如何分析jvm mp 内存日志
当服务器挂起,崩溃或者性能底下时,就需要抓取服务器的线程堆栈(Thread Dump)用于后续的分析.
Thread mp提供了当前活动的线程的快照. 它提供了JVM中所有java线程的栈跟踪信息
有很多方式可用于获取Thread Dump, 一些是操作系统特定的命令.
操作系统命令获取ThreadDump:
Windows:
1. 转向服务器的标准输出窗口并按下Control + Break组合键, 之后需要将线程堆栈复制到文件中
UNIX/ Linux
首先查找到服务器的进程号(process id), 然后获取堆栈.
1. ps –ef | grep java
2. kill -3
注意一定要谨慎, 一步不慎就可能让服务器进程被杀死!
JVM 自带的工具获取线程堆栈:
JDK自带命令行工具获取PID并做ThreadDump:
1. jps
2. jstack
使用JVisualVM:
Threads 标签页àThreadDump按钮.
WebLogic 自带的获取 thread mp的工具:
1. webLogic.Admin 工具
a. 打开命令提示符, 通过运行/bin/setDomain.env设置相关类路径
b. 执行下面的命令
java weblogic.Admin -url t3://localhost:7001 -username weblogic -password weblogic1 THREAD_DUMP
注意: Thread Dump 会打印到标准输出, 如nohup日志或者进程窗口.
2. 使用 Admin Console
a. 登录 Admin Console , 点击对应的服务器
b. 点击Server à Monitoring àThreads
c. 点击: Dump Thread Stack 按钮
3. 使用WLST (WebLogic Scripting Tool)
connect(‘weblogic’,'weblogic1’,’t3://localhost:7001’)
cd(‘Servers’)
cd(‘AdminServer’)
threadDump()
disconnect()
exit()
注意: 线程堆栈将会保存在运行wlst的当前目录下.
4. 使用utils.ThreadDumper
用法:
C:\bea\wlserver_10.3\server\lib>java -cp weblogic.jar utils.ThreadDumper
Broadcast Thread mps disabled: must specify weblogic.debug.mpThreadAddr and
weblogic.debug.mpThreadPort
Exception in thread "main" java.lang.I llegalArgumentException: Port out of range
:-1
at java.net.DatagramPacket.setPort(Unknown Source)
at java.net.DatagramPacket.(Unknown Source)
at java.net.DatagramPacket.(Unknown Source)
at utils.ThreadDumper.sendDumpMsg(ThreadDumper.java:124)
at utils.ThreadDumper.main(ThreadDumper.java:145)
5. 如果服务器是作为Windows服务的方式运行, 请运行下列命令:
WL_HOME\bin\beasvc -mp -svcname:service-name
② 如何分析java Thread DUMP
一、Thread Dump介绍
1.1什么是Thread Dump?
Thread Dump是非常有用的诊断Java应用问题的工具。每一个Java虚拟机都有及时生成所有线程在某一点状态的thread-mp的能力,虽然各个 Java虚拟机打印的thread mp略有不同,但是大多都提供了当前活动线程的快照,及JVM中所有Java线程的堆栈跟踪信息,堆栈信息一般包含完整的类名及所执行的方法,如果可能的话还有源代码的行数。
1.2 Thread Dump特点
1. 能在各种操作系统下使用
2. 能在各种Java应用服务器下使用
3. 可以在生产环境下使用而不影响系统的性能
4. 可以将问题直接定位到应用程序的代码行上
1.3 Thread Dump 能诊断的问题
1. 查找内存泄露,常见的是程序里load大量的数据到缓存;
2. 发现死锁线程;
1.4如何抓取Thread Dump
一般当服务器挂起,崩溃或者性能底下时,就需要抓取服务器的线程堆栈(Thread Dump)用于后续的分析. 在实际运行中,往往一次 mp的信息,还不足以确认问题。为了反映线程状态的动态变化,需要接连多次做threadmp,每次间隔10-20s,建议至少产生三次 mp信息,如果每次 mp都指向同一个问题,我们才确定问题的典型性。
有很多方式可用于获取ThreadDump, 下面列出一部分获取方式:
操作系统命令获取ThreadDump:
Windows:
1.转向服务器的标准输出窗口并按下Control + Break组合键, 之后需要将线程堆栈复制到文件中;
UNIX/ Linux:
首先查找到服务器的进程号(process id), 然后获取线程堆栈.
1. ps –ef | grep java
2. kill -3 <pid>
注意:一定要谨慎, 一步不慎就可能让服务器进程被杀死。kill -9 命令会杀死进程。
JVM 自带的工具获取线程堆栈:
JDK自带命令行工具获取PID,再获取ThreadDump:
1. jps 或 ps –ef|grepjava (获取PID)
2. jstack [-l ]<pid> | tee -a jstack.log (获取ThreadDump)
二、java线程的状态转换介绍(为后续分析做准备)
2.1 新建状态(New)
用new语句创建的线程处于新建状态,此时它和其他Java对象一样,仅仅在堆区中被分配了内存。
2.2 就绪状态(Runnable)
当一个线程对象创建后,其他线程调用它的start()方法,该线程就进入就绪状态,Java虚拟机会为它创建方法调用栈和程序计数器。处于这个状态的线程位于可运行池中,等待获得CPU的使用权。
2.3 运行状态(Running)
处于这个状态的线程占用CPU,执行程序代码。只有处于就绪状态的线程才有机会转到运行状态。
2.4 阻塞状态(Blocked)
阻塞状态是指线程因为某些原因放弃CPU,暂时停止运行。当线程处于阻塞状态时,Java虚拟机不会给线程分配CPU。直到线程重新进入就绪状态,它才有机会转到运行状态。
阻塞状态可分为以下3种:
1)位于对象等待池中的阻塞状态(Blocked in object’s wait pool):当线程处于运行状态时,如果执行了某个对象的wait()方法,Java虚拟机就会把线程放到这个对象的等待池中,这涉及到“线程通信”的内容。
2)位于对象锁池中的阻塞状态(Blocked in object’s lock pool):当线程处于运行状态时,试图获得某个对象的同步锁时,如果该对象的同步锁已经被其他线程占用,Java虚拟机就会把这个线程放到这个对象的锁池中,这涉及到“线程同步”的内容。
③ 在新建虚拟机时出现问题
JConsole
JConsole 图形用户界面是一种符合 Java 管理扩展(JMX)规范的监视工具。JConsole 使用 Java 虚拟机 (Java VM) 的广泛检测来提供有关在 Java 平台上运行的应用程序的性能和资源消耗的信息。
使用方法 本地
使用jconsole命令:监视本地运行的所有 Java 应用程序,JConsole 可以连接到这些应用程序。
使用jconsole PID命令:监视指定PID的Java应用程序。
使用jsconsole hostName:portNum命令:hostName是运行应用程序的系统的名称,portNum是您在启动Java VM时启用 JMX 代理时指定的端口号。
使用service:jmx::命令:使用 JMX 服务 URL 进行连接。
内容分析
将 JConsole 连接到应用程序后,JConsole 由六个选项卡组成。
概述:显示有关 Java VM 和受监视值的概述信息。
内存:显示有关内存使用的信息。
线程:显示有关线程使用的信息。
类:显示有关类加载的信息。
VM:显示有关 Java VM 的信息。
MBeans:显示有关 MBeans 的信息。
显示有关 CPU 使用情况、内存使用情况、线程计数和在Java VM中加载的类的图形监视信息。
提供执行GC的操作,可以随时点击按钮进行垃圾回收
伊甸园空间(堆):最初为大多数对象分配内存的池。
幸存者空间(堆):包含在伊甸园空间垃圾回收中幸存下来的物体的池。
终身代(堆):包含在幸存者空间中存在一段时间的对象的池。
永久生成(非堆):包含虚拟机本身的所有反射数据的池,如类和方法对象。使用类数据共享的 Java VM,这一代分为只读和读写区域。
代码缓存(非堆):HotSpotJava VM 还包括一个代码缓存,其中包含用于编译和存储本机代码的内存。
Java VM管理两种类型的内存:堆内存和非堆内存,这两种内存都是在 Java VM 启动时创建的。
堆内存是Java VM为所有类实例和数组分配内存的运行时数据区域。堆的大小可能是固定的或可变的。垃圾回收器是一个自动内存管理系统,用于回收对象的堆内存。
非堆内存包括所有线程之间共享的方法区域和Java VM的内部处理或优化所需的内存。它存储每类结构,如运行时常量池、字段和方法数据,以及方法和构造函数的代码。方法区域在逻辑上是堆的一部分,但是,根据实现,Java VM 可能不会对它进行垃圾回收或压缩。与堆内存一样,方法区域可能为固定大小或可变大小。方法区域的内存不需要连续。
内存池和内存管理器是Java VM内存系统的关键方面。
内存池表示Java VM管理的内存区域。Java VM至少有一个内存池,它可能会在执行期间创建或删除内存池。内存池可以属于堆内存或非堆内存。
内存管理器管理一个或多个内存池。垃圾回收器是一种内存管理器,负责回收不可到达的对象使用的内存。Java VM可能具有一个或多个内存管理器。它可以在执行期间添加或删除内存管理器。内存池可以由多个内存管理器管理。
垃圾回收 (GC) 是Java VM释放不再引用的对象占用的内存的方式。通常认为具有活动引用为"活动"且未引用(或无法访问)对象的对象为"已死"。垃圾回收是释放死对象使用的内存的过程。GC 使用的算法和参数对性能有显着影响。
Java hotspot VM垃圾回收器使用代数 GC。生成 GC 利用大多数程序符合以下概括的观察。
它们创建许多寿命较短的对象,例如迭代器和局部变量。
它们创建一些寿命很长的对象,例如高级持久对象。
提供有关线程使用的信息。
查找监视器死锁线程:检测对象监视器锁上是否有任何线程死锁。此操作返回死锁线程指示的数组。
getThreadInfo:返回线程信息。这包括线程当前被阻止的名称、堆栈跟踪和监视器锁(如果有)以及持有该锁的线程以及线程争用统计信息。
获取ThreadCpu时间:返回给定线程消耗的 CPU 时间
显示有关类加载的信息。
提供有关Java VM的信息。
以通用方式显示有关在平台 MBean 服务器注册的所有 MBeans 的信息。MBeans 选项卡允许您访问平台 MXBean 检测的完整集,包括在其他选项卡中不可见的仪器。此外,您还可以使用 MBeans 选项卡监视和管理应用程序的 MBeans。
列出目标系统上已检测的 Java 虚拟机 (JVM)。
监视 Java 虚拟机 (JVM) 统计信息。
对Java应用程序的资源和性能进行实时的命令行的监控,包括了对Heap size和垃圾回收状况的监控。
命令格式
jstat [-option] [PID]
option参数
class:显示有关类加载器行为的统计信息。
compiler:显示有关Java HotSpot VM实时编译器行为的统计信息。
gc:显示有关垃圾回收堆行为的统计信息。
gccapacity:显示有关几代人容量及其相应空间的统计信息。
gccause:显示有关垃圾回收统计信息(与 相同)的摘要,以及最后和当前(如果适用)垃圾回收事件的原因。-gcutil
gcnew:显示新一代行为的统计信息。
gcnewcapacity:显示有关新一代大小及其相应空间的统计信息。
gcold:显示有关旧一代和元空间统计信息行为的统计信息。
gcoldcapacity:显示有关旧一代大小的统计信息。
gcmetacapacity:显示有关元空间大小的统计信息。
gcutil:显示有关垃圾回收统计信息的摘要。
printcompilation:显示 Java 热点 VM 编译方法统计信息。
1.jstat –class: 显示加载class的数量,及所占空间等信息。
2.jstat -compiler显示VM实时编译的数量等信息。
3.jstat -gc: 可以显示gc的信息,查看gc的次数,及时间。
4.jstat -gccapacity:可以显示,VM内存中三代(young,old,perm)对象的使用和占用大小
5.jstat -gcutil:统计gc信息
6.jstat -gcnew:年轻代对象的信息。
7.jstat -gcnewcapacity: 年轻代对象的信息及其占用量。
8.jstat -gcold:old代对象的信息。
9.jstat -gcoldcapacity: old代对象的信息及其占用量。
10.jstat -gcpermcapacity: perm对象的信息及其占用量。
11.jstat -printcompilation:当前VM执行的信息。
监视 Java 虚拟机 (JVM),并使远程监视工具能够连接到 JVM
命令格式
jstatd -[option]
option
-nr当找不到现有的RMI注册表时,不尝试使用jstatd进程创建一个内部的RMI注册表。
-p port在指定的端口查找RMI注册表。如果没有找到,并且没有指定-nr选项,则在该端口自行创建一个内部的RMI注册表。
-n rminameRMI注册表中绑定的RMI远程对象的名称。默认的名称为JStatRemoteHost。如果多个jstatd服务器在同一主机上运行,你可以通过指定该选项来让每个服务器导出的RMI对象具有唯一的名称。不管如何,这样做需要将唯一的服务器名称包含进监控客户端的hostid和vmid字符串中。
-Joption将选项参数传递给被javac调用的java启动程序。例如,-J-Xms48m设置启动内存为48 MB。使用-J将选项参数传递给执行Java应用程序的底层虚拟机,这是一种常见惯例。
使用方法
1.在jdk的bin目录下创建文件jstatd.all.policy
2.写入下面的安全配置
grant codebase "file:/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.191.b12-1.el7_6.x86_64/lib/tools.jar" {
permission java.security.AllPermission;
#此处写绝对路径,主要是防止路径错误问题,排查问题,应该写成相对路径
3.启动jstatd
./jstatd -J-Djava.security.policy=jstatd.all.policy -J-Djava.rmi.server.hostname=x.x.x.x &
4.使用jvisualvm工具远程连接,进行监控
jvisualvm
VisualVM,能够监控线程,内存情况,查看方法的CPU时间和内存中的对 象,已被GC的对象,反向查看分配的堆栈(如100个String对象分别由哪几个对象分配出来的).
同时他还提供很多插件可以自己安装,是一款不错的监控分析工具。
故障排除工具 JInfo
可以用来查看正在运行的 java 应用程序的扩展参数,包括Java System属性和JVM命令行参数;也可以动态的修改正在运行的 JVM 一些参数。当系统崩溃时,jinfo可以从core文件里面知道崩溃的Java应用程序的配置信息
命令格式
参数说明
pid对应jvm的进程id
executable core产生core mp文件
[server-id@]remote server IP or hostname远程的ip或者hostname,server-id标记服务的唯一性id
option
no option输出全部的参数和系统属性
-flag name输出对应名称的参数
-flag [+|-]name开启或者关闭对应名称的参数
-flag name=value设定对应名称的参数
-flags输出全部的参数
-sysprops输出系统属性
Javacore 概述
Javacore,也可以称为“threadmp”或是“javamp”,它是 Java 提供的一种诊断特性,能够提供一份可读的当前运行的 JVM 中线程使用情况的快照。即在某个特定时刻,JVM 中有哪些线程在运行,每个线程执行到哪一个类,哪一个方法。应用程序如果出现不可恢复的错误或是内存泄露,就会自动触发 Javacore 的生成。
使用方法
1.jinfo pid:输出当前 jvm 进程的全部参数和系统属性
2.jinfo -flag name pid:输出对应名称的参数使用该命令,可以查看指定的 jvm 参数的值。如:查看当前 jvm 进程是否开启打印 GC 日志。
3.jinfo -flag [+|-]name pid:开启或者关闭对应名称的参数
使用 jinfo 可以在不重启虚拟机的情况下,可以动态的修改 jvm 的参数。尤其在线上的环境特别有用。
4.jinfo -flag name=value pid:修改指定参数的值。
5.jinfo -flags pid:输出全部的参数
6.jinfo -sysprops pid:输出当前 jvm 进行的全部的系统属性
jhat
主要是用来分析java堆的命令,可以将堆中的对象以html的形式显示出来,包括对象的数量,大小等等,并支持对象查询语言。
1.使用jmap命令导出堆文件jmap -mp:live,file=a.log pid
也可以使用下面方式导出堆文件
1、使用jconsole选项通过HotSpotDiagnosticMXBean从运行时获得堆转储(生成mp文件)、
2、虚拟机启动时如果指定了-XX:+HeapDumpOnOutOfMemoryError选项, 则在抛出OutOfMemoryError时, 会自动执行堆转储。
3、使用hprof命令
2.使用jhat分析堆文件jhat -J-Xmx512M a1.log
3.查看分析的html页面
http://ip:7000/jhat中的OQL(对象查询语言)
如果需要根据某些条件来过滤或查询堆的对象,这是可能的,可以在jhat的html页面中执行OQL,来查询符合条件的对象
基本语法:
select
[from [instanceof] ]
[where ]
解释:
(1)class name是java类的完全限定名,如:java.lang.String,java.util.ArrayList, C是char数组,java.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) -- 唯一化数组
jmap
打印进程、核心文件或远程调试服务器的共享对象内存映射或堆内存详细信息。
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) 连接到远程调试服务
option
pid:目标进程的PID,进程编号,可以采用ps -ef | grep java查看java进程的PID;
executable:产生core mp的java可执行程序;
core:将被打印信息的core mp文件;
remote-hostname-or-IP:远程debug服务的主机名或ip;
server-id:唯一id,假如一台主机上多个远程debug服务;
使用方法
jmap -mp:[live,]format=b,file= PID:使用hprof二进制形式,输出jvm的heap内容到文件
jmap -finalizerinfo PID:打印正等候回收的对象的信息
jmap -heap PID:打印heap的概要信息,GC使用的算法,heap(堆)的配置及JVM堆内存的使用情况。
jmap -histo:live PID:打印每个class的实例数目,内存占用,类全名信息。VM的内部类名字开头会加上前缀”*”. 如果live子参数加上后,只统计活的对象数量.
jmap -permstat PID:打印classload和jvm heap长久层的信息. 包含每个classloader的名字、活泼性、地址、父classloader和加载的class数量。另外,内部String的数量和占用内存数也会打印出来。
-F强迫.在pid没有相应的时候使用-mp或者-histo参数。在这个模式下,live子参数无效。
-h | -help打印辅助信息
-J传递参数给jmap启动的jvm.
jstack命令主要用于调试java程序运行过程中的线程堆栈信息,可以用于检测死锁,进程耗用cpu过高报警问题的排查。jstack命令会打印出所有的线程,包括用户自己启动的线程和jvm后台线程。
命令格式
jstack -[option] pid
option
-F强制mp线程堆栈信息. 用于进程hung住,jstack命令没有响应的情况
-m同时打印java和本地(native)线程栈信息,m是mixed mode的简写
-l打印锁的额外信
公众号“Java精选”所发表内容注明来源的,版权归原出处所有(无法查证版权的或者未注明出处的均来自网络,系转载,转载的目的在于传递更多信息,版权属于原作者。如有侵权,请联系,笔者会第一时间删除处理!
最近有很多人问,有没有读者交流群!加入方式很简单,公众号Java精选,回复“加群”,即可入群!
(微信小程序):3000+道面试题,包含Java基础、并发、JVM、线程、MQ系列、Redis、Spring系列、Elasticsearch、Docker、K8s、Flink、Spark、架构设计等,在线随时刷题!
------ 特别推荐 ------
特别推荐:专注分享最前沿的技术与资讯,为弯道超车做好准备及各种开源项目与高效率软件的公众号,“大咖笔记”,专注挖掘好东西,非常值得大家关注。点击下方公众号卡片关注。
文章有帮助的话,在看,转发吧!
④ 怎样分析 JAVA 的 Thread Dumps
当有障碍,或者是一个基于 JAVA 的 WEB 应用运行的比预期慢的时候,我们需要使用 thread mps。如果对于你来说,thread mps 是非常复杂的,这篇文章或许能对你有所帮助。在这里我将解释在 JAVA 中什么是 threads,他们的类型,怎么被创建的,怎样管理它们,你怎样从正在运行的应用中 mp threads,最后你可以怎样分析它以及确定瓶颈或者是阻塞线程。本文来自于 JAVA 应用程序长期调试经验的结果。
Java and Thread
一个 web 服务器使用几十到几百个线程来处理大量并发用户,如果一个或多个线程使用相同的资源,线程之间的竞争就不可避免了,并且有时候可能会发生死锁。
Thread contention 是一个线程等待锁的一个状态,这个锁被另外一个线程持有,等待被释放,不同的线程频繁访问 WEB 应用的共享资源。例如,记录一条日志,线程尝试记录日志之前必须先获取锁来访问共享资源。
死锁是线程竞争的一个特殊状态,一个或是多个线程在等待其他线程完成它们的任务为了完成它们自己的任务。
线程竞争会引起各种不同的问题,为了分析这些这些问题,你需要使用 mp threads,mp threads 能给你提供每个线程的精确状态信息。
JAVA 线程的背景资料
线程同步
一个线程可以与其他线程在同一时间内被处理。为了确保一致性,当多个线程试图使用共享资源的时候,通过使用 hread synchronization 在同一时间内,应该只有一个线程能访问共享资源
JAVA 中的线程同步可以使用监视器,每个 JAVA 对象都有一个单独的监视器,这个监视器仅仅只能被一个线程拥有,对于拥有一个由不同的线程所拥有的监视器的线程,确实需要在队列中等待,以便其他线程释放它的监视器。
线程状态
为了分析一个 thread mp 文件,你需要知道线程状态。线程情况在 java.lang.Thread.State 中阐明了。
当使用 java.lang.Thread 对象创建线程的时候,线程被命名为 Thread-(Number) 。当使用 java.util.concurrent.DefaultThreadFactory 对象创建线程的时候,线程被命名为 named pool-(Number)-thread-(Number)。当为应用程序分析成百上千的线程的时候,如果线程依然用它们默认的名字,分析它们将变得非常困难,因为这是非常难以辨别这些线程来分析的。
因此,你被建议开发一个命名线程的规则当一个新线程被创建的时候。
当你使用 java.lang.Thread 创建线程,你可以通过创建参数给该线程定义个约定俗成的名字。
public Thread(Runnable target, String name);
public Thread(ThreadGroup group, String name);
public Thread(ThreadGroup group, Runnable target, String name);
public Thread(ThreadGroup group, Runnable target, String name, long stackSize);
当你使用 java.util.concurrent.ThreadFactory 创建线程的时候,你可以通过生成你自己的线程工厂来命名它,如果你不需要特别的功能性,你可以使用 MyThreadFactory 作为以下描述:
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
public class MyThreadFactory implements ThreadFactory {
private static final ConcurrentHashMap<String, AtomicInteger> POOL_NUMBER =
new ConcurrentHashMap<String, AtomicInteger>();
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
public MyThreadFactory(String threadPoolName) {
if (threadPoolName == null) {
throw new NullPointerException("threadPoolName");
}
POOL_NUMBER.putIfAbsent(threadPoolName, new AtomicInteger());
SecurityManager securityManager = System.getSecurityManager();
group = (securityManager != null) ? securityManager.getThreadGroup() :
Thread.currentThread().getThreadGroup();
AtomicInteger poolCount = POOL_NUMBER.get(threadPoolName);
if (poolCount == null) {
namePrefix = threadPoolName + " pool-00-thread-";
} else {
namePrefix = threadPoolName + " pool-" + poolCount.getAndIncrement() + "-thread-";
}
}
public Thread newThread(Runnable runnable) {
Thread thread = new Thread(group, runnable, namePrefix + threadNumber.getAndIncrement(), 0);
if (thread.isDaemon()) {
thread.setDaemon(false);
}
if (thread.getPriority() != Thread.NORM_PRIORITY) {
thread.setPriority(Thread.NORM_PRIORITY);
}
return thread;
}
}
使用 MBean 获取更多的细节信息
你可以使用 MBean 来获取 ThreadInfo 对象。你也可以获取更加多通过 thread mps 不能获取的信息。通过使用 ThreadInfo。
ThreadMXBean mxBean = ManagementFactory.getThreadMXBean();
long[] threadIds = mxBean.getAllThreadIds();
ThreadInfo[] threadInfos =
mxBean.getThreadInfo(threadIds);
for (ThreadInfo threadInfo : threadInfos) {
System.out.println(
threadInfo.getThreadName());
System.out.println(
threadInfo.getBlockedCount());
System.out.println(
threadInfo.getBlockedTime());
System.out.println(
threadInfo.getWaitedCount());
System.out.println(
threadInfo.getWaitedTime());
}
你可以使用方法 ThreadInfo 来提取阻塞线程或者是等待线程花费的时间。并利用这一点,你也可以得到那些处于非活动状态的时间异常长的线程列表。
⑤ jvm 内存调优用过哪些工具,jstate 做什么用的如何 mp 出当前线程状态
实例一:Waiting to lock 和 Blocked
"RMI TCP Connection(267865)-172.16.5.25" daemon prio=10 tid=0x00007fd508371000 nid=0x55ae waiting for monitor entry [0x00007fd4f8684000]
java.lang.Thread.State: BLOCKED (on object monitor)
at org.apache.log4j.Category.callAppenders(Category.java:201)
- waiting to lock <0x00000000acf4d0c0> (a org.apache.log4j.Logger)
at org.apache.log4j.Category.forcedLog(Category.java:388)
at org.apache.log4j.Category.log(Category.java:853)
at org.apache.commons.logging.impl.Log4JLogger.warn(Log4JLogger.java:234)
at com.tuan.core.common.lang.cache.remote.SpyMemcachedClient.get(SpyMemcachedClient.java:110)
说明:
1)线程状态是 Blocked,阻塞状态。说明线程等待资源超时!
2)“ waiting to lock <0x00000000acf4d0c0>”指,线程在等待给这个 0x00000000acf4d0c0 地址上锁(英文可描述为:trying to obtain 0x00000000acf4d0c0 lock)。
3)在 mp 日志里查找字符串 0x00000000acf4d0c0,发现有大量线程都在等待给这个地址上锁。如果能在日志里找到谁获得了这个锁(如locked < 0x00000000acf4d0c0 >),就可以顺藤摸瓜了。
4)“waiting for monitor entry”说明此线程通过 synchronized(obj) {……} 申请进入了临界区,从而进入了下图1中的“Entry Set”队列,但该 obj 对应的 monitor 被其他线程拥有,所以本线程在 Entry Set 队列中等待。
5)第一行里,"RMI TCP Connection(267865)-172.16.5.25"是 Thread Name 。tid指Java Thread id。nid指native线程的id。prio是线程优先级。[0x00007fd4f8684000]是线程栈起始地址。
实例二:Waiting on condition 和 TIMED_WAITING
"RMI TCP Connection(idle)" daemon prio=10 tid=0x00007fd50834e800 nid=0x56b2 waiting on condition [0x00007fd4f1a59000]
java.lang.Thread.State: TIMED_WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000acd84de8> (a java.util.concurrent.SynchronousQueue$TransferStack)
at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:198)
at java.util.concurrent.SynchronousQueue$TransferStack.awaitFulfill(SynchronousQueue.java:424)
at java.util.concurrent.SynchronousQueue$TransferStack.transfer(SynchronousQueue.java:323)
at java.util.concurrent.SynchronousQueue.poll(SynchronousQueue.java:874)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:945)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:907)
at java.lang.Thread.run(Thread.java:662)
说明:
1)“TIMED_WAITING (parking)”中的 timed_waiting 指等待状态,但这里指定了时间,到达指定的时间后自动退出等待状态;parking指线程处于挂起中。
2)“waiting on condition”需要与堆栈中的“parking to wait for <0x00000000acd84de8> (a java.util.concurrent.SynchronousQueue$TransferStack)”结合来看。首先,本线程肯定是在等待某个条件的发生,来把自己唤醒。其次,SynchronousQueue 并不是一个队列,只是线程之间移交信息的机制,当我们把一个元素放入到 SynchronousQueue 中时必须有另一个线程正在等待接受移交的任务,因此这就是本线程在等待的条件。
3)别的就看不出来了。
⑥ 如何分析java thread mp
thread mp解析
头部信息
时间,jvm信息
{code}
2011-11-02 19:05:06
Full thread mp Java HotSpot(TM) Server VM (16.3-b01 mixed mode):
{code}
线程info信息块
{code}
"Checkpointer" daemon prio=10 tid=0x68ce1c00 nid=0x7c11 in Object.wait() [0x68b5c000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x740ad988> (a java.lang.Object)
at java.lang.Object.wait(Object.java:485)
at com.sleepycat.je.utilint.DaemonThread.run(DaemonThread.java:163)
- locked <0x740ad988> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:619)
{code}
"Checkpointer" daemon prio=10 tid=0x68ce1c00 nid=0x7c11 in Object.wait() [0x68b5c000]
* 线程名称:Checkpointer
* 线程类型:daemon
* 优先级:10,默认是5
* jvm线程id:jvm内部线程的唯一标识,0x68ce1c00
* 对应系统线程id:和top命令查看的pid对应,不过一个是10进制,一个是16进制。0x7c11
* 线程状态:Object.wait().
* 起始栈地址
线程状态详解
Runnable
_The thread is either running or ready to run when it gets its CPU turn._
不解释。
Wait on condition
_The thread is either sleeping or waiting to be notified by another thread._
该状态出现在线程等待某个条件的发生或者sleep。
_最常见的情况是线程在等待网络的读写,比如当网络数据没有准备好读时,线程处于这种等待状态,而一旦有数据准备好读之后,线程会重新激活,读取并处理数据。_
Waiting for Monitor Entry and in Object.wait()
_The thread is waiting to get the lock for an object (some other thread may be holding the lock). This happens if two or more threads try to execute synchronized code. Note that the lock is always for an object and not for indivial methods._
当一个线程申请进入临界区时,获取到monitor,线程将处于 “Runnable”的状态,否则,线程 DUMP会显示处于 “waiting for monitor entry”。
当线程获得了 Monitor,进入了临界区之后,如果发现线程继续运行的条件没有满足,它则调用对象(一般就是被 synchronized 的对象)的 wait() 方法,放弃了 Monitor,进入 “Wait Set”队列。只有当别的线程在该对象上调用了 notify() 或者 notifyAll() , “ Wait Set”队列中线程才得到机会去竞争,但是只有一个线程获得对象的 Monitor,恢复到运行态。在 “Wait Set”中的线程, DUMP中表现为: in Object.wait()。
例:
<span style="background-color: rgb(255, 255, 255);"><span style="color:#ff6666;">{code}
"Timer-0" daemon prio=10 tid=0x695c3000 nid=0x7c00 in Object.wait() [0x69468000]
java.lang.Thread.State: TIMED_WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x744f2850> (a java.util.TaskQueue) ###继续wait
at java.util.TimerThread.mainLoop(Timer.java:509)
- locked <0x744f2850> (a java.util.TaskQueue) ###已经lock到0x744f2850
at java.util.TimerThread.run(Timer.java:462)
{code}</span></span>
参见:http://jameswxx.iteye.com/blog/1041173
{code}
java.lang.Thread.State: WAITING (on object monitor)
<p style="margin-top: 4px; margin-right: 0px; margin-bottom: 4px; margin-left: 0px; padding-top: 2px; padding-right: 0px; padding-bottom: 2px; padding-left: 0px; ">{code}</p>
线程状态运行:
WAITING||State || Description||
|blocked|This thread tried to enter a synchronized block, but the lock was taken by another thread. This thread is blocked until the lock gets released.|
|blocked (on thin lock)|This is the same state as blocked, but the lock in question is a thin lock.||waiting|This thread called Object.wait() on an object. The thread will remain there until some other thread sends a notification to that object.|
|sleeping|This thread called java.lang.Thread.sleep().||parked|This thread called java.util.concurrent.locks.LockSupport.park().||suspended|The thread's execution was suspended by java.lang.Thread.suspend() or a JVMTI agent call.|
{code}
at java.lang.Object.wait(Native Method)
- waiting on <0x740ad988> (a java.lang.Object) ###等待堆地址为0x740ad988的java.lang.Object对象的锁
at java.lang.Object.wait(Object.java:485)
at com.sleepycat.je.utilint.DaemonThread.run(DaemonThread.java:163)
- locked <0x740ad988> (a java.lang.Object) ###hold住堆地址为0x740ad988的java.lang.Object对象的锁
at java.lang.Thread.run(Thread.java:619)
{code}
⑦ Java 中怎么获取一份线程 mp 文件
当服务器挂起,崩溃或者性能底下时,就需要抓取服务器的线程堆栈(Thread Dump)用于后续的分析.
Thread mp提供了当前活动的线程的快照.它提供了JVM中所有Java线程的栈跟踪信息
有很多方式可用于获取Thread Dump,一些是操作系统特定的命令.
Windows:
1. 转向服务器的标准输出窗口并按下Control + Break组合键,之后需要将线程堆栈复制到文件中
UNIX/ Linux
首先查找到服务器的进程号(process id),然后获取堆栈.
1. ps –ef| grep java
2. kill -3 <pid>
注意一定要谨慎,一步不慎就可能让服务器进程被杀死!
JVM自带的工具获取线程堆栈:
JDK自带命令行工具获取PID并做ThreadDump:
1. jps
2.jstack <pid>
使用JVisualVM:
Threads标签页 →ThreadDump按钮
WebLogic自带的获取thread mp的工具:
1. webLogic.Admin工具
a.打开命令提示符,通过运行<DOMAIN_HOME>/bin/setDomain.env设置相关类路径
b.执行下面的命令
java weblogic.Admin -url t3://localhost:7001 -username weblogic -password weblogic1 THREAD_DUMP
注意: Thread Dump会打印到标准输出,如nohup日志或者进程窗口.
2.使用 Admin Console
a.登录Admin Console ,点击对应的服务器
b.点击ServeràMonitoringàThreads
c.点击: Dump Thread Stack按钮
3.使用WLST (WebLogic Scripting Tool)
connect(‘weblogic’,'weblogic1’,’t3://localhost:7001’)
cd(‘Servers’)
cd(‘AdminServer’)
threadDump()
disconnect()
exit()
注意:线程堆栈将会保存在运行wlst的当前目录下.
4.使用utils.ThreadDumper
用法:
C:eawlserver_10.3serverlib>java -cp weblogic.jar utils.ThreadDumper
Broadcast Thread mps disabled: must specify weblogic.debug.mpThreadAddr and
weblogic.debug.mpThreadPort
Exception in thread "main" java.lang.IllegalArgumentException: Port out of range
:-1
at java.net.DatagramPacket.setPort(Unknown Source)
at java.net.DatagramPacket.<init>(Unknown Source)
at java.net.DatagramPacket.<init>(Unknown Source)
at utils.ThreadDumper.sendDumpMsg(ThreadDumper.java:124)
at utils.ThreadDumper.main(ThreadDumper.java:145)
5.如果服务器是作为Windows服务的方式运行,请运行下列命令:
WL_HOMEineasvc -mp -svcname:service-name
$JAVA_
⑧ 如何抓取Thread Dump小结
当服务器挂起,崩溃或者性能底下时,就需要抓取服务器的线程堆栈(Thread Dump)用于后续的分析.
Thread mp提供了当前活动的线程的快照. 它提供了JVM中所有Java线程的栈跟踪信息
有很多方式可用于获取Thread Dump, 一些是操作系统特定的命令.
操作系统命令获取ThreadDump:
Windows:
1. 转向服务器的标准输出窗口并按下Control + Break组合键, 之后需要将线程堆栈复制到文件中
UNIX/ Linux
首先查找到服务器的进程号(process id), 然后获取堆栈.
1. ps –ef | grep java
2. kill -3 <pid>
注意一定要谨慎, 一步不慎就可能让服务器进程被杀死!
JVM 自带的工具获取线程堆栈:
JDK自带命令行工具获取PID并做ThreadDump:
1. jps
2. jstack <pid>
使用JVisualVM:
Threads 标签页àThreadDump按钮.
WebLogic 自带的获取 thread mp的工具:
1. webLogic.Admin 工具
a. 打开命令提示符, 通过运行<DOMAIN_HOME>/bin/setDomain.env设置相关类路径
b. 执行下面的命令
java weblogic.Admin -url t3://localhost:7001 -username weblogic -password weblogic1 THREAD_DUMP
注意: Thread Dump 会打印到标准输出, 如nohup日志或者进程窗口.
2. 使用 Admin Console
a. 登录 Admin Console , 点击对应的服务器
b. 点击Server à Monitoring àThreads
c. 点击: Dump Thread Stack 按钮
3. 使用WLST (WebLogic Scripting Tool)
connect(‘weblogic’,'weblogic1’,’t3://localhost:7001’)
cd(‘Servers’)
cd(‘AdminServer’)
threadDump()
disconnect()
exit()
注意: 线程堆栈将会保存在运行wlst的当前目录下.
4. 使用utils.ThreadDumper
用法:
C:\bea\wlserver_10.3\server\lib>java -cp weblogic.jar utils.ThreadDumper
Broadcast Thread mps disabled: must specify weblogic.debug.mpThreadAddr and
weblogic.debug.mpThreadPort
Exception in thread "main" java.lang.IllegalArgumentException: Port out of range
:-1
at java.net.DatagramPacket.setPort(Unknown Source)
at java.net.DatagramPacket.<init>(Unknown Source)
at java.net.DatagramPacket.<init>(Unknown Source)
at utils.ThreadDumper.sendDumpMsg(ThreadDumper.java:124)
at utils.ThreadDumper.main(ThreadDumper.java:145)
5. 如果服务器是作为Windows服务的方式运行, 请运行下列命令:
WL_HOME\bin\beasvc -mp -svcname:service-name
其它一些获取Thread Dump的工具有jrcmd, jrmc(JRockit VM自带) ,Samurai, JProfiler等, 还可通过JMX编程的方式获取, 如JDK自带示例代码:
$JAVA_HOME\demo\management\FullThreadDump