JVM调优(一)底层原理分析和问题定位

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/bbbbln/article/details/82791982

在这里,假设你已经读过《深入理解Java虚拟机》或者类似的书籍或查看过官方文档。

这个话题从为什么开始。

在虚拟机执行时,有时候会遇到各种线上问题。比如CPU使用居高不下,内存消耗过多,IO读写频繁等问题。我们得找到相应的方法来确定问题所在,再想出对策。

CPU占用问题

CPU占用高原因分析

原因a: 线程上下文切换消耗。

每个cpu或多核cpu中的一核,同一时间只能执行一个线程。在Java应用中,进行文件IO操作、网络IO操作、锁等待或者线程Sleep时,当前线程会进入阻塞或休眠状态,从而触发线程上下文切换,上下文切换过于频繁会造成内核占用过多的cpu资源,导致应用的响应变慢。

原因b:cpu上的运行队列。

每个核上处理runnable的线程的线程线反应其负载(load),load越大,核用越长的时间来完成。(建议每个核上load为1-3个)

原因c:cpu的利用率。

cpu利用率:用户进程、内核、中断处理、IO等待以及空闲五个部分组成。用户进程占用65-70%为宜,而内核占用为30-35%左右。

查看cpu使用情况工具

可用top或pidstat来查看cpu的使用情况。

top

使用top命令后,第三行为我们比较关注的信息。us(用户占用)、sy(系统占用)、ni(改变优先级占用)、id(空闲)。

对于多核cpu,可以按1显示每核的消耗情况。

以上默认为进程信息,如果要看线程信息,可以按shift+h来显示。

pidstat

此工具可以查看线程占用cpu情况,输入pidstat 1 2,表示在控制台上一秒输出一次,一共输出2次。可以多次查看。

分析cpu占用高的各种情况

us:用户占用高

top之后,发现us的百分比过高,表明应用消耗大部分cpu。我们得找到线程id,转换成16进制(linux上),再采用kill -3 [javapid]或jstack [javapid]来查看或dump导出查看。

当然,这部分占用高可能的原因是:线程一直处于runnable状态,通常是无阻塞、循环、正则或纯粹计算动作。

另一个可能是频繁的GC,最主要是Full GC。要从JVM内存消耗上查找原因。

sy:内核占用高

top之后,sy占用高,说明linux花费太多时间用于切换线程,造成这种情况主要原因是java应用启动的线程过多,且这些应用不断处于阻塞和执行状态变化之中,导致了操作系统要不断的进行线程上下文切换。按照上面的方法导出线程信息,查看线程状态、锁信息,找出等待状态或锁竞争过多的线程。

  1. 文件IO消耗

原因分析

在linux上,物理空闲内存够用,通常只有写文件时或者第一次读取文件时才会产生真正IO。

文件IO消耗的跟踪工具

pidstat

输入 pidstat -d -t - p [pid] 1 100,查看指定进程的io和额外信息。找到其中的io消耗高的线程,之后结合jstack来进一步分析,找到对应的java代码。

  1. 网线IO消耗

暂时略

  1. 内存消耗

内存消耗原因分析

多数生产情况下,通常将-Xms和-Xmx设为相同的值,避免运行期频繁申请内存。目前只有创建线程和使用Direct ByteBuffer时才会操作jvm堆外的内存。JVM内存消耗过多会导致GC执行频繁,CPU消耗增加。应用执行速度严重下降,严重会造成OutOfMemeryError,导致java进程退出。所以出现内存消耗过大问题时,根据以下详细分析,找出是jvm外的物理内存占用还是JVM Heap区。

如果os物理内存消耗过大,可能是JVM内存设置过大、创建的线程过多或Direct ByteBuffer往物理内存里放置过多的对象造成。

堆内存设置比物理内存大更容易出现问题。

Direct ByteBuffer是直接使用物理内存的,但是GC负责回收释放。

创建的线程数也会影响物理内存,这取决于-Xss值的大小,也取决于栈的深度。

内存查看工具

top

此工具查看的进行占用情况不太准,查看的为进程的占用全部内存。因为jvm启用时,会按照堆内存设置提前占据内存空间,但不表明在jvm里此内存已经被使用。

pidstat

pidstat -r -p [pid] [interval] [times],可查看指定进程的内存占用信息(包括物理内存和虚拟内存)。

程序执行慢的原因

锁竞争激烈

比较典型的是数据库连接池是有限的,而程序开太多的线程,那么有很多线程处于阻塞等待状态。

硬件资源未充分

这个好理解,多核CPU,只用单线程运行的话,无法充分发挥硬件优势。

数据量增长

这主要是相对数据库来说,若从1万条上升到1亿条,那么在查询时,应用程序多处在等待数据库给结果阶段。

那么知道以上原因和分析工具后,我们可以对jvm进行调优操作。

工具的常用命令:

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列表.

-m打印java和native c/c++框架的所有栈信息.

-h | -help打印帮助信息

pid 需要被打印配置信息的java进程id,可以用jps查询.

pidstat

pidstat [ 选项 ] [ <时间间隔> ] [ <次数> ]

常用的参数:

-u:默认的参数,显示各个进程的cpu使用统计
-r:显示各个进程的内存使用统计
-d:显示各个进程的IO使用情况
-p:指定进程号
-w:显示每个进程的上下文切换情况
-t:显示选择任务的线程的统计信息外的额外信息
-T { TASK | CHILD | ALL }
这个选项指定了pidstat监控的。TASK表示报告独立的task,CHILD关键字表示报告进程下所有线程统计信息。ALL表示报告独立的task和task下面的所有线程。
注意:task和子线程的全局的统计信息和pidstat选项无关。这些统计信息不会对应到当前的统计间隔,这些统计信息只有在子线程kill或者完成的时候才会被收集。
-V:版本号
-h:在一行上显示了所有活动,这样其他程序可以容易解析。
-I:在SMP环境,表示任务的CPU使用率/内核数量
-l:显示命令名和所有参数

猜你喜欢

转载自blog.csdn.net/bbbbln/article/details/82791982
今日推荐