进程是程序的一次动态执行,它对应着从代码加载,执行至执行完毕的一个完整的过程,是一个动态的实体,它有自己的生命周期。它因创建而产生,因调度而运行,因等待资源或事件而被处于等待状态,因完成任务而被撤消。
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行。
1、进程和线程的关系
一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。
进程作为资源分配的最小单位,资源是分配给进程的,同一进程的所有线程共享该进程的所有资源。
真正在处理机上运行的是线程。
2、进程与线程的区别
调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位。
并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可并发执行。
拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源。
系统开销:在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销明显大于创建或撤消线程时的开销。
3、java进程与线程资源状态排查
在众多业务系统中,基于java的业务系统是最耗费资源的,所以很多情况下都会坚持java进程以及相关线程的资源占用状态,并由此获取是哪些代码导致的,那么如何解决这些问题呢,其实只需要一些命令行的组合即可完成。
首先,需要获取java应用中(以tomcat为例)占用CPU过高的进程,方法上面已经介绍过了,是执行如下命令:
[root@localhost ~]# ps aux|head -1;ps aux|sort -rn -k3|head -1
此命令可以获取占用CPU资源最高的进程,我们记录这个进程的pid,比如说是3016。
tomcat应用中虽然只有一个java进程,但此进程是多线程机制的,通过多线程可以充分利用CPU的多核资源,那么如何查看进程对应的线程信息呢,方法很多,常用的命令有ps、top和htop命令。这里使用htop命令,通过控制台选项调出线程信息,如下图所示:
很简单吧,这样就可以清楚看到单个进程的线程视图了,并且状态信息也是实时刷新的。通过这个方法也可以查找出CPU利用率最厉害的线程号。然后记录下来。
接着,从上图找到最消耗CPU资源的java线程的id号,将线程的pid转换为16进制数:
[root@localhost ~]#printf ‘%x\n’ tid
注意,此处的tid为上一步找到的占CPU高的线程号。
最后一步,还需要一个java工具jstack,jstack是java虚拟机自带的一种堆栈跟踪工具。可以用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。 线程出现停顿的时候通过jstack来查看各个线程的调用堆栈,就可以知道没有响应的线程到底在后台做什么事情,或者等待什么资源。
总结一句话:jstack命令主要用来查看Java线程的调用堆栈的,可以用来分析线程问题(如死锁)。
想要通过jstack命令来分析线程的情况的话,首先要知道线程都有哪些状态,下面这些状态是我们使用jstack命令查看线程堆栈信息时可能会看到的线程的几种状态:
NEW,未启动的。不会出现在Dump中。
RUNNABLE,在虚拟机内执行的。运行中状态,可能里面还能看到locked字样,表明它获得了某把锁。
BLOCKED,受阻塞并等待监视器锁。被某个锁(synchronizers)給block住了。
WATING,无限期等待另一个线程执行特定操作。等待某个condition或monitor发生,一般停留在park(), wait(), sleep(),join() 等语句里。
TIMED_WATING,有时限的等待另一个线程的特定操作。和WAITING的区别是wait()等语句加上了时间限制wait(timeout)。
TERMINATED,已退出的。
那么怎么去使用jstack呢,很简单,用jstack打印线程信息 ,将信息重定向到文件中,可执行如下操作:
[root@localhost ~]# jstack pid |grep tid
例如:
[root@localhost ~]# jstack 30116 |grep 75cf >> jstack.out
这里的“75cf “就是线程的tid转换为16进制数的结果。
下面是jstack的输出信息:
"main" #1 prio=5 os_prio=0 tid=0x00007f9cb800a000 nid=0xbc9 runnable [0x00007f9cbf1d4000]
java.lang.Thread.State: RUNNABLE
at java.net.PlainSocketImpl.socketAccept(Native Method)
at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:409)
at java.net.ServerSocket.implAccept(ServerSocket.java:545)
at java.net.ServerSocket.accept(ServerSocket.java:513)
at org.apache.catalina.core.StandardServer.await(StandardServer.java:466)
at org.apache.catalina.startup.Catalina.await(Catalina.java:769)
at org.apache.catalina.startup.Catalina.start(Catalina.java:715)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:353)
at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:493)
Locked ownable synchronizers:
- None
这里面重点关注下输出的线程状态,如果有异常,会输出相关异常信息或者跟程序相关的信息,将这些信息给的开发人员,就可以马上定位CPU过高的问题,所以这个方法非常有效。
4、查询当前整个系统每个进程的线程数
我们经常遇到这样的问题,比如某台服务器的CPU使用率飙升,通过top或htop命令查看是某个程序(例如java)占用的cpu比较大,现在需要查询java各个进程下的线程数情况。可以通这一个命令组合实现:
[root@localhost ~]# for pid in $(ps -ef|grep -v grep|grep "java"|awk '{print $2}');do echo ${pid} > /tmp/a.txt ;cat /proc/${pid}/status|grep Threads > /tmp/b.txt;paste /tmp/a.txt /tmp/b.txt;done|sort -k3 -rn
先解释下这个脚本:
1)、for pid in $(ps -ef|grep -v grep|grep java |awk '{print $2}')
这部分是获取${pid}变量为java进程的pid号。
2)、echo ${pid} > /tmp/a.txt
这部分是将java进程的pid号都打印到/tmp/a.txt文件中。
3)、cat /proc/${pid}/status|grep Threads > /tmp/b.txt
这部分是将各个pid进程号下的线程信息打印到/tmp/b.txt文件中。
4)、paste /tmp/a.txt /tmp/b.txt
这部分是以列的形式展示a.txt和b.txt文件中的信息。
5)sort -k3 -rn
这部分是对输出的信息进行排序,其中,-k3表示以第三列进行排序,“-rn”表示降序排列。
将上面命令组合放入系统执行完毕后,输入内容如下:
从输出可以看出,第一列显示的是java的进程号,最后一列显示的每个java进程对应的线程数量。
这个例子是一个for循环加上ps命令和sort命令综合应用的实例。
更多Linux、云计算、云原生、大数据、docker、k8s知识,可访问: