垃圾收集器与内存分配策略--垃圾收集概述

概述

垃圾收集(Garbage Collection,下文简称GC)需要完成的三件事情:哪些内存需要回收?什么时候回收?如何回收?

第2章介绍了Java内存运行时区域的各个部分,其中程序计数器、虚拟机栈、本地方法栈3个区域的生命周期都是确定的,随线程而生,随线程而灭。栈中的栈帧随着方法的进入和退出而执行对应的入栈和出栈操作。同时,这几个区域所需的空间大小都是确定的,因此这几个区域的内存分配和回收都具备确定性,不需要过多考虑如何回收的问题:当方法结束或者线程结束时,内存自然就跟随着回收了。

而方法区与java堆则是动态且具备不确定性的

  • 方法区
    • 在运行时动态加载类
  • java堆
    • 对象实例在这里动态分配内存。对象的数量、大小(实现动态绑定时,不同的接口实现类实例可能需要不同大小的内存)以及生命周期在编译期间是不确定的,只能在程序运行时动态确定。

垃圾收集器所关注的正是这部分内存该如何管理,本文后续讨论中的内存分配与回收也仅仅特指这一部分内存。

1 对象死亡判断

垃圾收集器在对堆进行回收前,第一件事情就是要确定这些对象之中哪些还“存活”着,哪些已经“死去”。这主要涉及到两种算法。

1.1 引用计数算法(Reference Counting)
  • 原理
    • 每个对象有一个引用计数器,引用该对象时计数器增加,引用失效时计数器减少。计数器为零意味着对象不再被使用。
  • 优势
    • 引用计数算法(Reference Counting)虽然占用了一些额外的内存空间来进行计数,但它的原理简单,判定效率也很高,在大多数情况下它都是一个不错的算法。
  • 限制
    • 很多例外情况要考虑,必须要配合大量额外处理才能保证正确地工作。譬如无法解决对象间的循环引用问题:两个对象相互引用,即使它们不再被其他对象引用,它们的引用计数也不为零,从而无法被回收。
1.2 可达性分析算法(Reachability Analysis)

在这里插入图片描述

  • 原理
    • 通过一系列称为“GC Roots”的根对象作为起始点,如果某个对象到GC Roots没有任何引用链(该算法根据引用关系向下搜索,搜索过 程所走过的路径称为“引用链”(Reference Chain))相连,则该对象不再被使用。
  • GC Roots范围
    • 在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的 参数、局部变量、临时变量等。
    • 在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量。
    • 在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用。
    • 在本地方法栈中JNI(即通常所说的Native方法)引用的对象。
    • Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如 NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。
    • 所有被同步锁(synchronized关键字)持有的对象。
    • 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。
  • 优势
    • 能有效处理循环引用的情况,是目前主流的垃圾收集算法。

2 引用

JDK 1.2及之后版本中Java对引用概念进行了扩展,引入了强引用、软引用、弱引用和虚引用这四种不同类型的引用(强度依次递减)。

2.1 强引用(Strong Reference)
  • 定义:传统意义上的引用,例如Object obj = new Object();这类的引用关系。
  • 特点:只要强引用关系存在,垃圾收集器永远不会回收被引用的对象。
2.2 软引用(Soft Reference)
  • 定义:用来描述一些有用但并非必需的对象。
  • 特点
    • 当内存足够时,软引用关联的对象不会被回收。
    • 当内存不足即将抛出内存溢出异常之前,这类对象会被考虑进行回收。
    • JDK中提供了SoftReference类来实现软引用。
2.3 弱引用(Weak Reference)
  • 定义:比软引用更弱的一种引用关系。
  • 特点
    • 只能生存到下一次垃圾收集之前。
    • 无论内存是否足够,被弱引用关联的对象都会被回收。
    • JDK中提供了WeakReference类来实现弱引用。
2.4 虚引用(Phantom Reference)
  • 定义:最弱的一种引用关系,也称为“幽灵引用”或“幻影引用”。
  • 特点
    • 对象的生存时间与虚引用无关。
    • 虚引用主要用于跟踪对象被垃圾收集器回收的活动。
    • 无法通过虚引用获取对象实例。
    • JDK中提供了PhantomReference类来实现虚引用。

3 finalize()方法与对象的自救

  • 可达性分析:在垃圾收集过程中,首先通过可达性分析确定哪些对象是“存活”的,哪些是“死去”的。不可达的对象可能会被垃圾收集器回收。
  • Finalize机制
    • 在对象被判定为不可达后,它们不会立即被回收,而是进行第一次标记和筛选,筛选条件是该对象是否有必要执行finalize()方法(假如对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用 过,那么虚拟机将这两种情况都视为“没有必要执行”,如没有必要执行则直接回收)。
    • 对象的finalize()方法是对象被回收前来“自救”的最后且唯一的机会(即重新变为可达)。
  • Finalizer线程
    • 被判定需要执行finalize()方法的对象会被放入F-Queue队列。
    • Finalizer线程低优先级地执行这些对象的finalize()方法。
  • 第二次标记
    • 在finalize()执行后,垃圾收集器会对F-Queue中的对象进行第二次小规模标记。
    • 如果对象在finalize()后重新变为可达,则它将被移出待回收集合,即自救成功。
3.1 使用建议
  • 不推荐使用finalize():由于finalize()方法存在诸多问题(如不确定性、高开销、执行顺序不确定等),官方并不推荐使用。
  • 替代方案:使用try-finally或其他方式都可以完全代替finalize(),且做的更好,更及时。

4 方法区的垃圾收集

方法区的垃圾收集通常回收效果不如Java堆,特别是与新生代的垃圾收集相比。虽然《Java虚拟机规范》允许虚拟机不在方法区实现垃圾收集,但实际上许多JVM实现(如HotSpot)确实存在对方法区进行的垃圾收集,这主要涉及到两方面。

  • 废弃常量的回收:与对象回收类似。例如,不再被引用的字符串常量可以从常量池中被回收。

  • 不再使用的类型的回收:这部分更复杂,需要同时满足三个条件:

    • 该类所有的实例都已经被回收,也就是Java堆中不存在该类及其任何派生子类的实例。

    • 加载该类的类加载器已经被回收,这个条件除非是经过精心设计的可替换类加载器的场景,如 OSGi、JSP的重加载等,否则通常是很难达成的。

    • 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

      并且即使满足上述三个条件也只是“被允许”,具体回收与否则可通过相关参数控制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值