教你写Bug,常见的 OOM 异常分析( 二 )

  • 如果是业务峰值压力,可以考虑添加机器资源,或者做限流降级 。
  • 如果是内存泄漏,需要找到持有的对象,修改代码设计,比如关闭没有释放的连接

  • 教你写Bug,常见的 OOM 异常分析

    文章插图
     
    面试官:说说内存泄露和内存溢出
    加送个知识点,三连的终将成为大神~~
    内存泄露和内存溢出内存溢出(out of memory),是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个 Integer,但给它存了 Long 才能存下的数,那就是内存溢出 。
    内存泄露( memory leak),是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光 。
    memory leak 最终会导致 out of memory!
    三、GC overhead limit exceededJVM 内置了垃圾回收机制GC,所以作为 Javaer 的我们不需要手工编写代码来进行内存分配和释放,但是当 Java 进程花费 98% 以上的时间执行 GC,但只恢复了不到 2% 的内存,且该动作连续重复了 5 次,就会抛出 java.lang.OutOfMemoryError:GC overhead limit exceeded 错误(俗称:垃圾回收上头) 。简单地说,就是应用程序已经基本耗尽了所有可用内存,GC 也无法回收 。
    假如不抛出 GC overhead limit exceeded 错误,那 GC 清理的那么一丢丢内存很快就会被再次填满,迫使 GC 再次执行,这样恶性循环,CPU 使用率 100%,而 GC 没什么效果 。
    3.1 写个 bug出现这个错误的实例,其实我们写个无限循环,往 List 或 Map 加数据就会一直 Full GC,直到扛不住,这里用一个不容易发现的栗子 。我们往 map 中添加 1000 个元素 。
    /** * JVM 参数: -Xmx14m -XX:+PrintGCDetails */public class KeylessEntry {    static class Key {        Integer id;        Key(Integer id) {            this.id = id;        }        @Override        public int hashCode() {            return id.hashCode();        }    }    public static void main(String[] args) {        Map m = new HashMap();        while (true){            for (int i = 0; i < 1000; i++){                if (!m.containsKey(new Key(i))){                    m.put(new Key(i), "Number:" + i);                }            }            System.out.println("m.size()=" + m.size());        }    }}...m.size()=54000m.size()=55000m.size()=56000Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded从输出结果可以看到,我们的限制 1000 条数据没有起作用,map 容量远超过了 1000,而且最后也出现了我们想要的错误,这是因为类 Key 只重写了 hashCode() 方法,却没有重写 equals() 方法,我们在使用 containsKey() 方法其实就出现了问题,于是就会一直往 HashMap 中添加 Key,直至 GC 都清理不掉 。
    ‍ 面试官又来了:说一下HashMap原理以及为什么需要同时实现equals和hashcode
    执行这个程序的最终错误,和 JVM 配置也会有关系,如果设置的堆内存特别小,会直接报 Java heap space 。算是被这个错误截胡了,所以有时,在资源受限的情况下,无法准确预测程序会死于哪种具体的原因 。
    3.2 解决方案