# 垃圾回收
Garbage Collection,GC
GC只在**堆和本地方法區**中起作用。
## 一、確定對象存活
### 1.1 引用計數法
在對象中添加一個引用計數器,每當有一個地方引用該對象的時候,計數器的值就加1;當引用失效的時候計數器的值就減1;計數器的值為0的對象就是不再被使用的對象。python就是使用這種方式管理內存的。
這種方式無法解決循環引用的問題,**Java中不使用這種方式判斷一個對象是否存活,但是Netty的ByteBuf是采用這種方式進行回收的。**
### 1.2 可達性分析算法
**Java中使用的算法**
通過一系列的“GC Roots”的根對象作為起始節點集,從這些節點開始根據引用關系向下搜索,如果某個對象沒有任何“引用鏈”可以到達,就證明該對象不再被使用。
哪些對象可以作為GC Root?
* 系統類:Object,HashMap...;
* 本地方法棧的類 Native Stack;
* synchronized關鍵字持有的對象;
* 棧幀中局部變量表的對象,堆棧中的參數,臨時變量。
* 方法區中靜態屬性引用的對象。
* 基本類型對應的Class對象。
## 二、引用類型
JKD1.2 版本之后將引用概念進行擴充,分為強引用、軟引用、弱引用、虛引用。
### 2.1 強引用 Strongly Reference
最傳統的引用定義,直接使用關鍵new之后的即為強引用關系。強引用關系直接被GC Root對象引用。沒有Gc Root引用時才會被回收。
### 2.2 軟引用 Soft Reference
用來修飾一些還有用,但是非必須的對象。在系統發生內存溢出之前,會對這些對象進行第二次回收,如果回收之后內存還不夠,才會發生內存溢出異常。
使用`SoftReference類表示`。
### 2.3 弱引用 Weak Reference
比軟引用的關系更弱一些,被弱引用關聯的對象只能存活到下次垃圾回收,不管內存是否足夠都會進行回收。在ThreadLocalMap的Entry中就使用WeakReference來引用作為鍵的ThreadLocal對象。
【備注:關于ThreadLocal的內容可以查看《第一章 Java基礎》-ThreadLocal】
使用`WeakReference類表示`。
~~~
?在弱引用和軟引用中有個一般會關聯一個引用隊列,當引用對象被清理時,弱引用和軟引用本身就會被放到引用隊列中,等待被Reference Handler線程清理。可以不配合引用隊列使用。
~~~
### 2.4 虛引用
引用直接內存對象的引用,Cleaner。`會關聯一個引用隊列。`
記錄直接內存地址,當放到引用隊列中時會被一個定時調用的線程調用Unsafe.freeMemory方法清理直接內存。
??對象真正死亡時間!
?一個對象就算沒有被GC Roots的鏈所引用,也不一定會被清除;會經歷兩次標記過程:
? ? ?1. 第一次篩選看是否有必要執行finalize()方法,如果對象沒有覆蓋finalize()方法或者該方法已經被調用過了,則都會認為沒有必要執行。如果對象被認為有必要執行,則會被放入一個F-Queue隊列中,有一條低優先級的Finalizer線程執行它們的finalize()方法,不一定會等方法執行完畢。
? ? ?2. 第二次再對F-Queue隊列進行篩選,如果對象還是沒有被任何GC Roots鏈所引用,就會真正被清除掉。
??
?finalize()方法只會被執行一次。這種在finalize()方法中重新引用的方式也被稱為終結器引用。
## 三、垃圾回收算法
大多數的垃圾回收算法都是基于“分代收集”理論的(因此要注意在Java運行時數據區其實并沒有分代的概念的,分代的概念是用在垃圾回收當中的)。
> 分代假說:
>
> 1. 弱分代假說:絕大數對象都是朝生夕滅的。 --> 引申出新生代的概念。
>
> 2. 強分代假說:熬過越多次垃圾回收的對象越難消除。 --> 引申出老年代的概念。
>
> 3. 跨分代假說:跨代引用相對于同代引用來說是非常少的。
>
注意:將堆劃分出不同的區域之后,才有了"Minor GC","Major GC","Full GC"這些不同稱呼的垃圾回收器和”標記-復制算法“,”標記-清除算法“,”標記-整理算法“這些針對不同對象生存情況的算法。
**垃圾回收器的分類**
1. 部分收集Partial GC:
1. 新生代收集 MinorGC:針對新生代的垃圾收集。
2. 老年代收集 MajorGC:針對老年代的垃圾收集。--> CMS收集器
3. 混合收集MixedGC:針對整個新生代的垃圾收集以及部分老年代的垃圾收集。--> G1收集器。
2. 整堆收集 Full GC:收集整個堆和方法區的垃圾收集器。
### 3.1 算法
#### 3.1.1 標記清除算法
標記階段:首先標記出所有需要回收的對象;
清除階段:標記結束之后統一清除要回收的對象。
:-: 
優點:不需要額外的內存空間。
缺點:
* 兩次掃描,當堆中有大量的要回收的對象的時候會嚴重浪費時間。
* 產生內存碎片,當大對象無法找到足夠的內存空間的時候又不得不觸發另外一次垃圾回收。
#### 3.1.2 標記復制算法
簡稱為復制算法,解決標記清除算法中回收大量對象時慢的缺點。一開始提出是將內存容量分為兩塊大小一致的空間,每次只使用其中一塊,當一塊空間用完了,就將存活的對象復制到另外一塊上去,清空掉原來的那塊空間。
這種方式每次都對一整塊空間操作,不用考慮內存碎片的問題,也比較快。
在**后來用到新生代**的時候,將新生代分為Eden空間和兩塊較小的Survivor空間(比例為8:1:1)。每次在使用的時候只使用Eden區和一塊Survivor的空間(from區),在發生垃圾回收的時候將Eden區和From區的存活對象放入另外一塊Survivor區中(to區),接著將Eden區和From區都清空掉。這種劃分方法是基于對象都是“朝生夕滅”(分代假說)的,只用了額外的10%的空間。這個空間的比例可以通過虛擬機參數調整:
~~~
?-XX:InitialSurvivorRatio=ratio
~~~
:-: 
> PS:當TO區不夠存放存活的對象的時候,會通過一種叫做分配擔保的機制將這些對象放入老年代。
#### 3.1.3 標記整理算法
上面兩種算法更多針對新生代的,而標記整理算法更多針對老年代的。標記整理算法前期過程與標記清除算法類似,但是在標記完成之后會將所有存活的對象移動到一遍,然后清空掉另外一邊的內存。
:-: 
在進行垃圾回收的時候需要移動對象的方式需要暫停應用程序的運行,官方稱之為Stop The World。
### 3.2 垃圾收集器
:-: 
圖中的每個連線都是一種組合
#### 3.2.1 Serial收集器
**Serial作用于新生代**,Serial Old作用于老年代;都是單線程,在進行垃圾回收的時候需要暫停其他用戶線程(STW)。
:-: 
Serial收集器雖然是單線程的,但是仍然是HotSpot在客戶端下的默認新生代的垃圾回收器,特別適合在微服務這種架構下單個服務占用的內存很少,即是使用單線程收集也不會占用太多時間。隨之而來的好處就是簡單,高效。
#### 3.2.2 ParNew收集器
是Serial的多線程版本,在新生代的垃圾回收中可以使用多個線程進行垃圾回收,但是仍然會造成STW。

