本文主要介紹JVM和GC解析
本文較長,分爲上下篇(可收藏,勿吃塵)
若有須要,能夠參考
若有幫助,不忘 點贊 ❥java
一文理清JVM和GC上篇
linux
StackOverflowError
public static void main(String[] args) {
stackOverflowError(); //Exception in thread "main" java.lang.StackOverflowError
}
private static void stackOverflowError() {
stackOverflowError();
}
複製代碼
OutOfMemeoryError:java heap space
public static void main(String[] args) {
String str = "cbuc";
for (; ; ) {
str += str + UUID.randomUUID().toString().substring(0,5); //+= 不斷建立對象
}
}
複製代碼
OutOfMemeoryError:GC overhead limit exceeded
程序在垃圾回收上花費了98%的時間,卻收集不會2%的空間。
加入不拋出GC overhead limit ,會形成:
1. GC清理的一點點內存很快會再次填滿,迫使GC再次執行,這樣就造成了惡性循環。
2. CPU的使用率一直是100%,而GC卻沒有任何成果ios
OutOfMemeoryError:Direct buffer memory
- 寫NIO程序常用 ByteBuffer 來讀取或者寫入數據,這是一種基於通道(Channel)和緩衝區(Buffer)的 I/O 方式,它可使用 Native 函數庫直接分配堆外內存,而後經過一個存儲在Java 堆裏面的DirectByteBuffer 對象做爲這塊內存的引用進行操做。這樣能在一些場景中顯著提升性能,由於避免了在Java堆和Native堆中來回複製數據。
- ByteBuffer.allocate(capability) :這一種方式是分配JVM堆內存,屬於GC管轄範圍,因爲須要拷貝因此速度相對較慢。
- ByteBuffer.allocateDirect(capability):這一種方式是分配OS本地內存,不屬於GC管轄範圍,因爲不須要內存拷貝,因此速度相對較快。
- 可是若是不斷分配本地內存,堆內存不多使用,那麼JVM就不須要執行GC,DirectByteBuffer 對象就不會被回收,這時候堆內存充足,但本地內存可能就已經使用光了,再次嘗試分配本地內存就會出現OutOfMemeoryError,那程序就直接奔潰了。
public static void main(String[] args) {
/**
* 虛擬機配置參數
* -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
* */
System.out.println("配置的maxDirectMemeory:"+(sun.misc.VM.maxDirectMemory()/(double)1024/1024)+"MB");
try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}
// -XX:MaxDerectMemorySize=5m 配置爲5m, 這個時候咱們使用6m
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(6*1024*1024);
}
複製代碼
OutOfMemeoryError:unable to create new native thread
高併發請求服務器是,常常會出現該異常
致使緣由web
- 你的應用建立了太多線程了,一個應用進程建立多個線程,超過系統承載權限。
- 你的服務器並不容許你的應用程序建立這麼多線程,linux系統默認容許的那個進程能夠建立的線程數時1024個,你的應用建立超過這個數量就會報OutOfMemeoryError:unable to create new native thread
解決辦法算法
- 想方法減低你應用程序建立線程的數量,分析應用是否真的須要建立那麼多線程,若是不是,改代碼將線程數降到最低。
- 對於有點應用,確實須要建立不少線程,遠超過linux系統默認1024個線程的限制,能夠經過修改linux服務器配置,擴大linux默認限制
public static void main(String[] args) {
for (int i = 1; ; i++) {
System.out.println("輸出 i: " + i);
new Thread(()->{
try {TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);} catch (InterruptedException e) {e.printStackTrace();}
},"線程"+i).start();
}
}
複製代碼
OutOfMemeoryError:Metaspace
Java 8以後的版本使用Metaspace來替代永久代
Metaspace是方法區在HotSpot中的實現,它與持久帶最大的區別在於:Metespace並不在虛擬機內存中而是使用本地內存
永久代(java8 後被原空間Metaspace取代了)存放了如下信息:服務器
- 虛擬機加載的類信息
- 常量池
- 靜態常量
- 即時編譯後的代碼
- GC算法(引用計數/複製/標清/標整)是內存回收的方法,垃圾收集器就是算法的實現
- 目前爲止尚未完美的收集器出現,更加沒有萬能的收集器,只是針對具體應用最合適的收集器,進行分代收集
串行垃圾回收器(Serial)
它爲單線程環境設計而且只是用一個線程進行垃圾回收,會暫停全部的用戶線程。因此不適合服務器環境。並行垃圾回收器(parallel)
多個垃圾回收線程並行工做,此時用戶線程是暫停的,適用於科學計算/大數據處理等弱交互場景併發垃圾回收器(CMS)
用戶線程和垃圾收集線程同時執行(不必定是並行,可能交替執行),不須要停頓用戶線程,適用於對響應時間有要求的場景G1垃圾回收器
G1垃圾回收器將堆內存分割成不一樣的區域而後併發的對其進行垃圾回收
查看默認的垃圾收集器
java -XX:+PrintCommandLineFlags -version
網絡
默認的垃圾收集器
多線程
新生代
併發
一個單線程的收集器,在進行垃圾收集的時候,必須暫停其餘全部的工做線程知道它收集結束
JVM設置參數
-XX:+UseSerialGC開啓後會使用:Serial(Young區用)+Serial Old(Old區用的)收集器組合,表示:
新生代、老年代都會使用串行回收收集器,新生代使用複製算法,老年代使用標記-整理算法app
並行GC(ParNew)使用多線程進行垃圾回收,在垃圾收集時,會Stop-The-World暫停其餘全部工做的線程知道它收集結束
JVM設置參數
XX:+UseParNewGC 啓用 ParNew收集器,隻影響新生代的收集,不影響老年代。開啓上述參數後,會使用:ParNew (新生代區用)+Serial Old(老年代區用)策略,新生代使用複製算法,老年代使用標記-整理算法。
並行回收GC(Parallel)/(Parallel Scavenge)
關注點:
JVM設置參數
-XX:UseParallelGC 或 -XX:UseParallelOldGC(可互相激活),開啓後:新生代使用複製算法,老年代使用標記-整理算法。
老年代
ParallelScavenge
收集器,只能保證新生代的吞吐量優先,沒法保證總體的吞吐量。在JDK1.6以前(Parallel Scavenge+Serial Old)JVM設置參數
-XX:+UseParallelOldGC 開啓 Parallel Old收集器,設置該參數後,使用 新生代Parallel + 老年代Parallel Old 策略
併發標記清除GC(CMS)
優勢: 併發收集低停頓
缺點:
- 併發執行,對CPU資源壓力大:
因爲併發進行,CMS在收集與應用線程會同時會增長對堆內存的佔用,也就是說,CMS必需要在老年代堆內存用盡以前完成垃圾回收,不然CMS回收失敗時,將觸發擔保機制,串行老年代收集器將會以STW的方式進行一次GC,從而形成較大停頓時間。- 採用的標記清除算法會致使大量碎片:
標記清除算法沒法整理空間碎片,老年代空間會隨着應用時長被逐步耗盡,隨後將不得不經過擔保機制對堆內存進行壓縮。CMS也提供了參數-XX:CMSFulllGCsBeForeCompaction(默認0,即每次都進行內存整理)來指定多少次CMS收集以後,進行一次壓縮的Full GC。
關鍵4步:
1.Initial Mark (初始標記): 標記GC Root能夠直達的對象,耗時短。
- Concurrent Mark(並行標記): 從第一步標記的對象出發,併發地標記可達對象。
- Remark(從新標記): 從新進行標記,修正Concurrent Mark期間因爲用戶程序運行而致使對象間的變化及新建立的對象,耗時短。
- Concurrent Sweep(並行回收): 並行地進行無用對象的回收。
![]()
-.-
如何選擇垃圾收集器
之前垃圾收集器的特色
1.年輕代和老年代是各自獨立且連續的內存塊
2.年輕代中Eden+S0+S1使用複製算法進行收集
3.老年代收集必須掃描整個老年代區域
4.都是以儘量少而快速地執行GC爲設計原則
G1概念
Garbage-First 收集器,是一款面向服務端應用的收集器
- 整理空閒空間更快
- 須要更多的時間來預測GC停頓時間
- 不但願犧牲大量的吞吐性能
- 不須要更大的Java Heap
G1收集器的設計目標是取代CMS收集器
優點:
主要改變是Eden,Survivor和Tenured等內存區域再也不是連續的了,而是變成了一個個大小同樣的region,每一個region從1M到32M不等。一個region有可能屬於Eden,Survivor或者Tenured內存區域。
G1特色
G1底層原理
(1)Region區域化垃圾收集器
區域化內存劃片Region,總體編爲了一下列不連續的內存區域,避免了全內存區的GC操做。
核心思想: 將整個堆內存區域分紅大小相同的子區域(Region),在JVM啓動時會自動配置這些子區域的大小。
在堆的使用上,G1並不要求對象的存儲必定是物理上連續的只要邏輯上連續便可,每一個分區也不會固定地爲某個代服務,能夠按需在年輕代和老年代之間切換。啓動時能夠經過參數 -XX:G1HeapRegionSize=n 可指定分區大小(1MB~32MB,且必須是2的冪),默認將整堆劃分爲2048個分區。
大小範圍在1MB~32MB,最多能設置2048個區域,也即可以支持的最大內存爲:32MB*2048=65536MV=64G內存
最大好處就是化整爲零,避免全內存掃描,只須要按照區域來進行掃描便可
(2)回收步驟
針對Eden區進行收集,Eden區耗盡後會被觸發,主要是小區域收集+造成連續的內存塊,避免內存碎片
(3)4步過程
(4)經常使用配置參數
開啓G1垃圾收集器
設置G1區域的大小。值是2的冪,範圍是1M到32M。目標是根據最小的Java堆大小劃分出約2048個區域
最大停頓時間,這是個軟目標,JVM將盡量(但不保證)停頓時間小於這個時間
堆佔用了多少的時候就觸發GC,默認是45
併發GC使用的線程數
設置做爲空閒時間的預留內存百分比,以下降目標空間溢出的風險,默認值是10%
(5)與CMS相比的優點
1)G1不會產生內存碎片。
2)是能夠精確控制停頓,該收集器是把整個堆(新生代、老年代)劃分紅多個固定大小的區域,每次根據容許停頓的時間去收集垃圾最多的區域。
(6)總結
17:16:47:
當前時間up 23:47:
系統運行時間2 users:
當前登陸用戶數load average:0.21,0.27,0.19:
系統負載,既任務隊列的平均長度,三個數值分別爲1分鐘、5分鐘、15分鐘前到如今的平均值 procs
cpu
內存充足
須要增長內存
內存基本夠用
-o:該參數是用戶自定義格式
-p:pid進程使用cpu的時間
-m : 顯示全部線程
- 步驟4:
將須要的線程ID轉換爲16進制格式(英文小寫格式)
再使用:printf "%x/\n" 有問題的線程ID- 步驟5:
jstat 進程ID | grep tid(16進制線程ID小寫英文)
本文較長,能看到這裏的都是好樣的,成長之路學無止境
今天的你多努力一點,明天的你就能少說一句求人的話!好久好久以前,有個傳說,聽說:
看完不讚,都是壞蛋