jvm入門-進(jìn)階

前言

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)存整體布局凹联。

1.jpg

從上圖可以看出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ū))。如下圖:

2.png

在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è)階段:

  1. 加載階段(Loading)
  2. 驗(yàn)證階段(Verification)
  3. 準(zhǔn)備階段(PreParation)
  4. 解析階段(Resolution)
  5. 初始化階段(Initialization)
  6. 使用階段(Using)
  7. 卸載階段(Unloading)
3.png

其中驗(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提供了下面幾種類加載器來加載我們的代碼:

4.png

引導(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)佛南,可以被回收

5.png

****垃圾回收算法****

如何回收jvm的內(nèi)存垃圾梗掰,Java提供了三種回收算法:

標(biāo)記-清除算法
6.png

如上圖,先標(biāo)記出無效的對(duì)象嗅回,再進(jìn)行清理及穗,它有兩個(gè)缺點(diǎn):
效率問題:標(biāo)記和清除過程的效率都不高
空間問題:如上圖,清除后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片

復(fù)制算法
7.png

如上圖绵载,先將可用內(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)記-整理算法
8.png

如上圖,類似于“標(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ì)象观话,以下是新生代和老年代使用回收器的分布

9.png

新生代收集器有: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)記-清除算法)

10.png

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)存溢出 .。析恋。良哲。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市助隧,隨后出現(xiàn)的幾起案子筑凫,更是在濱河造成了極大的恐慌,老刑警劉巖并村,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件巍实,死亡現(xiàn)場離奇詭異,居然都是意外死亡哩牍,警方通過查閱死者的電腦和手機(jī)棚潦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來膝昆,“玉大人丸边,你說我怎么就攤上這事⊥馇保” “怎么了原环?”我有些...
    開封第一講書人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長处窥。 經(jīng)常有香客問我嘱吗,道長,這世上最難降的妖魔是什么滔驾? 我笑而不...
    開封第一講書人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任谒麦,我火速辦了婚禮,結(jié)果婚禮上哆致,老公的妹妹穿的比我還像新娘绕德。我一直安慰自己,他們只是感情好摊阀,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開白布耻蛇。 她就那樣靜靜地躺著,像睡著了一般胞此。 火紅的嫁衣襯著肌膚如雪臣咖。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,598評(píng)論 1 305
  • 那天漱牵,我揣著相機(jī)與錄音夺蛇,去河邊找鬼。 笑死酣胀,一個(gè)胖子當(dāng)著我的面吹牛刁赦,可吹牛的內(nèi)容都是我干的娶聘。 我是一名探鬼主播,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼甚脉,長吁一口氣:“原來是場噩夢啊……” “哼丸升!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起宦焦,我...
    開封第一講書人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤发钝,失蹤者是張志新(化名)和其女友劉穎顿涣,沒想到半個(gè)月后波闹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡涛碑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年精堕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蒲障。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡歹篓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出揉阎,到底是詐尸還是另有隱情庄撮,我是刑警寧澤,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布毙籽,位于F島的核電站洞斯,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏坑赡。R本人自食惡果不足惜烙如,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望毅否。 院中可真熱鬧亚铁,春花似錦、人聲如沸螟加。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽捆探。三九已至然爆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間徐许,已是汗流浹背施蜜。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留雌隅,地道東北人翻默。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓缸沃,卻偏偏與公主長得像,于是被迫代替她去往敵國和親修械。 傳聞我的和親對(duì)象是個(gè)殘疾皇子趾牧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容