> For the complete documentation index, see [llms.txt](https://yangsx95.gitbook.io/notes/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://yangsx95.gitbook.io/notes/programming-language/java/jvm/gc-suan-fa-yu-hui-shou-qi.md).

# GC算法与回收器

GC会针对内存中的无用对象进行回收，回收对象需要明确三个问题：

* **哪些内存区域需要GC回收**
* **对象存活判断**：GC如何判断区域中的对象是否需要回收
* **GC回收算法**：GC如何回收垃圾对象

## 1. 哪些内存区域需要GC回收？

| 线程私有                                       | 线程共享                                                    |
| ------------------------------------------ | ------------------------------------------------------- |
| 程序计数器、虚拟机栈、本地方法栈                           | **java堆、方法区**                                           |
| 随线程生而生，随线程去而去。线程分配多少内存都是有数的，当线程销毁时，内存就被释放了 | 堆和方法区的内存都是动态分配的（使用new关键字），所以也需要动态回收。 **这部分内存的回收依赖GC完成** |

## 2. 对象存活判断？

共有两种垃圾对象的判断策略：

* 引用计数法
* 可达性分析

### 引用计数法

给每个对象添加一个引用计数器，当没发生引用时，计数器加一。当引用失效时，计数器减一。当计数器为0时，表示对象不被引用。

```java
Object a = new Object(); // a的引用计数为1
a = null; // a的引用计数为0，等待GC回收
```

但是此种方式不能解决对象之间的循环引用：

```java
Object a = new Object(); // a的引用计数为1
Object b = new Object(); // b的引用计数为1

a.next = b; // a的引用计数为2
b.next = a; // b的引用计数为2

a = null; // a的引用计数为1,尽管已经显示地将a赋值为null，但是由于引用计数为1，GC无法回收a
b = null; // b的引用计数为1，同理，GC也不回收b
```

* **优点**：实现简单，判断效率高（应用：`FlashPlayer`，`Python`等，都是使用此种存货判断方式）
* **缺点**：对象循环引用问题无法解决

### 可达性分析

设立若干根对象（GC Root），每个对象都是一个子节点，当一个对象找不到根时，就认为该对象不可达。

![可达性分析](https://2351062869-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7b2CdwBN9liniVJpfEAc%2Fuploads%2Fgit-blob-bd7fa0b523ab9f625dc8b60728a6d3b2152e9d8c%2F20170817171138449.png?alt=media)

上图中GC Root无法到达Object4和Object5，说明其是不可达的，GC将会回收他们。在Java中，**可以作为GC Root的对象有**：

* 方法区中静态属性引用的对象
* 方法区中常量引用的对象
* 虚拟机栈引用的对象 (栈帧中本地变量表)
* 本地方法栈中JNI引用的对象 (Native方法)

主流JVM都是用此种方式。

## 3. GC回收算法

* 标记清除法
* 复制算法
* 标记整理法
* 分代收集算法

### 标记清除法

遍历所有的`GC Root`，分别标记处可达的对象和不可达的对象，然后将不可达的对象回收。

* **适用**：存活对象较多的垃圾回收
* **缺点**：效率低，标记清除后产生大量不连续的内存碎片，给大对象分配内存时没有足够连续的内存空间，导致提前出发垃圾回收动作

  ![img](https://2351062869-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7b2CdwBN9liniVJpfEAc%2Fuploads%2Fgit-blob-42a54493c41b713f9018b330bfecf9739a91eaee%2F1069298-20170319222123291-792166374.png?alt=media)

### 复制算法

将内存分为两块，每次只使用一块，当当前块内存满了，就将存货的对象复制到另一块上，并且严格按照内存地址排列，然后把已使用的那块内存统一回收。

* **适用**：存活对象较少的垃圾回收
* **优点**：
  * 每次对整个半区进行内存回收，不用考虑内存碎片问题，只要移动堆顶指针，按顺序分配内存即可
  * 实现简单，运行高效
* **缺点**：浪费内存

![复制算法](https://2351062869-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7b2CdwBN9liniVJpfEAc%2Fuploads%2Fgit-blob-102e912574c2dc71b7ffc214494ea94ee13caf76%2FSouthEast.png?alt=media)

### 标记整理法

先标记要回收的对象，将存活对象移至一端，最后清理端边界以外的内存。

* **适用**：存活对象较少的垃圾回收

![img](https://2351062869-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7b2CdwBN9liniVJpfEAc%2Fuploads%2Fgit-blob-8758bcb503fe0bbb0964cb03d939be18c653d43c%2F1069298-20170319230833635-2091067163.png?alt=media)

### 分代收集算法

根据对象存活周期将内存划分为新生代、老年代、永久代，然后**根据每个年代的特点使用合适的回收算法**。

如：

* 新生代存活对象少可以采用复制算法；
* 老年代存活对象多并且没有分配担保必须使用标记清理或标记整理回收算法
