标记阶段:对象是否存活
在堆里存放着所有的java对象,在GC执行垃圾回收之前,首先需要区分哪些对象是否存活,哪些对象已经死亡。只有被标记已经死亡的对象,GC才会在执行垃圾回收的释放掉其占用的额内存空间,因此这个过程我们称之为垃圾标记阶段。那么在jvm中如何标记一个对象已经死亡呢?简单来说,当一个对象已经不再被任何存活的对象引用时,就宣判该对象已经死亡。判断一个对象是否存活一般有两种方式:引用计数算法、可达性分析算法
- 引用计数算法(Reference Counting)
- 对每一个对象保存一个整型的引用计数的属性,用于记录对象呗引用的情况
- 对于一个对象A,只要有任何一个对象引用了A则A的引用计数就+1;当引用失效时,引用计数就-1.只要对象的引用计数器的值为0时,即表示对象A不可能再被使用,可以进行回收
- 优点:
- 实现简单
- 垃圾对象便于识别
- 判断效率高
- 回收没有延迟性
- 缺点:
- 它需要单独的字段用于存储计数器,这样增加了存储空间的开销
- 每次赋值需要跟新计数器,伴随着加法和减法的操作,这样增加了时间的开销
- 引用计数器有一个严重的问题:无法处理循环引用的情况。(这是一条致命的缺陷,java的垃圾回收期中没有使用这类算法)
- 可达性分析算法(根搜索算法、追踪性垃圾收集)
- 相对于引用计数算法,可达性分析算法不仅同样具备实现简单和执行高效等特点,更重要的是该算法有效的解决在引用计算算法中循环引用的问题,防止内存泄漏的发生
- 相较于引用计算算法,这里的可达性分析算法是java、c#的选择。这类的收集通常也叫做追踪性垃圾收集(Tracing Garbage Collection)
- 所谓“GC Rtoos”根集合是一组必须活跃的引用
- 基本思路:
- 可达性分析算法是以根对象集合(GC Rtoos)为起始点,按照从上至下的方式搜索被根对象集合所连接的目标对象是否可达
- 使用可达性分析算法后,内存中的存活的对象会被根对象集合直接或者间接连接着,搜索所走的路径称之为:引用链(Reference Chain)
- 如果目标对象没有任何的引用链相连,则是不可达,就意味着该对象已经死亡,可以标记为垃圾对象
- 在可达性分析算法中,只有能够被根对象集合直接或者间接连接的对象才是存活对象
- 在java语言中,GC Roots包含以下几类元素
- 虚拟机栈中引用的对象
- 本地方法栈内(JNI)引用的对象
- 方法区中静态属性引用的对象
- 方法区中常量引用的对象
- 所有同步锁synchronized持有的对象
- java虚拟机内部的引用
- 反映java虚拟机内部情况的JMXBean、JVMMI中注册的回调、本地代码缓存等
对象的finalization机制
- Java语言提供了对象终止机制(finalzation)机制来允许开发人员提供对象销毁之前的自定义操作逻辑
- 当垃圾收集器发现没有引用指向一个对象,即:垃圾回收此对象之前,总会先调用该对象的finalize ( )方法
- finalize ( )方法允许子类重写,用于在对象被回收时进行资源的释放。通常在这个方法中进行一些资源的释放和清理工作,比如文件的关闭、套接字和数据库的连接释放等
- 永远不要主动调用finalize ( )方法,应该交给垃圾回收器调用
- finalize ( )方法被调用时可能导致对象的复活
- finalize ( )方法的执行时间是没有保障的,它完全由GC线程决定,极端情况下若不发生GC,则finalize ( )方法将没有机会执行
- 一个糟糕的finalize ( )方法会严重影响GC的性能
- 从功能上说,finalize ( )方法与c++中的虚构函数比较相似,但是java采用的是基于垃圾收集器的自动内存管理机制,所以finalize ( )方法本质上不同于c++中的虚构函数
- 由于finalize ( )方法的存在,虚拟机中的对象一般处于三种可能的状态
- 可触及的:从根节点开始可以到达这个对象
- 可复活的:对象的所有引用都被释放,但是对象有可能在finalize ( )中复活
- 不可触及的:对象的finalize( )被调用,并没有复活,那么就会进入不可触及状态。因此finalize( )只会被调用一次
public class Object {
(********************省略很多******************)
/**
* Called by the garbage collector on an object when garbage collection
* determines that there are no more references to the object.
* A subclass overrides the {@code finalize} method to dispose of
* system resources or to perform other cleanup.
* <p>
* The general contract of {@code finalize} is that it is invoked
* if and when the Java™ virtual
* machine has determined that there is no longer any
* means by which this object can be accessed by any thread that has
* not yet died, except as a result of an action taken by the
* finalization of some other object or class which is ready to be
* finalized. The {@code finalize} method may take any action, including
* making this object available again to other threads; the usual purpose
* of {@code finalize}, however, is to perform cleanup actions before
* the object is irrevocably discarded. For example, the finalize method
* for an object that represents an input/output connection might perform
* explicit I/O transactions to break the connection before the object is
* permanently discarded.
* <p>
* The {@code finalize} method of class {@code Object} performs no
* special action; it simply returns normally. Subclasses of
* {@code Object} may override this definition.
* <p>
* The Java programming language does not guarantee which thread will
* invoke the {@code finalize} method for any given object. It is
* guaranteed, however, that the thread that invokes finalize will not
* be holding any user-visible synchronization locks when finalize is
* invoked. If an uncaught exception is thrown by the finalize method,
* the exception is ignored and finalization of that object terminates.
* <p>
* After the {@code finalize} method has been invoked for an object, no
* further action is taken until the Java virtual machine has again
* determined that there is no longer any means by which this object can
* be accessed by any thread that has not yet died, including possible
* actions by other objects or classes which are ready to be finalized,
* at which point the object may be discarded.
* <p>
* The {@code finalize} method is never invoked more than once by a Java
* virtual machine for any given object.
* <p>
* Any exception thrown by the {@code finalize} method causes
* the finalization of this object to be halted, but is otherwise
* ignored.
*
* @throws Throwable the {@code Exception} raised by this method
* @see java.lang.ref.WeakReference
* @see java.lang.ref.PhantomReference
* @jls 12.6 Finalization of Class Instances
*/
protected void finalize() throws Throwable { }
}
清除阶段
当成功的区分出内存中存活对象和死亡对象后,GC接下来的任务就是执行垃圾回收,释放掉无用的的内存空间,以便有足够的可用内存空间为新的对象分配内存空间。目前,JVM中比较常见的三种垃圾收集算法是:标记-清除算法(Mark-Sweep)、复制算法、标记-压缩算法(Mark-Compact)。
- 标记-清除算法
- 当堆中有效的内存空间被耗尽的时候,就会停止整个程序(也被称为stop the world),然后进行两项工作:
- 标记:Collector从引用根节点开始遍历,标记所有被引用的对象。一般是在对象的Header中记录为可达对象
- 清除:Collector对堆内内存从头到尾的进行线性遍历,如果发现某个对象在其Header中没有被标记为可达对象,则将其回收
- 缺点
- 效率不高
- 在进行GC时,需要停止整个程序,导致用户体验差
- 会产生内存碎片,需要维护一个空闲列表
- 当堆中有效的内存空间被耗尽的时候,就会停止整个程序(也被称为stop the world),然后进行两项工作:
- 复制算法
- 将内存分为两块,每次只使用其中一块,在垃圾回收时将正在使用的内存中存活的对象复制到未被使用的内存块中,之后清除正在使用的内存块中的所有对象,交换内存的角色,最后完成垃圾回收
- 优点
- 没有标记-清除的过程,实现简单、运行效率高
- 复制完成之后保证空间连续,不会出现“碎片”问题
- 缺点
- 需要两倍的内存空间
- 需要执行复制操作,相对来说开销不小
- 标记-压缩算法(也称为:标记整理算法)
- 第一阶段,和标记清除算法一样,从根节点开始标记所有被引用的对象
- 第二阶段,将所有的存活对象压缩到内存的另一端,按照顺序排放,之后清除边界外的所有空间
- 优点
- 解决了标记-清除算法中的内存“碎片”的问题,我们需要给新对象分配内存空间时,JVM只需要持有一个内存的起始地址就可以了
- 消除了复制算法中的内存减半的问题
- 缺点
- 从效率上说,标记-整理算法要低于赋值算法
- 移动对象的同时,如果对象被其他对象引用的话,则还需要调整引用地址
- 移动过程中,需要暂停应用程序,即:STW