JVM內存問題分析

JVM內存佈局規定了Java在運行過程當中內存申請、分配、管理的策略,保證了JVM的高效穩定運行。
JVM運行時數據區:
一、方法區:類信息(類名,訪問修飾符、字段描述、方法 描述等)、常量、靜態變量、即時編譯後的class文件等。在GC時用永久代來實現方法區
二、運行時常量池:是方法區的一部分,存放編譯期生成的各類字面量和符號引用(字面量就是實際的值,如1,"abc",符號引用是不知道實際引用對象的實際地址而抽象
  出的一種引用)。
  字面量如:文本字符串,聲明爲final的常量值;符號引用包括了三種常量,分別是:類和接口的全限定名,字段的名稱和描述符,方法的名稱和修飾符
三、堆:存放對象的實例,數組內存在此分配(全部的對象實例和數組都在堆上分配),可能劃分出多個線程私有的分配緩衝區(Thread Local Allocation Buffer,TLAB)
四、棧:局部變量表(局部基本變量的值和引用類型在堆中的地址值,方法參數),方法的返回值等信息
 
問題:字符串在JVM中如何存放?
  一、使用字符串初始化的字符串對象,它的值存放在字符串常量池中
  二、使用字符串構造方法建立的字符串對象,它的值存放在堆內存中
java.lang.String.intern(),這個API能夠手動將一個字符串對象的值轉移到字符串常量池中
在1.7及以前,字符串常量池是在永久代,大小是固定的,也不能被垃圾回收器回收,若是有太多了字符換調用了intern方法的話,就有可能形成OOM。
1.8裏,字符串常量池移到了堆內存中,能夠被垃圾回收器回收下降了字符串常量池OOM的風險。
String str1="hello";
String str2="he"+new String("llo");
System.out.println(str1==str2);//false

一、兩個或者以上的字符串常量相加,【String str="s1"+"s2"】,在預編譯的時候「+」會被優化,至關於把兩個或者兩個以上字符串常量自動合成一個字符串常量html

二、字符串的+操做本質上是new了StringBuilder對象進行append操做,拼接後調用toString()返回String對象(可經過javap -c xxx.class查看字節碼指令)java

 Code:
       0: ldc           #10                 // String hello
       2: astore_1
       3: new           #11                 // class java/lang/StringBuilder
       6: dup
       7: invokespecial #12                 // Method java/lang/StringBuilder."<init>":()V
      10: ldc           #13                 // String he
      12: invokevirtual #14                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      15: new           #15                 // class java/lang/String
      18: dup
      19: ldc           #16                 // String llo
      21: invokespecial #17                 // Method java/lang/String."<init>":(Ljava/lang/String;)V
      24: invokevirtual #14                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      27: invokevirtual #18                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
 
 
直接內存:Direct Memory
Direct Memory容量可經過-XX:MaxDirectMemorySize指定,若是不指定則默認和Java堆的最大值-Xmx同樣。
不受到Java堆內存大小的限制,只會收到計算機總內存(包括RAM以及SWAP或者分頁文件)大小以及處理器尋址空間的限制,若直接內存和JVM各個區域佔用總內存大小超過物理內存限制則會出現OutOfMemoryError。
NIO:JDK1.4引入的一種基於通道channel和緩衝區Buffer的IO方式;它可使用Native函數庫直接分配堆外內存,而後經過一個存儲在Java堆中的DirectByteBuffer對象做爲這塊內存的引用進行操做,
避免了在Java堆和Native堆中來回複製數據,所以能在一些場景中顯著提升性能。
 
 
 
Java堆中對象分配,佈局和訪問過程
一、對象建立(普通Java對象,不包括數組和Class對象)
虛擬機遇到一條new指令時,首先去常量池中檢查這個指令的參數可否定位到一個類的符號引用,並檢查這個符號引用表明的類是否已經被加載,解析和初始化過,若是沒有先執行類的初始化過程。
 在類加載檢查經過後,對象所需的內存的大小在類加載完成後即可徹底肯定,接下來虛擬機將爲新生對象分配內存,即把一塊肯定大小的內存從堆中劃分出來。
