JVM 內存溢出 實戰 (史上最全)


文章很長,建議收藏起來,慢慢讀! 瘋狂創客圈為小伙伴奉上以下珍貴的學習資源:


推薦: 瘋狂創客圈 高質量 博文

高並發 必讀 的精彩博文
nacos 實戰(史上最全) sentinel (史上最全+入門教程)
Zookeeper 分布式鎖 (圖解+秒懂+史上最全) Webflux(史上最全)
SpringCloud gateway (史上最全) TCP/IP(圖解+秒懂+史上最全)
10分鍾看懂, Java NIO 底層原理 Feign原理 (圖解)
更多精彩博文 ..... 請參見【 瘋狂創客圈 高並發 總目錄

史上最全 Java 面試題 28 專題 總目錄

精心梳理、吐血推薦、史上最強、建議收藏 阿里、京東、美團、頭條.... 隨意挑、橫着走!!!
1.Java算法面試題(史上最強、持續更新、吐血推薦) 2.Java基礎面試題(史上最全、持續更新、吐血推薦)
3.JVM面試題(史上最強、持續更新、吐血推薦) 4、架構設計面試題 (史上最全、持續更新、吐血推薦)
5、Spring面試題 專題 6、SpringMVC面試題 專題
7.SpringBoot - 面試題(史上最強、持續更新) 8、Tomcat面試題 專題部分
9.網絡協議面試題(史上最全、持續更新、吐血推薦) 10、TCP/IP協議(圖解+秒懂+史上最全)
11.JUC並發包與容器 - 面試題(史上最強、持續更新) 12、設計模式面試題 (史上最全、持續更新、吐血推薦)
13.死鎖面試題(史上最強、持續更新) 15.Zookeeper 分布式鎖 (圖解+秒懂+史上最全)
14、Redis 面試題 - 收藏版(史上最強、持續更新) 16、Zookeeper 面試題(史上最強、持續更新)
17、分布式事務面試題 (史上最全、持續更新、吐血推薦) 18、一致性協議 (史上最全)
19、Zab協議 (史上最全) 20、Paxos 圖解 (秒懂)
21、raft 圖解 (秒懂) 26、消息隊列、RabbitMQ、Kafka、RocketMQ面試題 (史上最全、持續更新)
22.Linux面試題(史上最全、持續更新、吐血推薦) 23、Mysql 面試題(史上最強、持續更新)
24、SpringCloud 面試題 - 收藏版(史上最強、持續更新) 25、Netty 面試題 (史上最強、持續更新)
27、內存泄漏 內存溢出(史上最全) 28、JVM 內存溢出 實戰 (史上最全)

JVM性能優化面試題

JVM內存區域常見問題

Java 中會存在內存泄漏嗎,簡述一下?

Java 內存分配?

Java 堆的結構是什么樣子的?

什么是堆中的永久代(Perm Gen space)?

簡述各個版本內存區域的變化?

說說各個區域的作用?

JVM的執行子系統常見問題

Java 類加載過程?

描述一下 JVM 加載 Class 文件的原理機制?什么是類加載器?

類加載器有哪些?

類加載器雙親委派模型機制?

垃圾回收常見問題

什么是GC?

為什么要有 GC?

簡述一下Java 垃圾回收機制?

如何判斷一個對象是否存活?

垃圾回收的優點和原理,並考慮 2 種回收機制?

垃圾回收器的基本原理是什么?

垃圾回收器可以馬上回收內存嗎?

有什么辦法主動通知虛擬機進行垃圾回收?

深拷貝和淺拷貝?

System.gc() 和 Runtime.gc() 會做些什么?

如果對象的引用被置為 null,垃圾收集器是否會立即釋放對象占用的內存?

什么是分布式垃圾回收(DGC)?

它是如何工作的?

串行(serial)收集器和吞吐量(throughput)收集器的區別是什么?

在 Java 中,對象什么時候可以被垃圾回收?簡述Minor GC 和 Major GC?JVM 的永久代中會發生垃圾回收么?Java 中垃圾收集的方法有哪些?

性能優化常見問題

講講你理解的性能評價及測試指標?

常用的性能優化方式有哪些?

什么是GC調優?

JVM調優工具

Jconsole,jProfile,VisualVM

Jconsole : jdk自帶,功能簡單,但是可以在系統有一定負荷的情況下使用。對垃圾回收算法有很詳細的跟蹤。詳細說明參考這里

