内存泄露和溢出及常见的排查和原因
内存泄漏
内存泄漏:不再使用的对象,仍占用内存(存在GCRoots引用),无法回收,导致程序运行过程中,可使用的内存越来越少,或者固定占用了部分无法释放的内存。
情况:
- 静态集合持有非静态变量;
- 静态的对象、字段生命周期是与程序是一致的;无法释放;
- 如果元素不再使用,需要手动删除;
- 长生命周期的对象,持有短生命周期对象的引用;
- 即使短生命周期的对象不用了,依然无法释放,因为有引用;
- 此类情况需要考虑业务进行避免;
- 存在无法释放的数据库连接、IO连接等;
- 连接使用过程中,垃圾回收不会回收连接使用的对象;
- 用完资源一定要释放,且要考虑异常的可能,所以一般在finally中释放链接,或者使用try-with-resources语法
- 内部类持有外部类对象;
- 即使外部类对象不再使用,也不会被回收;
- ThreadLocal;
- 使用线程池的情况下,线程使用完ThreadLocal本地变量,只要线程不被回收,本地变了不会自动清空;
- 用完即清理,且不要存放大对象;
内存溢出
内存溢出:程序申请的内存超过了系统能够提供的最大值,或者超过了程序栈所能申请的最大内存,则会发生OOM;
三种常见内存溢出:
- 元空间内存不足->OOM
- OutOfMemoryError: PermGen space:元空间主要存储着大量的class信息,静态数据;
- 堆内存不足->OOM
- OutOfMemoryError: Java heap space
- 栈内存溢出
- OutOfMemory:栈动态内存扩展,内存不足时抛出; ==JVM没有动态扩展的栈,所以Java进程的栈不会OOM==
- StackOverflowError:栈创建栈帧,没有足够的内存,抛SOF; 一般是递归调用过程中抛出; 可以减少栈内存大小,提高栈帧深度;
内存逃逸
程序中最高效的内存结构是:栈 内存逃逸:当编译器无法保证一个变量的声明周期只在当前的函数内时,就会将其分配到堆中,以保证函数返回,栈帧回收后,此变量仍然有效;
- 闭包是一个典型的内存逃逸情况; 内存逃逸影响:栈内存是没有回收的,直接释放,速度很快,当发生内存逃逸,分配在堆上,就需要依靠GC来回收,会增加GC的压力;
内存逃逸场景
1、超出栈的大小,只能分配到堆上; 2、闭包; 3、golang中入参为不确定类型,如:interface;函数无法确定其大小,全都会逃逸,分配在堆上; 4、函数内创建的对象,被堆上对象引用;
逃逸分析
1、JVM的JIT动态编译代码时,会进行逃逸分析,如果对象没有逃逸,则不会分配到堆上;
JDK1.7之后,逃逸分析是默认开启的;