(若堆中內存是規整的,即用過的和空閒的各放在一邊,指針指向分界點,內存分配即指針向空閒移動對象所需的內存大小。這時內存分配採用指針碰撞(bump the pointer)的方式
若堆中的內存是不規整的,虛擬機需維護一張列表記錄哪些內存塊是可用的,在分配的時候找到一塊足夠大的內存塊劃分給對象,並更新列表,這種方式稱爲空閒列表(free list))
所以使用哪一種方式取決於堆內存是否規整,是否規整又取決於採用的垃圾收集器是否帶有壓縮整理功能決定。所以採用Serial、ParNew採用的是指針碰撞,採用CMS收集器使用的是空閒列表。
給對象分配內存須要考慮線程安全的問題,須要同步進行,避免兩個對象分配一塊內存;解決這個問題有兩種方案:
一、對內存分配空間的動做進行同步處理-實際上虛擬機採用CAS配上失敗重試的方式保證更新的原子性
二、把內存分配的動做按照線程劃分在不一樣的空間之中進行,即每一個線程在Java堆中預先分配一小塊內存,稱爲本地線程分配緩衝(Thread Local Allocation Buffer,TLAB),線程先在TLAB上分配,只有TLAB用完分配新的TLAB時才須要同步鎖定。
內存分配完成後JVM將分配到的內存空間都初始化爲0值,而後進行必要的設置,類的元數據信息,對象的哈希碼,對象的GC分代年齡都存在對象頭中,最後進行init初始化。
 
 二、對象的內存佈局
在HotSpot 虛擬機中,對象在內存中的存儲被分紅了3塊區域:對象頭(Header)、實例數據(Instance Data)和對齊填充(Padding)
對象頭分紅兩部分:
一、用於存儲對象自身的運行時數據,如哈希碼、GC分代年齡、鎖狀態標誌、線程持有的鎖、偏向線程ID、偏向時間戳等
二、類型指針,即對象指向它的類元數據的指針。JVM經過這個指針來肯定這個對象是哪一個類的實例。若是是數組對象,對象頭中還需記錄數組的長度
實例數據:對象真正存儲的有效信息,便是代碼中定義的字段內容,包括從父類繼承下來的和自身定義的
對齊填充:並非必然存在的,僅僅起着佔位符的做用。JVM要求對象的大小必須是8字節的整數倍。
 
三、對象的訪問定位
創建對象是爲了使用對象,Java程序經過棧上的reference數據來操做堆上的具體對象。reference只是指向對象的一個引用,如何訪問定位對象在堆上的具體位置呢?
一、句柄:Java堆中劃分出一塊內存來做爲句柄池,reference中存儲的就是對象的句柄地址,句柄中包含了對象實例數據和類型數據各自的具體地址信息。
  好處是穩定:如垃圾收集時若對象被移動,只需修改句柄中的實例數據指針,reference無需修改。
二、直接指針:reference中存放的直接就是對象地址。好處是速度快,節省了一次指針定位的開銷,HotSpot就是經過這種方式訪問的。
 
四、Java對象模型
 
 
線程共享:堆,方法區(常量池)
線程私有:棧,程序計數器,本地方法棧
 
 
 
OutOfMemoryError
除了程序計數器外,其他的幾個運行數據區都有可能發生OutOfMemoryError(OOM)的可能。
所以在遇到OOM的問題時應能根據異常的信息快速定位到時哪一個內存區域的內存溢出,知道什麼樣的代碼會致使OOM,以及該如何處理。 
 
一、Java堆溢出
 
