.1. java監控工具使用java
.1.1. jconsole算法
jconsole是一種集成了上面全部命令功能的可視化工具,能夠分析jvm的內存使用狀況和線程等信息。數據庫
啓動jconsole數組
經過JDK/bin目錄下的「jconsole.exe」啓動Jconsole後,將自動搜索出本機運行的全部虛擬機進程,不須要用戶使用jps來查詢了,雙擊其中一個進程便可開始監控。也能夠「遠程鏈接服務器,進行遠程虛擬機的監控。」安全
概覽頁面服務器
概述頁面顯示的是整個虛擬機主要運行數據的概覽。多線程
.1.2. jvisualvm併發
提供了和jconsole的功能相似,提供了一大堆的插件。eclipse
插件中,Visual GC(可視化GC)仍是比較好用的,可視化GC能夠看到內存的具體使用狀況。jvm
.2. java內存模型
.2.1. 內存模型圖解
Java虛擬機在執行Java程序的過程當中,會把它所管理的內存劃分爲若干個不一樣的數據區。這些區域有各自的用途,以及建立和銷燬的時間,有的區域隨着虛擬機進程的啓動而存在,有的區域則依賴用戶線程的啓動和結束而創建和銷燬,咱們能夠將這些區域統稱爲Java運行時數據區域。
以下圖是一個內存模型的關係圖(詳情見圖:內存劃分.png):
如上圖所示,Java虛擬機運行時數據區域被分爲五個區域:堆(Heap)、棧(Stack)、本地方法棧(Native Stack)、方法區(Method Area)、程序計數器(Program Count Register)。
.2.2. 堆(Heap)
對於大多數應用來講,Java Heap是Java虛擬機管理的內存的最大一塊,這塊區域隨着虛擬機的啓動而建立。在實際的運用中,咱們建立的對象和數組就是存放在堆裏面。若是你據說線程安全的問題,就會很明確的知道Java Heap是一塊共享的區域,操做共享區域的成員就有了鎖和同步。
與Java Heap相關的還有Java的垃圾回收機制(GC),Java Heap是垃圾回收器管理的主要區域。程序猿所熟悉的新生代、老生代、永久代的概念就是在堆裏面,如今大多數的GC基本都採用了分代收集算法。若是再細緻一點,Java Heap還有Eden空間,From Survivor空間,To Survivor空間等。
Java Heap能夠處於物理上不連續的內存空間中,只要邏輯上是連續的便可。
.2.3. 棧(Stack)
相對於Java Heap來說,Java Stack是線程私有的,她的生命週期與線程相同。Java Stack描述的是Java方法執行時的內存模型,每一個方法執行時都會建立一個棧幀(Stack Frame)用語存儲局部變量表、操做數棧、動態連接、方法出口等信息。從下圖從能夠看到,每一個線程在執行一個方法時,都意味着有一個棧幀在當前線程對應的棧幀中入棧和出棧。
圖中能夠看到每個棧幀中都有局部變量表。局部變量表存放了編譯期間的各類基本數據類型,對象引用等信息。
.2.4. 本地方法棧(Native Stack)
本地方法棧(Native Stack)與Java虛擬機站(Java Stack)所發揮的做用很是類似,他們之間的區別在於虛擬機棧爲虛擬機棧執行java方法(也就是字節碼)服務,而本地方法棧則爲使用到Native方法服務。
.2.5. 方法區(Method Area)
方法區(Method Area)與堆(Java Heap)同樣,是各個線程共享的內存區域,它用於存儲虛擬機加載的類信息,常量,靜態變量,即時編譯器編譯後的代碼等數據。雖然Java虛擬機規範把方法區描述爲堆的一個邏輯部分,可是她卻有一個別名叫作非堆(Non-Heap)。分析下Java虛擬機規範,之因此把方法區描述爲堆的一個邏輯部分,應該以爲她們都是存儲數據的角度出發的。一個存儲對象數據(堆),一個存儲靜態信息(方法區)。
在上文中,咱們看到堆中有新生代、老生代、永久代的描述。爲何咱們將新生代、老生代、永久代三個概念一塊兒說,那是由於HotSpot虛擬機的設計團隊選擇把GC分代收集擴展至方法區,或者說使用永久代來實現方法區而已。這樣HotSpot的垃圾收集器就能想管理Java堆同樣管理這部份內存。簡單點說就是HotSpot虛擬機中內存模型的分代,其中新生代和老生代在堆中,永久代使用方法區實現。根據官方發佈的路線圖信息,如今也有放棄永久代並逐步採用Native Memory來實現方法區的規劃,在JDK1.7的HotSpot中,已經把本來放在永久代的字符串常量池移出。
.2.6. 總結
一、 線程私有的數據區域有:
Java虛擬機棧(Java Stack)
本地方法棧(Native Stack)
二、 線程共有的數據區域有:
堆(Java Heap)
方法區
.3. GC算法
.3.1. 標記-清除算法(Mark-Sweep)
一、標記出全部須要回收的對象,在標記完成後統一回收全部被標記的對象
二、在標記完成後統一回收全部被標記的對象
缺點:一個是效率問題,標記和清除兩個過程的效率都不高;
另外一個是空間問題,標記清除以後會產生大量不連續的內存碎片,空間碎片太多可能會致使之後在程序運行過程當中
須要分配較大對象時,沒法找到足夠的連續內存而不得不提早觸發另外一次垃圾收集動做。
.3.2. 複製算法(Copying)
一、將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊。
二、當這一塊的內存用完了,就將還存活着的對象複製到另一塊上面,而後再把已使用過的內存空間一次清理掉。
優勢:這樣使得每次都是對整個半區進行內存回收,內存分配時也就不用考慮內存碎片等
複雜狀況,只要移動堆頂指針,按順序分配內存便可,實現簡單,運行高效。只是這種算法的代價是將內存縮小爲
了原來的一半,未免過高了一點。
缺點:複製收集算法在對象存活率較高時就要進行較多的複製操做,效率將會變低
.3.3. 標記-整理算法(Mark-Compact)
一、標記
二、讓全部存活的對象都向一端移動,而後直接清理掉端邊界之外的內存
.3.4. 分代收集算法(Generational Collection)
一、根據對象存活週期的不一樣將內存劃分爲幾塊。
二、通常是把Java堆分爲新生代和老年代,這樣就能夠根據各個年代的特色採用最適當的收集算法。
三、在新生代中,每次垃圾收集時都發現有大批對象死去,只有少許存活,那就選用複製算法,只須要付出少許存活對象的複製成本就能夠完成收集。
四、老年代中由於對象存活率高、沒有額外空間對它進行分配擔保,就必須使用「標記—清理」或者「標記—整理」算法來進行回收。
.4. 垃圾回收器
.4.1. Serial收集器:
一、是一個單線程的收集器,「Stop The World」
二、對於運行在Client模式下的虛擬機來講是一個很好的選擇
四、簡單而高效
.4.2. Serial Old收集器
一、Serial收集器的老年代版本,它一樣是一個單線程收集器,使用「標記-整理」算法。
二、主要意義也是在於給Client模式下的虛擬機使用。
三、若是在Server模式下,那麼它主要還有兩大用途:
一種用途是在JDK 1.5以及以前的版本中與Parallel Scavenge收集器搭配使用[1],
另外一種用途就是做爲CMS收集器的後備預案,在併發收集發生Concurrent Mode Failure時使用。
.4.3. ParNew收集器
一、Serial收集器的多線程版本
二、單CPU不如Serial
三、Server模式下新生代首選,目前只有它能與CMS收集器配合工做
四、使用-XX:+UseConcMarkSweepGC選項後的默認新生代收集器,也可使用-XX:+UseParNewGC選項來強制指定它。
五、-XX:ParallelGCThreads:限制垃圾收集的線程數。
.4.4. Parallel Scavenge收集器
一、吞吐量優先」收集器
二、新生代收集器,複製算法,並行的多線程收集器
三、目標是達到一個可控制的吞吐量(Throughput)。
四、吞吐量=運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間),虛擬機總共運行了100分鐘,其中垃圾收集花掉1分鐘,那吞吐量就是99%。
五、兩個參數用於精確控制吞吐量:
-XX:MaxGCPauseMillis是控制最大垃圾收集停頓時間
-XX:GCTimeRatio直接設置吞吐量大小
-XX:+UseAdaptiveSizePolicy:動態設置新生代大小、Eden與Survivor區的比例、晉升老年代對象年齡
六、並行(Parallel):指多條垃圾收集線程並行工做,但此時用戶線程仍然處於等待狀態。
七、併發(Concurrent):指用戶線程與垃圾收集線程同時執行(但不必定是並行的,可能會交替執行),用戶
程序在繼續運行,而垃圾收集程序運行於另外一個CPU上。
.4.5. Parallel Old收集器
一、Parallel Scavenge收集器的老年代版本,使用多線程和「標記-整理」算法。
二、在注重吞吐量以及CPU資源敏感的場合,均可以優先考慮Parallel Scavenge加Parallel Old收集器。
CMS收集器一款優秀的收集器
一、以獲取最短回收停頓時間爲目標的收集器。
二、很是符合互聯網站或者B/S系統的服務端上,重視服務的響應速度,但願系統停頓時間最短的應用
三、基於「標記—清除」算法實現的
四、CMS收集器的內存回收過程是與用戶線程一塊兒併發執行的
五、它的運做過程分爲4個步驟,包括:
初始標記,「Stop The World」,只是標記一下GC Roots能直接關聯到的對象,速度很快
併發標記,併發標記階段就是進行GC RootsTracing的過程
從新標記,Stop The World」,是爲了修正併發標記期間因用戶程序繼續運做而致使標記產生變更的那一部分對象的標記記錄,但遠比並發標記的時間短
併發清除(CMS concurrent sweep)
六、優勢:併發收集、低停頓
七、缺點:
對CPU資源很是敏感。
沒法處理浮動垃圾,可能出現「Concurrent Mode Failure」失敗而致使另外一次Full GC的產生。
一款基於「標記—清除」算法實現的收集器
G1(Garbage-First)收集器
一、當今收集器技術發展的最前沿成果之一
二、G1是一款面向服務端應用的垃圾收集器。
三、優勢:
並行與併發:充分利用多CPU、多核環境下的硬件優點
分代收集:不須要其餘收集器配合就能獨立管理整個GC堆
空間整合:「標記—整理」算法實現的收集器,局部上基於「複製」算法不會產生內存空間碎片
可預測的停頓:能讓使用者明確指定在一個長度爲M毫秒的時間片斷內,消耗在垃圾收集上的時間不得超過N毫秒
四、G1收集器的運做大體可劃分爲如下幾個步驟:
初始標記:標記一下GC Roots能直接關聯到的對象,須要停頓線程,但耗時很短
併發標記:是從GC Root開始對堆中對象進行可達性分析,找出存活的對象,這階段耗時較長,但可與用戶程序併發執行
最終標記:修正在併發標記期間因用戶程序繼續運做而致使標記產生變更的那一部分標記記錄
篩選回收:對各個Region的回收價值和成本進行排序,根據用戶所指望的GC停頓時間來制定回收計劃
垃圾收集器參數總結
收集器設置:
-XX:+UseSerialGC:年輕串行(Serial),老年串行(Serial Old)
-XX:+UseParNewGC:年輕並行(ParNew),老年串行(Serial Old)
-XX:+UseConcMarkSweepGC:年輕並行(ParNew),老年串行(CMS),備份(Serial Old)
-XX:+UseParallelGC:年輕並行吞吐(Parallel Scavenge),老年串行(Serial Old)
-XX:+UseParalledlOldGC:年輕並行吞吐(Parallel Scavenge),老年並行吞吐(Parallel Old)
收集器參數:
-XX:ParallelGCThreads=n:設置並行收集器收集時使用的CPU數。並行收集線程數。
-XX:MaxGCPauseMillis=n:設置並行收集最大暫停時間
-XX:GCTimeRatio=n:設置垃圾回收時間佔程序運行時間的百分比。公式爲1/(1+n)
-XX:+CMSIncrementalMode:設置爲增量模式。適用於單CPU狀況。
-XX:ParallelGCThreads=n:設置併發收集器年輕代收集方式爲並行收集時,使用的CPU數。並行收集線程數。
服務器垃圾回收器通常用G1或者CMS
演示堆內存溢出:
在eclipse經過run configurations配置下列參數
VM Args:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=c:/dump.txt
參數-XX:+HeapDumpOnOutOfMemoryError可讓虛擬機在出現堆內存溢出異常時Dump出當前的內存堆轉儲快照以便過後進行分析,文件在 c:/dump.txt中
.5. JVM參數列表[dht1]
java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0 -Xmx3550m:最大堆內存爲3550M。 -Xms3550m:初始堆內存爲3550m。 此值能夠設置與-Xmx相同,以免每次垃圾回收完成後JVM從新分配內存。 -Xmn2g:設置年輕代大小爲2G。 整個堆大小=年輕代大小 + 年老代大小 + 持久代大小。持久代通常固定大小爲64m,因此增大年輕代後,將會減少年老代大小。此值對系統性能影響較大,Sun官方推薦配置爲整個堆的3/8。 -Xss128k:設置每一個線程的堆棧大小。 JDK5.0之後每一個線程堆棧大小爲1M,在相同物理內存下,減少這個值能生成更多的線程。可是操做系統對一個進程內的線程數仍是有限制的,不能無限生成,經驗值在 3000~5000左右。 -XX:NewRatio=4:設置年老代與年輕代(包括Eden和兩個Survivor區)的比值(除去持久代)。設置爲4,則年輕代與年老代所佔比值爲1:4,年輕代佔整個堆棧的1/5 -XX:SurvivorRatio=4:設置年輕代中Eden區與Survivor區的大小比值。 設置爲4,則兩個Survivor區與一個Eden區的比值爲2:4,一個Survivor區佔整個年輕代的1/6 -XX:MaxPermSize=16m:設置持久代大小爲16m。 -XX:MaxTenuringThreshold=15:設置垃圾最大年齡。 若是設置爲0的話,則年輕代對象不通過Survivor區,直接進入年老代。對於年老代比較多的應用,能夠提升效率。若是將此值設置爲一個較大值,則年輕代對象會在Survivor區進行屢次複製,這樣能夠增長對象再年輕代的存活時間,增長在年輕代即被回收的概論。
收集器設置 -XX:+UseSerialGC:設置串行收集器 -XX:+UseParallelGC:設置並行收集器 -XX:+UseParalledlOldGC:設置並行年老代收集器 -XX:+UseConcMarkSweepGC:設置併發收集器 垃圾回收統計信息 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:filename 並行收集器設置 -XX:ParallelGCThreads=n:設置並行收集器收集時使用的CPU數。並行收集線程數。 -XX:MaxGCPauseMillis=n:設置並行收集最大暫停時間 -XX:GCTimeRatio=n:設置垃圾回收時間佔程序運行時間的百分比。公式爲1/(1+n) 併發收集器設置 -XX:+CMSIncrementalMode:設置爲增量模式。適用於單CPU狀況。 -XX:ParallelGCThreads=n:設置併發收集器年輕代收集方式爲並行收集時,使用的CPU數。並行收集線程數。 |
.6. jvm案例演示
內存:
內存標籤至關於可視化的jstat命令,用於監視收集器管理的虛擬機內存(java堆和永久代)的變化趨勢。
咱們經過下面的一段代碼體驗一下它的監視功能。運行時設置的虛擬機參數爲:-Xms100m -Xmx100m -XX:+UseSerialGC,這段代碼的做用是以64kb/50毫秒的速度往java堆內存中填充數據。
public class TestMemory { static class OOMObject { public byte[] placeholder = new byte[64 * 1024]; }
public static void fillHeap(int num) throws Exception { ArrayList<OOMObject> list = new ArrayList<OOMObject>(); for (int i = 0; i < num; i++) { Thread.sleep(50); list.add(new OOMObject()); } System.gc(); }
public static void main(String[] args) throws Exception { fillHeap(1000); Thread.sleep(500000); } } |
從圖中能夠看出,運行軌跡成曲線增加,循環1000次後,雖然整個新生代Eden和Survivor區都基本上被清空了,可是老年代仍然保持峯值狀態,這說明,填充的數據在GC後仍然存活,由於list的做用域沒有結束。若是把System.gc();移到fillHeap(1000);後,就能夠所有回收掉。
線程:
線程至關於可視化了jstack命令,遇到線程停頓時,可使用這個也籤進行監控分析。線程長時間停頓的主要緣由有:等待外部資源(數據庫鏈接等),死循環、鎖等待。下面的代碼將演示這幾種狀況:
package cn.java.jvm;
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader;
public class TestThread { /** * 死循環演示 * * @param args */ public static void createBusyThread() { Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println("createBusyThread"); while (true) ; } }, "testBusyThread"); thread.start(); }
/** * 線程鎖等待 * * @param args */ public static void createLockThread(final Object lock) { Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println("createLockThread"); synchronized (lock) { try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } }
} }, "testLockThread"); thread.start(); }
public static void main(String[] args) throws Exception { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); br.readLine(); createBusyThread(); br.readLine(); Object object = new Object(); createLockThread(object); } } |
main線程:追蹤到須要鍵盤錄入
testBusyThread線程:線程阻塞在18行的while(true),直到線程切換,很耗性能
testLockThread線程:出於waitting狀態,等待notify
死鎖:
package cn.java.jvm;
public class TestDeadThread implements Runnable { int a, b;
public TestDeadThread(int a, int b) { this.a = a; this.b = b; }
@Override public void run() { System.out.println("createDeadThread"); synchronized (Integer.valueOf(a)) { synchronized (Integer.valueOf(b)) { System.out.println(a + b); } } }
public static void main(String[] args) { for (int i = 0; i < 100; i++) { new Thread(new TestDeadThread(1, 2)).start(); new Thread(new TestDeadThread(2, 1)).start(); } } } |
點擊檢查死鎖,會出現死鎖的詳情。
thread-5的鎖被thread-10持有,相反亦是,形成死鎖。