本文章为《深入浅出 Java 虚拟机》系列课程学习笔记,侵删。学习地址为 深入浅出 Java 虚拟机
1 JVM 中如何进行垃圾回收?
JVM 的 GC 动作并不受程序控制,它会在满足条件的时候,自动触发。
在发生 GC 的时候,一个对象,JVM 总能够找到引用它的祖先。找到最后,如果发现这个祖先已经名存实亡了,它们都会被清理掉。而能够躲过垃圾回收的那些祖先,比较特殊,它们的名字就叫作 GC Roots。
从 GC Roots 向下追溯、搜索,会产生一个叫作 Reference Chain 的链条。当一个对象不能和任何一个 GC Root 产生关系时,就会被清除。
2 什么是 GC Roots?
GC Roots 是活跃的引用,可以理解为程序通过引用可以访问到的对象。一般分为以下三类:
- 活动线程相关的各种引用,例如线程中与栈帧相关的各种引用(被调用的方法的引用类型参数、局部变量、临时值)
- 类的静态变量的引用
- JNI 引用
注意,我们这里指的是活跃的引用,而不是对象,对象是不能作为 GC Roots 的。
3 引用级别
- 强引用:即使程序会异常终止,这种对象也不会被回收。这种引用属于最普通最强硬的一种存在,只有在和 GC Roots 断绝关系时,才会被消灭掉。例如
Object obj = new Object();
- 软引用:软引用用于维护一些可有可无的对象。在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。软引用适用于缓存技术上
- 弱引用:弱引用对象相比较软引用,要更加无用一些,它拥有更短的生命周期。当 JVM 进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。它的应用场景和软引用类似,可以在一些对内存更加敏感的系统里采用
- 虚引用:如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收,主要用来跟踪对象被垃圾回收的活动。虚引用必须与一个引用队列关联,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象之前,把这个虚引用加入到与之关联的引用队列中。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动
4 OOM
OOM 即 Out Of Memory。当内存空间不足,系统撑不住了,JVM 就会抛出 OutOfMemoryError 错误。内存区域有哪些会发生 OOM 呢?
除了程序计数器,其他区域都有 OOM 溢出的可能。但是最常见的还是发生在堆上。
为什么会引起 OOM 呢?主要原因如下:
- 内存的容量太小了,需要扩容,或者需要调整堆的空间
- 错误的引用方式,发生了内存泄漏。没有及时的切断与 GC Roots 的关系。比如线程池里的线程,在复用的情况下忘记清理 ThreadLocal 的内容
- 接口没有进行范围校验,外部传参超出范围。比如数据库查询时的每页条数等
- 对堆外内存无限制的使用。这种情况一旦发生更加严重,会造成操作系统内存耗尽
典型的内存泄漏场景,原因在于对象没有及时的释放自己的引用。比如一个局部变量,被外部的静态集合引用。