不停的建立對象,而且GC Roots到對象之間有可達路徑,超過堆的最大內存容量就會OOM
如,在while(true)循環中不停的建立對象並將對象add到List集合中
Java堆內存溢出時異常堆棧信息會在「java.lang.OutOfMemoryError」後提示Java heap space
如何定位?
設置參數-XX:+HeapDumpOnOutOfMemoryError 來配置當內存耗盡時記錄下當時的內存快照,通常根據MAT分析具體緣由是內存泄漏仍是內存溢出
內存泄漏:根據工具查看泄漏對象到GC Roots的引用鏈,找到泄漏對象時經過怎麼樣的路徑與GC Roots相關聯並致使垃圾收集器沒法自動回收它們的。根據對象類型和GC Roots引用鏈就能夠準肯定位到泄漏代碼的位置。
內存溢出:檢查虛擬機參數堆參數可否擴大,代碼上檢查是否存在某些對象生命週期過長、持有狀態時間過長。
 
二、虛擬機棧和本地方法棧溢出
HotSpot虛擬機不區分虛擬機棧和本地方法棧,所以設置本地方法棧的參數-Xoss其實是無效的,棧的參數只能根據-Xss設定
若是線程請求的棧深度超過了JVM容許的最大深度,將拋出StackOverflowError   
若是虛擬機在擴展棧時沒法申請到足夠的內存將跑出OutOfMemoryError
一、StackOverflowError   容易定位,多是遞歸沒有出口,正常的方法調用深度在1000-2000之間徹底沒有問題
二、OOM多是創建的線程數量很是多,每一個線程瓜分棧內存,當線程都存活着沒有足夠的內存去分配給線程時會拋出OOM
 
三、方法區和運行時常量池溢出
Java方法區和運行時常量池溢出異常堆棧信息會在「java.lang.OutOfMemoryError」後提示PermGen space
如String字符串存放在常量池中
 
四、本機直接內存溢出
Unsafe的allocateMemory能夠申請分配內存(Unsafe實例可經過反射獲取)
由DirectMemory致使的內存溢出在Heap Dump文件中看不出明顯的異常,若是OOM以後的Dump文件很小,而程序中直接或者間接使用了NIO,那多是這方面的緣由。
DirectMemory-XX:MaxDirectMemory指定,若是不指定默認與Java堆內存最大值同樣。
垃圾收集時,虛擬機 只有在Full GC時會順便回收DirectMemory中廢棄的對象。所以DirectMemory內存滿了以後,只有等待系統的下一次Full GC,或者拋出內存異常在catch中調用System.gc()
 
 

1、GC日誌分析linux

爲了在內存溢出時排查緣由,能夠在JVM啓動時加一些參數來控制,當JVM內存出問題時能夠經過分析記錄下來的GC日誌,GC的頻率和每次GC回收了哪些內存算法

GC的日誌輸入有如下參數sql

一、-verbose:gc  能夠輔助輸出一些詳細的GC信息數組

二、-XX:+PrintGCDetails  輸出GC的詳細信息瀏覽器

三、-XX:+PrintGCApplicationStoppedTime  輸出GC形成應用程序暫停的時間安全

四、-XX:+PrintGCDateStamps  輸出GC發生的時間信息服務器

五、-XX:PrintHeapAtGC  在GC先後輸出堆中各個區域的大小多線程

六、-Xloggc:[file]  將GC信息輸出到單獨的文件

 

每種GC方式輸出日誌的形式不一樣,除CMS的日誌和其餘GC方式差別較大外,其他GC方式的日誌能夠抽象成以下方式

[GC [<collector>: <starting occupancy1> -> <ending occupancy1> (total size1) , <pause time1> secs]

<starting occupancy2> -> <ending occupancy2> (total size2) , <pause time2> secs] ]

說明以下

一、<collectot>GC  表示垃圾收集器的名稱

二、<starting occupancy1>  表示Young區在GC前佔用的內存

三、<ending occupancy1>  表示Young區在GC後佔用的內存

四、(total size1)  表示Young區的總內存大小

五、<pause time1>   表示Young區局部收集時JVM暫停處理的時間 secs表示單位秒

六、<starting occupancy2>   表示Heap在GC前佔用的內存

七、<ending occupancy2>  表示Heap在GC後佔用的內存

八、(total size2)  表示Heap的總內存

