此次异常是在集群上运行的spark程序日志中发现的。由于这个异常导致sparkcontext被终止,以致于任务失败:
出现的一些原因
java.lang.OutOfMemoryError有几种分类的,这次碰到的是java.lang.OutOfMemoryError: GC overhead limit exceeded,下面就来说说这种类型的内存溢出。
简单来说,java.lang.OutOfMemoryError: GC overhead limit exceeded发生的原因是,当前已经没有可用内存,经过多次GC之后仍然没能有效释放内存。
众所周知,JVM的GC过程会因为STW,只不过停顿短到不容易感知。当引起停顿时间的98%都是在进行GC,但是结果只能得到小于2%的堆内存恢复时,就会抛出java.lang.OutOfMemoryError: GC overhead limit exceeded这个错误。Plumbr给出一个示意图:
这个错误其实就是空闲内存与GC之间平衡的一个限制,当经过几次GC之后,只有少于2%的内存被释放,也就是很少的空闲内存,可能会再次被快速填充,这样就会触发再一次的GC。这就是一个恶性循环了,CPU大部分的时间在做GC操作,没有时间做具体的业务操作,可能几毫秒的任务需要几分钟都无法完成,整个应用程序就形同虚设了。
此次异常分析
首先,在spark日志中记载异常如下:
这个异常一开始让人无从下手,后面找到异常出自程序的部分代码,但是依旧没有找到具体问题:
最后发现可能不是程序的问题,而是和hive相关的metadata内存不足了。
进入hive发现此表分区的确很多,于是删除了部分分区后再运行spark程序,问题解决!
总结
一些可能的原因和处理办法
1. JVM参数
JVM给出一个参数避免这个错误:-XX:-UseGCOverheadLimit。
但是,这个参数并不是解决了内存不足的问题,只是将错误发生时间延后,并且替换成java.lang.OutOfMemoryError: Java heap space。
2. 优化
找到占用内存大的地方,把代码优化,通过heap dump生产jvm快照,通过分析快照找到占用内存大的对象,从而找到代码位置。
通过设置-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heapdump参数来生产快照,然后通过VisualVM或者MAT等工具分析快照内容进行定位。通过这个参数是将发生OOM时的堆内存所有信息写入快照文件,也就是说,如果此时堆内存中有敏感信息的话,那就可能造成信息泄漏了。
3.通过日志定位问题
此次问题的解决就是将所有报错都提取出来,找到可能报错的点,此次异常基本可以确定就是由hive的metadata内存溢出引起的。