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

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 内部的对象

 

细分引用类型

为了提供类似缓存的能力,内存足够时不着急回收对象、解决对象一直被错误地引用导致的内存溢出等问题,JDK 1.2 开始将引用分了为了四种类型。

引用强度由强到弱,分别为

  • 强引用(Strong Reference):String s = new String("ConstXiong"),代码中直接的赋值,引用关系在对象不会被回收

  • 软引用(Soft Reference):SoftReference 实现,系统发生内存溢出前,回收软引用对象

  • 弱引用(Weak Reference):WeakReference 实现,下一次垃圾收集会回收掉即会回收弱引用对象

  • 虚引用(Phantom Reference):PhantomReference 实现,对垃圾收集器回收内存行为没有影响,仅为了虚引用对象被回收时能收到系统通知

例如:ThreadLocal 中的 ThreadLocalMap 中的 Entry 就继承了 WeakReference 类,用来解决线程本地变量内存泄漏的问题。

 

最后一次逃离的机会

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

 


【Java学习资源】整理推荐

 

 


【Java面试题与答案】整理推荐

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java垃圾回收机制是自动管理内存的一种机制,它通过周期性地检测和释放不再使用的对象回收内存空间,以避免内存泄漏和提高程序的性能。 Java垃圾回收机制基于以下两个核心原则: 1. 引用计数:Java垃圾回收机制最早采用了引用计数算法。该算法通过在对象中维护一个引用计数器,记录有多少个引用指向该对象。当引用计数为0时,表示对象不再被引用,即为垃圾对象,可以被回收。然而,引用计数算法无法解决循环引用的问题,即使对象之间相互引用并且没有外部引用指向它们,它们的引用计数也不变为0,导致内存泄漏。 2. 可达性分析:为了解决引用计数算法的不足,Java后来采用了可达性分析算法。该算法从一组称为"根"的起始对象开始,通过对象之间的引用链追踪,判断哪些对象是可达的(即可以被访问到的),而哪些对象是不可达的。不可达的对象被认为是垃圾对象,可以被垃圾回收回收Java垃圾回收过程主要分为以下几个阶段: 1. 标记阶段:垃圾回收器从根对象开始,遍历对象的引用链,标记所有可达的对象。 2. 清除阶段:垃圾回收器清除所有未被标记的对象,即不可达的对象,并回收它们所占用的内存空间。 3. 压缩阶段(可选):某些垃圾回收器在清除阶段之后可能进行内存压缩操作,将存活的对象移动到内存的一端,以便为新对象的分配提供更连续的内存空间。 Java垃圾回收机制的具体实现取决于所使用的垃圾回收器类型,例如串行回收器、并行回收器、并发回收器等。不同的垃圾回收器有不同的性能特点和适用场景,开发人员可以根据应用程序的需求选择合适的垃圾回收器。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值