本教程是为了理解基本的java垃圾回收以及它是如何工作的。这是垃圾回收教程系列的第二部分。希望你已经读过了第一部分:《简单介绍java垃圾回收机制》。
java垃圾回收是一项自动化的过程,用来管理程序所使用的运行时内存。通过这一自动化过程,jvm解除了程序员在程序中分配和释放内存资源的开销。
启动java垃圾回收
作为一个自动的过程,程序员不需要在代码中显示地启动垃圾回收过程。system.gc()和runtime.gc()用来请求jvm启动垃圾回收。
虽然这个请求机制提供给程序员一个启动gc过程的机会,但是启动由jvm负责。jvm可以拒绝这个请求,所以并不保证这些调用都将执行垃圾回收。启动时机的选择由jvm决定,并且取决于堆内存中eden区是否可用。jvm将这个选择留给了java规范的实现,不同实现具体使用的算法不尽相同。
毋庸置疑,我们知道垃圾回收过程是不能被强制执行的。我刚刚发现了一个调用system.gc()有意义的场景。通过这篇文章了解一下适合调用system.gc()这种极端情况。
java垃圾回收过程
垃圾回收是一种回收无用内存空间并使其对未来实例可用的过程。
eden区:当一个实例被创建了,首先会被存储在堆内存年轻代的eden区中。
注意:如果你不能理解这些词汇,我建议你阅读这篇垃圾回收介绍,这篇教程详细地介绍了内存模型、jvm架构以及这些术语。
survivor区(s0和s1):作为年轻代gc(minorgc)周期的一部分,存活的对象(仍然被引用的)从eden区被移动到survivor区的s0中。类似的,垃圾回收器会扫描s0然后将存活的实例移动到s1中。
(译注:此处不应该是eden和s0中存活的都移到s1么,为什么会先移到s0再从s0移到s1?)
死亡的实例(不再被引用)被标记为垃圾回收。根据垃圾回收器(有四种常用的垃圾回收器,将在下一教程中介绍它们)选择的不同,要么被标记的实例都会不停地从内存中移除,要么回收过程会在一个单独的进程中完成。
老年代:老年代(oldortenuredgeneration)是堆内存中的第二块逻辑区。当垃圾回收器执行minorgc周期时,在s1survivor区中的存活实例将会被晋升到老年代,而未被引用的对象被标记为回收。
老年代gc(majorgc):相对于java垃圾回收过程,老年代是实例生命周期的最后阶段。majorgc扫描老年代的垃圾回收过程。如果实例不再被引用,那么它们会被标记为回收,否则它们会继续留在老年代中。
内存碎片:一旦实例从堆内存中被删除,其位置就会变空并且可用于未来实例的分配。这些空出的空间将会使整个内存区域碎片化。为了实例的快速分配,需要进行碎片整理。基于垃圾回收器的不同选择,回收的内存区域要么被不停地被整理,要么在一个单独的gc进程中完成。
垃圾回收中实例的终结
在释放一个实例和回收内存空间之前,java垃圾回收器会调用实例各自的finalize()方法,从而该实例有机会释放所持有的资源。虽然可以保证finalize()会在回收内存空间之前被调用,但是没有指定的顺序和时间。多个实例间的顺序是无法被预知,甚至可能会并行发生。程序不应该预先调整实例之间的顺序并使用finalize()方法回收资源。
任何在finalize过程中未被捕获的异常会自动被忽略,然后该实例的finalize过程被取消。
jvm规范中并没有讨论关于弱引用的垃圾回收机制,也没有很明确的要求。具体的实现都由实现方决定。
垃圾回收是由一个守护线程完成的。
对象什么时候符合垃圾回收的条件?
所有实例都没有活动线程访问。
没有被其他任何实例访问的循环引用实例。
java中有不同的引用类型。判断实例是否符合垃圾收集的条件都依赖于它的引用类型。
| 引用类型 | 垃圾收集 |
|---|---|
| 强引用(strong reference) | 不符合垃圾收集 |
| 软引用(soft reference) | 垃圾收集可能会执行,但会作为最后的选择 |
| 弱引用(weak reference) | 符合垃圾收集 |
| 虚引用(phantom reference) | 符合垃圾收集 |
在编译过程中作为一种优化技术,java 编译器能选择给实例赋 null 值,从而标记实例为可回收。