除了Serial Old外,只有ParNew能和CMS配合使用,多用于服務端的垃圾回收。在JDK9之前ParNew + CMS的組合是官方推薦的在服務端模式下的收集器的解決方案;在JDK9之后ParNew只能和CMS組合使用了。
#### 3.2.3 Parallel Scavenge收集器
`新生代收集器`,與上面兩個一樣基于標記-復制算法,也是能夠并行的多線程收集器。該收集器更多的關注的是吞吐量優先,也被稱為“吞吐量優先收集器”。
Parallel Scavenge Old基于標記整理算法的作用于老年代的收集器。
#### 3.2.4 CMS收集器
Concurrent Mark Sweep,基于標記清除算法的用于**老年代**的收集器,同時也是一款以獲取最短回收停頓時間為目標的收集器。
其包含的過程為:
1. 初始標記`需要STW`
僅僅標記一下GC Roots能直接關聯的對象,耗時非常快。
2. 并發標記
從GC Roots直接遍歷整個對象圖,這個過程比較耗時,但是不用暫停用戶線程;
3. 重新標記`需要STW`
重新標記是為了再一次標記那些在并發標記過程中用戶線程運行可能產生的垃圾,比初始標記耗時相對高一點點。
4. 并發清除
清除標記出來的對象,不需要移動對象,可以和用戶線程一起運行。
:-: 
CMS收集器可以實現并發低停頓。
#### 3.2.5 G1收集器
Garbage First,被官方稱為“全功能的垃圾收集器”,與上面其他基于分代回收的垃圾回收器不同的是,G1回收器基于Region的內存布局進行回收,同時作用與老年代和新生代,因此G1也被稱為為Mixed GC。JDK9之后G1開始替代Parallel Scanvenge和Parallel Old組合,稱為服務端的默認垃圾回收器,同時CMS不被推薦使用。
> G1目標 -> 停頓時間模型:
> 能夠支持指定在一個長度為M毫秒的時間片段內,消耗在垃圾收集上的時間大概率不超過N毫秒。
G1的內存布局:將堆分為多塊Region,每塊Region的大小可以根據參數`-XX:G1HeapRegionSize`設置為1~32MB,且為2的N次冪大小。在G1中沒有嚴格的新生代和老年代的概念,而是用多塊(不一定連續)的Region來充當這些角色。同時Region中有一塊特殊的Humongouns區域,用來保存大對象。
G1將Region作為回收的最小單元,同時在后臺中跟蹤記錄收集每塊Region的可獲得空間大小和時間代價,并維護成一個優先級列表。每次在規定的停頓時間內(-XX:MaxGCPauseMillis,默認為200ms)就先對優先級高的Region進行回收。其包括4個如下的過程:
1. 初始標記:標記GC Roots能夠關聯到的對象,并且修改TAMS指針(用于并發標記時用戶生成對象的分配),需要停頓線程。
2. 并發標記:從GC Roots開始對堆中的對象進行可達性分析,遞歸掃描整個堆,找出要回收的對象,可與用戶線程并發執行。
3. 最終標記:停頓用戶線程,處理并發階段用戶線程產生的垃圾。
4. 篩選回收:對Region的優先級進行排序,根據停頓時間收集優先級高的Region。收集的時候采用標記復制算法進行清除,將存活的對象復制到空的Region,然后清除掉整個Region。這個過程由多條線程執行,但是需要暫停用戶線程 。
:-: 
**CMS和G1的選擇**
未來G1必定會替代CMS,但是在小內存的服務中仍然可以使用CMS,G1會多占用20%左右的堆內存來維護G1回收器的運行。
【參考】
1. 《深入理解Java虛擬機》第3章 垃圾收集器與內存分配策略
- 第一章 Java基礎
- ThreadLocal
- Java異常體系
- Java集合框架
- List接口及其實現類
- Queue接口及其實現類
- Set接口及其實現類
- Map接口及其實現類
- JDK1.8新特性
- Lambda表達式
- 常用函數式接口
- stream流
- 面試
- 第二章 Java虛擬機
- 第一節、運行時數據區
- 第二節、垃圾回收
- 第三節、類加載機制
- 第四節、類文件與字節碼指令
- 第五節、語法糖
- 第六節、運行期優化
- 面試常見問題
- 第三章 并發編程
- 第一節、Java中的線程
- 第二節、Java中的鎖
- 第三節、線程池
- 第四節、并發工具類
- AQS
- 第四章 網絡編程
- WebSocket協議
- Netty
- Netty入門
- Netty-自定義協議
- 面試題
- IO
- 網絡IO模型
- 第五章 操作系統
- IO
- 文件系統的相關概念
- Java幾種文件讀寫方式性能對比
- Socket
- 內存管理
- 進程、線程、協程
- IO模型的演化過程
- 第六章 計算機網絡
- 第七章 消息隊列
- RabbitMQ
- 第八章 開發框架
- Spring
- Spring事務
- Spring MVC
- Spring Boot
- Mybatis
- Mybatis-Plus
- Shiro
- 第九章 數據庫
- Mysql
- Mysql中的索引
- Mysql中的鎖
- 面試常見問題
- Mysql中的日志
- InnoDB存儲引擎
- 事務
- Redis
- redis的數據類型
- redis數據結構
- Redis主從復制
- 哨兵模式
- 面試題
- Spring Boot整合Lettuce+Redisson實現布隆過濾器
- 集群
- Redis網絡IO模型
- 第十章 設計模式
- 設計模式-七大原則
- 設計模式-單例模式
- 設計模式-備忘錄模式
- 設計模式-原型模式
- 設計模式-責任鏈模式
- 設計模式-過濾模式
- 設計模式-觀察者模式
- 設計模式-工廠方法模式
- 設計模式-抽象工廠模式
- 設計模式-代理模式
- 第十一章 后端開發常用工具、庫
- Docker
- Docker安裝Mysql
- 第十二章 中間件
- ZooKeeper