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

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


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

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

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

精心梳理、吐血推薦、史上最強、建議收藏 阿里、京東、美團、頭條.... 隨意挑、橫着走!!!
1.Java算法面試題(史上最強、持續更新、吐血推薦) 2.Java基礎面試題(史上最全、持續更新、吐血推薦)
3.JVM面試題(史上最強、持續更新、吐血推薦) 四、架構設計面試題 (史上最全、持續更新、吐血推薦)
五、Spring面試題 專題 六、SpringMVC面試題 專題
7.SpringBoot - 面試題(史上最強、持續更新) 八、Tomcat面試題 專題部分
9.網絡協議面試題(史上最全、持續更新、吐血推薦) 十、TCP/IP協議(圖解+秒懂+史上最全)
11.JUC併發包與容器 - 面試題(史上最強、持續更新) 十二、設計模式面試題 (史上最全、持續更新、吐血推薦)
13.死鎖面試題(史上最強、持續更新) 15.Zookeeper 分佈式鎖 (圖解+秒懂+史上最全)
1四、Redis 面試題 - 收藏版(史上最強、持續更新) 1六、Zookeeper 面試題(史上最強、持續更新)
1七、分佈式事務面試題 (史上最全、持續更新、吐血推薦) 1八、一致性協議 (史上最全)
1九、Zab協議 (史上最全) 20、Paxos 圖解 (秒懂)
2一、raft 圖解 (秒懂) 2六、消息隊列、RabbitMQ、Kafka、RocketMQ面試題 (史上最全、持續更新)
22.Linux面試題(史上最全、持續更新、吐血推薦) 2三、Mysql 面試題(史上最強、持續更新)
2四、SpringCloud 面試題 - 收藏版(史上最強、持續更新) 2五、Netty 面試題 (史上最強、持續更新)
2七、內存泄漏 內存溢出(史上最全) 2八、JVM 內存溢出 實戰 (史上最全)

JVM性能優化面試題

JVM內存區域常見問題java

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

Java 內存分配?面試

Java 堆的結構是什麼樣子的?算法

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

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

說說各個區域的做用?編程

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,有的主要監控內存,有的監控線程等。
在這裏插入圖片描述
如何安裝:

一、從主菜單中選擇「工具」>「插件」。二、在「可用插件」標籤中,選中該插件的「安裝」複選框。單擊「安裝」。三、逐步完成插件安裝程序。

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

實戰

準備模擬內存泄漏demo

​ 一、定義靜態變量HashMap

​ 二、分段循環建立對象,並加入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");
    }
}

三、配置jvm參數以下:

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

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

JVisualVM 遠程監控 Tomcat

一、修改遠程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端口。

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

三、輸入主機的名稱,直接寫ip,以下:
在這裏插入圖片描述
右鍵新建的主機,選擇添加JMX鏈接,輸入在tomcat中配置的端口便可。

四、雙擊打開。完畢!

使用JVisualVM分析內存泄漏

一、查看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之後對內存空間進行壓縮、整理。

相關文章
相關標籤/搜索