JProfiler:商業軟件,需要付費。功能強大。詳細說明參考這里

VisualVM:JDK自帶,功能強大,與JProfiler類似。推薦。

JVisualVM初探

​ VisualVM 是Netbeans的profile子項目,已在JDK6.0 update 7 中自帶(java啟動時不需要特定參數,監控工具在bin/jvisualvm.exe),能夠監控線程,內存情況,查看方法的CPU時間和內存中的對 象,已被GC的對象,反向查看分配的堆棧(如100個String對象分別由哪幾個對象分配出來的)。

​ 在JDK_HOME/bin(默認是C:\Program Files\Java\jdk1.6.0_13\bin)目錄下面,有一個jvisualvm.exe文件,雙擊打開,從UI上來看,這個軟件是基於NetBeans開發的了。

​ 可以進行遠程和本地監控。遠程監控需要打開jmx,下面內容會提到。

​ 其默認頁面為:

img

左側分為本地和遠程。雙擊本地中VisualVM線程,可以看到如下監控內容:

img

具體的介紹參看:

http://www.ibm.com/developerworks/cn/java/j-lo-visualvm/

VisualVM可以根據需要安裝不同的插件,每個插件的關注點都不同,有的主要監控GC,有的主要監控內存,有的監控線程等。
在這里插入圖片描述
如何安裝:

1、從主菜單中選擇“工具”>“插件”。2、在“可用插件”標簽中,選中該插件的“安裝”復選框。單擊“安裝”。3、逐步完成插件安裝程序。

我這里以 Eclipse(pid 22296)為例,雙擊后直接展開,主界面展示了系統和jvm兩大塊內容,點擊右下方jvm參數和系統屬性可以參考詳細的參數信息.
在這里插入圖片描述
因為VisualVM的插件太多,我這里主要介紹三個我主要使用幾個:監控、線程、Visual GC
監控的主頁其實也就是,cpu、內存、類、線程的圖表
在這里插入圖片描述
線程和jconsole功能沒有太大的區別
在這里插入圖片描述
Visual GC 是常常使用的一個功能,可以明顯的看到年輕代、老年代的內存變化,以及gc頻率、gc的時間等。
在這里插入圖片描述
以上的功能其實jconsole幾乎也有,VisualVM更全面更直觀一些,另外VisualVM非常多的其它功能,可以分析dump的內存快照,
dump出來的線程快照並且進行分析等,還有其它很多的插件大家可以去探索
在這里插入圖片描述

實戰

准備模擬內存泄漏demo

​ 1、定義靜態變量HashMap

​ 2、分段循環創建對象,並加入HashMap

​ 代碼如下:

