Java虚拟机的垃圾回收主要从三个方面去概括:

  • 垃圾判断算法
  • GC(垃圾回收)算法
  • 垃圾回收器的实现和选择

垃圾判断算法

垃圾判断算法,用于确定什么是垃圾内存。主要包括引用计数算法和根搜索算法。

  • 引用计数算法

引用计数算法,给对象添加一个引用计数器,当有一个地方引用它,计数器加1,当引用失效了,计数器减1,当计数器的值为0,表示这个对象不再被使用,内存可以回收。有点:算法简单,缺点是无法解决循环引用问题。

  • 根搜索算法

通过一系列称为GC Roots的点作为搜素的起始点,如果一个对象到GC Roots节点没有任何引用链,则表示这个对象不可用了。

GC Roots包括:

  • 栈帧中的局部变量表中引用
  • 方法区中的静态引用
  • JNI中的引用

方法区的垃圾回收

JVM规范并不要求回收方法区中的垃圾内存,因为性价比极低。在堆中,尤其在新生代,一次GC通常可以回收70%~95%的空间,GC在方法区中回收的比例远低于此。

当前商业JVM都实现了方法区的GC,主要回收废弃常量和无用类,要求非常苛刻。

垃圾回收算法

  1. 标记-清除算法(Mark-Sweep)

算法分为标记清除两个阶段,首先标记出所有需要回收的对象,然后回收所有标记的对象。存在的问题: 1. 效率问题:标记和清除的效率都不高。需要扫描所有对象,堆越大,GC越慢 2. 空间问题:标记清除之后会产生大量不连续的内存碎片,内存碎片太多会导致后续无法找到足够的连续内存而提前触发一次GC。GC次数越多,内存碎片越严重。

原始 0011

标记 0012

清除 0013

  1. 标记-整理算法(Mark-Compact)

标记过程和上面一样,清除时令存活对象的一段移动,然后清除指针边界一侧的内存区域。

这种算法的特点:

  • 没有内存碎片
  • 比标记清除算法花更多时间进行compact
  1. 复制算法(Copying)

将可用内存分成两块,每次只使用其中的一块,当半区内存用完了,就把当前存活的对象复制到另外一块中去,然后将原来的那一块内存整体一次性清除掉。这样每次回收内存都是对整个半区回收,不用考虑内存碎片问题,分配内存时候只需要移动堆的指针,按顺序分配即可,实现简单,运行高效。缺点是内存变为原来的一半,空间代价太高。

现代商业虚拟机都是采用复制算法来回收新生代,将新生代划分为eden和2个survivor区,真正只使用eden和其中一块survivor区,另一块survivor区保持空闲,GC时,会将eden和survivor中存货的对象一次性拷贝到空闲的那个survivor中,然后一次性清除eden和原来的survivor区。

复制算法特点:

  • 只需要扫描存活的对象,效率更高
  • 不会产生内存扫描
  • 需要浪费额外的内存空间作为复制区域
  • 复制算法适合生命周期非常短的对象,这样每次能回收大部分对象,存活的对象少,相应的复制空间开销就少
  1. 分代算法(Generational)

商业虚拟机都是采用分代收集算法,根据对象的存活周期将对象划分为新生代和老年代,各个年代根据自己的特点采用合适的收集算法。比如,新生代大量的对象生命周期很短,存活对象少,只需要付出少量对象的复制空间成本即可。对于老年代可以采用标记清除或标记整理算法回收。 gc01 年轻代包括Eden Space,From Space和To Space这三部分,From Space和To Space又叫Survivor,默认情况下他们的大小比例是8:1:1。 gc02

对象一般在Eden Space中生成,当Eden Space满了时,存活的对象被复制到From Space,当Eden Space再次满了,Eden Space中存活的对象和From Space对象中存活的对象一并被复制到To Space,如果To Space对象满了,多余的对象或者存活超龄的对象,就会被复制到老年代。From Space和To Space完全对称,轮流替换。

大多数对象在Eden Space上分配内存,对于一些大对象,直接在老年代分配内存。

GC时的引用处理

GC要做的事情是将那些dead对象所占的内存回收。Hotspot认为没有引用的对象是dead的。Hotspot的引用分为4种:

  • Strong 通过Object o = new Object()创建的引用
  • Soft、Weak和Phantom都是继承自Reference 当Full GC时候,对Reference引用特殊处理:
  • Soft:内存不够或长期不用时会被GC
  • Weak:一定被GC
  • Phantom:本来就没引用,当从JVM 堆中释放内存时会通知

GC的时机

在分代收集的基础上,GC分为Minor GC和Full GC。

  • Minor GC

触发条件:新对象生成时,Eden 满了 特点:大多数对象在Minor GC时回收,效率高,执行的时间比较短

  • Full GC

对整个JVM进行整理,包括Young,Old和Metaspace。

触发时机:a. Old满了 b. Metaspace满了 c. 调用System.gc()

效率低,尽量少执行。一旦出现Full GC,就会出现Stop The World,业务线程暂停执行。

垃圾回收器

分代模型是GC的宏观愿景,而垃圾回收器是GC算法的具体实现。Hotspot提供了多种垃圾回收器,应当根据具体的业务场景采用不同的垃圾回收器,没有一种垃圾回收器是万能的。

GC时并行和并发的概念:

  • 并行(Parallel):多个GC线程同时工作,而用户线程处于等待状态。
  • 并发(Concurrent):垃圾回收的同时,运行用户线程工作。

单线程收集器(Serial):收集时会暂停所有的工作线程,使用复制算法它是JVM在Client模式下的默认新生代垃圾回收器。主要特点:

  • 最早的收集器,单线程
  • 新生代和老年代都可以使用
  • 复制算法 + 标记整理算法

ParNew收集器:Serial收集器的多线程版本,算法、STW等都和Serial一样,它是JVM在Server模式下默认的新生代垃圾回收器。

Parallel Scavenge收集器:多线程收集器,复制算法,它主要特点是吞吐量高,也就是GC的总运行时间短。STW不理想。

Serial Old:单线程版本收集器,老年代版本。

Parallel Old:Parallel Scavenge的老年代实现,多线程的标记清除算法,吞吐量好。Parallel Scavenge + Parallel Old=高吞吐量,但是STW不理想。

CMS(Concurrent Mark Sweep):很复杂的老年代收集器,主要特点:追求最短停顿时间,并发(GC和用户线程同时进行),标记清除算法,一般结合ParNew收集器。它通过占用很多CPU资源来减少用户线程停顿。