背景:
當(dāng)操作系統(tǒng)內(nèi)存出現(xiàn)瓶頸時(shí),我們便會(huì)重點(diǎn)排查那個(gè)應(yīng)用占用內(nèi)存過大。對(duì)于更深一步分析內(nèi)存的使用,就進(jìn)一步去了解內(nèi)存結(jié)構(gòu),應(yīng)用程序使用情況,以及內(nèi)存如何分配、如何回收,這樣你才能更好地確定內(nèi)存的問題。
JVM 內(nèi)存分配:
?文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-652571.html
JVM(Java虛擬機(jī))內(nèi)存分配是指Java程序運(yùn)行時(shí),JVM對(duì)內(nèi)存的分配和管理。JVM將內(nèi)存劃分為不同的區(qū)域,每個(gè)區(qū)域有不同的作用和生命周期。以下是JVM內(nèi)存分配的詳細(xì)解釋:
-
方法區(qū)(Method Area):
方法區(qū)用于存儲(chǔ)類的結(jié)構(gòu)信息,如類的字節(jié)碼、常量池、靜態(tài)變量、方法信息等。方法區(qū)在JVM啟動(dòng)時(shí)被創(chuàng)建,并且被所有線程共享。在JDK 8及之前,方法區(qū)是一個(gè)邏輯上的概念,實(shí)際上是通過永久代(Permanent Generation)實(shí)現(xiàn)的。但在JDK 8及以后,永久代被元空間(Metaspace)所取代。元空間使用本地內(nèi)存來(lái)存儲(chǔ)類的元數(shù)據(jù)。 -
堆(Heap):
堆是用于存儲(chǔ)對(duì)象實(shí)例的區(qū)域。所有在Java程序中創(chuàng)建的對(duì)象都存放在堆中。堆是線程共享的,被所有線程訪問和操作。堆的大小可以通過啟動(dòng)參數(shù)進(jìn)行調(diào)整。當(dāng)堆中沒有足夠的空間容納新創(chuàng)建的對(duì)象時(shí),會(huì)觸發(fā)垃圾回收(GC)來(lái)回收不再使用的對(duì)象,以釋放內(nèi)存。堆又可進(jìn)一步劃分為新生代(Young Generation)和老年代(Old Generation):
- 新生代:新創(chuàng)建的對(duì)象首先被分配到新生代。新生代又分為一個(gè)Eden區(qū)和兩個(gè)Survivor區(qū)(通常稱為From區(qū)和To區(qū))。
- 老年代:當(dāng)對(duì)象在新生代經(jīng)過多次垃圾回收后仍然存活,它們會(huì)被晉升到老年代。老年代主要存放生命周期較長(zhǎng)的對(duì)象。
-
棧(Stack):
棧用于存儲(chǔ)線程的方法調(diào)用和局部變量。每個(gè)線程在運(yùn)行時(shí)都會(huì)創(chuàng)建一個(gè)棧幀,用于存儲(chǔ)方法的局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法出口等信息。棧的大小是固定的,由虛擬機(jī)在啟動(dòng)時(shí)分配。棧是線程私有的,每個(gè)線程都有自己獨(dú)立的棧。棧又可進(jìn)一步劃分為:
- Java虛擬機(jī)棧(Java Virtual Machine Stack):存儲(chǔ)Java方法執(zhí)行的線程棧幀。
- 本地方法棧(Native Method Stack):存儲(chǔ)Native方法執(zhí)行的線程棧幀。
-
本地方法棧(Native Method Stack):
本地方法棧用于存儲(chǔ)Java程序調(diào)用本地方法(使用JNI接口)時(shí)的棧信息。 -
PC寄存器(Program Counter Register):
PC寄存器存儲(chǔ)當(dāng)前線程執(zhí)行的字節(jié)碼指令的地址。 -
常量池(Constant Pool):
常量池用于存放編譯期生成的各種字面量和符號(hào)引用。 -
直接內(nèi)存(Direct Memory):
直接內(nèi)存是堆外內(nèi)存,不受JVM內(nèi)存管理的限制。它通常由NIO(New Input/Output)庫(kù)使用,通過與操作系統(tǒng)進(jìn)行直接交互來(lái)提高I/O性能。
JVM會(huì)根據(jù)程序的運(yùn)行情況動(dòng)態(tài)調(diào)整各個(gè)內(nèi)存區(qū)域的大小,并進(jìn)行垃圾回收來(lái)釋放不再使用的內(nèi)存。對(duì)于編程者來(lái)說(shuō),特別需要注意的是堆和棧這兩個(gè)區(qū)域,因?yàn)樗鼈冎苯优c對(duì)象和方法調(diào)用相關(guān)。而這其中堆空間占據(jù)著 JVM 中最大的存儲(chǔ)區(qū)域,存放了很多對(duì)象,所以大多數(shù)基于 JVM 的內(nèi)存調(diào)優(yōu)也是對(duì)堆空間的調(diào)優(yōu)。
JVM 垃圾回收:
在JVM內(nèi)存管理中,內(nèi)存被劃分為新生代(Young Generation)和老年代(Old Generation),不同的垃圾回收算法和策略被應(yīng)用于這兩個(gè)代中。下面我將詳細(xì)講解新生代和老年代的垃圾回收過程。
新生代垃圾回收過程:
新生代是用于存放新創(chuàng)建的對(duì)象的區(qū)域,通常包括一個(gè)Eden區(qū)和兩個(gè)Survivor區(qū)(通常稱為From區(qū)和To區(qū))。新生代垃圾回收主要包括以下幾個(gè)階段:
Eden區(qū):新創(chuàng)建的對(duì)象首先會(huì)被分配到Eden區(qū)。當(dāng)Eden區(qū)滿時(shí),會(huì)觸發(fā)一次新生代垃圾回收。
標(biāo)記階段(Marking Phase):在標(biāo)記階段,垃圾回收器會(huì)標(biāo)記所有在Eden區(qū)和From區(qū)中仍然存活的對(duì)象。
復(fù)制階段(Copying Phase):在復(fù)制階段,垃圾回收器將標(biāo)記的存活對(duì)象從Eden區(qū)和From區(qū)復(fù)制到To區(qū)。同時(shí),它也會(huì)清空Eden區(qū)和From區(qū)的對(duì)象。
交換Survivor區(qū)(Swap Survivor):在復(fù)制階段完成后,垃圾回收器會(huì)交換From區(qū)和To區(qū)的角色,使得To區(qū)成為下一次垃圾回收時(shí)的From區(qū)。
清除階段(Sweeping Phase):在清除階段,垃圾回收器會(huì)清空From區(qū)中的對(duì)象。此時(shí),Eden區(qū)和To區(qū)是空的,可以用于下一輪的對(duì)象分配。
新生代采用的是復(fù)制算法,該算法的優(yōu)點(diǎn)是簡(jiǎn)單高效,但代價(jià)是需要將存活的對(duì)象復(fù)制到另一個(gè)區(qū)域,這對(duì)于存活對(duì)象較多的場(chǎng)景會(huì)帶來(lái)一定的性能開銷。
老年代垃圾回收過程:
老年代是用于存放存活時(shí)間較長(zhǎng)的對(duì)象的區(qū)域,通常包括大對(duì)象、長(zhǎng)時(shí)間存活的對(duì)象以及從新生代晉升過來(lái)的對(duì)象。老年代垃圾回收主要包括以下幾個(gè)階段:
標(biāo)記階段(Marking Phase):與新生代的標(biāo)記階段類似,垃圾回收器會(huì)標(biāo)記老年代中所有存活的對(duì)象。
清除階段(Sweeping Phase):在清除階段,垃圾回收器會(huì)清除未標(biāo)記的對(duì)象,并釋放它們占用的內(nèi)存空間。
整理階段(Compacting Phase):在整理階段,垃圾回收器會(huì)將存活的對(duì)象向老年代的一端移動(dòng),從而消除內(nèi)存碎片,使得老年代的空間得以連續(xù)。
老年代的垃圾回收一般采用標(biāo)記-清除-整理算法(Mark-Sweep-Compact),該算法通過整理階段解決了老年代內(nèi)存碎片的問題。
如何定位內(nèi)存占用問題:
已經(jīng)知道jvm一些基礎(chǔ)知識(shí),那么該如何去定位瓶頸問題呢,主要有兩個(gè)過程:
- 觀察 GC 的頻次;
- 定位占用內(nèi)存的對(duì)象。
1.如何觀察 GC 的頻次?
JDK 自帶的工具jstat,使用 jstat 來(lái)查看 GC 的頻次,如下所示:
[root]# jstat -gc 26607 1000 3
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
512.0 512.0 320.0 0.0 86016.0 27828.5 175104.0 157974.6 122840.0 116934.9 16128.0 15060.4 5328 37.311 4 1.042 38.353
512.0 512.0 320.0 0.0 86016.0 27981.9 175104.0 157974.6 122840.0 116934.9 16128.0 15060.4 5328 37.311 4 1.042 38.353
512.0 512.0 320.0 0.0 86016.0 28885.4 175104.0 157974.6 122840.0 116934.9 16128.0 15060.4 5328 37.311 4 1.042 38.353
輸出項(xiàng)中有很多以 C 或者 U 結(jié)尾。S0 則代表第一個(gè) Survivor 區(qū),也就是我上文說(shuō)的 From 區(qū)。通過以上的講解,比如 S1C 和 S1U 則表示第二個(gè) Survivor 區(qū)也就是 To 區(qū)的總?cè)萘亢褪褂萌萘俊?/p>
其他的輸出選項(xiàng)含義。
- EC / EU:Eden 區(qū)的總?cè)萘?已使用空間的大小。
- OC / OU:老年代總?cè)萘?老年代已使用空間大小。
- MC / MU:方法區(qū)總?cè)萘?方法區(qū)已使用容量大小。
- CCSC / CCSU:壓縮類總?cè)萘?壓縮類空間使用大小。
- YGC / YGCT:年輕代垃圾回收的次數(shù)/年輕代垃圾回收消耗時(shí)間。
- FGC / FGCT: 老年代垃圾回收次數(shù)/老年代垃圾回收消耗時(shí)間。
- GCT:垃圾回收消耗總時(shí)間。
通過GC的次數(shù)和時(shí)間可以初步判斷系統(tǒng)中是否存在垃圾回收的問題。以下是一些常見的情況,當(dāng)它們出現(xiàn)時(shí),可能提示存在垃圾回收問題:
頻繁的Full GC:
Full GC是指對(duì)整個(gè)堆內(nèi)存進(jìn)行垃圾回收的操作,它會(huì)導(dǎo)致應(yīng)用程序的停頓時(shí)間較長(zhǎng)。如果頻繁發(fā)生Full GC,即使在短時(shí)間內(nèi),這可能意味著內(nèi)存不足、內(nèi)存泄漏或?qū)ο笊芷诠芾聿划?dāng)?shù)葐栴}。
頻繁的Young GC:
Young GC是指對(duì)新生代進(jìn)行垃圾回收的操作。如果頻繁發(fā)生Young GC,尤其是在短時(shí)間內(nèi),可能表明新生代的內(nèi)存空間不足、對(duì)象過早晉升到老年代、對(duì)象存活時(shí)間過長(zhǎng)等問題。
長(zhǎng)時(shí)間的停頓:
如果垃圾回收的停頓時(shí)間過長(zhǎng)(比如幾百毫秒以上),會(huì)導(dǎo)致應(yīng)用程序的性能下降、響應(yīng)時(shí)間延長(zhǎng)等問題。長(zhǎng)時(shí)間的停頓可能是因?yàn)镕ull GC或者垃圾回收器的配置不合理。
如何定位占用內(nèi)存的對(duì)象:
1. 利用JDK 自帶的 JVM 監(jiān)控工具:jvisual,jvisual 能做的事情很多,監(jiān)控內(nèi)存泄漏、跟蹤垃圾回收、執(zhí)行時(shí)內(nèi)存分析、CPU 線程分析等,而且通過圖形化的界面指引就可以完成,具體用法
可自行參考。
2. 利用jmap 工具,通過jmap -histo pid | head -n 10,?查看該進(jìn)程的對(duì)象等詳細(xì)信息,可重點(diǎn)關(guān)注結(jié)果展示的第五列,找出屬于自己的業(yè)務(wù)類文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-652571.html
?
到了這里,關(guān)于如何分析 JVM 內(nèi)存瓶頸淺談的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!