import java.util.HashMap;
import java.util.Map;
public class CyclicDependencies {
    //聲明緩存對象
    private static final Map map = new HashMap();
    public static void main(String args[]){
        try {
            Thread.sleep(10000);//給打開visualvm時間
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //循環添加對象到緩存
        for(int i=0; i<1000000;i++){
            TestMemory t = new TestMemory();
            map.put("key"+i,t);
        }
        System.out.println("first");
        //為dump出堆提供時間
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for(int i=0; i<1000000;i++){
            TestMemory t = new TestMemory();
            map.put("key"+i,t);
        }
        System.out.println("second");
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for(int i=0; i<3000000;i++){
            TestMemory t = new TestMemory();
            map.put("key"+i,t);
        }
        System.out.println("third");
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for(int i=0; i<4000000;i++){
            TestMemory t = new TestMemory();
            map.put("key"+i,t);
        }
        System.out.println("forth");
        try {
            Thread.sleep(Integer.MAX_VALUE);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("qqqq");
    }
}

3、配置jvm參數如下:

         -Xms512m
         -Xmx512m
         -XX:-UseGCOverheadLimit
         -XX:MaxPermSize=50m

​ 4、運行程序並打開visualvm監控

JVisualVM 遠程監控 Tomcat

1、修改遠程tomcat的catalina.sh配置文件,在其中增加:

  • JAVA_OPTS="$JAVA_OPTS
  • Djava.rmi.server.hostname=192.168.122.128
  • Dcom.sun.management.jmxremote.port=18999
  • Dcom.sun.management.jmxremote.ssl=false
  • Dcom.sun.management.jmxremote.authenticate=false"

這次配置先不走權限校驗。只是打開jmx端口。

2、打開jvisualvm,右鍵遠程,選擇添加遠程主機:
在這里插入圖片描述

3、輸入主機的名稱,直接寫ip,如下:
在這里插入圖片描述
右鍵新建的主機,選擇添加JMX連接,輸入在tomcat中配置的端口即可。

4、雙擊打開。完畢!

使用JVisualVM分析內存泄漏

1、查看Visual GC標簽,內容如下,這是輸出first的截圖
在這里插入圖片描述
這是輸出forth的截圖:
在這里插入圖片描述
通過2張圖對比發現:
在這里插入圖片描述
在這里插入圖片描述
老生代一直在gc,當程序繼續運行可以發現老生代gc還在繼續:
在這里插入圖片描述
增加到了7次,但是老生代的內存並沒有減少。說明存在無法被回收的對象,可能是內存泄漏了。
如何分析是那個對象泄漏了呢?打開抽樣器標簽:點擊后如下圖:
在這里插入圖片描述
按照程序輸出進行堆dump,當輸出second時,dump一次,當輸出forth時dump一次。
進入最后dump出來的堆標簽,點擊類:
在這里插入圖片描述
點擊右上角:“與另一個堆存儲對比”。如圖選擇第一次導出的dump內容比較:
在這里插入圖片描述
比較結果如下:
在這里插入圖片描述
可以看出在兩次間隔時間內TestMemory對象實例一直在增加並且多了,說明該對象引用的方法可能存在內存泄漏。
如何查看對象引用關系呢?
右鍵選擇類TestMemory,選擇“在實例視圖中顯示”,如下所示:
在這里插入圖片描述
左側是創建的實例總數,右側上部為該實例的結構,下面為引用說明,從圖中可以看出在類CyclicDependencies里面被引用了,並且被HashMap引用。

如此可以確定泄漏的位置,進而根據實際情況進行分析解決。

如何進行JVM調優

觀察內存釋放情況、集合類檢查、對象樹

上面這些調優工具都提供了強大的功能,但是總的來說一般分為以下幾類功能

堆信息查看

img

可查看堆空間大小分配(年輕代、年老代、持久代分配)

提供即時的垃圾回收功能

垃圾監控(長時間監控回收情況)

img

查看堆內類、對象信息查看:數量、類型等

img

對象引用情況查看

有了堆信息查看方面的功能,我們一般可以順利解決以下問題:

–年老代年輕代大小划分是否合理

–內存泄漏

–垃圾回收算法設置是否合理

線程監控

img

線程信息監控:系統線程數量。

線程狀態監控:各個線程都處在什么樣的狀態下

img

Dump線程詳細信息:查看線程內部運行情況

死鎖檢查

熱點分析

img

CPU熱點:檢查系統哪些方法占用的大量CPU時間

內存熱點:檢查哪些對象在系統中數量最大(一定時間內存活對象和銷毀對象一起統計)

這兩個東西對於系統優化很有幫助。我們可以根據找到的熱點,有針對性的進行系統的瓶頸查找和進行系統優化,而不是漫無目的的進行所有代碼的優化。

快照

快照是系統運行到某一時刻的一個定格。在我們進行調優的時候,不可能用眼睛去跟蹤所有系統變化,依賴快照功能,我們就可以進行系統兩個不同運行時刻,對象(或類、線程等)的不同,以便快速找到問題

舉例說,我要檢查系統進行垃圾回收以后,是否還有該收回的對象被遺漏下來的了。那么,我可以在進行垃圾回收前后,分別進行一次堆情況的快照,然后對比兩次快照的對象情況。

內存泄漏檢查

內存泄漏是比較常見的問題,而且解決方法也比較通用,這里可以重點說一下,而線程、熱點方面的問題則是具體問題具體分析了。

內存泄漏一般可以理解為系統資源(各方面的資源,堆、棧、線程等)在錯誤使用的情況下,導致使用完畢的資源無法回收(或沒有回收),從而導致新的資源分配請求無法完成,引起系統錯誤。

內存泄漏對系統危害比較大,因為他可以直接導致系統的崩潰。

需要區別一下,內存泄漏和系統超負荷兩者是有區別的,雖然可能導致的最終結果是一樣的。內存泄漏是用完的資源沒有回收引起錯誤,而系統超負荷則是系統確實沒有那么多資源可以分配了(其他的資源都在使用)。

年老代堆空間被占滿

異常: java.lang.OutOfMemoryError: Java heap space

說明:

img

這是最典型的內存泄漏方式,簡單說就是所有堆空間都被無法回收的垃圾對象占滿,虛擬機無法再在分配新空間。

如上圖所示,這是非常典型的內存泄漏的垃圾回收情況圖。所有峰值部分都是一次垃圾回收點,所有谷底部分表示是一次垃圾回收后剩余的內存。連接所有谷底的點,可以發現一條由底到高的線,這說明,隨時間的推移,系統的堆空間被不斷占滿,最終會占滿整個堆空間。因此可以初步認為系統內部可能有內存泄漏。(上面的圖僅供示例,在實際情況下收集數據的時間需要更長,比如幾個小時或者幾天)

解決:

這種方式解決起來也比較容易,一般就是根據垃圾回收前后情況對比,同時根據對象引用情況(常見的集合對象引用)分析,基本都可以找到泄漏點。

持久代被占滿

異常:java.lang.OutOfMemoryError: PermGen space

說明:

Perm空間被占滿。無法為新的class分配存儲空間而引發的異常。這個異常以前是沒有的,但是在Java反射大量使用的今天這個異常比較常見了。主要原因就是大量動態反射生成的類不斷被加載,最終導致Perm區被占滿。

更可怕的是,不同的classLoader即便使用了相同的類,但是都會對其進行加載,相當於同一個東西,如果有N個classLoader那么他將會被加載N次。因此,某些情況下,這個問題基本視為無解。當然,存在大量classLoader和大量反射類的情況其實也不多。

解決:

  1. -XX:MaxPermSize=16m

  2. 換用JDK。比如JRocket。

堆棧溢出

異常:java.lang.StackOverflowError

說明:這個就不多說了,一般就是遞歸沒返回,或者循環調用造成

線程堆棧滿

異常:Fatal: Stack size too small

說明:java中一個線程的空間大小是有限制的。JDK5.0以后這個值是1M。與這個線程相關的數據將會保存在其中。但是當線程空間滿了以后,將會出現上面異常。

解決:增加線程棧大小。-Xss2m。但這個配置無法解決根本問題,還要看代碼部分是否有造成泄漏的部分。

系統內存被占滿

異常:java.lang.OutOfMemoryError: unable to create new native thread

說明

這個異常是由於操作系統沒有足夠的資源來產生這個線程造成的。系統創建線程時,除了要在Java堆中分配內存外,操作系統本身也需要分配資源來創建線程。因此,當線程數量大到一定程度以后,堆中或許還有空間,但是操作系統分配不出資源來了,就出現這個異常了。

分配給Java虛擬機的內存愈多,系統剩余的資源就越少,因此,當系統內存固定時,分配給Java虛擬機的內存越多,那么,系統總共能夠產生的線程也就越少,兩者成反比的關系。同時,可以通過修改-Xss來減少分配給單個線程的空間,也可以增加系統總共內生產的線程數。

解決:

  1. 重新設計系統減少線程數量。

  2. 線程數量不能減少的情況下,通過-Xss減小單個線程大小。以便能生產更多的線程。

jvm參數優化建議

​ 本質上是減少GC的次數。

如果是頻繁創建對象的應用,可以適當增加新生代大小。常量較多可以增加持久代大小。對於單例較多的對象可以增加老生代大小。比如spring應用中。

GC選擇,在JDK5.0以后,JVM會根據當前系統配置進行判斷。一般執行-Server命令便可以。gc包括三種策略:串行,並行,並發。

吞吐量大大應用,一般采用並行收集,開啟多個線程,加快gc的是否。

響應速度高的應用,一般采用並發收集,比如應用服務器。

​ 年老代建議配置為並發收集器,由於並發收集器不會壓縮和整理磁盤碎片,因此建議配置:

​ -XX:+UseConcMarkSweepGC #並發收集年老代

​ -XX:CMSInitiatingOccupancyFraction=80 # 表示年老代空間到80%時就開始執行CMS

​ -XX:+UseCMSCompactAtFullCollection # 打開對年老代的壓縮。可能會影響性能,但是可以消除內存碎片。

​ -XX:CMSFullGCsBeforeCompaction=10 # 由於並發收集器不對內存空間進行壓縮、整理,所以運行一段時間以后會產生“碎片”,使得運行效率降低。此參數設置運行次FullGC以后對內存空間進行壓縮、整理。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM