什麼是垃圾?java
內存中已經再也不使用到的空間就是垃圾linux
要進行垃圾回收,如何判斷一個對象是否能夠被回收ios
引用計數法:java中,引用和對象是由關聯的。若是要操做對象則必須用引用進行。算法
所以很顯然一個簡單的辦法是經過引用計數來判斷一個對象是否能夠回收,簡單說,給對象中添加一個引用計數器,每當有一個地方引用它,計數器加1,每當有一個引用失效時,計數器減1,任什麼時候刻計數器數值爲零的對象就是不可能再被使用的,那麼這個對象就是可回收對象。緩存
可是它很難解決對象之間相互循環引用的問題服務器
JVM通常不採用這種實現方式。網絡
==枚舉根節點作可達性分析(跟搜索路徑)==:多線程
爲了解決引用計數法的循環引用問題,java使用了可達性分析的方法。併發
所謂GC ROOT或者說Tracing GC的「根集合」就是一組比較活躍的引用。負載均衡
基本思路就是經過一系列「GC Roots」的對象做爲起始點,從這個被稱爲GC Roots 的對象開始向下搜索,若是一個對象到GC Roots沒有任何引用鏈相連時,則說明此對象不可用。也即給定一個集合的引用做爲根出發,經過引用關係遍歷對象圖,能被便利到的對象就被斷定爲存活;沒有被便利到的就被斷定爲死亡
如何查看運行中程序的JVM信息
jps查看進程信息
jinfo -flag 配置項 進程號
jinfo -flags 進程號 ==== 查看全部配置
標配參
-version
-help
各個版本之間穩定,不多有很大的變化
x參數
-Xint
-Xcomp
-Xmixed
==xx參數==
Boolean類型
公式:-XX+或者-某個屬性值
+表示開啓,-表示關閉
是否打印GC收集細節 -XX:+PrintGCDetails 開啓 -XX:-PrintGCDetails 關閉
是否使用串行垃圾回收器:-XX:-UseSerialGC
KV設值類型
公式:-XX:屬性key=屬性值value
case:
-XX:MetaspaceSize=128m
-XX:MaxTenuringThreshold=15
-Xms----> -XX:InitialHeapSize
-Xmx----> -XX:MaxHeapSize
-XX:+PrintFlagsInitial
查看初始默認
==java -XX:+PrintFlagsInitial -version==
-XX:+PrintFlagsFinal
查看修改後的 :=
說明是修改過的
-XX:+PrintCommandLineFlags
查看使用的垃圾回收器
-Xms
-Xmx
-Xmn
-Xms128m -Xmx4096m -Xss1024K -XX:MetaspaceSize=512m -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseSerialGC
-Xms
初始大小內存,默認爲物理內存1/64,等價於-XX:InitialHeapSize
-Xmx
最大分配內存,默認物理內存1/4,等價於-XX:MaxHeapSize
-Xss
設置單個線程棧的大小,默認542K~1024K ,等價於-XX:ThreadStackSize
-Xmn
設置年輕代的大小
-XX:MetaspaceSize
設置元空間大小
元空間的本質和永久代相似,都是對JVM規範中方法區的實現,不過元空間與永久代最大的區別在於:==元空間並不在虛擬機中,而是在本地內存中。==所以,默認元空間的大小僅受本地內存限制
-XX:+PrintGCDetails
輸出詳細GC收集日誌信息
[名稱:GC前內存佔用->GC後內存佔用(該區內存總大小)]
-XX:SurvivorRatio
設置新生代中Eden和S0/S1空間的比例
默認-XX:SurvivorRatio=8,Eden:S0:S1=8:1:1
-XX:NewRatio
設置年輕代與老年代在堆結構的佔比
默認-XX:NewRatio=2 新生代在1,老年代2,年輕代佔整個堆的1/3
NewRatio值幾句詩設置老年代的佔比,剩下的1給新生代
-XX:MaxTenuringThreshold
設置垃圾的最大年齡
默認-XX:MaxTenuringThreshold=15
若是設置爲0,年輕代對象不通過Survivor區,直接進入年老代。對於年老代比較多的應用,能夠提升效率。若是將此值設置爲一個較大的值,則年輕代對象回在Survivor區進行屢次複製,這樣能夠增長對對象在年輕代的存活時間,增長在年輕代即被回收的機率。
-XX:+UseSerialGC
串行垃圾回收器
-XX:+UseParallelGC
並行垃圾回收器
當內存不足,JVM開始垃圾回收,對於強引用對象,就算出現了OOM也不會堆該對象進行回收。
強引用是咱們最多見的普通對象引用,只要還有強引用指向一個對象,就能表面對象還「或者」,垃圾收集器不會碰這種對象。在Java中最多見的就是強引用,把一個對象賦給一個引用變量,這個引用變量就是一個強引用。當一個對象被強引用變量引用時,它處於可達狀態,他是不可能被垃圾回收機制回收的,既是該對象之後永遠都不會被用到 JVM也不會回收。所以強引用時形成java內存泄漏的主要緣由之一。
對於一個普通對象,若是沒有其餘的引用關係,只要超過了引用的做用域或者顯式的將應用(強)引用複製爲null,通常認爲就是能夠被垃圾收集的了
軟引用是一種相對強引用弱化了一些做用,須要用java.lang.ref.SOftReference
類來實現,可讓對象豁免一些垃圾收集。
當系統內存充足時他不會被回收,當內存不足時會被回收。
軟引用一般在對內存敏感的程序中,好比高速緩存就有用到軟引用,內存足夠的時候就保留,不夠就回收。
弱引用須要用java.lang.ref.WeakReference類來實現,比軟引用的生存期更短
只要垃圾回收機制一運行,無論JVM的內存空間是否足夠,都會回收。
談談WeakHashMap
key是弱引用
顧名思義,就是形同虛設,與其餘集中不一樣,虛引用並不會決定對象的生命週期
若是一個對象持有虛引用,那麼他就和沒有任何引用同樣,在任什麼時候候均可能被垃圾回收器回收,他不能單獨使用也不能經過它訪問對象,虛引用和引用隊列(ReferenceQueeu)聯合使用。
需應用的主要做用是跟蹤對象被垃圾回收的狀態。僅僅是提供了一種確保ui想被finalize之後,作某些事情的機制。
PhantomReference的get方法老是返回null,所以沒法訪問對應的引用對象。其意義在於說明一個對象已經進入finalization階段,能夠被gc回收,用來實現比finalization機制更靈活的回收操做
換句話說,設置虛引用關聯的惟一目的,就是這個對象被收集器回收的時候收到一個系統通知或者後續添加進一步的處理。
java容許使用finalize()方法在垃圾收集器將對象從內存中清除出去以前作必要的清理工做。
建立引用的時候能夠指定關聯的隊列,當gc釋放對象內存的時候,會把引用加入到引用隊列,若是程序發現某個虛引用已經被加入到引用隊列,那麼就能夠在所引用的對象的內存被回收以前採起必要的行動,至關於通知機制
當關聯的引用隊列中有數據的時候,意味着引用指向的對內存中的對象被回收。經過這種方式,jvm容許咱們在對象被小回收,作一些咱們本身想作的事情。
加入一個應用須要讀取大量的本地圖片,若是每次讀取圖片都從硬盤讀取會嚴重影響性能,若是一次性所有加載到內存又可能形成內存溢出,這時能夠用軟引用解決這個問題
設計思路:用一個HashMap來保存圖片路徑和相應圖片對象關聯的軟引用之間的映射關係,在內存不足時,JVM會自動共回收這些緩存圖片對象所佔的空間,避免OOM
Map<String, SoftReference<Bitmap>> imageCache = new HashMap<>();
複製代碼
java.lang.StackOverflowError
棧空間溢出 ,遞歸調用卡死
java.lang.OutOfMemoryError:Java heap space
堆內存溢出 , 對象過大
java.lang.OutOfMemoryError:GC overhead limit exceeded
GC回收時間過長
過長的定義是超過98%的時間用來作GC而且回收了而不倒2%的堆內存
連續屢次GC,都回收了不到2%的極端狀況下才會拋出
若是不拋出,那就是GC清理的一點內存很快會被再次填滿,迫使GC再次執行,這樣就惡性循環,
cpu使用率一直是100%,二GC卻沒有任何成果
int i = 0;
List<String> list = new ArrayList<>();
try{
while(true){
list.add(String.valueOf(++i).intern());
}
}catch(Throwable e){
System.out.println("********");
e.printStackTrace();
throw e;
}
複製代碼
java.lang.OutOfMemoryError:Direct buffer memory
執行內存掛了
寫NIO程序常用ByteBuffer來讀取或寫入數據,這是一種基於通道(Channel)與緩存區(Buffer)的I/O方式,它可使用Native函數庫直接分配堆外內存,而後經過一個存儲在java堆裏面的DirectByteBuffer對象做爲這塊內存的引用進行操做,這樣能在一些場景中顯著提升性能,由於避免了在java堆和native堆中來回複製數據
ByteBuffer.allocate(capability) 第一種方式是分配JVM堆內存,屬於GC管轄,因爲須要拷貝因此速度較慢
ByteBuffer.alloctedDirect(capability)分配os本地內存,不屬於GC管轄,不須要拷貝,速度較快
但若是不斷分配本地內存,堆內存不多使用,那麼jvm就不須要執行GC,DirectByteBuffer對象們就不會被回收,這時候堆內存充足,但本地內存可能已經使用光了,再次嘗試分配本地內存救護i出現oom,程序崩潰
java.lang.OutOfMemoryError:unable to create new native thread
==好案例==
解決辦法
下降應用程序建立線程的數量,分析應用給是否針對須要這麼多線程,若是不是,減到最低
修改linux服務器配置
java.lang.OutOfMemoryError:Metaspace
元空間主要存放了虛擬機加載的類的信息、常量池、靜態變量、即時編譯後的代碼
static class OOMTest{}
public static void main(String[] args){
int i = 0;
try{
while(true){
i++;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOMTest.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor(){
@Override
public Object intercept(Object o,Method method,Object[] objects, MethodProxy methodProxy)throws Throwable{
return methodProxy.invokeSuper(o,args);
}
});
enhancer.create();
}
} catch(Throwable e){
System.out.println(i+"次後發生了異常");
e.printStackTrace();
}
}
複製代碼
垃圾收集器就是算法的具體實現
Serial 串行回收
爲單線程換進該設計且只是用過一個線程進行垃圾回收,會暫停全部的用戶線程,不適合服務器環境
Paralle 並行回收
多個垃圾收集線程並行工做,此時用戶線程是暫停的,適用於科學計算/大數據處理首臺處理等弱交互場景
CMS 併發標記清除
用戶線程和垃圾手機線程同時執行(不必定是並行,可能交替執行),不須要停頓用戶線程,互聯網公司多用它,適用堆響應時間有要求的場景
G1
將堆內存分割城不一樣的區域而後併發的對其進行垃圾回收
ZGC
查看:java -XX:+PrintCommandLinedFlags -version
配置:
有七種:UseSerialGC
UseParallelGC
UseConcMarkSweepGC
UseParNewGC
UseParallelOldGC
UseG1GC
串行GC (Serial)/(Serial Copying)
串行收集器:Serial收集器 1:1
一個單線程的收集器,在進行垃圾收集的時候,必須暫停其餘全部的工做線程知道它收集結束。
串行收集器是最古老,最穩定以及效率最高的收集器,只使用一個線程去回收但其在進行垃圾收集過程當中可能會產生較長的停頓(Stop-The-World 狀態)。雖然在收集垃圾過程當中須要暫停全部其餘的工做線程,可是它簡單高效,==對於限定單個CPU環境來講,沒有線程交互的開銷能夠得到最高的單線程垃圾收集效率,所以Serial垃圾收集器依然是java虛擬機運行在Client模式下默認的新生代垃圾收集器。==
對應的JVM參數是:==-XX:+UseSerialGC==
==開啓後會使用:Serial(Young區)+Serial Old(Old區)的收集器組合==
表示:==新生代、老年代都會使用串行回收收集器,新生代使用複製算法,老年代使用標記整理算法==
DefNew ----- Tenured
並行GC (ParNew)
並行收集器 N:1
使用多線程進行垃圾回收,在垃圾收集時,會Stop-the-world暫停其餘全部的工做線程知道它收集結束。
==ParNew收集器其實就是Serial收集器新生代的並行多線程版本,==最多見的應用場景時配合==老年代的CMS GC工做==,其他的行爲和Serial收集器徹底同樣,ParNew垃圾收集器在垃圾收集過程成中童謠也要暫停全部其餘的工做線程,他是不少==java虛擬機運行在Server模式下新生代的默認垃圾收集器。==
經常使用對應JVM參數:==-XX:+UseParNewGC== 啓用ParNew收集器,隻影響新生代的收集,不影響老年代
==開啓後會使用:ParNew(Young區)+Serial Old(Old 區)的收集器組合==,新生代使用複製算法,老年代採用標記-整理算法
ParNew ------ Tenured
備註:
==-XX:ParallelGCThreads== 限制線程數量,默認開啓和CPU數目相同的線程數
並行回收GC (Parallel)/(Parallel Scavenge)
並行收集器 N:N
相似ParNew也是一個新生代垃圾收集器,使用複製算法,也是一個並行的多線程的垃圾收集器,俗稱吞吐量有限收集器。串行收集器在新生代和老年代的並行化。
他重點關注的是:
==-XX:ParallelGCThreads=數字N== 表示啓動多少個GC線程
cpu>8 n=5/8
cpu<8 n=實際個數
==-XX:+UseParallelGC==、==-XX:+UseParallelOldGC==
串行GC(Serial Old)/(Serial MSC)
並行GC(Parallel Old)/(Parallel MSC)
Parallel Scavenge的老年代版本,使用多線程的標記-整理算法,Parallel Old收集器在JDK1.6開始提供
1.6以前,新生代使用ParallelScavenge收集器只能搭配年老代的Serial Old收集器,只能保證新生代的吞吐量優先,沒法保證總體的吞吐量。在1.6以前(ParallelScavenge+SerialOld)
Parallel Old正式爲了在老年代一樣提供吞吐量遊戲的垃圾收集器,若是系統對吞吐量要求比較高,JDK1.8後能夠考慮新生代Parallel Scavenge和年老代Parallel Old收集器的搭配策略
經常使用參數:==-XX:+UseParallelOldGC==》》》》》新生代Paralle+老年代Paralle Old
併發標記清除GC(CMS)
CMS收集器(Concurrent Mark Sweep:併發標記清除)是一種以獲取最短回收停頓時間爲目標的收集器。
適合在互聯網站或者B/S系統的服務器上,這列應用尤爲中使服務器的響應速度,但願系統停頓時間最短。
CMS很是適合堆內存大、CPU核數多的服務區端應用,也是G1出現之大型應用的首選收集器。
併發標記清除收集器:ParNew+CMS+Serial Old
CMS,併發收集低停頓,併發指的是與用戶線程一塊兒執行
JVM參數:==-XX:+UseConcMarkSweepGC==,開啓該參數後會自動將-XX:UseParNewGC打開
開啓該參數後,使用ParNew+CMS+Serial Old的收集器組合,Serial Old將做爲CMS出錯的後備收集器
4步過程:
初始標記(CMS initial mark)
只是標記一下GC Roots可以直接關聯的對象,速度很快,仍然須要暫停全部的工做線程。
併發標記(CMS concurrent mark)和用戶線程一塊兒
進行GC Roots跟蹤過程,和用戶線程一塊兒工做,不須要暫停工做線程。主要標記過程,標記所有對象
從新標記(CMS remark)
爲了修正併發標記期間,因用戶程序繼續運行而致使標記產生變更的那一部分對象的標記記錄,仍然須要暫停全部的工做線程,因爲併發標記時,用戶線程依然運行,所以在正式清理前,再作修正
併發清除(CMS concurrent sweep)和用戶線程一塊兒
清除GC Roots不可達對象,和用戶線程一塊兒工做,不須要暫停工做線程。基於標記結果,直接清理對象
因爲耗時最長的併發標記和併發清除過程當中,垃圾收集線程能夠和用戶如今一塊兒併發工做,因此整體上看來CMS收集器的內存回收和用戶線程是一塊兒併發的執行。
優缺點:
併發收集停頓低
併發執行,cpu資源壓力大
因爲併發進行,CMS在收集與應用線程會同時增長對堆內存的佔用,也就是說,==CMS必需要在老年代堆內存用盡以前完成垃圾回收==,不然CMS回收失敗時,將出發擔保機制,串行老年代收集器將會以STW的方式進行一次GC,從而形成較大停頓時間。
採用的標記清除算法會致使大量的碎片
標記清除算法沒法整理空間碎片,老年代空間會隨着應用時長被逐步耗盡,最後將不得不經過擔保機制堆堆內存進行壓縮。CMS也提供了參數==-XX:CMSFullGCsBeForeCompaction==(默認0,即每次都進行內存整理)來制定多少次CMS收集以後,進行一次壓縮的FullGC。
單CPU或小內存,單機內存
-XX:+UseSerialGC
多CPU,須要最大吞吐量,如後臺計算型應用
-XX:+UseParallelGC -XX:+UseParallelOldGC
多CPU,最求低停頓時間,需快速相應,如互聯網應用
-XX:+ParNewGC -XX:+UseConcMarkSweepGC
參數 | 新生代垃圾收集器 | 新生代算法 | 老年代垃圾收集器 | 老年代算法 |
---|---|---|---|---|
UseSerialGC | SerialGC | 複製 | SerialOldGC | 標整 |
UseParNewGC | ParNew | 複製 | SerialOldGC | 標整 |
UseParallelGC UseParallelOldGC |
Parallel[Scavenge] | 複製 | Parallel Old | 標整 |
UseConcMarkSweepGC | ParNew | 複製 | CMS+Serial Old的收集器組合(Serial Old 做爲CMS出錯的後備收集器) | 標清 |
UseG1GC | G1總體上採用標整 | 局部是經過複製算法 |
將堆內存分割城不一樣的區域而後併發的對其進行垃圾回收
G1(Garbage-First)收集器,是一款面向服務端應用的收集器
應用在多處理器和大容量內存環境中,在實現高吞吐量的同時,儘量的知足垃圾收集暫停時間的要求
G1收集器的設計目標是取代CMS收集器,和CMS相比,在如下放木表現更出色:
CMS垃圾收集器雖然減小了暫停應用程序的運行時間,可是他仍是存在着內存碎片問題。因而爲了取出內存碎片問題,同時又保留CMS垃圾收集器低暫停時間的優勢,JAVA7發佈了G1垃圾收集器。
主要改變的是Eden,Survivor和Tenured等內存區域再也不是連續的了,而是變成了一個個大小同樣的region(區域化),每一個region從1M到32M不等。一個region有可能屬於Eden,Survivor或Tenured內存區域。
Region區域化垃圾收集器
最大好處是化整爲零,避免全內存掃描,只須要按照區域來進行掃描便可
區域化內存劃片Region,總體編爲了 一系列不連續的內存區域,避免了全內存區的GC操做。
核心思想是講整個堆內存區域分紅大小相同的子區域,在JVM啓動時會自動共設置這些子區域的大小
在堆的使用上,G1並不要求對象的存儲必定是物理上連續的,只要邏輯上連續便可,每一個分區也不會固定地爲某個代服務,能夠按需在年輕代和老年代之間切換。啓動時能夠經過參數==-XX:G1HeapRegionSize==可指定分區大小(1~32M,且必須是2的冪),默認將整堆劃分爲2048個分區。
大小範圍在1-32M,最多能設置2048個區域,也即可以支持的最大內存爲64G
G1算法將堆劃分爲諾幹個區域,他仍然屬於分代收集器
這些Region的一部分包含新生代,新生代的垃圾收集依然採用暫停全部應用線程的方式,將存活對象拷貝到老年代或Survivor空間,這些Region的一部分包含老年代,G1收集器經過將對象從一個區域複製到另一個區域,完成了清理工做。這就意味着,在正常的處理過程當中,G1完成了堆的壓縮,這樣也就不會有CMS內存碎片問題的存在了
在G1中,還有一種特殊區域,Humongous區域,若是一個對象張勇的空間超過了分區容量50%以上,G1收集器就認爲i這是一個巨型對象。這些巨型對象默認直接會被分配在年老代,可是若是他是一個短時間存在的巨型對象,就會對垃圾收集器形成負面影響。爲了解決這個問題,G1劃分了一個Humongous區,他用來專門存放巨型對象。若是一個H區裝不下,那麼G1就會尋找連續的H分區來存儲。爲了能找到連續的H區,有時候不得不啓動Full GC
回收步驟
針對Eden區進行收集,Eden區耗盡後會被觸發,主要小區域收集+造成連續的內存塊,避免內存碎片
4步過程
-XX:+UseG1Gc
-XX:G1HeapRegionSize=n
設置的G1區域的大小,值是2的冪,範圍是1-32MB,目標是根據最小的java堆大小劃分出約2048個區域
-XX:MaxGCPauseMillis=n
最大GC停頓時間,這是個軟目標,JVM將盡量(但不保證)停頓小於這個時間
-XX:InitiatingHeapOccupancyRercent=n
堆佔用了多少的時候就觸發GC,默認45
-XX:ConcGcThreads=n
併發GC使用的線程數
-XX:G1ReservePercent=n
設置做爲空閒空間的預留內存百分比,以下降目標空間溢出的風險,默認10%
整機:top 系統性能
uptime 精簡版
load average:系統負載均衡 1min 5min 15min 系統的平均負載值 相加/3>60%壓力夠
CPU:vmstat
查看CPU
vmstat -n 2 3 第一個參數是採樣的時間間隔數單位s,第二個參數是採樣的次數
procs
r:運行和等待CPU時間片的進程數,原則上1核CPu的運行隊列不要超過2,真個系統的運行隊列不能超過總核數的2倍,不然表示系統壓力過大
b:等待資源的進程數,好比正在等待磁盤I/O,網絡I/O等
cpu
us:用戶進程消耗cpu時間百分比,us高,用戶進程消耗cpu時間多,若是長期大於50%,優化程序
sy:內核進程消耗的cpu時間百分比
us+sy:參考值爲80%,若是大於80,說明可能存在cpu不足
id:處於空閒的cpu百分比
wa:系統等待IO的cpu時間百分比
st:來自於一個虛擬機偷取的cpu時間的百分比
查看額外
內存:free
查看內存 free -m free -g
pidstat -p 進程編號 -r 採樣間隔秒數
硬盤:df
查看磁盤剩餘空間 df -h
磁盤IO:iostat
磁盤I/O性能評估
iostat -hdk 2 3
pidstat -d 採樣間隔秒數 -p 進程號
網絡IO:ifstat
ifstat l
結合Linux和JDK命令一塊分析
先用top命令找出cpu佔比最高的
ps -ef或者jps進一步定位,得知是一個怎樣的後臺程序惹事
定位到具體線程或者代碼
ps -mp 進程編號 -o Thread,tid,time
定位到具體線程
-m :顯示全部的線程
-p pid 進程使用cpu的時間
-o:該參數後是用戶自定義格式
將須要的線程ID轉換爲16禁止格式(英文小寫格式)
jstack 進程Id|grep tid(16進制線程id小寫英文) -A60
查看運行軌跡,堆棧異常信息