jstack分析java内存

本文详细介绍了Java中top、ps和jstack命令的使用方法,深入解析了ThreadDump日志中的线程信息,包括线程状态、系统线程状态及JVM线程运行状态,帮助开发者理解线程行为,诊断和解决线程相关问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、top命令
top -c 显示进程详细的信息
top常用快捷键:
    1 对于多核服务器,可以显示各个CPU占用资源的情况
    shift+h 显示所有的线程信息
top -H -p <pid> :将进程中的线程打印出来,还可以加上  -bn1 ,表示只输出一次结果,而不是显示一个动态的结果。
二、ps命令
ps -mp <pid> -o THREAD,tid,time | sort -k2r 命令查看,后面的sort参数根据线程占用的cpu比例进行排序。
三、jstack命令
    -l 长列表,打印关于锁的附加信息
    -m 打印java和jni框架的所有栈信息
因为thread id在栈信息中是以十六进制的形式显示的,因此需要使用 printf "%x \n" <tid> 命令将现场id转成十六进制的值,然后执行 jstack -l <pid> | grep <thread-hex-id> -A 10 命令显示出错的堆栈信息,-A 10 参数用来指定显示行数,否则只会显示一行信息。

cat jstack.log | grep "java.lang.Thread.State" | sort -nr | uniq -c  对 jstack 的状态有一个整体的把握

四、jstack -l <pid> 生成Thread Dump日志信息

1、"Attach Listener"

该线程负责接收外部命令,执行该命令并把结果返回给调用者,此种类型的线程通常在桌面程序中出现。

2、"DestroyJavaVM"

执行main()的线程在执行完之后调用JNI中的 jni_DestroyJavaVM() 方法会唤起DestroyJavaVM 线程。在JBoss启动之后,也会唤起DestroyJavaVM线程,处于等待状态,等待其它线程(java线程和native线程)退出时通知它卸载JVM。

3、"Service Thread"

用于启动服务的线程

4、"CompilerThread"

用来调用JITing,实时编译装卸CLASS。通常JVM会启动多个线程来处理这部分工作,线程名称后面的数字也会累加,比如CompilerThread1。

5、"Signal Dispatcher"

Attach Listener线程的职责是接收外部jvm命令,当命令接收成功后,会交给signal dispather 线程去进行分发到各个不同的模块处理命令,并且返回处理结果。
signal dispather线程也是在第一次接收外部jvm命令时,进行初始化工作。

6、"Finalizer"

这个线程也是在main线程之后创建的,其优先级为10,主要用于在垃圾收集前,调用对象的finalize()方法;关于Finalizer线程的几点:
(1)只有当开始一轮垃圾收集时,才会开始调用finalize()方法;因此并不是所有对象的finalize()方法都会被执行;
(2)该线程也是daemon线程,因此如果虚拟机中没有其他非daemon线程,不管该线程有没有执行完finalize()方法,JVM也会退出;
(3)JVM在垃圾收集时会将失去引用的对象包装成Finalizer对象(Reference的实现),并放入ReferenceQueue,由Finalizer线程来处理;最后将该Finalizer对象的引用置为null,由垃圾收集器来回收;
(4)JVM为什么要单独用一个线程来执行finalize()方法呢?
如果JVM的垃圾收集线程自己来做,很有可能由于在finalize()方法中误操作导致GC线程停止或不可控,这对GC线程来说是一种灾难。

7、"Reference Handler"

JVM在创建main线程后就创建Reference Handler线程,其优先级最高,为10,它主要用于处理引用对象本身(软引用、弱引用、虚引用)的垃圾回收问题 。

8、"GC task thread#0 (ParallelGC)"

垃圾回收线程,该线程会负责进行垃圾回收。通常JVM会启动多个线程来处理这个工作,线程名称中#后面的数字也会累加。

五、Thread Dump日志的线程信息

"Thread-2" #315 prio=10 os_prio=0 tid=0x00007f44ec296000 nid=0x21a32 in Object.wait() [0x00007f45f6340000]
   java.lang.Thread.State: TIMED_WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    at com.qcloud.s1.cos.http.IdleConnectionMonitorThread.run(IdleConnectionMonitorThread.java:26)
    - locked <0x00007f472bfffd70> (a com.qcloud.s1.cos.http.IdleConnectionMonitorThread)

"Thread-2":线程名称

prio=10:线程优先级,默认为5,数字越大优先级越高

tid=0x00007f44ec296000:JVM线程id,JVM内部线程的唯一标识,通过 java.lang.Thread.getId()获取,通常用自增的方式实现

nid=0x21a32:系统线程id,对应的系统线程id(Native Thread ID),可以通过 top 命令进行查看,线程id是十六进制的形式

Object.wait():系统线程状态

[0x00007f45f6340000]:起始栈地址

TIMED_WAITING:JVM线程状态

六、系统线程状态

1、deadlock

死锁线程,一般指多个线程调用期间进入了相互资源占用,导致一直等待无法释放的情况。

2、runnable

一般指该线程正在执行状态中,该线程占用了资源,正在处理某个操作,如通过SQL语句查询数据库、对某个文件进行写入等。

3、blocked

线程正处于阻塞状态,指当前线程执行过程中,所需要的资源长时间等待却一直未能获取到,被容器的线程管理器标识为阻塞状态,可以理解为等待资源超时的线程。

