# :-: 常見JVM內存錯誤及解決方案
* [一、**Java heap space**](http://www.hmoore.net/zlt2000/microservices-platform/1203547#Java_heap_space_1)
* [1.1. **原因分析**](http://www.hmoore.net/zlt2000/microservices-platform/1203547#11__5)
* [1.2. **解決方案**](http://www.hmoore.net/zlt2000/microservices-platform/1203547#12__13)
* [二、**GC overhead limit exceeded**](http://www.hmoore.net/zlt2000/microservices-platform/1203547#GC_overhead_limit_exceeded_20)
* [三、**Permgen space**](http://www.hmoore.net/zlt2000/microservices-platform/1203547#Permgen_space_26)
* [3.1. **原因分析**](http://www.hmoore.net/zlt2000/microservices-platform/1203547#31__30)
* [3.2. **解決方案**](http://www.hmoore.net/zlt2000/microservices-platform/1203547#32__40)
* [四、**Metaspace**](http://www.hmoore.net/zlt2000/microservices-platform/1203547#Metaspace_49)
* [五、**Unable to create new native thread**](http://www.hmoore.net/zlt2000/microservices-platform/1203547#Unable_to_create_new_native_thread_55)
* [5.1. **原因分析**](http://www.hmoore.net/zlt2000/microservices-platform/1203547#51__59)
* [5.2. **解決方案**](http://www.hmoore.net/zlt2000/microservices-platform/1203547#52__73)
* [六、**Out of swap space?**](http://www.hmoore.net/zlt2000/microservices-platform/1203547#Out_of_swap_space_87)
* [6.1. **原因分析**](http://www.hmoore.net/zlt2000/microservices-platform/1203547#61__91)
* [6.2. **解決方案**](http://www.hmoore.net/zlt2000/microservices-platform/1203547#62__99)
* [七、**Kill process or sacrifice child**](http://www.hmoore.net/zlt2000/microservices-platform/1203547#Kill_process_or_sacrifice_child_107)
* [7.1. **原因分析**](http://www.hmoore.net/zlt2000/microservices-platform/1203547#71__113)
* [7.2. **解決方案**](http://www.hmoore.net/zlt2000/microservices-platform/1203547#72__119)
* [八、**Requested array size exceeds VM limit**](http://www.hmoore.net/zlt2000/microservices-platform/1203547#Requested_array_size_exceeds_VM_limit_124)
* [九、**Direct buffer memory**](http://www.hmoore.net/zlt2000/microservices-platform/1203547#Direct_buffer_memory_131)
* [9.1. **原因分析**](http://www.hmoore.net/zlt2000/microservices-platform/1203547#91__135)
* [9.2. **解決方案**](http://www.hmoore.net/zlt2000/microservices-platform/1203547#92__139)
* [十、**推薦工具**](http://www.hmoore.net/zlt2000/microservices-platform/1203547#_148)
## 一、**Java heap space**
當堆內存(Heap Space)沒有足夠空間存放新創建的對象時,就會拋出 java.lang.OutOfMemoryError: Java heap space 錯誤(根據實際生產經驗,可以對程序日志中的 OutOfMemoryError 配置關鍵字告警,一經發現,立即處理)。
### 1.1. **原因分析**
Java heap space 錯誤產生的常見原因可以分為以下幾類:
* 請求創建一個超大對象,通常是一個大數組。
* 超出預期的訪問量/數據量,通常是上游系統請求流量飆升,常見于各類促銷/秒殺活動,可以結合業務流量指標排查是否有尖狀峰值。
* 過度使用終結器(Finalizer),該對象沒有立即被 GC。
* 內存泄漏(Memory Leak),大量對象引用沒有釋放,JVM 無法對其自動回收,常見于使用了 File 等資源沒有回收。
### 1.2. **解決方案**
針對大部分情況,通常只需要通過 -Xmx 參數調高 JVM 堆內存空間即可。如果仍然沒有解決,可以參考以下情況做進一步處理:
* 如果是超大對象,可以檢查其合理性,比如是否一次性查詢了數據庫全部結果,而沒有做結果數限制。
* 如果是業務峰值壓力,可以考慮添加機器資源,或者做限流降級。
* 如果是內存泄漏,需要找到持有的對象,修改代碼設計,比如關閉沒有釋放的連接。
## 二、**GC overhead limit exceeded**
當 Java 進程花費 98% 以上的時間執行 GC,但只恢復了不到 2% 的內存,且該動作連續重復了 5 次,就會拋出 java.lang.OutOfMemoryError:GC overhead limit exceeded 錯誤。簡單地說,就是應用程序已經基本耗盡了所有可用內存, GC 也無法回收。
此類問題的原因與解決方案跟 Java heap space 非常類似,可以參考上文。
## 三、**Permgen space**
該錯誤表示永久代(Permanent Generation)已用滿,通常是因為加載的 class 數目太多或體積太大。
### 3.1. **原因分析**
永久代存儲對象主要包括以下幾類:
* 加載/緩存到內存中的 class 定義,包括類的名稱,字段,方法和字節碼;
* 常量池;
* 對象數組/類型數組所關聯的 class;
* JIT 編譯器優化后的 class 信息。
PermGen 的使用量與加載到內存的 class 的數量/大小正相關。
### 3.2. **解決方案**
根據 Permgen space 報錯的時機,可以采用不同的解決方案,如下所示:
* 程序啟動報錯,修改 -XX:MaxPermSize 啟動參數,調大永久代空間。
* 應用重新部署時報錯,很可能是沒有應用沒有重啟,導致加載了多份 class 信息,只需重啟 JVM 即可解決。
* 運行時報錯,應用程序可能會動態創建大量 class,而這些 class 的生命周期很短暫,但是 JVM 默認不會卸載 class,可以設置 -XX:+CMSClassUnloadingEnabled 和 -XX:+UseConcMarkSweepGC 這兩個參數允許 JVM 卸載 class。
如果上述方法無法解決,可以通過 jmap 命令 dump 內存對象 jmap -dump:format=b,file=dump.hprof ,然后利用 Eclipse MAT 功能逐一分析開銷最大的 classloader 和重復 class。
## 四、**Metaspace**
JDK 1.8 使用 Metaspace 替換了永久代(Permanent Generation),該錯誤表示 Metaspace 已被用滿,通常是因為加載的 class 數目太多或體積太大。
此類問題的原因與解決方法跟 Permgen space 非常類似,可以參考上文。需要特別注意的是調整 Metaspace 空間大小的啟動參數為 -XX:MaxMetaspaceSize。
## 五、**Unable to create new native thread**
每個 Java 線程都需要占用一定的內存空間,當 JVM 向底層操作系統請求創建一個新的 native 線程時,如果沒有足夠的資源分配就會報此類錯誤。
### 5.1. **原因分析**
JVM 向 OS 請求創建 native 線程失敗,就會拋出 Unable to create new native thread,常見的原因包括以下幾類:
* 線程數超過操作系統最大線程數 ulimit 限制。
* 線程數超過 kernel.pid\_max(只能重啟)。
* native 內存不足。
該問題發生的常見過程主要包括以下幾步:
1. JVM 內部的應用程序請求創建一個新的 Java 線程;
2. JVM native 方法代理了該次請求,并向操作系統請求創建一個 native 線程;
3. 操作系統嘗試創建一個新的 native 線程,并為其分配內存;
4. 如果操作系統的虛擬內存已耗盡,或是受到 32 位進程的地址空間限制,操作系統就會拒絕本次 native 內存分配;
5. JVM 將拋出 java.lang.OutOfMemoryError: Unable to create new native thread 錯誤。
### 5.2. **解決方案**
* 升級配置,為機器提供更多的內存;
* 降低 Java Heap Space 大小;
* 修復應用程序的線程泄漏問題;
* 限制線程池大小;
* 使用 -Xss 參數減少線程棧的大小;
* 調高 OS 層面的線程最大數:執行 ulimia -a 查看最大線程數限制,使用 ulimit -u xxx 調整最大線程數限制。
~~~
ulimit -a
.... 省略部分內容 .....
max user processes (-u) 16384
~~~
## 六、**Out of swap space?**
該錯誤表示所有可用的虛擬內存已被耗盡。虛擬內存(Virtual Memory)由物理內存(Physical Memory)和交換空間(Swap Space)兩部分組成。當運行時程序請求的虛擬內存溢出時就會報 Out of swap space? 錯誤。
### 6.1. **原因分析**
該錯誤出現的常見原因包括以下幾類:
* 地址空間不足;
* 物理內存已耗光;
* 應用程序的本地內存泄漏(native leak),例如不斷申請本地內存,卻不釋放。
* 執行 jmap -histo:live 命令,強制執行 Full GC;如果幾次執行后內存明顯下降,則基本確認為 Direct ByteBuffer 問題。
### 6.2. **解決方案**
根據錯誤原因可以采取如下解決方案:
* 升級地址空間為 64 bit;
* 使用 Arthas 檢查是否為 Inflater/Deflater 解壓縮問題,如果是,則顯式調用 end 方法。
* Direct ByteBuffer 問題可以通過啟動參數 -XX:MaxDirectMemorySize 調低閾值。
* 升級服務器配置/隔離部署,避免爭用。
## 七、**Kill process or sacrifice child**
有一種內核作業(Kernel Job)名為 Out of Memory Killer,它會在可用內存極低的情況下“殺死”(kill)某些進程。OOM Killer 會對所有進程進行打分,然后將評分較低的進程“殺死”,具體的評分規則可以參考 Surviving the Linux OOM Killer,詳情點擊:[https://dev.to/rrampage/surviving-the-linux-oom-killer-2ki9](https://dev.to/rrampage/surviving-the-linux-oom-killer-2ki9)
不同于其他的 OOM 錯誤,Kill process or sacrifice child 錯誤不是由 JVM 層面觸發的,而是由操作系統層面觸發的。
### 7.1. **原因分析**
默認情況下,Linux 內核允許進程申請的內存總量大于系統可用內存,通過這種“錯峰復用”的方式可以更有效的利用系統資源。
然而,這種方式也會無可避免地帶來一定的“超賣”風險。例如某些進程持續占用系統內存,然后導致其他進程沒有可用內存。此時,系統將自動激活 OOM Killer,尋找評分低的進程,并將其“殺死”,釋放內存資源。
### 7.2. **解決方案**
* 升級服務器配置/隔離部署,避免爭用。
* OOM Killer 調優,詳情點擊:[https://access.redhat.com/documentation/en-us/red\_hat\_enterprise\_linux/6/html/performance\_tuning\_guide/s-memory-captun](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/performance_tuning_guide/s-memory-captun)
## 八、**Requested array size exceeds VM limit**
JVM 限制了數組的最大長度,該錯誤表示程序請求創建的數組超過最大長度限制。JVM 在為數組分配內存前,會檢查要分配的數據結構在系統中是否可尋址,通常為 Integer.MAX\_VALUE - 2。
此類問題比較罕見,通常需要檢查代碼,確認業務是否需要創建如此大的數組,是否可以拆分為多個塊,分批執行。
## 九、**Direct buffer memory**
Java 允許應用程序通過 Direct ByteBuffer 直接訪問堆外內存,許多高性能程序通過 Direct ByteBuffer 結合內存映射文件(Memory Mapped File)實現高速 IO。
### 9.1. **原因分析**
Direct ByteBuffer 的默認大小為 64 MB,一旦使用超出限制,就會拋出 Direct buffer memory 錯誤。
### 9.2. **解決方案**
* Java 只能通過 ByteBuffer.allocateDirect 方法使用 Direct ByteBuffer,因此,可以通過 Arthas 等在線診斷工具攔截該方法進行排查。
* 檢查是否直接或間接使用了 NIO,如 netty,jetty 等。
* 通過啟動參數 -XX:MaxDirectMemorySize 調整 Direct ByteBuffer 的上限值。
* 檢查 JVM 參數是否有 -XX:+DisableExplicitGC 選項,如果有就去掉,因為該參數會使 System.gc() 失效。
* 檢查堆外內存使用代碼,確認是否存在內存泄漏;或者通過反射調用 sun.misc.Cleaner 的 clean() 方法來主動釋放被 Direct ByteBuffer 持有的內存空間。
* 內存容量確實不足,升級配置。
## 十、**推薦工具**
* [Eclipse Memory Analyzer](https://www.eclipse.org/mat/) —— JVM 內存分析工具
* [Arthas](https://github.com/alibaba/arthas) - Java 在線診斷工具
- 項目介紹
- 項目聲明
- 項目簡介
- 架構設計
- 項目亮點功能介紹
- 技術棧介紹
- 核心功能
- 運行環境
- 項目更新日志
- 文檔更新日志
- F&Q
- 部署教程
- 環境準備
- JDK安裝
- JDK1.8,17共存
- maven
- 分布式緩存Redis
- 單機版
- 集群
- 注冊&配置中心alibaba/nacos
- 介紹
- Nacos安裝
- Nacos配置中心
- Nacos注冊發現
- Nacos生產部署方案
- 服務監控-BootAdmin
- 基本介紹
- 如何使用
- 整合Admin-Ui
- 客戶端配置
- 鏈路追蹤
- 基本介紹
- SkyWalking-1
- Skywalking-1
- 消息隊列
- Kafka
- docker安裝kafka
- Linux集群
- Maven私服
- nexus安裝部署
- nexus使用介紹
- 全文搜索elasticsearch
- windows集群搭建
- docker安裝es
- ElasticHD
- linux集群部署
- 統一日志解決方案
- 日志解決方案設計
- 介紹與相關資料
- ELK安裝部署
- elasticsearch 7.5
- logstash-7.5
- kibana-7.5
- filebeat
- 服務監控-Prometheus
- Prometheus安裝配置
- Prometheus介紹
- grafana
- 持續集成部署CICD
- 自動化部署Jenkins
- 安裝部署win
- 打包發布遠程執行
- 安裝部署linux
- jenkins+gitlab+docker容器化工程自動化部署
- Git
- CICD說明
- 阿里云效
- CentOS_MYSQL安裝
- docker
- 安裝
- Docker安裝Nginx
- Docker部署啟動springboot
- dockerCompose
- harbor
- Docker私有鏡像倉庫
- Portainer
- Docker遠程連接設置
- 打包工程
- 必要啟動模塊
- 核心模塊
- 登錄認證
- 緩存功能
- 日志模塊
- 分布式鎖
- 消息隊列
- 異常處理
- 系統接口
- 參數驗證
- es檢索
- 數據導出
- 系統設計
- 系統總體架構
- 擴展模塊(可選)
- 限流熔斷alibaba/sentinel
- 使用Sentinel實現gateway網關及服務接口限流
- Sentinel使用Nacos存儲規則及同步
- 服務調用Feign
- Feign基本介紹
- 如何使用
- 負載均衡
- 請求超時
- 請求攔截器
- 分布式任務調度
- XXL-JOB
- 分布式事務
- TX-LCN
- Seata
- Seata原理解析
- 數據庫分庫分表
- swagger文檔
- 分布式ID生成器解決方案
- 服務網關CloudGateway
- 基本介紹
- 使用網關
- 路由配置
- 全局過濾器
- 服務認證授權架構設計
- 認證服務流程
- 授權服務流程
- 系統冪等性設計與實踐
- 分布式日志鏈路跟蹤
- 實時搜索系統設計
- 應用性能
- 壓力測試工具
- Apache JMeter介紹和安裝
- ApacheJMeter使用
- JVM
- JVM性能調優
- 常見JVM內存錯誤及解決方案
- JVM 分析工具詳解
- Spring Cloud性能調優
- Linux運維
- Linux 常用命令
- Linux開啟端口