什么样的 Java 对象会被当垃圾回收?

2020-11-09

Java 是一门不需要自己手动控制内存释放的语言。那在程序运行中,它是如何判断哪些内存可以回收呢?

 

从 JVM 的实现角度总体来看

  • 主要考虑的是堆内存区域的 Java 对象的回收

  • 一般使用可达性分析算法,通过 GC Roots 找到不可达对象进行回收

  • 回收对象时需要考虑对象被引用的强度

  • 回收对象前,会调用对象的 finalize() 方法,若在方法里把当前对象赋值给其他变量引用,会有且仅有一次避免被回收的机会

 

可回收内存区域

JVM 在运行的内存分配区域包括线程私有和线程共享两大类。

 

线程私有的内存区域

分为程序计数器、虚拟机栈、本地方法栈这三个区域,三者的生命周期与线程相同,即线程执行结束这三个区域的内存就可以被回收了。

 

线程共享的内存区域

包括堆和方法区,这两部分内存是在程序执行中动态分配与回收的,具有不确定性,所以垃圾收集器主要关注这两部分区域的内存回收。

 

绝大多数 Java 对象是在堆内存中分配的;方法区中存放了 Class 信息、常量、静态变量、即时编译器编译后的代码缓存等数据,排除大量动态类信息创建和卸载的情况,一般方法区内存回收的效率是很低的。所以垃圾收集器最主要关注的是堆内存的回收。

 

方法区的垃圾回收主要包含

1、废弃的常量,包括:字符串常量、接口 方法 和 字段的符号引用。

2、不再使用的类,满以下三个要求可以被回收但并非必然。

  • 堆中不存在类及其子类的实例对象

  • 该类的加载器已被回收

  • Class 对象未被引用,也无法反射访问

 

另外,直接内存不在 JVM 规范中定义的内存区域,使用 Unsafe 的 native 方法直接分配 (allocateMemory) 和释放 (freeMemory) 内存。nio 中通过在堆内存分配 DirectByteBuffer 对象,该对象可以操作直接内存,DirectByteBuffer 对象被回收后 gc 回收未释放的直接内存。

说到这,Java 对象的内存回收的问题,大致可以转变为「如何判断堆中 Java 对象占用的内存可以被回收?」,那得看下面具体的判断对象可回收的算法。

 

内存可回收的算法

常见的判断对象可以被回收的算法有两种:

  • 引用计数算法。理解起来很简单,对象里添加计数器,被引用计数加一,去除引用计数减一,计数为零的时刻就认为对象可以被回收。由于这种算法不好解决 Java 对象循环引用的问题,所以主流垃圾回收器不使用。

  • 可达性分析算法。这个算法的核心思想就是先找到内存中的所有根对象,即 "GC Roots",然后根据引用关系搜索引用对象,未被查到的对象就是可以被回收的对象。

 

GC Roots,根对象包括:

  • 虚拟机栈中引用的对象,如方法中的局部变量、参数等

  • 类的静态变量引用的对象

  • 常量引用的对象

  • native 方法引用的对象

  • JVM 内部引用,如基本类型的 Class 对象、异常对象、系统类加载器对象

  • synchronized 锁住的对象

  • JVM 内部的对象

 

细分引用类型

ConstXiong 备案号:苏ICP备16009629号-3