4、waiting on condition

线程正处于等待资源或等待某个条件的发生,具体的原因需要结合下面堆栈信息进行分析。

(1)如果堆栈信息明确是应用代码,则证明该线程正在等待资源,一般是大量读取某种资源且该资源采用了资源锁的情况下,线程进入等待状态,等待资源的读取,或者正在等待其他线程的执行等。

(2)如果发现有大量的线程都正处于这种状态,并且堆栈信息中得知正等待网络读写,这是因为网络阻塞导致线程无法执行,很有可能是一个网络瓶颈的征兆:

  • 网络非常繁忙,几乎消耗了所有的带宽,仍然有大量数据等待网络读写;
  • 网络可能是空闲的,但由于路由或防火墙等原因,导致包无法正常到达;

所以一定要结合系统的一些性能观察工具进行综合分析,比如netstat统计单位时间的发送包的数量,看是否很明显超过了所在网络带宽的限制;观察CPU的利用率,看系统态的CPU时间是否明显大于用户态的CPU时间。这些都指向由于网络带宽所限导致的网络瓶颈。

(3)还有一种常见的情况是该线程在 sleep,等待 sleep 的时间到了,将被唤醒。

5、waiting for monitor entry 或 in Object.wait()

Moniter 是Java中用以实现线程之间的互斥与协作的主要手段,它可以看成是对象或者class的锁,每个对象都有,也仅有一个 Monitor。

从上图可以看出,每个Monitor在某个时刻只能被一个线程拥有,该线程就是 "Active Thread",而其他线程都是 "Waiting Thread",分别在两个队列 "Entry Set"和"Waint Set"里面等待。其中在 "Entry Set" 中等待的线程状态是 waiting for monitor entry,在 "Wait Set" 中等待的线程状态是 in Object.wait()

(1)"Entry Set"里面的线程。
我们称被 synchronized 保护起来的代码段为临界区

当一个线程申请进入临界区时,它就进入了 "Entry Set" 队列中,这时候有两种可能性:

  • 该Monitor不被其他线程拥有,"Entry Set"里面也没有其他等待的线程。本线程即成为相应类或者对象的Monitor的Owner,执行临界区里面的代码;此时在Thread Dump中显示线程处于 "Runnable" 状态。
  • 该Monitor被其他线程拥有,本线程在 "Entry Set" 队列中等待。此时在Thread Dump中显示线程处于 "waiting for monity entry" 状态。

临界区的设置是为了保证其内部的代码执行的原子性和完整性,但因为临界区在任何时间只允许线程串行通过,这和我们使用多线程的初衷是相反的。如果在多线程程序中大量使用synchronized,或者不适当的使用它,会造成大量线程在临界区的入口等待,造成系统的性能大幅下降。如果在Thread Dump中发现这个情况,应该审视源码并对其进行改进。

(2)"Wait Set"里面的线程
当线程获得了Monitor,进入了临界区之后,如果发现线程继续运行的条件没有满足,它则调用对象(通常是被synchronized的对象)的wait()方法,放弃Monitor,进入 "Wait Set"队列。只有当别的线程在该对象上调用了 notify()或者notifyAll()方法,"Wait Set"队列中的线程才得到机会去竞争,但是只有一个线程获得对象的Monitor,恢复到运行态。"Wait Set"中的线程在Thread Dump中显示的状态为 in Object.wait()。通常来说,

通常来说,当CPU很忙的时候关注 Runnable 状态的线程,反之则关注 waiting for monitor entry 状态的线程。

七、JVM线程运行状态 (JVM Thread Status)

1、new

至今尚未启动的线程的状态。线程刚被创建,但尚未启动。

2、runnable

可运行线程的线程状态。线程正在JVM中执行,有可能在等待操作系统中的其他资源,比如处理器。

3、blocked

受阻塞并且正在等待监视器的某一线程的线程状态。处于受阻塞状态的某一线程正在等待监视器锁,以便进入一个同步的块/方法,或者在调用 Object.wait 之后再次进入同步的块/方法。
在Thread Dump日志中通常显示为 java.lang.Thread.State: BLOCKED (on object monitor) 。

4、waiting

某一等待线程的线程状态。线程正在无期限地等待另一个线程来执行某一个特定的操作,线程因为调用下面的方法之一而处于等待状态:

  • 不带超时的 Object.wait 方法,日志中显示为 java.lang.Thread.State: WAITING (on object monitor)
  • 不带超时的 Thread.join 方法
  • LockSupport.park 方法,日志中显示为 java.lang.Thread.State: WAITING (parking)

5、timed_waiting

指定了等待时间的某一等待线程的线程状态。线程正在等待另一个线程来执行某一个特定的操作,并设定了指定等待的时间,线程因为调用下面的方法之一而处于定时等待状态:

  • Thread.sleep 方法
  • 指定超时值的 Object.wait 方法
  • 指定超时值的 Thread.join 方法
  • LockSupport.parkNanos
  • LockSupport.parkUntil

6、terminated

线程处于终止状态。

根据Java Doc中的说明,在给定的时间上,一个只能处于上述的一种状态之中,并且这些状态都是JVM的状态,跟操作系统中的线程状态无关。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值