0%

Java 常见异常分析

内存泄漏

问题现象

JVM 内存耗尽,后台日志抛出OutOfMemeryError异常。

问题原因

内存溢出问题可能的原因比较多,可能是全局的List、Map等对象不断被扩大,也可能是程序不慎将大量数据读到内存里;可能是循环操作导致,也可能后台线程触发加载数据导致。

解决方法

对于纯Java应用,在jvm启动时设置 -XX:+HeapDumpOnOutOfMemoryError参数,会在内存溢出时生成heapdump文件。使用 ha456.jar 工具打开headdump文件,分析大对象是如何产生的。

当然,在heapdump中对象类型可能只是List这种结构,看不出具体哪个业务代码创建的对象。此时要分析所有的全局对象,列出可疑的List或Map对象,排查其溢出原因。

全局对象、引用的初始化,修改要慎重。建议应用梳理所有可能存放全局对象的代码统一管控。

JVM 垃圾回收频繁

问题现象

top -H -p pid 命令查看,GC Slave 线程CPU占用排名始终为前三名,同时Jconsole查看jvm内存占用较高,垃圾回收频繁。使用ga456.jar 分析gc日志,查看gc频率、时长。

打印gc日志的方法:在jvm启动参数里增加 -verbose:gc -Xverbosegclog:/filepath/gcdetail.log

问题原因

高并发下内存对象较多,jvm堆内存不够用

解决方法

扩大堆内存大小 -Xmx2048m -Xms2048

CPU 高

问题现象

50并发压测,监控工具显示bp、前置CPU占用90%以上。

问题原因

业务处理中存在大量CPU计算操作。

解决方法

采用更高效的算法、数据结构替换原来消耗CPU的代码、或者采用新的设计绕过瓶颈代码,比如查找数据的逻辑,可以把List改为Map,以空间换时间;比如Json报文替换XML报文,提高传输、解析和打印日志的效率。

导致Cpu计算资源高消耗的代码

报文格式转换、加解密、正则表达式、低效的循环、低效的正则表达式。

排除方法

  1. 压测进行时,使用jvisualvm工具远程连接应用,点抽样器CPU,点快照生成线程快照。采样一段时间后,抽样器会显示各个方法占用cpu时间,可以针对cpu时间占用高的方法进行优化。
  2. 使用tprofiler,jprofiler,OracleDeveloperStudio 12.6-liunx-86工具分别分析消耗cup时间长的方法,以上工具分析结果可能有些差别。针对cpu计算耗时最长的方法进行优化。

批处理时间长、数据库逐笔插入缓慢

问题现象

大批量数据(10万条以上)更新或插入数据库、耗时较长。

问题原因

批量数据处理时,如果逐步更新数据库库,则会存在大量网络IO、磁盘io,耗时较长,而且对数据库资源消耗较大。

解决方法

  1. 采用java提供的batchUpdate方法批量更新数据库,每1000条commit一次,可大幅度提高数据更新效率。
  2. 单线程改成线程池,并行处理,充分利用多核CPU,通过数据库或者其他同步锁控制并行性;增加缓冲池,降低数据库或磁盘IO访问频率。

数据库CPU高

问题现象

后台指令发送满负荷工作时,数据库CPU高

问题原因

后台指令发送线程每次对全量查询结果排序,结果集很大,然后取一条记录;索引区分度不高,满负荷执行时;查询频率很高;压测显示,并行发送指令的后台线程越多,数据库CPU越高,效率越低。

解决方法

  1. 去掉Order by,增加索引后,效果不明显。因为结果集大和查询频繁两个问题没有解决,因此网络使用设计新的方案。
  2. 新方案:设计指令发送线程池,生产者线程后台任务服务器只有一个线程,负责查询待发送指令,每次查询50条指令。每条指令包装成一个Runable对象,放进ThreadPoolExecutor线程池,线程池大小参数设置为100或200.每当线程池满时,生产者停止生产指令,休息15秒后继续。消费者线程即线程池里的线程,参数设置为4,8或12(和不同指令类型的指令数据量成正比)。

改进后的方案,数据库CPU降到10%一下,发送效率单机提升6倍,且可线性扩展任务服务器。

压测TPS曲线剧烈下降或抖动

问题现象

50并发压测,TPS曲线正常应该是平缓的,波动不大,如果突然出现剧烈下降,并且短时间内无法恢复,则可能存在问题。

问题原因

一般是由于前置或者bp的jvm进行垃圾回收,或者日志记录磁盘满导致的,

解决方法

如果不是特别剧烈的波动或者TPS曲线下降后长时间不反弹,则可以忽略该问题。否则,需要分析曲线下降的时刻,系统当时正在发生的事情。可以通过top命令监控当时CPU占用比较高的线程,也可以kill -3 pid 杀javacode来查看线程堆栈。