九、<pause time2>  表示在GC過程當中JVM暫停處理的總時間

能夠根據日誌來判斷是否存在內存泄漏的問題:

<starting occupancy1> - <ending occupancy1>  和 <starting occupancy2> - <ending occupancy2> 比較

一、若是前者差等於後者差,代表Young區GC 對象100%被回收,沒有對象進入 Old區或者Perm區

二、若是前者大於後者,那麼差值就是此次GC對象進入Old或者Perm區的大小

若是隨着時間的的延長,<ending occupancy2>的大小一直在增加,並且Full GC很頻繁,那麼極可能就是內存泄漏致使的。

 

 

2、堆快照文件分析

一、經過命令   jmap -dump:format=b,file=[filename][pid]  jmap(Memory Map for Java)

來記錄下堆的內存快照,而後利用第三方工具如eclipse 插件MAT來分析整個Heap的對象關聯狀況。

若是內存耗盡可直接致使JVM退出,能夠經過參數

-XX:+HeapDumpOnOutOfMemoryError 來配置當內存耗盡時記錄下當時的內存快照

-XX:HeapDumpPath  指定內存快照文件的路徑  文件快照的名稱格式爲 java_[pid].hprof

若是是OOM,可能有兩方面的緣由

一、內存分配太小,不知足程序運行所須要的內存

二、內存泄漏(FullGC頻繁,回收後Heap佔用的內存不斷增加)

 

3、JVM Crash 日誌分析

TODO

 

 

垃圾收集器與內存分配策略

棧的內存隨着方法的結束和線程結束自動回收,所以Java堆和方法區是垃圾收集器所關注的內存

判斷對象是否能夠回收

一、 引用計數法:給對象中添加一個引用計數器,當有一個地方被引用時加1,引用失效減1,計數器爲0的就是能夠回收的,可是會有互相引用的狀況

二、可達性分析法   對象到一系列稱爲GC Roots的對象有沒有引用鏈相連

 

GCROOTS:
1.虛擬機棧中引用的對象
2.方法區靜態屬性引用的對象
3.方法區中常量引用的對象
4.本地方法棧中引用的對象(native方法)
 

即便在可達性分析法中不可達的對象,也至少要經歷兩次標記過程

第一次標記:可達性分析後無與GC Roots相連的引用鏈

第二次標記:第一次標記後篩選(finalize()方法沒有被JVM調用過)後放置在F-Queue隊列中,仍無引用鏈和GC Roots相連則進行第二次標記

方法區的收集:廢棄常量和無用的類

廢棄的常量:如常量池中的字符串常量「abc」,沒有String對象引用常量池的這個「abc」常量,那麼abc就是廢棄常量能夠移除常量池

無用的類:一、該類的實例都被回收  二、加載該類的ClassLoader已被回收  三、該類的Class對象沒有在任何地方被引用,也就是沒法經過反射訪問該類的方法

 

垃圾收集算法

一、複製

將內存劃分爲大小相等的兩塊,每次只使用其中一塊,當其中的一塊用完了將其上面存活的對象複製到另外一塊上面,而後把使用過的內存空間一次清理掉。缺點是將可用內存縮小爲了原來的一半,對象存活率較高時不適合使用。

新生代中的對象98%都是朝生夕死的,所以新生代按照8:1:1的比例分爲了eden,survivor from 和survivor to空間,每次回收將eden和survivor from中存活的對象複製到survivor to中,不夠的話再放到old中,而後將eden,survivor from一次清除掉。

二、標記-清除

首先標記須要回收的對象,在標記完成後統一回收  問題一、效率問題:標記和清除效率都不高 二、空間問題:清除後會產生大量內存碎片,過多的話會致使之後分配大對象如數組找不到一塊連續的內存而提早觸發一次GC 

三、標記-整理

首先標記須要回收的對象,而後將全部存活的對象向一側移動與將要回收的對象分隔開,而後將要回收的對象一次清理掉,適合用再老年代上。

