今天在代码调试过程中,在通过swagger调试某个接口(restful api)的时候,遇到了该错误:OutOfMemoryError GC overhead limit exceeded
通过百度之后,知道该错误产生的原因是:jvm的垃圾回收期回收效率太低,并且多次触发gc之后,还是无法有效回收内存。
显然,该错误是我的代码中,迅速且持续不断的产生新的对象,导致jvm一直gc,并且每次gc过后,又有一堆新的对象产生了,故而又重新触发gc,如此反复循环,jvm一直忙于gc,电脑的cpu和内存使用率一直居高不下,几乎都跑满100%,系统非常的卡顿,无法处理其他事情,这个系统一直维持着这样一种超负荷状态,但就是不会奔溃,反而是我要奔溃了。
分析原因过程:
一开始是,打算用jvm内存分析工具,来查看问题,比如plumbr, VisualVM, JConsole等,但是第一次用不熟悉,只能看到cpu和内存波动很大 ,无法有效定位问题。
现在先撇开工具,
……
我猜想是,代码中出现了无限递归、死循环或者死锁的情况。
首先是无限递归,这种情况应该可以排除,因为这种情况已出现的话,系统是直接奔溃,报错应该是 stackOverFlow。
然后是死锁,这种情况是多线程的调度问题,而我写的代码里面,没有涉及到线程的调度,估计也不是这种情况。
最后就剩下死循环的情况了。
……
经过了几个世纪的挣扎,突然灵光一闪,会不会是之前加入的接口数据返回格式统一配置这里,出了问题,在解析返回数据时,出现了死循环。
看了下代码:
public static List<Field> getAllFields(Object object) { List<Field> allFields = new ArrayList<>(); Class<?> aClass = object.getClass(); allFields.addAll(Arrays.asList(aClass.getDeclaredFields())); while (aClass.getSuperclass() != Object.class && aClass.getSuperclass() != null) { aClass = object.getClass().getSuperclass(); allFields.addAll(Arrays.asList(aClass.getDeclaredFields())); } return allFields; }
终于找到问题原因所在了,自己写的方法,有个严重bug:对象的类型(aClass)迭代无效,一直是同一个。修改代码如下即可:
public static List<Field> getAllFields(Object object) { List<Field> allFields = new ArrayList<>(); Class<?> aClass = object.getClass(); while (aClass != Object.class && aClass != null) { allFields.addAll(Arrays.asList(aClass.getDeclaredFields())); aClass = aClass.getSuperclass(); } return allFields; }
以此为戒,以后写过的函数,要经过仔细的测试,尤其涉及到循环和递归的东西,更要慎重,注意其结束条件。