[TOC]
垃圾回收步驟
1. 確定哪些對象是垃圾:引用計數、可達性分析
2. 何時回收:垃圾算法
3. 如何回收:垃圾回收器

# 1. 找到垃圾
1.引用計數法
2.根可達 root searhing
## 1.1 引用計數法
### 1.1.1 引用計數法原理
> 通過在對象頭中分配一個空間來保存該對象被引用的次數。如果該對象被其它對象引用,則它的引用計數加一,如果刪除對該對象的引用,那么它的引用計數就減一,當該對象的引用計數為0時,那么該對象就會被回收。

### 1.1.2 引用分類
強、軟、弱、虛
#### 一、 強引用
new 出來的對象 “Object obj = new Object();”或者 String s=”abc”中變量s就是字符串對象”abc”的一個強引用,**任何被強引用指向的對象都不能被垃圾回收器回收**,這些對象都是在程序中需要的
#### 二、 軟引用
1. 只有在jvm需要內存時,才會回收這些對象
2. 軟引用可以很好的用來實現緩存,當JVM需要內存時,垃圾回收器就會回收這些只有被軟引用指向的對象
Java中的軟引用使用java.lang.ref.SoftReference類來表示,如下軟引用緩存類
```public class SoftCache<T> {
// 引用隊列
private ReferenceQueue<T> referenceQueue = new ReferenceQueue<>();
// 保存軟引用集合,在引用對象被回收后銷毀
private List<Reference<T>> list = new ArrayList<>();
// 添加緩存對象
public synchronized void add(T obj){
// 構建軟引用
Reference<T> reference = new SoftReference<T>(obj, referenceQueue);
// 加入列表中
list.add(reference);
}
// 獲取緩存對象
public synchronized T get(int index){
// 先對無效引用進行清理
clear();
if (index < 0 || list.size() < index){
return null;
}
Reference<T> reference = list.get(index);
return reference == null ? null : reference.get();
}
public int size(){
return list.size();
}
@SuppressWarnings("unchecked")
private void clear(){
Reference<T> reference;
while (null != (reference = (Reference<T>) referenceQueue.poll())){
list.remove(reference);
}
}
}
```
#### 三、 弱引用
1. 如果一個對象只有**弱引用**指向它,垃圾回收器會立即回收該對象,這是一種急切回收方式
2. 而弱引用非常適合存儲元數據,例如:存儲ClassLoader引用。如果沒有類被加載,那么也沒有指向ClassLoader的引用。一旦上一次的強引用被去除,只有弱引用的ClassLoader就會被回收
#### 四、虛引用
擁有虛引用的對象可以在任何時候被垃圾回收器回收
## 1.2 根可達
### 可作為GC Roots的對象
> 通過一系列的稱為“GCsRoots”的對象作為起始點,從這些節點向下開始搜索,搜索所走過的路徑稱為引用鏈(Reference Chain)。 當一個對象到GC Roots沒有任何引用鏈相連(即從GC Roots到這個對象不可達)時,則證明該對象是不可用的。

可作為跟節點的對象
1. 虛擬機棧(棧幀中的本地變量表)中引用的對象
2. 方法區中類靜態屬性引用的對象
3. 方法區中常量引用的對象
4. 本地方法棧中JNI(即native方法)引用的對象
# 2. 清除垃圾
## 2.1 標記清除
標記-清除算法將垃圾回收分為兩個階段:標記階段和清除階段
一種可行的實現是,在標記階段,首先通過根節點,標記所有從根節點開始的可達對象。因此,未被標記的對象就是未被引用的垃圾對象;然后,在清除階段,清除所有未被標記的對象。
當堆中的有效內存空間(available memory)被耗盡的時候,就會停止整個程序(也被成為stop the world),然后進行兩項工作,第一項則是標記,第二項則是清除

缺點
* 涉及大量的內存遍歷工作,所以執行性能較低,這也會導致“stop the world”時間較長,java程序吞吐量降低;
* 對象被清除之后,被清除的對象留下內存的空缺位置,造成內存不連續,空間浪費。
## 2.2 復制清除(年輕代:eden區和survivor區)
將現有的內存空間分為兩快,每次只使用其中一塊,在垃圾回收時將正在使用的內存中的存活對象復制到未被使用的內存塊中,之后,清除正在使用的內存塊中的所有對象,交換兩個內存的角色,完成垃圾回收。