分代收集

 新生代每次垃圾回收都有大量的對象死去少許存活,只需付出少許對象的複製成本便可完成收集。採用複製算法

老年代對象存活率高,沒有額外的空間作擔保, 只能採用標記-清除或者標記-整理算法

 新生代垃圾收集器:Serial、ParNew、ParallelScavenge、G1

老年代垃圾收集器:CMS、Serial Old(MSC)、Parallel Old、G1

垃圾收集器的發展,使用戶線程的停頓時間在不斷縮短,可是仍沒辦法徹底消除,所以尋找更優秀的垃圾收集器仍在繼續!

 

Serial收集器:單線程,採用複製算法,並且進行垃圾收集時,必須暫停JVM其餘全部的工做進程,直到它收集結束。還是Client模式下虛擬機新生代默認收集器

ParNew收集器:Serial的多線程版本,採用複製算法,其餘基本相同。是運行在server模式下的虛擬機首選的新生代收集器

Parallel Scavenge收集器:與其餘收集器關注點在縮短用戶線程停頓時間不一樣,它關注點是達到一個可控制的吞吐量,吞吐量=運行用戶代碼時間/(運行代碼時間+垃圾收集時間)如:JVM總運行100分鐘,垃圾收集1分鐘,那吞吐量=99%,若是新生代採用了此收集器,那老年代只能使用Serial Old收集器

Serial Old收集器:Serial收集器的老年代版本,一樣單線程,採用標記-整理算法,存在乎義是給Client客戶端JVM使用

Parallel Old收集器:Parallel Scavenge收集器的老年代版本,採用多線程和標記-整理算法

CMS收集器(Concurrent Mark Sweep):一種以獲取最短回收停頓時間爲目標的收集器,基於標記- 清除算法實現。(只會收集老年代和永久代,1.8後改成元空間(須要設置  CMSClassUnloadingEnabled)),不會收集年輕代

  一、初始標記  標記GCRoots直接關聯的對象

  二、併發標記  往下跟蹤標記全部與GCRoots有引用鏈可達的對象  (可與用戶線程同時工做),就是進行GCROOTS Tracing的過程

  三、從新標記  修正併發標記期間因用戶線程運行而致使的標記變更的一部分對象

  四、併發清除  清除未標記的對象  (可與用戶線程同時工做)

缺點:

  一、雖然在併發階段可與用戶線程同時工做,可是會佔用CPU資源,致使應用程序變慢,總吞吐量會下降

  二、沒法處理浮動垃圾,即在併發清除階段新產生的垃圾,只有留待下一次GC時再清理掉

  三、使用標記-清除算法,會有大量內存碎片產生

 

G1收集器(Garbage-First):特色:

  一、並行與併發:充分利用多CPU,多核環境的硬件優點,來縮短停頓時間,在GC期間可經過併發的方式讓Java程序繼續執行

  二、分代收集:採用不一樣的算法去收集剛建立的對象,存活了一段時間的對象和熬過屢次GC的對象,以獲取更好的收集效果

  三、空間整合:總體基於標記-整理算法,內部region之間採用複製算法,都不會產生內存空間碎片

  四、可預測的停頓:除了追求短期停頓外,還創建了可預測停頓模型,使在M毫秒內,在垃圾收集上的時間不超過N毫秒

  G1邏輯上將整個Java堆劃分爲多個大小相等的獨立區域(region)。仍保留新生代和老年代的概念,但它們之間不是物理隔離了,新生代和老年代都是一部分region的集合了。G1之因此

能創建可預測停頓模型,由於它能夠有計劃的避免在整個Java堆中進行全區域的垃圾收集,G1跟蹤各個Region裏面的垃圾堆積的價值大小(回收所得到的空間大小以及回收所需時間的最優),在後臺維護一個優先列表,每次根據容許的收集時間,優先回收價值最大的region,這也是Garbage-First的由來。這種使用region劃份內存空間以及有優先級的區域回收方式保證了G1在有限的時間內可獲取儘量高的收集效率。

  在G1收集器中,Region之間的對象引用以及其餘收集器中的新生代和老年代之間的對象引用都是使用Remembered Set來避免全堆掃描的。G1中每一個Region都有一個與之對應的Remembered Set,

