我的Java程序查错之路

近一段时间写过一些程序,也出了一些问题,自己通过各种手段追查并解决了问题,记录下来作为经验总结。


一、CPU飙升到接近100%,程序卡死。

       背景:一次工作中需要将hdfs上经过hive分析处理的大量数据导入到neo4j图库。

        首先将hdfs上的日志文件使用hive进行处理,处理成可以直接插入到neo4j图库的文件,将文件下到 linux 本地,然后使用开源的代码将文件读取,并批量导入到neo4j图库,但是当数据大约导入到第100多万条的时候,CPU飙升到100%,但是程序无法往下运行。

       

       问题解决:

       1)  首先 jps 查到运行的程序的进程号pid

        2)  查看是哪个线程最占cpu  ——>    ps -eL -o pid,%cpu,lwp|grep -i  pid  得到线程号 tid

  3)  linux上执行printf  "%x\n"  tid 得到16进制的tid格式

      4) jstack  pid 得到线程文件,在文件全局搜索3)中得到的字符串,观察最占cpu的线程堆栈,看他都在执行哪些操作,为什么,这么占CPU,最终定位问题代码

   

      最终结论:

         这个问题困扰我很久,因为是使用开源代码,代码量大,而且不熟,所以只能靠java小工具排查问题。最终我按照上面的解决办法定位占cpu操作是因为不断的存在

StringBuilder.append(char) 操作,然后就是扩容导致的不断memcopy耗损CPU。

          之所以会一致memcopy是因为有一行数据存在一个双引号,而且是成单出现,这样即使换行符出现,应该跳转到下一条记录,但是因为引号没有结束,还是认为是

  本条记录的内容,所以会把接下来的全部数据当成本条问完成记录,一直没有结束,这样程序也不异常,但是stringbuilder不断加长,cpu不断memcopy,程序无法向下推进


二、标准的heap OOM异常

背景:项目组需要从盘古系统的数据库导出大批量的数据到 hbase进行数据分析。使用了ibatis框架来查询mysql数据库并导出数据(此处这种方案好坏姑且不论,或许数据库到hbase有直接对接的通道,且听下文),但是当大约导入到20万条记录以后,抛出heap OOM异常。


问题解决:在程序运行过程中,执行以下命令来观察程序运行和垃圾回收情况

 jstat  -gcutil  pid  1s   每秒打印一次gc情况

 jmap -histo  pid

         jmap -histo pid   查看堆内统计情况

         jmap -dump:format=b,file=heap.bin   pid 生成堆转存文件进行分析

将生成的堆转存文件heap.bin下载到本地,使用MAT进行内存泄漏分析。


问题结论:

同样是因为使用开源的ibatis框架,框架内部处理对于我相当于黑盒子,只能借助java小工具分析。最终发现对于同一个线程打开的SqlSession对象,每次select之后,会在本地使用Map缓存,这样时间一场,缓存内容太大,出现内存溢出。


三、同样是heap OOM异常

背景:我们写了个队列服务,接受上游发布数据,存储在文件系统,并接受下游订阅,将存在文件系统的内容再下发下去。后来改了代码之后出现ArrayOutOfIndexException,

  解决了上面异常又出现OOM错误


问题解决:

出现ArrayOutOfIndexException是因为代码改后将每次存储的文件名改为全路径,而之前只是文件名结尾部分,限定了文件名大小为一个数组,结果全文件名超过数组大小,数组越界。

但是可能因为之前接受下游订阅读取文件都是在一个线程池执行,没有catch住ArrayOutOfIndexException , 结果异常外抛,异常堆栈信息只到Thread.run() 就没有了,没有业务逻辑的详细堆栈信息,结果造成排查问题的难度。为了排查问题,我打算把一次写一个文件的大小改小进行观察(之前是一个文件2G,复现问题难度大),最终发现了这个问题,但是这个时候出现了OOM异常。

出现OOM异常,使用MAT分析堆转存文件没有内存泄漏,这个时候造成我非常大的疑惑,同时在windows本机上跑,异常堆栈信息只到Thread.run()方法,无处查起。

这个时候发现每次读完一个文件换文件的时候会出现OOM错误,开始重点分析换文件的时候。结果发现调试时候,出现读出的内容不是整块整块出现,即读完一条一条记录,最终剩下那么几个字节,然后再读4个字节的int作为接下来的内容的length,分配length长度的byte数组,结果发现length太大,OOM出现。

为什么文件中会剩下不完整记录呢。各种调试均无法复现,这个时候把写文件的日志打印出出来,发现同一个文件被从头写了数次,最终定位代码,发现生成文件的方式有问题,是一秒钟生成一个同样名字的文件,因为我把文件改小了,所以1秒钟生成同一个文件多次,多次写入,结果文件最终不是若干个完整的记录。


问题结论:

有时候调试不完全能解决问题,特别是多线程问题,打印日志才是王道。有时候OOM可能是一次性要求分配的内存太大,不一定能从堆转存文件中分析出来。

四、线程数只升不降,无法回收

背景:还是我们写的队列的bug,java程序线程数不断飙升,每次重启客户端重连服务器服务器的线程数就往上飙升一截。


问题解决:



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值