前言
java面試的時(shí)候經(jīng)常要問到什么是jvm,到底什么是JVM歪架?
jvm包含哪些塊吉捶?
一個(gè).java文件的生命旅途講一下我碟?
jvm垃圾收集器有哪些放案?
各自回收算法?優(yōu)缺點(diǎn)矫俺?
你還了解哪些最新的gc算法吱殉?
實(shí)戰(zhàn)調(diào)優(yōu)?實(shí)戰(zhàn)定位問題厘托?
本文從兩個(gè)方面出發(fā)
第一:jvm相關(guān)的知識(shí)概念介紹友雳。
第二:從實(shí)戰(zhàn)例子出來,解決工作中遇到的問題铅匹。
JVM是什么
簡單來說就是:解析Java字節(jié)碼文件押赊,轉(zhuǎn)化為不同操作系統(tǒng)可運(yùn)行的機(jī)器碼,并提供了一套內(nèi)存管理機(jī)制包斑,來創(chuàng)建流礁、使用、回收J(rèn)ava產(chǎn)生的對(duì)象罗丰。
所以說Java是跨平臺(tái)的神帅,這也是Java能“一次編寫,到處運(yùn)行”的原因萌抵。
運(yùn)行時(shí)數(shù)據(jù)區(qū)
JVM的種類有很多找御,目前使用最廣泛的是HotSpot虛擬機(jī)元镀,所以本文只針對(duì)HotSpot虛擬機(jī)做介紹,下面讓我們來揭開它的神秘面紗霎桅,了解JVM的內(nèi)存整體布局凹联。
從上圖可以看出JVM總共分為:
程序計(jì)數(shù)器、虛擬機(jī)棧哆档、本地方法棧、堆住闯、方法區(qū)瓜浸,下面一一來介紹。
程序計(jì)算器
指向當(dāng)前線程正在執(zhí)行的字節(jié)碼指令地址比原,行號(hào)插佛。可以理解為一個(gè)書簽量窘,比如你看到一本書的第20頁準(zhǔn)備睡覺了雇寇,可以在第20頁放一個(gè)書簽,下次可以接著看蚌铜,同理一個(gè)線程是在一個(gè)CPU上運(yùn)行锨侯,但一個(gè)CPU不僅僅只運(yùn)行一個(gè)線程冬殃,當(dāng)CPU從一個(gè)線程切換到另一個(gè)線程時(shí),就需要先記錄程序的運(yùn)行位置,當(dāng)切換回來后痴荐,就可以接著上次的位置繼續(xù)執(zhí)行膝宁。
虛擬機(jī)棧
存儲(chǔ)當(dāng)前線程運(yùn)行方法所需要的數(shù)據(jù)、指令昆汹、返回地址明刷。每運(yùn)行一個(gè)方法,都會(huì)創(chuàng)建一個(gè)棧針满粗,而一個(gè)棧針又包括:
局部變量表辈末、操作數(shù)棧、動(dòng)態(tài)鏈接、返回地址
我們以搭積木為例挤聘,一個(gè)盒子里裝滿了各種積木塊(對(duì)應(yīng)局部變量表)轰枝,然后從局部變量表中我們一個(gè)一個(gè)拿出積木(變量)放到積木臺(tái)(對(duì)應(yīng)操作數(shù)棧)進(jìn)行拼裝,比如我們要拼裝一個(gè)汽車组去,拼裝完成后汽車即對(duì)應(yīng)返回地址鞍陨,動(dòng)態(tài)鏈接很多文章給的定義比較抽象,比如:將方法區(qū)中的符號(hào)引用轉(zhuǎn)為直接引用从隆,為了形象話诚撵,你可以這樣理解,在拼裝汽車的時(shí)候键闺,缺少輪子部件寿烟,但盒子里有一個(gè)紙條,上面寫著:“各種輪子部件在盒子B中”辛燥,那你就可以根據(jù)提示信息筛武,找到需要的輪子,然后拼裝上去挎塌,這里的紙條可以理解為動(dòng)態(tài)鏈接徘六。
本地方法棧
本地方法棧與虛擬機(jī)棧類似,它存儲(chǔ)是native修飾的方法所需要的數(shù)據(jù)榴都、指令硕噩、返回地址,比如System類中帶native的方法缭贡,這些底層都是C++來實(shí)現(xiàn)的炉擅。
方法區(qū)
存儲(chǔ)類信息、常量阳惹、靜態(tài)變量谍失、JIT。
類信息:包括類的名稱莹汤、類的修飾符快鱼、版本號(hào)、父類是什么纲岭、具體實(shí)現(xiàn)類是哪些等抹竹。
常量、靜態(tài)變量:表示用final止潮、static修飾的變量窃判。
JIT:運(yùn)行時(shí)會(huì)使用JIT即時(shí)編譯器對(duì)熱點(diǎn)代碼進(jìn)行優(yōu)化,優(yōu)化方式為將字節(jié)碼編譯成機(jī)器碼喇闸。通常情況下袄琳,JVM使用“解釋執(zhí)行”的方式執(zhí)行字節(jié)碼询件,即JVM在讀取到一個(gè)字節(jié)碼指令時(shí),會(huì)將其按照預(yù)先定好的規(guī)則執(zhí)行棧操作唆樊,而棧操作會(huì)進(jìn)一步映射為底層的機(jī)器操作宛琅;通過JIT編譯后,執(zhí)行的機(jī)器碼會(huì)直接和底層機(jī)器打交道
注:在 JDK 1.7 時(shí) HotSpot 虛擬機(jī)已經(jīng)把原本放在永久代的字符串常量池和靜態(tài)變量等移出了方法區(qū)
堆
堆是內(nèi)存中最重要的一塊逗旁,大部分?jǐn)?shù)據(jù)都存儲(chǔ)在堆上嘿辟,說到堆,就需要說說JMM(Java內(nèi)存模型)片效,JVM堆分為新生代和老年代红伦,新生代又分為eden(伊甸園)、survivor0(幸存者0區(qū))堤舒、survivor1(幸存者1區(qū))。如下圖:
在1.8之前哺呜,一般把方法區(qū)稱為永久代舌缤,但1.8后,廢棄了永久代某残,改為Meta Space国撵。
這樣劃分堆的內(nèi)存主要用于后面的對(duì)象的回收,具體對(duì)象垃圾回收后面會(huì)講到玻墅。
對(duì)象分配:當(dāng)創(chuàng)建一個(gè)對(duì)象介牙,jvm會(huì)嘗試在eden分配內(nèi)存,如果空間不夠澳厢,會(huì)觸發(fā)一次Yong GC (年輕代垃圾回收环础,后面會(huì)講到),清理非存活的對(duì)象后剩拢,把存活的對(duì)象移動(dòng)到Survivor0线得,如果Survivor0滿了,會(huì)將存活的對(duì)象再移動(dòng)到Survivor1中徐伐,在Surivivor每移動(dòng)一次贯钩,對(duì)象年齡加1歲,當(dāng)它的年齡增加到一定程度(默認(rèn)為15歲)時(shí)办素,就會(huì)被晉升到老年代中角雷,當(dāng)老年代中的空間滿了后會(huì)觸發(fā)一次FullGC(老年代垃圾回收,后面會(huì)講到)性穿,F(xiàn)ull GC會(huì)對(duì)整個(gè)堆進(jìn)行回收勺三,會(huì)造成整個(gè)應(yīng)用停止,所以要盡量減少Full GC的次數(shù)
類的加載機(jī)制
上面談到了JVM的內(nèi)存布局需曾,但我們編寫了一段代碼檩咱,這段代碼是怎樣被加載到內(nèi)存中的呢揭措?這就涉及到了類的加載機(jī)制,類的加載機(jī)制可分為7個(gè)階段:
- 加載階段(Loading)
- 驗(yàn)證階段(Verification)
- 準(zhǔn)備階段(PreParation)
- 解析階段(Resolution)
- 初始化階段(Initialization)
- 使用階段(Using)
- 卸載階段(Unloading)
其中驗(yàn)證刻蚯、準(zhǔn)備绊含、解析3個(gè)階段稱為連接(Linking),一般說JVM類加載通常說的是:加載炊汹、驗(yàn)證躬充、準(zhǔn)備、解析讨便、初始化這五個(gè)階段
加載階段
通過類名查找對(duì)應(yīng)的類充甚,并將類的字節(jié)碼轉(zhuǎn)換為方法區(qū)運(yùn)行時(shí)的數(shù)據(jù)結(jié)構(gòu),在內(nèi)存中生成一個(gè)能代表類的java.lang.Class對(duì)象霸褒,作為其他各種數(shù)據(jù)的訪問入口伴找。
注:加載和連接是交叉執(zhí)行的,可能文件內(nèi)容尚未加載完成废菱,就已經(jīng)開始部分字節(jié)碼文件的驗(yàn)證工作了
驗(yàn)證階段
驗(yàn)證階段很重要技矮,主要是為了防止一些惡意代碼攻擊,導(dǎo)致系統(tǒng)崩潰殊轴,它是JVM自我保護(hù)的一項(xiàng)重要措施衰倦,主要包括以下幾個(gè)驗(yàn)證:
-
文件格式校驗(yàn)
驗(yàn)證字節(jié)碼文件是否符合Class規(guī)范,比如是否以魔數(shù)(0xCAFEBABE)開頭旁理,其它各部分是否被追加了其它信息等
-
元數(shù)據(jù)校驗(yàn)
對(duì)字節(jié)碼語意進(jìn)行分析樊零,是否符合Java規(guī)范,比如:是否有父類孽文,是否繼承了不允許的父類驻襟,
字節(jié)碼校驗(yàn):對(duì)字節(jié)碼進(jìn)行校驗(yàn),確保沒有危害虛擬機(jī)安全的信息
符號(hào)引用校驗(yàn):對(duì)類自身以外如常量池中各種符號(hào)引用確保能匹配到直接引用信息芋哭。
準(zhǔn)備階段
為類靜態(tài)變量分配內(nèi)存并設(shè)置類變量的初始值塑悼,這些靜態(tài)變量會(huì)被分配到方法區(qū)上,比如下面代碼楷掉,會(huì)在準(zhǔn)備階段設(shè)置int初始值0
注:如果v變量被final修飾厢蒜,則初始值會(huì)被設(shè)置為123456
public static int v= 123456
解析階段
將虛擬機(jī)常量池中的符號(hào)引用替換為直接引用。
所謂符號(hào)引用是以一組符號(hào)來描述所引用的目標(biāo)烹植,符號(hào)引用可以是任何形式的字面量斑鸦,只要使用是能無歧義地定位到目標(biāo)即可,
而直接引用是可以直接指向目標(biāo)的指針草雕、相對(duì)偏移量或者是一個(gè)能間接定位到目標(biāo)的句柄巷屿。
符號(hào)引用和直接引用有一個(gè)重要的區(qū)別:使用符號(hào)引用時(shí)被引用的目標(biāo)不一定已經(jīng)加載到內(nèi)存中;而使用直接引用時(shí)墩虹,引用的目標(biāo)必定已經(jīng)存在虛擬機(jī)的內(nèi)存中了
初始化階段
初始化階段才是真正開始執(zhí)行類中定義的java程序代碼嘱巾,Java中對(duì)類變量進(jìn)行初始化有兩種方式
1憨琳、聲明類變量是指定初始值
2、使用靜態(tài)代碼塊為類變量設(shè)定初始值
初始化步驟為:
1旬昭、假如這個(gè)類還沒有被加載和連接篙螟,則程序先加載并連接該類
2、假如該類的直接父類還沒有被初始化问拘,則先初始化其直接父類
3遍略、假如類中有初始化語句,則系統(tǒng)依次執(zhí)行這些初始化語句
public class P{
public static int a = 1;
static {
a = 2;
}
}
public class C extends P{
public static int b = a;
public static void main(String[] args) {
System.out.printf("b: "+C.b);
}
}
如上代碼骤坐,會(huì)打印出:b:2绪杏,因?yàn)閎引用了a,初始化C會(huì)先初始化P中a的值纽绍,jvm會(huì)按照順序先將a賦值為1蕾久,然后static靜態(tài)代碼塊會(huì)將a更改為2,所以b的值為2拌夏,如果將“public static int a =1”和靜態(tài)代碼塊調(diào)換位置僧著,那么會(huì)打印出:b:1
類加載器
說完類加載機(jī)制,但這些類是被誰加載到內(nèi)存的辖佣?這就又要說到j(luò)ava類加載器霹抛,java提供了下面幾種類加載器來加載我們的代碼:
引導(dǎo)類加載器
負(fù)責(zé)加載jre/lib目錄下的核心類庫搓逾,比如rt.jar/charsets.jar等卷谈,出于安全考慮,Bootstrap啟動(dòng)類加載器只加載包名為java霞篡、javax世蔗、sun等開頭的類
拓展類加載器
負(fù)責(zé)加載jre/lib/ext目錄下的jar類包
應(yīng)用類加載器
負(fù)責(zé)加載classPath路徑下的class字節(jié)碼文件,主要就是加載你項(xiàng)目中寫的那些類
自定義類加載器
負(fù)責(zé)加載用戶自定義路徑下的class字節(jié)碼文件朗兵,自定義類加載器需要繼承java.lang.ClassLoader類
小結(jié):java類加載機(jī)制最初是由父類加載污淋、如果加載失敗才會(huì)由子類加載,這被稱為雙親委派原則余掖,這樣做的好處是避免類被重復(fù)加載寸爆,比如通過網(wǎng)絡(luò)傳遞一個(gè)名為java.lang.Integer的類,通過雙親委托模式傳遞到啟動(dòng)類加載器盐欺,而啟動(dòng)類加載器在核心Java API發(fā)現(xiàn)這個(gè)名字的類赁豆,發(fā)現(xiàn)該類已被加載,并不會(huì)重新加載網(wǎng)絡(luò)傳遞的過來的java.lang.Integer冗美,而直接返回已加載過的Integer.class魔种,這樣便可以防止核心API庫被隨意篡改。
垃圾回收
當(dāng)類被加載到JVM內(nèi)存中后粉洼,有些對(duì)象在被使用過后以后就不會(huì)再被使用节预,這些對(duì)象要及時(shí)回收掉叶摄,避免對(duì)象不斷堆積,導(dǎo)致內(nèi)存溢出安拟。
哪些內(nèi)存區(qū)域需要回收蛤吓?
我們知道JAVA內(nèi)存模型包括:Java堆,方法區(qū)去扣,線程棧(分為:程序計(jì)數(shù)器柱衔,虛擬機(jī)棧,本地方法棧)
線程棧(程序計(jì)數(shù)器愉棱,虛擬機(jī)棧唆铐,本地方法棧)這三個(gè)是隨著線程而生,線程而滅奔滑。棧中的棧針隨著方法的進(jìn)入和退出有條不紊的執(zhí)行者出棧和入棧的操作艾岂,每一個(gè)棧幀中分配多少內(nèi)存基本上是在類結(jié)構(gòu)確定下來就已知的,所以方法結(jié)束或線程結(jié)束后朋其,內(nèi)存自然就跟著回收了王浴,所以這部分不用過多考慮內(nèi)存回收的問題
Java堆和方法區(qū),這些里面都是用來存一些靜態(tài)的類梅猿,或者動(dòng)態(tài)代理生成的類氓辣,創(chuàng)建的對(duì)象,這些都是在程序運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建的袱蚓,所需要的內(nèi)存我們就不確定了钞啸,如果創(chuàng)建的對(duì)象使用完后,一直沒有被回收喇潘,就導(dǎo)致內(nèi)存溢出了体斩,所以這部分就是垃圾回收器所要關(guān)心的,它要識(shí)別出那些無效的對(duì)象回收掉
如何判定對(duì)象是垃圾颖低?
垃圾回收提供了2種算法來判定一個(gè)對(duì)象是否是垃圾:引用計(jì)數(shù)法和根可達(dá)算法
引用計(jì)數(shù)法:即判斷某個(gè)對(duì)象不在被其它對(duì)象引用絮吵,當(dāng)某個(gè)對(duì)象被一個(gè)對(duì)象引用時(shí),計(jì)數(shù)器+1忱屑,當(dāng)引用失效時(shí)蹬敲,則-1,直到減為0時(shí)莺戒,表示可以回收了伴嗡,但這不能解決A,B之間相互引用的問題,A中有一個(gè)變量指向B脏毯,同時(shí)B中有一個(gè)變量指向A闹究,所以即使A,B最后都不會(huì)再用了,由于他們的引用關(guān)系還在食店,仍然不能被回收渣淤。
根可達(dá)算法:JAVA中定義了一個(gè)名為“GC Roots” 的對(duì)象來作為起始點(diǎn)赏寇,從這個(gè)節(jié)點(diǎn)開始找,如果某個(gè)對(duì)象到這個(gè)“GC Roots”對(duì)象沒有引用連接价认,那么表示這個(gè)對(duì)象就可以被回收了嗅定。
注:JAVA中使用的就是根可達(dá)算法來判斷對(duì)象是否存活的
哪些對(duì)象可以作為GC Roots呢?Java中定義了這些對(duì)象:
1用踩、虛擬機(jī)棧(棧幀中的本地變量表)中引用的對(duì)象渠退。
2、方法區(qū)中類靜態(tài)屬性引用的對(duì)象
3脐彩、方法區(qū)中常量引用的對(duì)象
4碎乃、本地方法棧中(一般說的本地方法)應(yīng)用的對(duì)象
如下圖,object1,object2,object3,object4不能被回收惠奸,因?yàn)樗鼈兛梢灾苯踊蜷g接直達(dá)GC Roots梅誓,object5,object6,object7雖然有互連,但是沒有和GC Roots關(guān)聯(lián)佛南,可以被回收
****垃圾回收算法****
如何回收jvm的內(nèi)存垃圾梗掰,Java提供了三種回收算法:
標(biāo)記-清除算法如上圖,先標(biāo)記出無效的對(duì)象嗅回,再進(jìn)行清理及穗,它有兩個(gè)缺點(diǎn):
效率問題:標(biāo)記和清除過程的效率都不高
空間問題:如上圖,清除后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片
如上圖绵载,先將可用內(nèi)存劃為為大小相等的兩塊埂陆,每次只使用其中一塊,當(dāng)這一塊內(nèi)存用完了尘分,將存活的對(duì)象復(fù)制到另一份上猜惋,并將原先那塊一次性清理掉
缺點(diǎn):以空間換時(shí)間丸氛,這樣培愁,可用內(nèi)存就縮小為原來的一半了,當(dāng)然JVM做了也做了優(yōu)化
在使用這種算法回收新生代時(shí)缓窜,不用要求內(nèi)存分為1:1等量的兩塊定续,因?yàn)樾律话愣际浅λ溃灾粫?huì)有少部分對(duì)象存活下來禾锤,JVM將新生代分為了Eden和兩塊較小的Survivor私股,Eden和Survivor內(nèi)存為8:1,當(dāng)Survivor內(nèi)存不夠時(shí)恩掷,就需要其他內(nèi)存(老年代)來擔(dān)保為其承擔(dān)部分內(nèi)存倡鲸。
標(biāo)記-整理算法如上圖,類似于“標(biāo)記-清理”黄娘,也是先標(biāo)記處存活的對(duì)象峭状,但之后是先將存活的對(duì)象都向一端移動(dòng)克滴,然后清理掉邊界之外的內(nèi)存,這種算法一般適用于JAVA老年代的回收
說明:新生代每次回收都會(huì)有大批對(duì)象死去优床,只有少量存活劝赔,所以采用復(fù)制算法
而老年代每個(gè)對(duì)象的存活率很高,且沒有額外的空間對(duì)其擔(dān)保胆敞,所以可以采用“標(biāo)記-清理”或“標(biāo)記-整理”算法來回收
垃圾回收器
以上介紹的是垃圾回收算法着帽,垃圾回收器是垃圾回收算法的一種實(shí)現(xiàn),就比如小米掃地機(jī)器人和華為掃地機(jī)器人雖然是兩種產(chǎn)品移层,但底層可能采用了同一種清掃算法仍翰,Java中新生代,老年代就是通過回收器來回收垃圾對(duì)象观话,以下是新生代和老年代使用回收器的分布
新生代收集器有:Serial歉备、ParNew、Parallel Scavenge 老年代收集器有:CMS匪燕、Serial Old Parallel Old
Serial 使用單線程清理蕾羊,清理的時(shí)候,會(huì)停止應(yīng)用線程帽驯,待清理完成后龟再,應(yīng)用線程繼續(xù)執(zhí)行,優(yōu)點(diǎn):對(duì)于單核CPU運(yùn)行高效尼变,不會(huì)來回切換線程利凑,缺點(diǎn):因?yàn)闀?huì)停止應(yīng)用線程,界面會(huì)產(chǎn)生卡頓的現(xiàn)象嫌术。
Serial收集器(復(fù)制算法)
新生代單線程收集器哀澈,使用標(biāo)記清理算法,且標(biāo)記度气、清理都是單線程割按,
優(yōu)點(diǎn):運(yùn)行高效,缺點(diǎn):界面會(huì)產(chǎn)生卡頓的現(xiàn)象
Serial Old收集器(標(biāo)記-整理)
老年代單線程收集器磷籍,Serial老年代收集版本
ParNew serial多線程版本适荣,多核CPU比較有優(yōu)勢,原來是1個(gè)人干的活院领,現(xiàn)在多個(gè)人一起來干了弛矛,同樣會(huì)暫停應(yīng)用線程
ParNew收集器(停止-復(fù)制算法)
新生代收集器,Serial的多線程版本比然,并發(fā)標(biāo)記丈氓、清理,在多CPU下有比Serial更好的性能
Parallel Scavenge收集器(停止-復(fù)制算法)
新生代收集器,也是使用多線程并行收集万俗,但關(guān)注的是高吞吐量(最高效率的利用CPU時(shí)間)鱼鼓,吞吐量=用戶線程時(shí)間/(用戶線程時(shí)間+GC線程時(shí)間),適用于后臺(tái)對(duì)交互性不高的應(yīng)用
Parallel Old收集器(停止-復(fù)制算法)
老年代收集器该编,并行收集迄本,吞吐量優(yōu)先
CMS (Concurrent Mark Sweep)收集器(標(biāo)記-清除算法)
CMS分為四個(gè)階段:
初始標(biāo)記:只是標(biāo)記一下GC root能直接關(guān)聯(lián)到的對(duì)象,速度很快
并發(fā)標(biāo)記:并發(fā)標(biāo)記階段就是根據(jù)GC root標(biāo)記需要清理的對(duì)象
重新標(biāo)記:重新標(biāo)記是為了修正并發(fā)標(biāo)記期間因用戶程序繼續(xù)運(yùn)作而導(dǎo)致需要清理對(duì)象變動(dòng)的記錄课竣,這個(gè)階段用戶線程會(huì)終止一段時(shí)間嘉赎,會(huì)比初始標(biāo)記階段稍長一些,但遠(yuǎn)比并發(fā)標(biāo)記的時(shí)間短
并發(fā)清理:并發(fā)清理是和用戶線程一起于樟,清理需要回收的對(duì)象
除了初始標(biāo)記和重新標(biāo)記需要停止用戶線程公条,其它階段都不需要停止,所以CMS以獲取最短停頓時(shí)間為目標(biāo)的收集器迂曲,應(yīng)用于如(B/S網(wǎng)站)靶橱,并發(fā)收集,占用CPU資源較高路捧。
因?yàn)镃MS使用的是標(biāo)記-清除算法关霸,必然會(huì)產(chǎn)生很多碎片,所以可以在執(zhí)行多少次CMS杰扫,再執(zhí)行一次壓縮的FullGC队寇,使用:-XX:CMSFullGCsBeforeCompaction參數(shù)(默認(rèn)為0,即每次每次都進(jìn)行內(nèi)存整理)
目前主流的web項(xiàng)目一般使用ParNew+CMS+Serial Old(老年代備用方案)來回收垃圾章姓。
G1 ZGC最新的垃圾收集器佳遣,基本不需要調(diào)優(yōu),能支持更大的堆空間回收
內(nèi)存操作命令
jvm提供了一系列命令來查看內(nèi)存情況凡伊,并處理內(nèi)存事故問題零渐,包括:jps,jstat,jmap,jhat,jstack,jinfo
jps
JVM Process Status Tool,顯示指定系統(tǒng)內(nèi)所有的HotSpot虛擬機(jī)進(jìn)程。
命令格式:
jps [options] [hostid] //options系忙、hostid參數(shù)可省略
options參數(shù):
-l : 輸出主類全名或jar路徑
-q : 只輸出LVMID
-m : 輸出JVM啟動(dòng)時(shí)傳遞給main()的參數(shù)
-m : 輸出JVM啟動(dòng)時(shí)傳遞給main()的參數(shù)
$ jps -l21072 sun.tools.jps.Jps
3108 org.jetbrains.idea.maven.server.RemoteMavenServer
1200 org.jetbrains.jps.cmdline.Launcher
8080 com.example.springdemo.SpringDemoApplication
jstat
jstat(JVM statistics Monitoring)是用于監(jiān)視虛擬機(jī)運(yùn)行時(shí)狀態(tài)信息的命令诵盼,它可以顯示出虛擬機(jī)進(jìn)程中的類裝載、內(nèi)存笨觅、垃圾收集拦耐、JIT編譯等運(yùn)行數(shù)據(jù)耕腾。
命令格式:
jps [option] LVMID [interval] [count]
參數(shù)
option: 操作參數(shù)
-q : 本地虛擬機(jī)ID
-m : 連續(xù)輸出的時(shí)間間隔
-m : 連續(xù)輸出的次數(shù)
option 操作參數(shù)介紹:
Option | Displays… |
---|---|
class | class loader的行為統(tǒng)計(jì)见剩。Statistics on the behavior of the class loader. |
compiler | HotSpt JIT編譯器行為統(tǒng)計(jì)。Statistics of the behavior of the HotSpot Just-in-Time compiler. |
gc | 垃圾回收堆的行為統(tǒng)計(jì)扫俺。Statistics of the behavior of the garbage collected heap. |
gccapacity | 各個(gè)垃圾回收代容量(young,old,perm)和他們相應(yīng)的空間統(tǒng)計(jì)苍苞。Statistics of the capacities of the generations and their corresponding spaces. |
gcutil | 垃圾回收統(tǒng)計(jì)概述。Summary of garbage collection statistics. |
gccause | 垃圾收集統(tǒng)計(jì)概述(同-gcutil),附加最近兩次垃圾回收事件的原因羹呵。Summary of garbage collection statistics (same as -gcutil), with the cause of the last and |
gcnew | 新生代行為統(tǒng)計(jì)骂际。Statistics of the behavior of the new generation. |
gcnewcapacity | 新生代與其相應(yīng)的內(nèi)存空間的統(tǒng)計(jì)。Statistics of the sizes of the new generations and its corresponding spaces. |
gcold | 年老代和永生代行為統(tǒng)計(jì)冈欢。Statistics of the behavior of the old and permanent generations. |
gcoldcapacity | 年老代行為統(tǒng)計(jì)歉铝。Statistics of the sizes of the old generation. |
gcpermcapacity | 永生代行為統(tǒng)計(jì)。Statistics of the sizes of the permanent generation. |
printcompilation | HotSpot編譯方法統(tǒng)計(jì)凑耻。HotSpot compilation method statistics. |
$ jstat -gcutil 8080 2000
上面命令表示每隔2秒打印出GC的使用回收情況太示,若FGC很多很可能出現(xiàn)了內(nèi)存泄露
jmap
jmap是一個(gè)多功能的命令杭措。它可以生成 java 程序的 dump 文件啦扬, 也可以查看堆內(nèi)對(duì)象示例的統(tǒng)計(jì)信息、查看 ClassLoader 的信息以及 finalizer 隊(duì)列文兢。
命令格式:
jmap [option] LVMID
option參數(shù)
dump: 生成堆轉(zhuǎn)儲(chǔ)快照
finalizerinfo : 顯示在F-Queue隊(duì)列等待Finalizer線程執(zhí)行finalizer方法的對(duì)
heap : 顯示Java堆詳細(xì)信息
histo : 顯示堆中對(duì)象的統(tǒng)計(jì)信息
permstat : to print permanent generation statistics
F : 當(dāng)-dump沒有響應(yīng)時(shí)邻吭,強(qiáng)制生成dump快照
$ jmap -heap 8080
查看Java內(nèi)存的分配情況及新生代餐弱、老年代內(nèi)存的使用情況,確定是否內(nèi)存分配過写亚纭膏蚓?
$ jmap -histo:live 8080
按使用大小進(jìn)行了排序,重點(diǎn)查看排在前面的對(duì)象畸写,看是否程序?qū)懙挠袉栴}降允。另外可以將jvm的堆內(nèi)存導(dǎo)出來分析,使用:
jmap -dump:format=b,file=dump.hprof pid
jhat
jhat(JVM Heap Analysis Tool)命令是與jmap搭配使用艺糜,用來分析jmap生成的dump剧董,jhat內(nèi)置了一個(gè)微型的HTTP/HTML服務(wù)器,生成dump的分析結(jié)果后破停,可以在瀏覽器中查看翅楼。在此要注意,一般不會(huì)直接在服務(wù)器上進(jìn)行分析真慢,因?yàn)閖hat是一個(gè)耗時(shí)并且耗費(fèi)硬件資源的過程毅臊,一般把服務(wù)器生成的dump文件復(fù)制到本地或其他機(jī)器上進(jìn)行分析,對(duì)于jhat分析內(nèi)存在后面實(shí)戰(zhàn)中會(huì)講到黑界。
命令格式
jhat [dumpfile]
參數(shù)
-stack false|true
關(guān)閉對(duì)象分配調(diào)用棧跟蹤(tracking object allocation call stack)管嬉。如果分配位置信息在堆轉(zhuǎn)儲(chǔ)中不可用. 則必須將此標(biāo)志設(shè)置為 false. 默認(rèn)值為 true.>-refs false|true
關(guān)閉對(duì)象引用跟蹤(tracking of references to objects)。默認(rèn)值為 true. 默認(rèn)情況下, 返回的指針是指向其他特定對(duì)象的對(duì)象,如反向鏈接或輸入引用(referrers or incoming references), 會(huì)統(tǒng)計(jì)/計(jì)算堆中的所有對(duì)象朗鸠。>-port port-number
設(shè)置 jhat HTTP server 的端口號(hào). 默認(rèn)值 7000.>-exclude exclude-file
指定對(duì)象查詢時(shí)需要排除的數(shù)據(jù)成員列表文件(a file that lists data members that should be excluded from the reachable objects query)蚯撩。例如, 如果文件列列出了 java.lang.String.value , 那么當(dāng)從某個(gè)特定對(duì)象 Object o 計(jì)算可達(dá)的對(duì)象列表時(shí), 引用路徑涉及 java.lang.String.value 的都會(huì)被排除。>-baseline exclude-file
指定一個(gè)基準(zhǔn)堆轉(zhuǎn)儲(chǔ)(baseline heap dump)烛占。在兩個(gè) heap dumps 中有相同 object ID 的對(duì)象會(huì)被標(biāo)記為不是新的(marked as not being new). 其他對(duì)象被標(biāo)記為新的(new). 在比較兩個(gè)不同的堆轉(zhuǎn)儲(chǔ)時(shí)很有用.>-debug int
設(shè)置 debug 級(jí)別. 0 表示不輸出調(diào)試信息胎挎。值越大則表示輸出更詳細(xì)的 debug 信息.>-version
啟動(dòng)后只顯示版本信息就退出>-J< flag >
因?yàn)?jhat 命令實(shí)際上會(huì)啟動(dòng)一個(gè)JVM來執(zhí)行, 通過 -J 可以在啟動(dòng)JVM時(shí)傳入一些啟動(dòng)參數(shù). 例如, -J-Xmx512m 則指定運(yùn)行 jhat 的Java虛擬機(jī)使用的最大堆內(nèi)存為 512 MB. 如果需要使用多個(gè)JVM啟動(dòng)參數(shù),則傳入多個(gè) -Jxxxxxx.
jstack jstack用于生成java虛擬機(jī)當(dāng)前時(shí)刻的線程快照沟启。線程快照是當(dāng)前java虛擬機(jī)內(nèi)每一條線程正在執(zhí)行的方法堆棧的集合,生成線程快照的主要目的是定位線程出現(xiàn)長時(shí)間停頓的原因犹菇,如線程間死鎖德迹、死循環(huán)、請求外部資源導(dǎo)致的長時(shí)間等待等揭芍。線程出現(xiàn)停頓的時(shí)候通過jstack來查看各個(gè)線程的調(diào)用堆棧胳搞,就可以知道沒有響應(yīng)的線程到底在后臺(tái)做什么事情,或者等待什么資源称杨。如果java程序崩潰生成core文件流酬,jstack工具可以用來獲得core文件的java stack和native stack的信息,從而可以輕松地知道java程序是如何崩潰和在程序何處發(fā)生問題列另。另外芽腾,jstack工具還可以附屬到正在運(yùn)行的java程序中,看到當(dāng)時(shí)運(yùn)行的java程序的java stack和native stack的信息, 如果現(xiàn)在運(yùn)行的java程序呈現(xiàn)hung的狀態(tài)页衙,jstack是非常有用的摊滔。
命令格式
jstack [option] LVMID
option參數(shù)
-F : 當(dāng)正常輸出請求不被響應(yīng)時(shí),強(qiáng)制輸出線程堆棧
-l : 除堆棧外店乐,顯示關(guān)于鎖的附加信息
-m : 如果調(diào)用到本地方法的話艰躺,可以顯示C/C++的堆棧
在后面實(shí)戰(zhàn)中,會(huì)具體講到如何通過jstack來定位死鎖和CPU占用過高的問題眨八。
jinfo jinfo(JVM Configuration info)這個(gè)命令作用是實(shí)時(shí)查看和調(diào)整虛擬機(jī)運(yùn)行參數(shù)腺兴。之前的jps -v口令只能查看到顯示指定的參數(shù),如果想要查看未被顯示指定的參數(shù)的值就要使用jinfo口令
命令格式
jinfo [option] [args] LVMID
option參數(shù)
-flag : 輸出指定args參數(shù)的值
-flags : 不需要args參數(shù)廉侧,輸出所有JVM參數(shù)的值
-sysprops : 輸出系統(tǒng)屬性页响,等同于System.getProperties()
示例
$ jinfo -flags 716
實(shí)戰(zhàn)
怎么定位內(nèi)存泄漏
上面介紹jvm命令的時(shí)候,已經(jīng)說了段誊,可以是用jstat -gcutil來查看內(nèi)存的回收情況闰蚕,如果頻繁出現(xiàn)FullGC(FGC指標(biāo))表示可能出現(xiàn)了內(nèi)存泄漏,首先先說說jvm哪幾塊內(nèi)存區(qū)域會(huì)出現(xiàn)內(nèi)存泄漏:
1连舍,堆溢出(java.lang.OutOfMemoryError: java heap space)
當(dāng)生成一個(gè)新對(duì)象没陡,JVM內(nèi)存申請如下流程:
1)jvm先嘗試在eden分配新對(duì)象所需的內(nèi)存,若內(nèi)存足夠索赏,則將對(duì)象放入eden返回
2)若內(nèi)存不夠盼玄,jvm啟動(dòng)youngGC,試圖將eden不活躍的對(duì)象釋放掉潜腻,若釋放后仍不足以分配內(nèi)存埃儿,則將Eden活躍的對(duì)象放入survivor中。
3)survivor作為Eden和old的中間交換區(qū)域砾赔,若old空間足夠蝌箍,survivor去對(duì)象會(huì)被移動(dòng)到old區(qū)青灼,否則留在survivor區(qū)
4)當(dāng)old區(qū)不夠時(shí)暴心,jvm會(huì)在old區(qū)進(jìn)行fullGC妓盲,若fullGC后,survivor和old仍然無法存放從Eden復(fù)制過來的對(duì)象专普,則會(huì)出現(xiàn)“outOfMemoryError: java heap space”
解決方法:加大堆內(nèi)存的大小悯衬,通過設(shè)置-Xms(java heap初始化大小,默認(rèn)是物理內(nèi)存1/64) -Xmx(java heap的最大值) -Xmn(新生代heap的大小檀夹,一般為Xmx3或4分之一筋粗,注:增加新生代后會(huì)減少老年代的大小)
2炸渡,方法區(qū)內(nèi)存溢出(java.lang.OutMemoryError: permGen space)
方法區(qū)主要是用來存放類信息娜亿,常量、靜態(tài)變量等蚌堵,所以程序中類加載過多(引入第三方包)买决,或者過多使用反射、cglib這種動(dòng)態(tài)代理吼畏,就可能導(dǎo)致該區(qū)域內(nèi)存溢出
解決方法:通過設(shè)置-XX:PermSize(內(nèi)存永久區(qū)初始值)和-XX:MaxPerSize(內(nèi)存永久區(qū)最大值)的大小
3督赤,線程棧溢出(java.lang.StackOverflowError)
線程棧是線程獨(dú)有的一塊內(nèi)存區(qū)域,所以線程棧溢出必定是線程運(yùn)行是出現(xiàn)錯(cuò)誤泻蚊,一般是遞歸太深躲舌,或者方法層級(jí)調(diào)用太深引起的
解決方法:設(shè)置棧區(qū)的大小,通常棧的大小是1-2M性雄,可通過-Xss設(shè)置線程的棧的大小没卸,jdK5以后每個(gè)棧默認(rèn)大小為1M。
注:默認(rèn)內(nèi)存調(diào)到128k就足夠了秒旋,因?yàn)榇蠖鄶?shù)項(xiàng)目中不會(huì)存在很多遞歸調(diào)用办悟。
以上說的解決方法是項(xiàng)目確實(shí)需要那么多的內(nèi)存,可通過調(diào)節(jié)對(duì)應(yīng)參數(shù)來設(shè)置內(nèi)存區(qū)域的大小滩褥,但很多情況是由于我們代碼異常導(dǎo)致內(nèi)存溢出病蛉,一般出現(xiàn)內(nèi)存溢出,可dump出內(nèi)存文件進(jìn)行分析瑰煎,可以在項(xiàng)目啟動(dòng)參數(shù)添加:
| -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${目錄} |
或者使用jstat -gcutil來查看內(nèi)存的回收情況铺然,如果頻繁出現(xiàn)FullGC(FGC指標(biāo))表示可能出現(xiàn)了內(nèi)存泄漏,然后手動(dòng)dump內(nèi)存文件酒甸,使用下面命令:
jmap -dump:format=b,file=dump.hprof pid
知道了項(xiàng)目出現(xiàn)了內(nèi)存溢出魄健,那么怎么知道具體是哪個(gè)類,哪個(gè)方法導(dǎo)致的呢插勤?以下介紹了兩種方法:使用jhat沽瘦、MAT革骨、Jprofiler分析java dump文件
通過工具定位內(nèi)存溢出 .。析恋。良哲。