为何半夜告警电话狂打不停,为何上线用户投诉不断,是道德的沦丧还是人性的扭曲,NO,是代码的缺陷。
Java8的JVM内存管理中,大对象生成直接放入老年代的,当老年代空间不足,就会进行FullGC,频繁的生成大对象,会进行频繁的FullGC,甚至直接OOM导致应用宕机。
有时候一段平平无奇的代码,看似温柔,实则暗藏着巨大隐患,这时候算法显得格外的重要,这也是为什么算法是一些大厂招人必备考核项目的原因之一。如何去用最短的时间,最少的空间去使整个产品更加健壮流畅。
在某个场景中需要计算出时长,已知开始时间,结束时间,去除的时间,去除的时间包含多个时间段,比如2019-11-20 00:00:00到2020-12-01 00:00:00,需要去除掉2018-12-20 05:09:00到2019-12-19 09:09:00,2020-04-20 11:00:00到2020-06-23 00:08:00,2020-11-19 00:55:00到2020-12-20 00:00:00,以及2021-12-20 00:00:00到2021-11-19 07:45:00这些时间段,需要计算出在开始和结时间内,除去 需要去除的时间段的秒数,剩余多少秒数。
最初的代码,也就是造成OOM的代码如下,用一个set集合A来保存开始到结束时间内的秒数,用另一个set集合B来保存去除时间段的秒数,然后A除去B中的元素,返回A的size。
看似功能上没啥毛病,但是如果当时长过大的时候,就需要一个很大的数组来存放元素,大对象直接放入老年代,用户量高峰期、后台任务高峰期,就会生成大量的大数组,导致老年代空间不足。
public static int countSecond(long start, long end, List<Map<String, Long>> excludes) {
HashSet<Long> seconds = new HashSet<Long>();
for(long second=start; second<=end; second++) {
seconds.add(second);
}
HashSet<Long> excludeSeconds = new HashSet<Long>();
if(excludes != null && excludes.size()>0) {
for (Map<String, Long> exclude : excludes) {
Long exStart = exclude.get("start");
Long exEnd = exclude.get("end");
for (long second = exStart; second <= exEnd; second++) {
excludeSeconds.add(second);
}
}
}
if(excludeSeconds.size() > 0) seconds.removeAll(excludeSeconds);
return seconds.size();
}
该代码问题暴露的时间恰巧是半夜凌晨,运维重启无效,告警一直持续,联系到开发定位,定位到该段代码,连夜紧急修复并上线,第一修复版本,先把秒数除以/60,得到分钟数,最后结果再乘*60并返回,这样容量就能够缩小60倍,当时的数据缩小60倍之后,当同样的集群资源下能够正常运行。
public static int countSecond(long start, long end, List<Map<String, Long>> excludes) {
HashSet<Long> seconds = new HashSet<Long>();
start = start / 60;
end = end / 60;
for(long second=start; second<=end; second++) {
seconds.add(second);
}
HashSet<Long> excludeSeconds = new HashSet<Long>();
if(excludes != null && excludes.size()>0) {
for (Map<String, Long> exclude : excludes) {
Long exStart = exclude.get("start") / 60;
Long exEnd = exclude.get("end") / 60;
for (long second = exStart; second <= exEnd; second++) {
excludeSeconds.add(second);
}
}
}
if(excludeSeconds.size() > 0) seconds.removeAll(excludeSeconds);
return seconds.size() * 60;
}
其实会发现第一修复版本依然还是会存在OOM的问题的,如果时长足够的话,并且除以60存在精度问题。而且这种算法的空间复杂度和时间复杂度都过于高。
通过相加减的方式直接得出,去除交集即可。