虛擬機發現程序在對Reference類型的數據進行寫操做時,會產生一個Write Barrier暫時中斷寫操做,檢查Reference引用的對象是否處於不一樣的Region之中(在分代的例子中就是檢查是否老年代中的對象

引用了新生代中的對象),若是是,便經過CardTable把相關引用信息記錄到被引用對象所屬的Region的Remembered Set之中。當進行內存回收時,在GC根節點的枚舉範圍中加入Remember Set便可保

證不對全堆掃描也不會有遺漏。

若是不計算維護Remembered Set的操做,G1收集器的運做大體可劃分爲如下幾個步驟:

  一、初始標記

  二、併發標記

  三、最終標記

  四、篩選回收

 

 

內存分配與回收策略

Minor GC 新生代GC:通常比較頻繁,回收速度也比較快

Major GC/Full GC 老年代GC:調用System.gc() 強制執行GC爲Full GC

(Full GC停頓時間比Minor GC高几個量級,通常爲50倍以上)

對象優先在Eden區上分配,Eden區上沒有足夠的空間分配時,觸發一次Minor GC(新生代GC),將存活的對象複製進Survivor to區,若Survivor to區沒有足夠的空間存放,則經過分配擔保機制將對象轉移到老年代中。同時,通過一次Minor GC進入到survivor to區的對象,年齡計數器設爲1,在Survivor from區的對象每通過一次Minor GC,年齡加1,當年齡增長到 -XX:MaxTenuringThreshold 設定的閥值(默認15)或者在Survivor區中有相同年齡的全部對象大小總和大於Survivor區大小的一半,那麼大於這個年齡的對象,將會被移動到老年代中。

每進行Minor GC以前,在容許擔保失敗的狀況下,JVM將查看老年代中最大可用連續空間是否大於歷次minor GC晉升到老年代的對象的平均大小,若是大於,將進行一次Minor GC;若是minor GC後老年代空間不足,則緊接着觸發Full GC,若是小於,則直接觸發Full GC。(新生代和老年代的比例默認是1:2)

(HandlePromotionFailure設置是否容許擔保失敗(默認容許),若是不容許擔保失敗,那麼每次Minor GC前JVM查看老年代中最大的連續空間是否大於新生代全部對象的大小總和,若是小於,則直接觸發Full GC)

大的對象可能直接進入老年代,避免在Eden區和兩個Survivor區之間發生大量的內存複製。典型的大對象是那種很長的字符串對象或者數組。超過 -XX:PretenureSizeThreshold參數配置的大小的對象直接在老年代分配內存。

 

 

JVM性能監控和故障處理 

  經過工具導出和處理分析 運行日誌、異常堆棧、GC日誌、線程快照(threaddump/javacore文件)、堆轉儲快照(headdump/hprof文件)等

  jps:JVM process status tool,顯示指定系統內全部的HotSpot虛擬機進程

  jstat:JVM statistics Monitoring Tool,收集HotSpot虛擬機各方面的運行數據

  jinfo:Configuration Info for java,顯示虛擬機配置信息

  jmap:Memory map for Java,生成虛擬機的內存轉儲快照(heapdump文件)

  jhat:JVM Heap Dump Browser,用於分析heapdump文件,它會創建一個http/html服務器,讓用戶能夠在瀏覽器上查看分析結果

  jstack:Stack trace for Java,顯示虛擬機的線程快照

 

jps:和linux中ps命令類似,可列出正在運行的虛擬機進程,並顯示虛擬機執行主類和這些進程的惟一ID(Local Virtual Machine Identifier LVMID)

格式:jps [option] [hostid(主機名)]

參數:-l  輸出主類的全路徑  -v  輸出虛擬機進程啓動是的JVM參數

   

