文章目录
- 简述
- 引用计数算法
- 可达性分析算法
- 4种对象引用
- finalize()方法
- 回收方法区
简述
在堆里面存放着Java世界中几乎所有的对象实例,垃圾收集器在对堆进行回收前,第一件事情就是要确定这些对象之中哪些还“存活”着,哪些已经“死去”(“死去”即不可能再被任何途径使用的对 象)了。
引用计数算法
引用计数算法是一种用于内存管理的垃圾回收算法。它的核心思想是为每个对象维护一个引用计数,表示有多少个引用指向该对象。当引用计数为零时,说明该对象不再被任何引用指向,可以安全地回收内存。
引用计数算法的主要特点和优点包括:
- 简单:引用计数算法非常简单直观,每次对象的引用发生变化时,引用计数都会相应地增加或减少。
- 实时性:由于引用计数是实时维护的,一旦引用计数为零,就可以立即回收对象,不需要等待垃圾回收器的运行。
- 最小化停顿时间:引用计数算法通常不需要执行全局性的垃圾回收操作,因此不会引起大规模的内存回收导致的长时间停顿。
然而,引用计数算法也存在一些明显的缺点:
- 循环引用问题:引用计数算法无法处理循环引用的情况。如果两个或多个对象相互引用,它们的引用计数永远不会为零,即使它们已经不再被程序使用。这会导致内存泄漏。
- 性能开销:每次引用发生变化时,都需要增加或减少引用计数,这会增加额外的性能开销。此外,需要额外的内存来存储引用计数。
- 不适用于多线程环境:在多线程环境中,引用计数需要进行原子操作,以确保计数的准确性。这会引入额外的开销和复杂性。
综合考虑,引用计数算法在简单场景中可能是一种有效的垃圾回收方法,但在复杂的应用程序中通常不是首选。许多现代的编程语言和运行时环境使用基于引用计数的回收算法与其他算法(如标记-清除、标记-整理等)相结合,以克服引用计数算法的缺点,提高内存管理的效率和可靠性。
可达性分析算法
该算法的基本思路就是通过 一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连, 或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的。
下图中的对象object 5、object 6、object 7虽然互有关联,但是它们到GC Roots是不可达的, 因此它们将会被判定为可回收的对象。
在Java技术体系里面,固定可作为GC Roots的对象包括以下几种:
-
在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的 参数、局部变量、临时变量等。
-
在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量。
-
在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用。
-
在本地方法栈中JNI(即通常所说的Native方法)引用的对象。
-
Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如 NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。
-
所有被同步锁(synchronized关键字)持有的对象。 ·反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。
除了这些固定的GC Roots集合以外,根据用户所选用的垃圾收集器以及当前回收的内存区域不 同,还可以有其他对象“临时性”地加入,共同构成完整GC Roots集合。
可达性分析算法的优点是能够准确识别出不再被程序引用的对象,因此可以有效地回收内存,避免内存泄漏。然而,它的缺点是需要遍历整个对象图,因此在对象数量庞大、引用关系复杂的情况下,可能会消耗较多的时间和计算资源。因此,现代的垃圾回收器通常会结合不同的回收算法来优化性能。
4种对象引用
在JDK 1.2版之后,Java对引用的概念进行了扩充,将引用分为强引用(Strongly Re-ference)、软 引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)4种,这4种引用强度依次逐渐减弱。
四种不同类型的引用,它们分别是:
- 强引用(Strong Reference):
- 强引用是最常见的引用类型。
- 当一个对象具有强引用时,垃圾回收器不会回收该对象,即使内存不足时也不会回收。
- 只有当该对象的所有强引用都失效时,对象才会被回收。
- 软引用(Soft Reference):
- 软引用用于描述一些还有用但并非必需的对象。
- 当内存不足时,垃圾回收器可能会回收被软引用指向的对象,但在内存足够的情况下,不会回收它们。
- 可以通过
java.lang.ref.SoftReference
类来创建软引用。
- 弱引用(Weak Reference):
- 弱引用用于描述非必需的对象,通常用于缓存。
- 弱引用指向的对象在下一次垃圾回收时就会被回收,无论内存是否足够。
- 可以通过
java.lang.ref.WeakReference
类来创建弱引用。
- 虚引用(Phantom Reference):
- 虚引用是最弱的引用类型,通常用于跟踪对象被垃圾回收的状态。
- 虚引用无法通过它访问对象的数据或者方法,它主要用于在对象被回收之前收到一个系统通知。
- 可以通过
java.lang.ref.PhantomReference
类来创建虚引用。
这些不同类型的引用允许开发者更灵活地控制对象的生命周期。例如,软引用和弱引用通常用于缓存,允许缓存中的对象在内存不足时被回收,从而避免内存溢出。虚引用则可以用于执行一些清理操作或者记录对象被垃圾回收的情况。需要根据具体的需求选择适当的引用类型。
finalize()方法
finalize()
方法是Java中的一个特殊方法,它属于java.lang.Object
类,用于在对象被垃圾回收之前执行一些清理和资源释放操作。虽然它存在,但是从Java 9 开始,已经被标记为“过时”(deprecated),因为它具有一些问题和不稳定性,不建议在新代码中使用。
以下是有关 finalize()
方法的一些重要信息和注意事项:
-
finalize() 方法的签名:
protected void finalize() throws Throwable { // 执行清理和资源释放操作 }
finalize()
方法的访问修饰符为protected
,表示只有同一类或其子类中的方法才能调用它。 -
finalize() 方法的执行时机:
finalize()
方法的执行时机不确定,不同的垃圾回收器在不同的情况下执行它。有些垃圾回收器可能根本不执行它。- 在对象被垃圾回收之前,垃圾回收器会首先调用
finalize()
方法,然后才会回收对象。但是不能保证finalize()
方法一定会被执行,因此不应该依赖它来进行资源释放或清理操作。
-
finalize() 方法的问题:
finalize()
方法的执行时机不确定,可能会导致资源释放的延迟。- 在执行
finalize()
方法期间,对象仍然处于"活跃"状态,可能导致一些并发问题。 - 由于不稳定性和性能问题,Java 9 引入了更先进的垃圾回收机制,逐渐减少了对
finalize()
方法的依赖。
-
建议的替代方法:
- 对于资源释放,应该使用
try-with-resources
或finally
块等结构来确保及时释放资源。 - 对于对象的生命周期管理,应该采用更可靠的方法,如弱引用、虚引用等,而不是依赖于
finalize()
方法。
- 对于资源释放,应该使用
总之,finalize()
方法虽然存在,但不再被推荐使用,因为它存在一些问题和不确定性,可能导致代码的不稳定性和性能问题。在现代Java编程中,应该使用更可靠和可控的方式来管理对象的生命周期和资源释放。
注意:任何一个对象的finalize()方法都只会被系统自动调用一次
回收方法区
Java虚拟机的方法区(Method Area)通常不会像堆内存那样被垃圾回收器进行垃圾回收。方法区主要用于存储类的结构信息、常量、静态变量以及字节码等,这些数据在整个程序的生命周期内都会被加载和使用,不会因对象的可达性而被回收。
然而,尽管方法区通常不会被垃圾回收,但在某些情况下,方法区中的一些数据可能会被回收或卸载:
-
无用的类卸载:当一个类不再被引用,并且不再被任何活动的类加载器加载时,这个类可能会被卸载,其在方法区中的相关信息也会被回收。这个过程通常发生在类加载器被回收时,例如在Web应用程序重新加载时。
-
常量池中的无用常量回收:在运行时常量池中的一些常量,特别是字符串常量,可能会因为没有引用而被回收。
-
优化技术:一些Java虚拟机实现采用了方法区中的数据的优化技术,可以在运行时进行热替换(HotSwap)或类的动态生成,这些技术可能会导致方法区中的一些数据被回收或替换。
需要注意的是,方法区的回收和内存管理与堆内存的垃圾回收不同。通常,方法区的内存管理和回收是由Java虚拟机自己负责的,而不需要开发者干预。而堆内存的垃圾回收通常涉及到开发者手动管理和代码中的对象引用关系。在Java 8及之后的版本中,方法区被元数据区(Metaspace)取代,它的管理方式也有所不同,包括了更灵活的内存管理机制。
在大量使用反射、动态代理、CGLib等字节码框架,动态生成JSP以及OSGi这类频繁自定义类加载 器的场景中,通常都需要Java虚拟机具备类型卸载的能力,以保证不会对方法区造成过大的内存压力。
综上所述,方法区通常不需要显式进行回收操作,而是由Java虚拟机自动管理。开发者需要关注的是对象的生命周期和内存管理,而方法区的管理通常不需要额外的干预。