## 2.3 mark-compact標記壓縮
標記:它的第一個階段與標記/清除算法是一模一樣的,均是遍歷GC Roots,然后將存活的對象標記。
整理:移動所有存活的對象,且按照內存地址次序依次排列,然后將末端內存地址以后的內存全部回收。因此,第二階段才稱為整理階段。

優點:
因為標記清除算法會造成內存的不連續,所以標記整理(壓縮)算在標記清除算法的基礎上,增加了整理過程,解決了標記清除算法內存不連續的問題。同時也消除了復制算法當中,內存減半的高額代價。
缺點:
標記整理(壓縮)也會產生“stop the world”,不能和java程序并發執行。在壓縮過程中一些對象內存地址會發生改變,java程序只能等待壓縮完成后才能繼續。
## 2.4 分代收集算法(新生代的GC+老年代的GC)
當前商業虛擬機都采用分代收集算法。
分代的垃圾回收策略,是基于這樣一個事實:不同的對象的生命周期是不一樣的。因此,不同生命周期的對象可以采取不同的收集方式,以便提高回收效率。
在新生代,每次垃圾收集器都發現有大批對象死去,只有少量的存活,那就選擇復制算法,只需要付出少量存活對象的復制成本就可以完成收集。 在老年代因為對象的存活率較高、沒有額外空間對它進行分配擔保,就必須使用“標記-清除”或者“標記-整理”算法進行回收。
注:老年代的對象中,有一小部分是因為在新生代回收時,老年代做擔保,進來的對象;絕大部分對象是因為很多次GC都沒有被回收掉而進入老年代。
# 3. 垃圾回收器
隨著內存增大,垃圾回收器演進,垃圾回收主要針對年輕代和老年代,并且用的回收器也有多重組合方式,如下圖的虛線連接

## 3.1 CMS
用于老年代回收,常與ParNew搭配
1. gc root標記垃圾
2. 運行中也要標記
3. 糾錯標記(運行后可能垃圾有變成有用的了)
4. 清除垃圾

## 2.2 G1
年輕代YGC時會全部區域回收,當年輕代內存較大時,停頓會越來越長
## 3.3 ZGC
彌補了G1的短板,不區分年輕代和老年代、
# 4. 內存泄漏和內存溢出
1. 內存泄漏最終導致內存溢出
2. 內存泄漏指:應該被垃圾回收測內存,沒有被有效的回收,導致空間的浪費
3. 內存溢出:沒有足夠的內存給對象分配,導致的OOM
## 4.1 內存泄漏
> 內存回收,簡單說就是應該被垃圾回收的對象沒有被垃圾回收

