通常来说,要写 Java 代码,你基本上都没必要听说垃圾回收这个概念的。这不,对于已经写了 5 年多 Java 代码的我来说,我还没有哪次经历说是需要使用垃圾回收方面的知识来解决问题的。但是,我依然督促自己花了几天时间系统性地(也比较浅显地)学习了 Java 垃圾回收机制。我认为学习 Java 垃圾回收机制至少可以得到以下几方面的好处:
(一)Java 堆内存的分代管理
Java 垃圾回收是需要消耗 CPU 和内存资源的,其速度随着内存的变大而减慢,这将严重影响系统的性能。同时,Java 系统中存在着这么一种现象:大多数 Java 对象都是 "短命" 的。基于此,Java 采用了分代的内存管理方式,并在不同的内存代中采用不同的垃圾回收算法,从而达到对内存更细粒度的管理,最大限度地减小垃圾回收对系统本身的影响。
由上图所示,Java 的堆空间被分为了三个区域,分别是新生代(Young Generation)、老年代(Old Generation)和永久代(Permanent Generation)。新创建出来的对象首先存放在新生代,经过新生代中多次垃圾回收 (在 Survivor 0 和 Survivor 1 之间来回复制),存活下来的对象将被转移到老年代。新生代中垃圾回收很频繁,这样多数 "短命" 的对象将得到及时清理;又由于新生代内存空间通常不大,回收速度也相对较快。在老年代中,存放着从新生代中经历了多次垃圾回收后仍然存活的对象,这些对象相对较少,而老年代内存一般很大,并不容易塞满,因此老年代的垃圾回收频率要远远低于新生代,从而减少了对系统性能的影响。永久代中主要存放 Java 类本身的数据信息,当 Java 类不再被使用时,也会被垃圾回收掉。开发者们通常无法预测永久代的大小,导致程序经常出现 "java.lang.OutOfMemoryError: Permgen space" 错误,因此在 Java 8 中,使用 jvm 进程原生内存空间的 Metaspace 代替了永久代。在默认情况下,Metaspace 将使用 jvm 进程所有可用的内存。 在新生代进行的 GC 叫做 minor GC,在老年代进行的 GC 都叫 major GC,Full GC 同时作用于新生代和老年代。在垃圾回收过程中经常涉及到对对象的挪动(比如上文提到的对象在 Survivor 0 和 Survivor 1 之间的复制),进而导致需要对对象引用进行更新。为了保证引用更新的正确性,Java 将暂停所有其他的线程,这种情况被称为 "Stop-The-World",导致系统全局停顿。Stop-The-World 对系统性能存在影响,因此垃圾回收的一个原则是尽量减少 "Stop-The-World" 的时间。上图展示了不同垃圾收集器的 Stop-The-World 情况,可以看出 Serial、Parallel 和 CMS 收集器均存在不同程度的 Stop-The-Word 情况;而即便是最新的 G1 收集器也不例外。(二)垃圾回收算法最早的垃圾回收算法有引用计数法,但由于其性能不好以及无法回收循环引用对象的问题,工程上并没有得到使用。当前 Java 的垃圾回收主要基于标记 - 清除(Mark-Sweep)算法,该算法大致包括两个步骤:标记压缩算法(Mark Sweep Compact)
为了解决内存碎片问题,标记压缩算法(如下图所示)在回收内存之后会将存活的对象集体压到内存的一端。压缩过程需要更新对象的引用,如前文所述,这将增加系统 Stop-The-World 时间。
标记复制算法(Mark Copy)
标记复制算法是一种效率相对较高的算法,因为它不涉及对无用对象的删除,只需要将标记存活的对象从一个内存区拷贝到另一个内存区。但是标记复制算法不适用于存活对象较多的老年代,因为大量的对象拷贝会降低系统性能。Java 在新生代中主要采用了标记复制算法,其中包括从 Eden 区到 Survivor 区的复制和两个 Survivor 区之间的复制。
(三)垃圾收集器在 Java 中主要有 4 中垃圾收集器,他们各自对于不同的内存代采用不同的算法。Java 会根据当前系统的基本配置确定一个默认的垃圾收集器,你可以通过以下命令查看:
- java -XX:+PrintCommandLineFlags -version
在笔者的电脑上输出为:
- -XX:InitialHeapSize=268435456 -XX:+PrintCommandLineFlags -XX:+UseParallelGC
- java version "1.8.0_45"
- Java(TM) SE Runtime Environment (build 1.8.0_45-b14)
- Java HotSpot(TM) 64-Bit Server VM (build 25.45-b02, mixed mode)
由红色部分可以看出,默认情况下使用了 Parallel 收集器,这也是多数 Java 机器(特别是服务器)默认的垃圾收集器。
串行收集器(Serial Collector)
顾名思义,串行收集器指采用单线程进行垃圾回收,回收时会导致长时间的 Stop-The-World,主要用于单机程序。该收集器在新生代采用复制算法,在老年代采用标记 - 压缩算法。可以通过 - XX:+UseSerialGC 命令行选项激活该收集器。并行收集器(Parallel Collector)该收集器同样在新生代采用复制算法,在老年代采用标记 - 压缩算法,只是使用了多线程的方式进行垃圾回收,从而大大提高了回收效率,但是回收过程中同时需要 Stop-The-World。可以通过 - XX:+UseParallelGC 激活该收集器。多数情况下,并行收集器是 Java 的默认收集器。并发标记清除收集器(Concurrent Mark Sweep Collector,CMS)该收集器在在新生代中采用复制算法,在老年代采用标记 - 清除算法(不是标记 - 压缩)。之所以叫 "并发",是因为在回收过程的某些阶段,回收线程和用户线程同时执行,当然不是整个回收过程都可以和用户线程并行的,该收集器也存在 Stop-The-World 的时候,只是相比于其他收集器来说 Stop-The-World 持续时间较少而已。可以通过 - XX:+UseConcMarkSweepGC 激活该收集器。G1 收集器(Garbage First Collector) G1 收集器是 Java 世界最新的收集器,在 Java 9 中,它将成为默认的垃圾收集器。该收集器采用与上文中提到的收集器不同方式来对待 Java 对内存,如下图所示。可以通过 - XX:+UseG1GC 激活该收集器。来源: http://www.cnblogs.com/davenkin/p/java-garbage-collection.html