java 性能问题定位

先来看一下java 虚拟机的各个含义

-Xms 堆的初始值
-Xmx 堆的最大值
-Xmn 堆中新生代的值
-XX:MetaspaceSize 元数据区值
-XX:MaxMetaspaceSize 元数据区最大值
-Xss 线程空间大小
-XX:SurvivorRatio 新生代中 伊甸区与s0 和s1的比例
-XX:NewRatio 老年代与新生代的比例 

上述内容有如下注意:

堆包括 新生代和老年代。
新生代 包括 伊甸区 、s0、s1 。
元数据区占用堆外内存,直接使用物理内存,如果不设置最大值,则会无限上涨。
-xss大小为线程空间,使用的也是 堆外内存,直接占用物理内存,在物理内存一定量的情况下:该值越大,则整个系统可使用的线程越少,但栈的递归深度会增加。

定位问题:

场景: 一个桌面的java swing程序,是一个工具大集合,再点击了其中的几个工具后,发现整个工具开始卡顿。 先看一下拉起时的bat脚本中的java参数:

set JVM_OPTS=-Xms32m -Xmx64m -Xmn16m -XX:MetaspaceSize=32m -XX:MaxMetaspaceSize=32m

由于是一个桌面的程序,所以,没有给太多的内存,根据参数,可以得出各个区域的大小为:

最大堆时为 64M

老年代为 48M

新生代为 16M

元数据区 为32M

对于性能问题,有两个定位思路: 一个是没有内存卡,第二个是cpu被占用卡,三是看网络卡不卡(和网络相关)

好,那我们一个一个看,首先我们先看cpu被占用卡顿

从window 任务管理器 查看到我们启动java程序,看一下 程序 占用的cpu,如果很高,则说明是线程出了问题。 那我们需要将java进程找到到底是哪个线程 出的问题,此时需要借助工具ProcessExplorer 来查看线程

打开工具,找到对应的进程,右击 属性,在thread 页面,可以看到 线程的id。

此时,我们需要先借助jstack 进程id 命令,打印出java 进程中素有的线程,

下一步,我们要讲之前看到的cpu使用很高的线程id 变成16进制,然后在 jstack 线程 信息中,寻找nid 为该值的线程,根据线程的信息,确定到底是哪个类除了问题。

如果是linux系统的话,这可以通过top 产看进程 top -Hp 进程id 产看线程id,后面处理就和window的一样了。

程序卡住不动时,我们先用 jstat -gc 进程id 来获取 当前的 内存使用情况如下

C:\Windows\system32>jstat -gc 4848 1000 20
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT
512.0  512.0   0.0    0.0   15360.0    2.0     49152.0    10068.4   32768.0 32079.0 3712.0 3601.6   3150    4.263 2943   110.611  114.874
512.0  512.0   0.0    0.0   15360.0    2.0     49152.0    10068.4   32768.0 32079.0 3712.0 3601.6   3150    4.263 2943   110.611  114.874
512.0  512.0   0.0    0.0   15360.0    2.0     49152.0    10068.4   32768.0 32079.0 3712.0 3601.6   3150    4.263 2943   110.611  114.874
512.0  512.0   0.0    0.0   15360.0    2.0     49152.0    10068.4   32768.0 32079.0 3712.0 3601.6   3150    4.263 2943   110.611  114.874

其中 4848 是进程id,1000为1s采集一次 20代表采集20次

需要关注的是,这个地方列出的 单位是KB 其中

s0c代表新生代的s0 区分配0.5M的内存
s1c代表新生代的s1 区分配0.5M的内存
s0u代表新生代的s0 区使用了0 内存
s1u代表新生代的s1 区使用了0 内存
EC代表新生代伊甸区为15M
EU代表新生代伊甸区使用了2KB
OC OU 代表老年代 分配空间和使用情况,当前分配了48M,用了10M
CCSC:压缩类空间大小
CCSU:压缩类空间使用大小
YGC:年轻代垃圾回收次数
YGCT:年轻代垃圾回收消耗时间
FGC:老年代垃圾回收次数
FGCT:老年代垃圾回收消耗时间
GCT:垃圾回收消耗总时间

重点关注 的是 MC和MU 这两列的值,发现在程序卡顿的时候,这个值非常接近我们对jvm 设置参数时候的最大值32M

也就是说:元数据区 的空间不够了!

那么这个元数据区 是干啥的? 其作用和jdk7及其之前的版本中的方法区一样,是用来存储 类信息,当然jdk8 之后 叫元数据区了,和jdk7有区别。

居然是加载的类太多了?

那如何看到jvm加载了那些类呢?

在jvm的参数上面加上 -verbose:class 可以看到加载了那些类 推荐 将 结果 保存到文件,然后分析

但是你会发现 有些类名 被压缩了,根本看不出来,此时,你需要再加上一个参数

-Dcom.sun.xml.bind.v2.bytecode.ClassTailor.noOptimize= true

此命令也可以表示直接关闭bytecode反射机制

加上jvm参数后,可以继续运行程序,看一下到底是加载了那些类,导致的这个问题。

后面定位分析,就需要你结合自身的代码,确定到底是哪个类,加载过多,由于公司安全,我无法上代码了。

最后:这里再补充一下,在发生性能问题的时候,应该说100%的问题是cpu使用率问题造成的,而堆内存不够或者是元数据内存不够,只是 java卡顿在内存上的黑盒表现,在卡顿时,通过监控java 进程中线程的使用率,你会发现,cpu 很高的往往是 GC线程,这时,你要关注的本质上,还是内存不够了。

最后,附上两位大神的定位记录,非常棒:

https://www.cnblogs.com/tylorliu/p/6978049.html

https://blog.csdn.net/cockroach02/article/details/52330478

猜你喜欢

转载自my.oschina.net/u/1473861/blog/1787691