JVM之垃圾收集策略(三)

垃圾收集策略

程序计数器,虚拟机栈,本地方法栈随线程而生,随线程而灭,栈中的栈帧随着方法的进入和退出而执行入栈和出栈操作,每个栈帧分配多少内存基本上是在类结构确定下来时就已知的,因此这几个区域的分配和回收具备确定性。在这几个区域不需要过多考虑如何回收的问题,当方法执行结束或者线程结束,内存自然就跟随着回收。
而Java堆和方法区,只有程序运行期间才知道会创建多少对象,这部分内存的分配和回收都是动态的,GC关注的正是这部分内存。

判断对象是否存活

如果一个对象不被任何对象或变量引用,那么他就是无效对象,需要被回收。判断是存活主要有以下几种算法。

引用计数算法

在对象中添加一个引用计数器,每当有一个对地方引用它时,计数器值就加一,当引用失效时,计数器值就减一,任何时刻计数器为零的对象就是不可能再被使用的。
引用计数原理简单,判定效率也高,但是主流的JVM并没有选用引用计数来管理内存,主要原因是这个算法有很多例外情况需要考虑,比如对象之间相互循环引用。

对象 objA 和 objB 都有字段 instance,令 objA.instance = objB 并且 objB.instance = objA,由于它们互相引用着对方,导致它们的引用计数都不为 0,于是引用计数算法无法通知 GC 收集器回收它们。

可达性分析算法

所有和 GC Roots 直接或间接关联的对象都是有效对象,和 GC Roots 没有关联的对象就是无效对象。

GC Roots作为起始节点,根据引用关系向下搜索,搜索过程所走过的路径称为引用链,如果某个对象到GC Roots之间没有任何引用链相连,就证明此对象不可能在被使用的。

在Java技术体系里面,固定可作为GC Roots的对象包括:

  • 在虚拟机栈(栈帧的本地变量表)中引用的对象,比如各个线程被调用的方法堆栈中使用到的参数,局部变量,临时变量等
  • 在方法区中常量引用的对象,比如字符串常量池里面的引用
  • 在本地方法栈中JNI(Native)引用的对象
  • Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象,比如NullPointerExceptionOutofMemoryError等,还有系统类加载器
  • 所有被同步锁(synchronized)持有的对象
  • 反映Java虚拟机内部情况的JMXBeanJVMTI中注册的回调,本地代码缓存等

引用

判定对象是否存活与“引用”有关。在 JDK 1.2 以前,Java 中的引用定义很传统,一个对象只有被引用或者没有被引用两种状态,我们希望能描述这一类对象:当内存空间还足够时,则保留在内存中;如果内存空间在进行垃圾手收集后还是非常紧张,则可以抛弃这些对象。很多系统的缓存功能都符合这样的应用场景。

在 JDK 1.2 之后,Java 对引用的概念进行了扩充,将引用分为了以下四种。不同的引用类型,主要体现的是对象不同的可达性状态reachable和垃圾收集的影响。

强引用

类似Object object = new Object()这类的引用,只要强引用还存在,垃圾收集器就永远不会回收掉被引用的对象。

软引用

软引用是用来描述一些还有用,但非必须的对象,只被软引用关联着的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收。即如果还有空闲内存,即暂时保存,当内存不足时清理掉。JDK1.2之后提供了SoftReference来实现软引用。

弱引用

弱引用用来描述那些非必须对象,它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生为止。JDK 1.2之后提供WeakReference类来实现弱引用

虚引用

虚引用也成为幽灵引用或者幻影引用,他是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存空间构成影响。也无法通过虚引用来获取一个对象实例,为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知,在JDK1.2之后提供PhantomReference类来实现虚引用。

回收过程

对于可达性分析中不可达的对象,那么它将会被第一次标记,随后进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法,假如对象没有覆盖finalize()方法,或者finalize()方法已被虚拟机调用过,那么虚拟机将这两种情况都视为“没有必要执行”,那么对象基本上就真的被回收了。

如果对象被判定为有必要执行finalize()方法,那么对象将会被放置在一个名为F-Queue的队列之中,并在稍后由一条由虚拟机自动创建的,低调度的优先级的Finalizer线程去执行它们的finalize()方法,但并不确保所有的finalize()方法都会执行结束,如果方法出现耗时操作,虚拟机就会直接停止该方法,将对象清除。

如果在执行finalize()方法时,将this赋给了某一个引用,那么对象就重生。
任何一个对象的finalize()方法都只会被系统自动调用一次。

回收方法区内存

由于方法区中主要存放生命周期较长的类信息,常量,静态变量,所以方法区的垃圾回收成果往往低于堆的回收成果。方法区的垃圾回收主要回收两种垃圾:

  • 废弃的常量
  • 不再使用的类型

判定废弃常量

只要常量池中的常量不被任何变量或者对象引用,那么这些常量就会被清除掉,常量池中其他类信息,方法,字段的符号引用也类似。

判定无用的类

判定一个无用的类,需要同时满足下面三个条件:

  • 该类的所有实例都已被回收,也就是Java堆中不存在该类及任何派生子类的实例
  • 加载该类的类加载器已经被回收
  • 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法
文章作者: L1nker4
文章链接: https://l1n.wang/2020/03/jvm03-gc-strategy/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 L1nker4's Blog