**1. 靜態集合類引起內存泄漏**
靜態集合的生命周期和 JVM 一致,所以靜態集合引用的對象不能被釋放。
~~~java
public class OOM {
static List list = new ArrayList();
public void oomTests(){
Object obj = new Object();
list.add(obj);
}
}
~~~
**2. 單例模式**:
和上面的例子原理類似,單例對象在初始化后會以靜態變量的方式在 JVM 的整個生命周期中存在。如果單例對象持有外部的引用,那么這個外部對象將不能被 GC 回收,導致內存泄漏。
**3. 數據連接、IO、Socket等連接**
創建的連接不再使用時,需要調用 **close** 方法關閉連接,只有連接被關閉后,GC 才會回收對應的對象(Connection,Statement,ResultSet,Session)。忘記關閉這些資源會導致持續占有內存,無法被 GC 回收。
~~~text
try {
Connection conn = null;
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("url", "", "");
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("....");
} catch (Exception e) {
}finally {
//不關閉連接
}
}
~~~
**4. 變量不合理的作用域**
一個變量的定義作用域大于其使用范圍,很可能存在內存泄漏;或不再使用對象沒有及時將對象設置為 null,很可能導致內存泄漏的發生。
~~~
public class Simple {
Object object;
public void method1(){
object = new Object();
//...其他代碼
//由于作用域原因,method1執行完成之后,object 對象所分配的內存不會馬上釋放
object = null;
}
}
~~~
**5. 引用了外部類的非靜態內部類**
非靜態內部類(或匿名類)的初始化總是需要依賴外部類的實例。默認情況下,每個非靜態內部類都包含對其**包含類**的隱式引用,若在程序中使用這個內部類對象,那么**即使在包含類對象超出范圍之后,也不會被回收**(內部類對象隱式地持有外部類對象的引用,使其成不能被回收)。
**6. Hash 值發生改變**
對象Hash值改變,使用HashMap、HashSet等容器中時候,由于對象修改之后的Hah值和存儲進容器時的Hash值不同,會導致無法從容器中**單獨刪除**當前對象,造成內存泄露。
**7. ThreadLocal** 造成的內存泄漏
ThreadLocal 可以實現變量的線程隔離,但若使用不當,就可能會引入內存泄漏問題。
## 4.2 如何排查內存泄漏
- 計算機網絡
- 基礎_01
- tcp/ip
- http轉https
- Let's Encrypt免費ssl證書(基于haproxy負載)
- what's the http?
- 網關
- 網絡IO
- http
- 工具
- Git
- 初始本地倉庫并上傳
- git保存密碼
- Gitflow
- maven
- 1.生命周期命令
- 聚合與繼承
- 插件管理
- assembly
- 資源管理插件
- 依賴范圍
- 分環境打包
- dependencyManagement
- 版本分類
- 找不到主類
- 無法加載主類
- 私服
- svn
- gradle
- 手動引入第三方jar包
- 打包exe文件
- Windows
- java
- 設計模式
- 七大原則
- 1.開閉原則
- 2. 里式替換原則
- 3. 依賴倒置原則
- 4. 單一職責原則
- 單例模式
- 工廠模式
- 簡單工廠
- 工廠方法模式
- 抽象工廠模式
- 觀察者模式
- 適配器模式
- 建造者模式
- 代理模式
- 適配器模式
- 命令模式
- json
- jackson
- poi
- excel
- easy-poi
- 規則
- 模板
- 合并單元格
- word
- 讀取
- java基礎
- 類路徑與jar
- 訪問控制權限
- 類加載
- 注解
- 異常處理
- String不可變
- 跨域
- transient關鍵字
- 二進制編碼
- 泛型1
- 與或非
- final詳解
- Java -jar
- 正則
- 讀取jar
- map
- map計算
- hashcode計算原理
- 枚舉
- 序列化
- URLClassLoader
- 環境變量和系統變量
- java高級
- java8
- 1.Lambda表達式和函數式接口
- 2.接口的默認方法和靜態方法
- 3.方法引用
- 4.重復注解
- 5.類型推斷
- 6.拓寬注解的應用場景
- java7-自動關閉資源機制
- 泛型
- stream
- 時區的正確理解
- StringJoiner字符串拼接
- 注解
- @RequestParam和@RequestBody的區別
- 多線程
- 概念
- 線程實現方法
- 守護線程
- 線程阻塞
- 筆試題
- 類加載
- FutureTask和Future
- 線程池
- 同步與異步
- 高效簡潔的代碼
- IO
- ThreadLocal
- IO
- NIO
- 圖片操作
- KeyTool生成證書
- 壓縮圖片
- restful
- 分布式session
- app保持session
- ClassLoader.getResources 能搜索到的資源路徑
- java開發規范
- jvm
- 高并發
- netty
- 多線程與多路復用
- 異步與事件驅動
- 五種IO模型
- copy on write
- code style
- 布隆過濾器
- 筆試
- 數據庫
- mybatis
- mybatis與springboot整合配置
- pagehelper
- 分頁數據重復問題
- Java與數據庫之間映射
- 攔截器
- 攔截器應用
- jvm
- 堆內存測試
- 線程棧
- 直接內存
- 內存結構
- 內存模型
- 垃圾回收
- 調優
- 符號引用
- 運行參數
- 方法區
- 分帶回收理論
- 快捷開發
- idea插件
- 注釋模板
- git
- pull沖突
- push沖突
- Excel處理
- 圖片處理
- 合并單元格
- easypoi
- 模板處理
- 響應式編程
- reactor
- reactor基礎
- jingyan
- 規范
- 數據庫