调优

调优方向:

规划与预调优

优化运行环境

  1. 系统CPU经常100%,如何调优?(面试高频) CPU100%那么一定有线程在占用系统资源,
    1. 找出哪个进程cpu高(top)
    2. 该进程中的哪个线程cpu高(top -Hp)
    3. 导出该线程的堆栈 (jstack)
    4. 查找哪个方法(栈帧)消耗时间 (jstack)
    5. 工作线程占比高 | 垃圾回收线程占比高
  2. 系统内存飙高,如何查找问题?(面试高频)
    1. 导出堆内存 (jmap)
    2. 分析 (jhat jvisualvm mat jprofiler ... )
  3. 如何监控JVM
    1. jstat jvisualvm jprofiler arthas top...

案例分析

大内存硬件的部署策略

对于用户交互性强、对停顿时间敏感、内存又较大的系统,可以考虑使用一些以延迟为目标的垃圾收集器(如ZGC)来解决问题

使用这种方式有如下缺点:

这种方式通过负载均衡的方式来达到内存资源的充分利用,避免了上面的一些问题的同时也带来了下面的问题:

集群同步的内存溢出问题

对于一些集群,需要通过网络同步数据,如果同步的数据产生的速度大于网络速率,则大量数据在内存里堆积,就会发生溢出的问题

直接内存溢出

JVM对直接内存的回收不能像新生代、老年代那样,发现空间不足了就主动通知收集器进行垃圾回收,它只能等待老年代满后Full GC出现后,“顺便”帮它清理掉内存的废弃对象

此时如果程序用完了直接内存,只能眼睁睁看着堆中还有许多空闲内存,自己却不得不抛出内存溢出异常了

外部命令导致系统缓慢

一些系统调用对于JVM来说是十分重的,如果频繁调用,系统资源的消耗必定很大

不合适的数据结构

如果数据很大,不选择合适的数据结构,空间利用效率不高,就会让原本不富裕的内存更加雪上加霜

Windows虚拟内存导致的停顿

https://hllvm-group.iteye.com/group/topic/28745

该案例给出了一个由于程序最小化working set会被trim的情况,也就是内存被交换到了硬盘中,此时发生GC的时候需要花费很长时间将内存数据再交换到内存中,这就导致了明明堆内存很小,GC时间却非常长

安全点导致的长时间停顿

https://juejin.im/post/5d1b1fc46fb9a07ef7108d82

该案例给出了一个由于部分线程执行到安全点非常慢从而导致拖慢整个用户线程停顿时间

方法调用、循环跳转、异常跳转这些位置都可能会设置有安全点,但是HotSpot虚拟机为了避免安全点过多带来过重的负担,对循环还有一项优化措施,认为循环次数较少的话,执行时间应该也不会太长,所以使用int类型或范围更小的数据类型作为索引值的循环默认是不会被放置安全点的

一个内存溢出与频繁GC的案例

某天接收到上下游系统的给我们反馈,说视图库系统接口响应很慢,一个接口响应要等几十秒,随即登录上机器排查,发现视图库的 Java 进程内存占用很高,CPU占用也很高。

在进行实际排查前心里根据这个现象假设了挺多可能:最近发布的代码有问题,是不是有死循环;是不是外部接口依赖出现了问题,导致线程过多等等...

首先通过 top 命令查看到这个进程下 CPU 占用比较高的线程,发现有挺多,因为系统还在对外提供服务,所以通过 jstack 挨个查看这些线程,除了一些做数据转换处理的线程外,也发现了挺多的 GC 线程在运行,于是重点就关注了 GC 这个方向。

于是通过 jstat 命令查看了一下 GC 情况,发现没一会就有一次 FGC,而且 FGC 完了之后内存并没有下降多少,于是怀疑是内存泄露,于是决定准备 jmap 导出 dump 文件,但这个时候发生 OOM 崩了,这也验证了我的猜测,有些对象回收不掉导致了这个问题。

后面等了几天线上终于又出现这个问题了,然后趁内存还没爆之前,赶紧把 hprof 文件导了出来,下载到本地用 VisuamVM 分析了一下,发现一个 Point 对象占了将近一半的内存,于是在代码里寻找这个类的引用,运气比较好,发现有一个地方使用了 HashSet 来存储外部推送过来的 Point。

这个 Point 存放的内容就是经纬度加上其他的一些点位信息,是由上级系统不定时推送过来的,我们在视频图像数据处理的时候需要针对这些点位做一些特殊处理。最根据开始我们的评估,由于泉州也就几十万个定位,所以存放在内存中是足够的,问题就在于 Point 这个类没有重写 hashcode 与 equals 方法,导致随着上级系统不断地推送,容器的数据越来越多,最后就崩掉了。

这个问题在前期没有及时发现的原因是:

  1. 早期功能还在迭代,版本发布很频繁,重启的也很频繁,自然内存里的数据不会堆积
  2. 随着系统成型,前端摄像头设备不断加入,导致设备总量越来越多,继而上级推送过来的点位也就越来越多,这个问题也就暴露出来了