jstat:用於監視虛擬機各類運行狀態信息的命令行工具,它能夠顯示本地或者遠程虛擬機進程中的類加載、內存、垃圾收集、JIT編譯等運行數據,是定位虛擬機性能問題的首選工具。

格式:jstat [ option vmid [interval] [count] ]

interval和count是查詢間隔和次數,若是忽略這兩個參數則只查詢一次

選項:-class 監視類裝載、卸載數量、總空間以及類裝載消耗時間

-gc  監視Java堆情況,包含Eden區,兩個Survivor區、老年代、永久代等的容量,已用空間,GC時間等信息。

 

jmap:Java內存映射工具,用於生成堆存儲快照heapdump,生成堆快照還能夠經過設置參數使在OOM異常以後自動生成堆dump文件。

jmap還可查詢finalize執行隊列、Java堆和永久代的詳細信息(空間使用率,使用哪一種收集器等)。

格式:jmap [option] vmid

option參數

-dump:format=b,file=[filename] 生成堆轉儲快照

-heap  顯示Java堆詳細信息,如使用哪一種收集器、參數配置、分代情況等。

 

jhat:與jmap搭配使用,分析jmap生成的堆轉儲快照文件。通常不會直接使用jhat命令分析dump文件,一是不會直接在應用服務器上分析dump文件,由於分析耗時耗資源,二是jhat分析結果比較簡陋,可用VisualVM,MAT等工具

 

jstack:Java堆棧跟蹤工具,用戶生成虛擬機當前時刻的線程快照,線程快照就是當前虛擬機內每一條線程正在執行的方法堆棧的集合,生成快照的主要目的是定位線程出現長時間等待的緣由,如線程間死鎖、死循環、請求外部資源(如sql)致使的長時間等待等

線程出現停頓的時候經過jstack來查看各個線程的調用堆棧,就能夠知道沒有響應的線程到底在後臺作什麼事情或者等待什麼資源

格式:jstack [option] vmid

option參數,-F:強制輸出線程堆棧

-l:除堆棧外顯示關於鎖的附加信息

-m:可顯示調用本地方法的堆棧

JDK1.5以後的Thread類新增了getAllStackTraces()方法用戶獲取虛擬機中全部線程的StackTraceElement對象。和jstack功能相似

public static Map<Thread,StackTraceElement[]> getAllStackTraces()   返回從 ThreadStackTraceElement 數組的一個 Map,表明相應線程的堆棧跟蹤。 、

總結若是要定位OOM問題使用jps和jmap組合命令,先用jps或者linux的ps命令查看虛擬機進程的vmid,而後用jamp命令生成堆快照文件,最後使用工具分析dump文件定位問題。
若是要定位線程響應時間過長的問題,使用jps和jstack命令,先用jps或者linux的ps命令查看虛擬機進程的vmid,而後用jstack命令查看線程堆棧信息

 

 

JVM調優

JVM調優是經過分析GC日誌等來分析java內存和垃圾回收的狀況,來調整各內存區域內存佔比和垃圾回收策略。充分使用系統資源,減小GC停頓時間和停頓次數,因爲Full GC的停頓時間遠比Minor GC的停頓時間長,所以要控制Full GC的頻率。控制Full GC的頻率的關鍵是看應用中的絕大多數對象是否符合「朝生夕滅」的原則,即大多數的對象的生存時間都不該太長,尤爲是不能有成批量的、長時間存活的對象產生,這樣這些對象在Minor GC就會被回收,不會進入老年代,這樣才能保證老年代的穩定。好比對於十幾小時乃至一天才出現一次Full GC的系統能夠經過定時任務的方式在夜間觸發Full GC。若是FullGC次數過多多是下面的緣由:一、內存佔用高:代碼中建立了大量的對象致使內存泄漏,不能回收內存,建立新對象致使空間不足觸發fullGC二、內存佔用不高:多是顯示的調用System.gc()次數太多致使的fullGC,能夠經過添加-XX:+DisableExplicitGC來禁用JVM對顯式GC的響應
相關文章
相關標籤/搜索