Skip to Content
DocumentationJavaJava虚拟机JVM对象引用关系

JVM内存回收-对象引用关系

说起内存回收,一般需要考虑以下三个事情:

  1. 哪些内存需要回收?
  2. 什么时候回收?
  3. 如何回收?

前面介绍了JVM中的内存区域分布,其中程序计数器,虚拟机栈和本地方法栈是和线程的生命周期绑定的,线程销毁时,其对应的内存空间也会被回收.

但堆和方法区就存在着不确定性:

  1. 一个程序在运行期会加载多少个类是不确定的
  2. 一个线程在运行期间会创建多少个对象是不确定的

而内存回收,就是关注这部分内存该如何管理

引用计数法

判断一个对象是否存活,道理很简单:一个对象只要被至少一个对象引用.那么就是存活的. 因此便有了引用计数法.

引用计数法的原理很简单: 对于每个对象,新增一个字段存储其被引用从次数,当建立引用关系时,引用次数+1,当取消引用关联时,引用次数-1. 很显然,当对象的引用次数=0时,说明这个对象需要被回收了.

在大多数情况下,这个算法的效率很高,缺点就是引入了额外的物理空间,但是也存在一个无法解决的问题: 循环引用

循环引用

假如有两个对象,ObjA和ObjB,他们相互引用,同时外部引入ObjC引用ObjA,他们的关系如下

ObjC -> ObjA <=> ObjB

此时,ObjA和ObjB的引用计数分别为ref(ObjA)=2ref(ObjB)=1. 当ObjC取消对ObjA的引用后,此时ref(ObjA)ref(ObjB)均为1,此时没有任何方式可以访问到着两个对象,但是其引用计数都不为0,所以他们无法被回收掉

可达性分析算法

前面提及的引用计数法无法解决循环依赖的问题,本质原因在于,无法确定当前对象能否被用户线程所看到,引用计数法只关注了引用的次数,却忽视了线程是否可达.

目前Java是通过可达性分析算法来判断对象是否存活: 通过一系列被称为GC Roots的对象作为起始节点,然后从这些节点开始,根据引用关系向下搜索,整个搜索过程所走过的路径被称为”引用链”.如果一个对象没有与任何引用链相连,说明这个对象不可能再次被使用

gc_roots

如上图所示,object 5,object 6object 7无法从GC Roots访问到,属于可回收对象

在Java体系中,固定可作为GC Root的对象包含以下几种

  1. 在虚拟机栈中引用的对象
  2. 类静态属性引用的对象
  3. 方法区常量引用的对象
  4. 本地方法栈中引用的对象
  5. Java虚拟机内部引用,比如基本属性类型对于的Class,常驻异常,类加载器
  6. 所有被同步锁(synchronized关键字)持有的对象
  7. JMX相关Bean

引用强弱

无论是引用计数法还是可达性分析算法,判断对象的存活都与”引用”离不开关系.

在JDK1.2之前,Java的引用是很传统的定义: 如果reference类型的数据中存储的是另一块内存的起始地址,就称该reference数据是代表某块内存,或者说是某个对象的引用

在JDK1.2之后,Java对引用的概念进行了扩充,将引用分为强引用(Strongly Reference), 软引用(Soft Reference), 弱引用(Weak Reference)虚引用(Phantom Reference)

  1. 强引用是最传统的引用的定义,这种关系下,只要引用关系还存在,就不会被回收掉
  2. 软应用用来描述一些还有用,但是非必要的对象. 被软引用关联的对象,在系统发生内存溢出之前,会把这些对象加入回收范围. JDK中使用SoftReference来实现软引用
  3. 弱引用用来描述那些非必须对象,被弱引用的对象,在下次内存回收触发之时一定会被回收. JDK使用WeakReference来实现弱引用
  4. 虚引用是最弱的应用. 一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来获取某个对象实例. 为对象设置一个虚引用仅仅只是为了能在被回收时收到系统通知. JDK使用PhantomReference来实现虚引用
Last updated on