文章很長,建議收藏起來,慢慢讀! 瘋狂創客圈爲小夥伴奉上如下珍貴的學習資源:html
高併發 必讀 的精彩博文 | |
---|---|
nacos 實戰(史上最全) | sentinel (史上最全+入門教程) |
Zookeeper 分佈式鎖 (圖解+秒懂+史上最全) | Webflux(史上最全) |
SpringCloud gateway (史上最全) | TCP/IP(圖解+秒懂+史上最全) |
10分鐘看懂, Java NIO 底層原理 | Feign原理 (圖解) |
更多精彩博文 ..... | 請參見【 瘋狂創客圈 高併發 總目錄 】 |
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調優?
Jconsole,jProfile,VisualVM
Jconsole : jdk自帶,功能簡單,可是能夠在系統有必定負荷的狀況下使用。對垃圾回收算法有很詳細的跟蹤。詳細說明參考這裏
JProfiler:商業軟件,須要付費。功能強大。詳細說明參考這裏
VisualVM:JDK自帶,功能強大,與JProfiler相似。推薦。
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,下面內容會提到。
其默認頁面爲:
左側分爲本地和遠程。雙擊本地中VisualVM線程,能夠看到以下監控內容:
具體的介紹參看:
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出來的線程快照而且進行分析等,還有其它不少的插件你們能夠去探索
一、定義靜態變量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監控
一、修改遠程tomcat的catalina.sh配置文件,在其中增長:
此次配置先不走權限校驗。只是打開jmx端口。
二、打開jvisualvm,右鍵遠程,選擇添加遠程主機:
三、輸入主機的名稱,直接寫ip,以下:
右鍵新建的主機,選擇添加JMX鏈接,輸入在tomcat中配置的端口便可。
四、雙擊打開。完畢!
一、查看Visual GC標籤,內容以下,這是輸出first的截圖
這是輸出forth的截圖:
經過2張圖對比發現:
老生代一直在gc,當程序繼續運行能夠發現老生代gc還在繼續:
增長到了7次,可是老生代的內存並無減小。說明存在沒法被回收的對象,多是內存泄漏了。
如何分析是那個對象泄漏了呢?打開抽樣器標籤:點擊後以下圖:
按照程序輸出進行堆dump,當輸出second時,dump一次,當輸出forth時dump一次。
進入最後dump出來的堆標籤,點擊類:
點擊右上角:「與另外一個堆存儲對比」。如圖選擇第一次導出的dump內容比較:
比較結果以下:
能夠看出在兩次間隔時間內TestMemory對象實例一直在增長而且多了,說明該對象引用的方法可能存在內存泄漏。
如何查看對象引用關係呢?
右鍵選擇類TestMemory,選擇「在實例視圖中顯示」,以下所示:
左側是建立的實例總數,右側上部爲該實例的結構,下面爲引用說明,從圖中能夠看出在類CyclicDependencies裏面被引用了,而且被HashMap引用。
如此能夠肯定泄漏的位置,進而根據實際狀況進行分析解決。
觀察內存釋放狀況、集合類檢查、對象樹
上面這些調優工具都提供了強大的功能,可是總的來講通常分爲如下幾類功能
堆信息查看
可查看堆空間大小分配(年輕代、年老代、持久代分配)
提供即時的垃圾回收功能
垃圾監控(長時間監控回收狀況)
查看堆內類、對象信息查看:數量、類型等
對象引用狀況查看
有了堆信息查看方面的功能,咱們通常能夠順利解決如下問題:
–年老代年輕代大小劃分是否合理
–內存泄漏
–垃圾回收算法設置是否合理
線程信息監控:系統線程數量。
線程狀態監控:各個線程都處在什麼樣的狀態下
Dump線程詳細信息:查看線程內部運行狀況
死鎖檢查
熱點分析
CPU熱點:檢查系統哪些方法佔用的大量CPU時間
內存熱點:檢查哪些對象在系統中數量最大(必定時間內存活對象和銷燬對象一塊兒統計)
這兩個東西對於系統優化頗有幫助。咱們能夠根據找到的熱點,有針對性的進行系統的瓶頸查找和進行系統優化,而不是漫無目的的進行全部代碼的優化。
快照
快照是系統運行到某一時刻的一個定格。在咱們進行調優的時候,不可能用眼睛去跟蹤全部系統變化,依賴快照功能,咱們就能夠進行系統兩個不一樣運行時刻,對象(或類、線程等)的不一樣,以便快速找到問題
舉例說,我要檢查系統進行垃圾回收之後,是否還有該收回的對象被遺漏下來的了。那麼,我能夠在進行垃圾回收先後,分別進行一次堆狀況的快照,而後對比兩次快照的對象狀況。
內存泄漏是比較常見的問題,並且解決方法也比較通用,這裏能夠重點說一下,而線程、熱點方面的問題則是具體問題具體分析了。
內存泄漏通常能夠理解爲系統資源(各方面的資源,堆、棧、線程等)在錯誤使用的狀況下,致使使用完畢的資源沒法回收(或沒有回收),從而致使新的資源分配請求沒法完成,引發系統錯誤。
內存泄漏對系統危害比較大,由於他能夠直接致使系統的崩潰。
須要區別一下,內存泄漏和系統超負荷二者是有區別的,雖然可能致使的最終結果是同樣的。內存泄漏是用完的資源沒有回收引發錯誤,而系統超負荷則是系統確實沒有那麼多資源能夠分配了(其餘的資源都在使用)。
年老代堆空間被佔滿
異常: java.lang.OutOfMemoryError: Java heap space
說明:
這是最典型的內存泄漏方式,簡單說就是全部堆空間都被沒法回收的垃圾對象佔滿,虛擬機沒法再在分配新空間。
如上圖所示,這是很是典型的內存泄漏的垃圾回收狀況圖。全部峯值部分都是一次垃圾回收點,全部谷底部分表示是一次垃圾回收後剩餘的內存。鏈接全部谷底的點,能夠發現一條由底到高的線,這說明,隨時間的推移,系統的堆空間被不斷佔滿,最終會佔滿整個堆空間。所以能夠初步認爲系統內部可能有內存泄漏。(上面的圖僅供示例,在實際狀況下收集數據的時間須要更長,好比幾個小時或者幾天)
解決:
這種方式解決起來也比較容易,通常就是根據垃圾回收先後狀況對比,同時根據對象引用狀況(常見的集合對象引用)分析,基本均可以找到泄漏點。
持久代被佔滿
異常:java.lang.OutOfMemoryError: PermGen space
說明:
Perm空間被佔滿。沒法爲新的class分配存儲空間而引起的異常。這個異常之前是沒有的,可是在Java反射大量使用的今天這個異常比較常見了。主要緣由就是大量動態反射生成的類不斷被加載,最終致使Perm區被佔滿。
更可怕的是,不一樣的classLoader即使使用了相同的類,可是都會對其進行加載,至關於同一個東西,若是有N個classLoader那麼他將會被加載N次。所以,某些狀況下,這個問題基本視爲無解。固然,存在大量classLoader和大量反射類的狀況其實也很少。
解決:
-XX:MaxPermSize=16m
換用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來減小分配給單個線程的空間,也能夠增長系統總共內生產的線程數。
解決:
從新設計系統減小線程數量。
線程數量不能減小的狀況下,經過-Xss減少單個線程大小。以便能生產更多的線程。
本質上是減小GC的次數。
若是是頻繁建立對象的應用,能夠適當增長新生代大小。常量較多能夠增長持久代大小。對於單例較多的對象能夠增長老生代大小。好比spring應用中。
GC選擇,在JDK5.0之後,JVM會根據當前系統配置進行判斷。通常執行-Server命令即可以。gc包括三種策略:串行,並行,併發。
吞吐量大大應用,通常採用並行收集,開啓多個線程,加快gc的是否。
響應速度高的應用,通常採用併發收集,好比應用服務器。
年老代建議配置爲併發收集器,因爲併發收集器不會壓縮和整理磁盤碎片,所以建議配置:
-XX:+UseConcMarkSweepGC #併發收集年老代
-XX:CMSInitiatingOccupancyFraction=80 # 表示年老代空間到80%時就開始執行CMS
-XX:+UseCMSCompactAtFullCollection # 打開對年老代的壓縮。可能會影響性能,可是能夠消除內存碎片。
-XX:CMSFullGCsBeforeCompaction=10 # 因爲併發收集器不對內存空間進行壓縮、整理,因此運行一段時間之後會產生「碎片」,使得運行效率下降。此參數設置運行次FullGC之後對內存空間進行壓縮、整理。