JVM調(diào)優(yōu)總結(jié)

目錄-----------------------------------------------------------------------------------------------------------------------------------------

  • 1.jvm運(yùn)行時(shí)內(nèi)存區(qū)域劃分
    • 1.1 運(yùn)行時(shí)內(nèi)存區(qū)
    • 1.1.1 方法區(qū)(Method Area)
    • 1.1.2 堆區(qū)(Heap)
    • 1.1.3 虛擬機(jī)棧(JVM Stack)
    • 1.1.4 本地方法棧(Native Method Stack)
    • 1.1.5 程序計(jì)數(shù)器(Program Counter Register)
    • 1.1.6 直接內(nèi)存(Direct Memory)
  • 2.jvm垃圾回收算法
    • 2.1 標(biāo)記-清除(Mark-Sweep)算法
    • 2.2 復(fù)制(Copying)算法
    • 2.3 標(biāo)記-整理(Mark-Compact)算法
    • 2.4 分代收集(Generational Collection)算法
  • 3.jvm垃圾回收器
    • 3.1 Serial收集器
    • 3.2 Parallel Scavenge收集器
    • 3.3 CMS(Concurrent Mark Sweep)收集器
    • 3.4 G1(Garbage-First)收集器
  • 4.jvm年輕代,老年代,元空間回收策略
    • 4.1 年輕代
    • 4.2 老年代
    • 4.3 元空間
  • 5.調(diào)優(yōu)參數(shù)
  • 6.調(diào)優(yōu)命令與工具
    • 6.1 jps
    • 6.2 jmap
    • 6.3 jstack
    • 6.4 jstat
    • 6.5 jinfo
    • 6.6 dump文件
  • 7.調(diào)優(yōu)案例
    • 7.1 需要調(diào)優(yōu)嗎?
    • 7.2 調(diào)優(yōu)量化指標(biāo)
    • 7.3 監(jiān)控前準(zhǔn)備工作
    • 7.4模擬案例
      • 7.4.1 OutOfMemoryError
      • 7.4.2 響應(yīng)延遲
      • 7.4.3 CPU飆升

1.jvm運(yùn)行時(shí)內(nèi)存區(qū)域劃分

1.1 運(yùn)行時(shí)內(nèi)存區(qū)

image

1.1.1 方法區(qū)(Method Area)

  • 在JDK 1.8中,方法區(qū)被實(shí)現(xiàn)為元空間(Metaspace)。這是與永久代(PermGen)不同的一個(gè)區(qū)域,它使用本地內(nèi)存而不是JVM堆內(nèi)存。
  • 元空間存儲(chǔ)了類(lèi)的元數(shù)據(jù),例如類(lèi)的名稱、字段、方法、常量池等。
  • 與永久代不同,元空間的大小不再受JVM參數(shù)-XX:MaxPermSize的限制,而是由-XX:MaxMetaspaceSize參數(shù)控制。

1.1.2 堆區(qū)(Heap)

1708503067791.png
  • 堆區(qū)是JVM中用于動(dòng)態(tài)分配內(nèi)存的區(qū)域,是所有線程共享的。
  • 堆區(qū)進(jìn)一步細(xì)分為新生代(Young Generation)和老年代(Old Generation)。
    • 新生代:又分為Eden區(qū)、Survivor From區(qū)和Survivor To區(qū),主要用于存放新創(chuàng)建的對(duì)象,分配比例默認(rèn)為三分之一的堆空間,默認(rèn)情況下,Eden區(qū)占據(jù)了年輕代空間的80%,而每個(gè)Survivor區(qū)則各占10%。
    • 老年代:用于存放長(zhǎng)時(shí)間存活的對(duì)象。
    • 當(dāng)Eden區(qū)沒(méi)有足夠的空間來(lái)存放新創(chuàng)建的對(duì)象時(shí),JVM會(huì)觸發(fā)一次Minor GC(垃圾回收)。在這個(gè)過(guò)程中,仍存活的對(duì)象會(huì)被移動(dòng)到一個(gè)Survivor區(qū)(如果對(duì)象的年齡未達(dá)到閾值),或者如果對(duì)象的年齡已經(jīng)達(dá)到了閾值,它們會(huì)被移動(dòng)到老年代(Old Generation)。
  • JVM使用垃圾收集器(Garbage Collector)自動(dòng)管理堆區(qū)內(nèi)存,回收不再使用的對(duì)象占用的內(nèi)存。

說(shuō)明
在JDK 1.8的JVM中,可以通過(guò)以下參數(shù)來(lái)調(diào)整年輕代的分配比例:

  1. -Xmn:這個(gè)參數(shù)用于直接設(shè)置年輕代的大小。例如,-Xmn256m 將年輕代的大小設(shè)置為256MB。這個(gè)值會(huì)直接影響Eden區(qū)和Survivor區(qū)的大小,因?yàn)槟贻p代是Eden區(qū)和Survivor區(qū)的總和。
  2. -XX:SurvivorRatio:這個(gè)參數(shù)用于設(shè)置Eden區(qū)與一個(gè)Survivor區(qū)的大小比例。例如,-XX:SurvivorRatio=8 表示Eden區(qū)與一個(gè)Survivor區(qū)的大小比例為8:1。如果Eden區(qū)的大小為8GB,那么每個(gè)Survivor區(qū)的大小就是1GB。
  3. -XX:NewRatio:這個(gè)參數(shù)用于設(shè)置年輕代與老年代的大小比例。例如,-XX:NewRatio=2 表示年輕代與老年代的大小比例為1:2。如果堆的總大小為3GB,那么年輕代的大小為1GB,老年代的大小為2GB。
  4. -XX:MaxNewSize:這個(gè)參數(shù)用于設(shè)置年輕代的最大值。當(dāng)年輕代的使用量達(dá)到這個(gè)值時(shí),JVM會(huì)觸發(fā)一次Minor GC。
  5. -XX:MinHeapFreeRatio-XX:MaxHeapFreeRatio:這兩個(gè)參數(shù)用于調(diào)整堆內(nèi)存的自動(dòng)擴(kuò)展和收縮行為。當(dāng)空閑堆內(nèi)存比例低于MinHeapFreeRatio時(shí),JVM會(huì)嘗試擴(kuò)展堆大小,直到達(dá)到-Xmx指定的最大堆大小。當(dāng)空閑堆內(nèi)存比例高于MaxHeapFreeRatio時(shí),JVM會(huì)嘗試收縮堆大小,直到達(dá)到-Xms指定的初始堆大小

1.1.3 虛擬機(jī)棧(JVM Stack)

  • 每個(gè)線程在創(chuàng)建時(shí)都會(huì)創(chuàng)建一個(gè)虛擬機(jī)棧,用于存儲(chǔ)方法調(diào)用的狀態(tài)信息,包括局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法出口信息等。
  • 每個(gè)方法執(zhí)行時(shí)都會(huì)創(chuàng)建一個(gè)棧幀(Stack Frame)來(lái)存儲(chǔ)該方法的局部變量、操作數(shù)棧、常量池引用等信息。
  • 棧幀隨著方法的進(jìn)入和退出而創(chuàng)建和銷(xiāo)毀。

1.1.4 本地方法棧(Native Method Stack)

  • 與虛擬機(jī)棧類(lèi)似,本地方法棧用于支持native方法的執(zhí)行。
  • 當(dāng)一個(gè)線程調(diào)用一個(gè)native方法時(shí),JVM會(huì)創(chuàng)建一個(gè)新的棧幀在本地方法棧中,用于存儲(chǔ)該native方法的局部變量、參數(shù)等信息。

1.1.5 程序計(jì)數(shù)器(Program Counter Register)

  • 是一塊較小的內(nèi)存空間,可以看作是當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器。
  • 字節(jié)碼解釋器工作時(shí)通過(guò)改變這個(gè)計(jì)數(shù)器的值來(lái)選取下一條需要執(zhí)行的字節(jié)碼指令。
  • 它是線程私有的,每個(gè)線程都有一個(gè)獨(dú)立的程序計(jì)數(shù)器。

1.1.6 直接內(nèi)存(Direct Memory)

  • 直接內(nèi)存并不是JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分,但它是被JVM規(guī)范所涵蓋的。
  • 在JDK 1.4中引入了NIO(New I/O)類(lèi),引入了一種基于通道(Channel)與緩沖區(qū)(Buffer)的I/O方式,它可以使用Native函數(shù)庫(kù)直接分配堆外內(nèi)存,然后通過(guò)一個(gè)存儲(chǔ)在Java堆里面的DirectByteBuffer對(duì)象作為這塊內(nèi)存的引用進(jìn)行操作。
  • 使用直接內(nèi)存可以避免在Java堆和Native堆之間來(lái)回復(fù)制數(shù)據(jù),因此在某些場(chǎng)景下可以提高性能。

2.jvm垃圾回收算法

2.1 標(biāo)記-清除(Mark-Sweep)算法

這是最基本的垃圾回收算法,分為“標(biāo)記”和“清除”兩個(gè)階段。標(biāo)記階段從根對(duì)象開(kāi)始,遞歸地訪問(wèn)對(duì)象圖,標(biāo)記所有可達(dá)的對(duì)象。清除階段則遍歷整個(gè)堆,回收所有未被標(biāo)記的對(duì)象。這種算法的主要缺點(diǎn)是會(huì)產(chǎn)生內(nèi)存碎片,影響程序的性能,其次標(biāo)記階段會(huì)stop-world
說(shuō)明
java語(yǔ)言中作為GC Root對(duì)象的有以下幾種

  • 虛擬機(jī)棧中(棧幀中的本地變量表)的引用的對(duì)象
  • 方法區(qū)中靜態(tài)屬性引用的對(duì)象
  • 方法區(qū)中常量引用的對(duì)象
  • 本地方法棧中JNI的引用對(duì)象

2.2 復(fù)制(Copying)算法

為了解決內(nèi)存碎片問(wèn)題,復(fù)制算法將可用內(nèi)存劃分為兩個(gè)等大的區(qū)域,每次只使用其中一個(gè)區(qū)域。當(dāng)這個(gè)區(qū)域用盡后,垃圾回收器會(huì)將還存活的對(duì)象復(fù)制到另一個(gè)區(qū)域,然后清空當(dāng)前使用的區(qū)域。這種算法在對(duì)象存活率較低時(shí)效率較高,但會(huì)浪費(fèi)一半的內(nèi)存空間。

2.3 標(biāo)記-整理(Mark-Compact)算法

標(biāo)記-整理算法結(jié)合了標(biāo)記-清除和復(fù)制算法的優(yōu)點(diǎn)。在標(biāo)記階段和標(biāo)記-清除算法一樣,但在清除階段,它會(huì)將所有存活的對(duì)象都向一端移動(dòng),然后直接清理掉邊界以外的內(nèi)存。這樣既可以避免內(nèi)存碎片的產(chǎn)生,也不需要額外的內(nèi)存空間。

2.4 分代收集(Generational Collection)算法

基于對(duì)象存活周期的不同,將堆內(nèi)存劃分為幾個(gè)不同的區(qū)域,每個(gè)區(qū)域使用不同的垃圾回收算法。在JDK 1.8中,年輕代(Young Generation)通常使用復(fù)制算法,而老年代(Old Generation)則使用標(biāo)記-清除或標(biāo)記-整理算法。
說(shuō)明
在JVM(Java虛擬機(jī))的內(nèi)存管理中,堆內(nèi)存被劃分為年輕代(Young Generation)和老年代(Old Generation)。年輕代又被細(xì)分為Eden區(qū)、Survivor From區(qū)和Survivor To區(qū),主要用于存放新創(chuàng)建的對(duì)象。這些區(qū)域的作用和存儲(chǔ)內(nèi)容如下:

  1. Eden區(qū)
    • 作用:Eden區(qū)是年輕代中用于存放新創(chuàng)建對(duì)象的主要區(qū)域。當(dāng)Java程序創(chuàng)建新對(duì)象時(shí),這些對(duì)象首先會(huì)被分配到Eden區(qū)。
    • 存儲(chǔ)內(nèi)容:新創(chuàng)建的對(duì)象,如果對(duì)象的大小超過(guò)了Eden區(qū)的可用空間,那么會(huì)觸發(fā)一次Minor GC(垃圾回收),此時(shí)大對(duì)象會(huì)被直接晉升到老年代。
  2. Survivor From區(qū)
    • 作用:Survivor From區(qū)用于存放經(jīng)歷了一次或多次Minor GC后仍然存活的對(duì)象。在每次Minor GC后,Eden區(qū)和Survivor From區(qū)中的存活對(duì)象會(huì)被復(fù)制到Survivor To區(qū),然后清空Eden區(qū)和Survivor From區(qū)。
    • 存儲(chǔ)內(nèi)容:在Minor GC后從Eden區(qū)復(fù)制過(guò)來(lái)的存活對(duì)象,以及之前Survivor To區(qū)復(fù)制過(guò)來(lái)的對(duì)象。
  3. Survivor To區(qū)
    • 作用:Survivor To區(qū)的作用與Survivor From區(qū)類(lèi)似,也用于存放經(jīng)歷了一次或多次Minor GC后仍然存活的對(duì)象。但在每次Minor GC后,Survivor From區(qū)和Eden區(qū)中的存活對(duì)象會(huì)被復(fù)制到Survivor To區(qū),然后Survivor From區(qū)和Survivor To區(qū)的角色會(huì)互換。
    • 存儲(chǔ)內(nèi)容:在Minor GC后從Eden區(qū)和Survivor From區(qū)復(fù)制過(guò)來(lái)的存活對(duì)象。
      這種年輕代的設(shè)計(jì)(特別是Eden區(qū)和兩個(gè)Survivor區(qū)的比例通常為8:1:1)是為了優(yōu)化垃圾回收的性能和效率。通過(guò)復(fù)制算法,JVM可以快速地清理不再使用的對(duì)象,并為新對(duì)象提供空間。同時(shí),通過(guò)動(dòng)態(tài)調(diào)整對(duì)象的年齡和晉升策略,JVM可以平衡內(nèi)存使用和性能之間的關(guān)系。

3.jvm垃圾回收器

3.1 Serial收集器

image

1.jdk1.3.1 之前都是使用這個(gè)收集器
2.垃圾回收器會(huì)停止所有用戶線程
3.單線程處理垃圾回收
4.新生代使用復(fù)制算法,老年代使用標(biāo)記-整理 算法

3.2 Parallel Scavenge收集器

image

使用多線程處理垃圾回收,jdk1.8 默認(rèn)垃圾回收器

3.3 CMS(Concurrent Mark Sweep)收集器

image

cms收集器時(shí)為了獲取最短回收停頓時(shí)間為目的的收集器,基于標(biāo)記-清除算法實(shí)現(xiàn),主要分為以下4個(gè)步驟
1.初始標(biāo)記 :停止用戶線程,標(biāo)記GC-Roots能直接關(guān)聯(lián)的對(duì)象,速度很快
2.并發(fā)標(biāo)記:就是roots-tracing(尋根)過(guò)程
3.重新標(biāo)記:修正并發(fā)標(biāo)記過(guò)程中用戶線程運(yùn)行導(dǎo)致標(biāo)記變動(dòng)的對(duì)象的再次標(biāo)記,停止用戶線程,時(shí)間較長(zhǎng)
4.并發(fā)清除:多線程清除回收對(duì)象
缺點(diǎn)
CMS收集器會(huì)產(chǎn)生內(nèi)存碎片,并且不適合處理大量浮動(dòng)垃圾的場(chǎng)景。

3.4 G1(Garbage-First)收集器

G1收集器

4.jvm年輕代,老年代,元空間回收策略

4.1 年輕代

年輕代主要存放新創(chuàng)建的對(duì)象,通常又被細(xì)分為Eden區(qū)和兩個(gè)Survivor區(qū)(S0和S1)。年輕代的回收策略通常采用復(fù)制(Copying)算法標(biāo)記-清除(Mark-Sweep)算法。

  • Eden區(qū):新對(duì)象首先被分配到Eden區(qū)。當(dāng)Eden區(qū)滿時(shí),會(huì)觸發(fā)一次Minor GC(也稱為Young GC)。在這次GC中,存活的對(duì)象會(huì)被復(fù)制到Survivor區(qū)(S0或S1),而死亡的對(duì)象會(huì)被清理掉。
  • Survivor區(qū):Survivor區(qū)用于存放經(jīng)歷了一次或多次Minor GC后仍然存活的對(duì)象。在每次Minor GC后,Eden區(qū)和一個(gè)Survivor區(qū)中的存活對(duì)象會(huì)被復(fù)制到另一個(gè)Survivor區(qū),然后清空Eden區(qū)和被復(fù)制的Survivor區(qū)。這個(gè)過(guò)程會(huì)不斷重復(fù),直到對(duì)象晉升到老年代。

4.2 老年代

老年代存放長(zhǎng)時(shí)間存活的對(duì)象。老年代的回收策略通常采用標(biāo)記-清除(Mark-Sweep)算法標(biāo)記-整理(Mark-Compact)算法。

  • 標(biāo)記-清除算法:首先標(biāo)記出存活的對(duì)象,然后清理掉未標(biāo)記的(即死亡的)對(duì)象。這種算法可能會(huì)導(dǎo)致內(nèi)存碎片。
  • 標(biāo)記-整理算法:在標(biāo)記存活對(duì)象后,將所有存活的對(duì)象向一端移動(dòng),然后清理掉邊界以外的內(nèi)存。這樣可以避免內(nèi)存碎片的產(chǎn)生。
    老年代的GC通常稱為Major GC或Full GC,因?yàn)樗鼤?huì)同時(shí)回收年輕代和老年代中的垃圾對(duì)象。Major GC的頻率通常比Minor GC低,但每次GC的停頓時(shí)間可能會(huì)更長(zhǎng),對(duì)應(yīng)用程序的影響也更大。
    說(shuō)明
    虛擬機(jī)會(huì)給每個(gè)對(duì)象定義一個(gè)對(duì)象年齡計(jì)數(shù)器,在Eden區(qū)每經(jīng)過(guò)一次Minor GC存活對(duì)象,并且Survivor區(qū)能個(gè)容納這些存活對(duì)象,被移動(dòng)到Survivor區(qū)后,年齡計(jì)數(shù)器就會(huì)+1 默認(rèn)是15,超過(guò)這個(gè)閾值就會(huì)進(jìn)入到老年區(qū),可以通過(guò)參數(shù)-XX:MaxTenuringThreshold 來(lái)設(shè)置這個(gè)閾值
    發(fā)生Minor GC 時(shí),虛擬機(jī)會(huì)檢測(cè)之前每次晉升到老年代的平均大小是否大于老年代剩余空間大小,如果大于,則直接進(jìn)行一次Full GC ,如果小于則查看HandlePromotionFailure設(shè)置是否允許擔(dān)保失敗,如果允許,只進(jìn)行MinorGC ,反之進(jìn)行Full GC.

4.3 元空間

元空間的垃圾回收主要發(fā)生在類(lèi)加載器不再需要加載類(lèi)時(shí),即當(dāng)類(lèi)加載器被垃圾回收時(shí),它加載的類(lèi)的元數(shù)據(jù)也會(huì)被清理掉。元空間的垃圾回收通常不會(huì)引發(fā)應(yīng)用程序的停頓。

5.調(diào)優(yōu)參數(shù)

  1. 堆內(nèi)存設(shè)置
    • -Xms-Xmx:分別設(shè)置JVM啟動(dòng)時(shí)的初始堆內(nèi)存和最大堆內(nèi)存。例如,-Xms256m -Xmx256m。
    • -XX:NewSize-XX:MaxNewSize:設(shè)置年輕代的初始大小和最大大小。
    • -XX:SurvivorRatio:設(shè)置Eden區(qū)與Survivor區(qū)的大小比值。例如,-XX:SurvivorRatio=8 表示Eden區(qū)與一個(gè)Survivor區(qū)的大小之比為8:1:1。
  2. 垃圾回收器選擇
    • -XX:+UseParallelGC:選擇Parallel Scavenge收集器和Parallel Old收集器作為垃圾回收器。
    • -XX:+UseConcMarkSweepGC:選擇CMS收集器。
    • -XX:+UseG1GC:選擇G1收集器。
  3. 垃圾回收調(diào)優(yōu)
    • -XX:ParallelGCThreads:設(shè)置Parallel Scavenge收集器的線程數(shù)。
    • -XX:CMSInitiatingOccupancyFraction:設(shè)置CMS收集器開(kāi)始GC前老年代占用的百分比。
    • -XX:G1HeapRegionSize:設(shè)置G1收集器中每個(gè)Region的大小。
  4. 元空間設(shè)置
    • -XX:MetaspaceSize:設(shè)置元空間的初始大小。
    • -XX:MaxMetaspaceSize:設(shè)置元空間的最大大小。
  5. 其他調(diào)優(yōu)參數(shù)
    • -XX:+PrintGC:打印GC日志。
    • -XX:+PrintGCDetails:打印詳細(xì)的GC日志。
    • -XX:+HeapDumpOnOutOfMemoryError:當(dāng)發(fā)生OutOfMemoryError時(shí),導(dǎo)出堆的快照。
    • -XX:OnOutOfMemoryError:當(dāng)發(fā)生OutOfMemoryError時(shí),執(zhí)行指定的命令。
    • -XX:HeapDumpPath=<path-to-heap-dump-file>: 指定堆轉(zhuǎn)儲(chǔ)文件的輸出路徑。
    • -Xloggc:<path-to-gc-log-file>: 指定 GC 日志文件的輸出路徑。
    • -XX:+PrintGCDetails: 打印詳細(xì)的 GC 日志。
    • -XX:+PrintGCDateStamps: 在 GC 日志中打印時(shí)間戳。
    • -XX:+PrintGCApplicationStoppedTime: 打印 GC 暫停應(yīng)用程序的時(shí)間。
    • -XX:+PrintHeapAtGC: 在每次 GC 后打印堆的詳細(xì)信息。
    • -XX:+PrintTenuringDistribution: 打印對(duì)象年齡分布。
    • -XX:+UseGCLogFileRotation: 啟用 GC 日志文件輪換。
    • -XX:NumberOfGCLogFiles=<number>: 設(shè)置要保留的 GC 日志文件的數(shù)量。
    • -XX:GCLogFileSize=<size>: 設(shè)置每個(gè) GC 日志文件的大小。
    • -XX:+PrintAdaptiveSizePolicy: 打印自適應(yīng)大小的 GC 策略信息。

6.調(diào)優(yōu)命令與工具

6.1 jps

jps是一個(gè)常用的命令行工具,用于顯示指定系統(tǒng)內(nèi)所有的HotSpot虛擬機(jī)進(jìn)程,并顯示JVM主類(lèi)名或jar包名,以及JVM進(jìn)程的本地虛擬機(jī)唯一標(biāo)識(shí)符(LVMID,即pid)
常用方法如下

jps 

這將列出所有HotSpot JVM進(jìn)程的pid和主類(lèi)名或jar包名。

jps -l

使用-l選項(xiàng),jps會(huì)輸出主類(lèi)或jar的完全路徑名。

ps -v

使用-v選項(xiàng),jps會(huì)輸出傳遞給JVM的命令行參數(shù)。

jps -m

使用-m選項(xiàng),jps會(huì)輸出傳遞給main方法的參數(shù)。

jps -l -v <pid>

你可以結(jié)合使用-l-v選項(xiàng),并指定pid,以獲取特定JVM進(jìn)程的詳細(xì)信息和參數(shù)。

6.2 jmap

用于生成堆內(nèi)存映射或堆轉(zhuǎn)儲(chǔ)文件。這對(duì)于分析內(nèi)存泄漏和內(nèi)存使用情況非常有幫助
常用命令如下

jmap -heap <pid>

這個(gè)命令用于打印堆內(nèi)存的使用情況,包括堆內(nèi)存的大小、新生代、老年代、元空間等區(qū)域的使用情況

jmap -dump:format=b,file=<filename> <pid>

這個(gè)命令用于生成堆的轉(zhuǎn)儲(chǔ)文件,通常用于后續(xù)的內(nèi)存分析。format=b表示輸出的文件是二進(jìn)制格式,<filename>是轉(zhuǎn)儲(chǔ)文件的名稱,<pid>是目標(biāo)Java進(jìn)程的進(jìn)程ID。

jmap -histo[:live] <pid>

這個(gè)命令用于統(tǒng)計(jì)堆中對(duì)象的數(shù)量,并按類(lèi)名、對(duì)象數(shù)量和所占空間大小進(jìn)行排序。加上:live選項(xiàng)后,只統(tǒng)計(jì)活的對(duì)象。

jmap -finalizerinfo <pid>

這個(gè)命令用于顯示那些在F-Queue隊(duì)列中等待Finalizer線程執(zhí)行finalizer方法的對(duì)象信息。

jmap -clstats <pid>

這個(gè)命令用于打印類(lèi)加載器的統(tǒng)計(jì)信息,包括加載的類(lèi)數(shù)量、總字節(jié)數(shù)等。

6.3 jstack

jstack(Stack Trace for Java)是一個(gè)用于生成Java虛擬機(jī)(JVM)當(dāng)前時(shí)刻的線程堆棧跟蹤信息的命令行工具。這對(duì)于分析線程問(wèn)題,如死鎖、線程阻塞、線程掛起等非常有用。

jstack <pid>

這個(gè)命令用于生成指定Java進(jìn)程ID(<pid>)的線程堆棧跟蹤信息,并將其打印到標(biāo)準(zhǔn)輸出(通常是終端或控制臺(tái))。

jstack <pid> > stacktraces.txt

jstack的輸出重定向到一個(gè)文件中,以便后續(xù)分析。在這個(gè)例子中,輸出被重定向到名為stacktraces.txt的文件中。

jstack -l <pid>

使用-l選項(xiàng),jstack會(huì)輸出關(guān)于鎖的附加信息,這有助于識(shí)別死鎖和鎖爭(zhēng)用情況

jstack -F <pid>

如果正常的jstack請(qǐng)求不被響應(yīng)(例如,進(jìn)程掛起或崩潰),你可以使用-F選項(xiàng)強(qiáng)制jstack輸出線程堆棧信息。這會(huì)將輸出發(fā)送到標(biāo)準(zhǔn)錯(cuò)誤(stderr)。

6.4 jstat

用于實(shí)時(shí)監(jiān)控JVM的各種運(yùn)行狀態(tài)信息,包括類(lèi)加載、垃圾回收、即時(shí)編譯等。

jstat -class <pid>

這個(gè)命令用于顯示關(guān)于類(lèi)加載的信息,包括加載的類(lèi)數(shù)量、所占用空間大小、未加載的數(shù)量等。

jstat -compiler <pid>

這個(gè)命令顯示JVM實(shí)時(shí)編譯的數(shù)量等信息。

jstat -gc <pid>

這個(gè)命令提供關(guān)于垃圾收集的統(tǒng)計(jì)信息,包括新生代、老年代、永久代的垃圾收集情況。

jstat -gcnew <pid>

這個(gè)命令顯示年輕代對(duì)象的信息,包括Eden區(qū)、Survivor區(qū)的使用情況。

jstat -gcnewcapacity <pid>

這個(gè)命令顯示年輕代對(duì)象的信息及其占用量。

jstat -gcold <pid>

這個(gè)命令顯示老年代對(duì)象的信息。

jstat -gcoldcapacity <pid>

這個(gè)命令顯示老年代對(duì)象的信息及其占用量。

jstat -gcpermcapacity <pid>

這個(gè)命令顯示永久代對(duì)象的信息及其占用量。

jstat -printcompilation <pid>

這個(gè)命令顯示當(dāng)前VM正在執(zhí)行的編譯任務(wù)。

jstat -gc <pid> 1000 10

這個(gè)命令會(huì)每隔1000毫秒(1秒)查詢一次垃圾收集統(tǒng)計(jì),總共查詢10次。

6.5 jinfo

用于實(shí)時(shí)查看和調(diào)整 Java 虛擬機(jī)的各項(xiàng)參數(shù)。它既可以顯示 JVM 的系統(tǒng)屬性,也可以顯示命令行參數(shù),甚至支持在運(yùn)行時(shí)動(dòng)態(tài)地更改部分參數(shù)。

jinfo [option] <pid>

其中,<pid> 是目標(biāo) Java 進(jìn)程的進(jìn)程 ID,而 option 可以是以下信息:

  • -flag <name>: 打印指定 Java 虛擬機(jī)的參數(shù)值。
  • -flag [+|-]<name>: 設(shè)置或取消指定 Java 虛擬機(jī)參數(shù)的布爾值。例如jinfo -flag +PrintGC 13297,啟用了PrintGC,GC后,便會(huì)輸出GC日志。
  • -flag <name>=<value>: 設(shè)置指定 Java 虛擬機(jī)的參數(shù)的值。例如 jinfo -flag HeapDumpPath=/java/dump.hprof 15525 設(shè)置dump文件的路徑

6.6 dump文件

  • 配置轉(zhuǎn)儲(chǔ)dump文件
    方式1:自動(dòng)生成到指定位置

    java -Xloggc:/path/to/gc.log -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dump.hprof  -jar your-application.jar
    

    方式2:手動(dòng)導(dǎo)出到指定位置

    jmap -dump:format=b,file=D://heapdump.hprof 14696
    

    14696 是進(jìn)程號(hào)

  • 使用工具分析dump文件

7.調(diào)優(yōu)案例

7.1 需要調(diào)優(yōu)嗎?

一般來(lái)說(shuō),JVM調(diào)優(yōu)不是常規(guī)手段,而是Java性能優(yōu)化的最后選擇。JVM經(jīng)過(guò)不斷的優(yōu)化迭代,其默認(rèn)參數(shù)通常能夠滿足絕大部分應(yīng)用場(chǎng)景的需求。

然而,在某些特殊情況下,如資源非常緊張或?qū)π阅苡袠O高要求的環(huán)境中,可能需要根據(jù)線上實(shí)際JVM各個(gè)區(qū)域的大小占比來(lái)進(jìn)行適當(dāng)?shù)膮?shù)調(diào)整。例如,當(dāng)Heap內(nèi)存(老年代)持續(xù)上漲達(dá)到設(shè)置的最大內(nèi)存值、Full GC次數(shù)頻繁、GC停頓時(shí)間過(guò)長(zhǎng)(超過(guò)1秒)、應(yīng)用出現(xiàn)OutOfMemory等內(nèi)存異常、應(yīng)用中有使用本地緩存且占用大量?jī)?nèi)存空間、系統(tǒng)吞吐量與響應(yīng)性能不高或下降等情況時(shí),可以考慮進(jìn)行JVM調(diào)優(yōu)。

在進(jìn)行JVM調(diào)優(yōu)時(shí),需要全面監(jiān)控和詳細(xì)分析性能數(shù)據(jù),確保調(diào)整的參數(shù)能夠真正提升系統(tǒng)性能。同時(shí),也要注意不要過(guò)度優(yōu)化,以免引入新的問(wèn)題或增加系統(tǒng)的復(fù)雜性。

7.2 調(diào)優(yōu)量化指標(biāo)

JVM調(diào)優(yōu)的量化指標(biāo)主要包括以下幾個(gè)方面:

  1. 內(nèi)存占用量:過(guò)高的內(nèi)存占用可能導(dǎo)致系統(tǒng)資源緊張,影響整體性能。通常,我們可以通過(guò)監(jiān)控工具來(lái)觀察JVM的內(nèi)存使用情況,包括堆內(nèi)存、棧內(nèi)存和方法區(qū)內(nèi)存的使用情況。在調(diào)優(yōu)過(guò)程中,需要關(guān)注內(nèi)存占用率是否過(guò)高,以及是否存在內(nèi)存泄漏等問(wèn)題。

  2. 延遲(或停頓時(shí)間):延遲是指由于垃圾收集等原因?qū)е碌某绦驎和r(shí)間。對(duì)于需要快速響應(yīng)的應(yīng)用來(lái)說(shuō),延遲是一個(gè)關(guān)鍵指標(biāo)。調(diào)優(yōu)過(guò)程中需要關(guān)注垃圾收集的頻率和持續(xù)時(shí)間,以及選擇合適的垃圾收集器來(lái)降低延遲。

  3. 吞吐量:吞吐量是指單位時(shí)間內(nèi)程序完成的任務(wù)數(shù)量。在JVM調(diào)優(yōu)中,我們通常關(guān)注CPU在用戶應(yīng)用程序運(yùn)行的時(shí)間與總運(yùn)行時(shí)間的比例,即程序的執(zhí)行效率。提高吞吐量可以通過(guò)優(yōu)化代碼、減少不必要的對(duì)象創(chuàng)建和銷(xiāo)毀、使用合適的數(shù)據(jù)結(jié)構(gòu)等方式實(shí)現(xiàn)。

  4. Full GC次數(shù):Full GC(全局垃圾收集)是JVM中一種較為耗時(shí)的垃圾收集方式,會(huì)暫停所有用戶線程進(jìn)行垃圾回收。因此,F(xiàn)ull GC的次數(shù)也是評(píng)估JVM性能的重要指標(biāo)之一。在調(diào)優(yōu)過(guò)程中,需要關(guān)注Full GC的觸發(fā)頻率和原因,以及如何通過(guò)調(diào)整JVM參數(shù)或優(yōu)化代碼來(lái)減少Full GC的次數(shù)。

  5. 響應(yīng)時(shí)間:對(duì)于交互式應(yīng)用來(lái)說(shuō),響應(yīng)時(shí)間是一個(gè)關(guān)鍵指標(biāo)。它反映了系統(tǒng)對(duì)用戶請(qǐng)求的響應(yīng)速度。在JVM調(diào)優(yōu)中,需要關(guān)注系統(tǒng)的平均響應(yīng)時(shí)間和最大響應(yīng)時(shí)間,以確保用戶體驗(yàn)的順暢性。

需要注意的是,以上指標(biāo)并不是孤立的,它們之間相互影響、相互制約。在JVM調(diào)優(yōu)過(guò)程中,需要綜合考慮各個(gè)指標(biāo)的表現(xiàn),找到最佳的平衡點(diǎn)。同時(shí),不同應(yīng)用場(chǎng)景和優(yōu)化目標(biāo)可能需要關(guān)注不同的指標(biāo)和調(diào)優(yōu)方法。因此,在進(jìn)行JVM調(diào)優(yōu)時(shí),需要明確優(yōu)化目標(biāo)并結(jié)合實(shí)際場(chǎng)景來(lái)選擇合適的調(diào)優(yōu)策略。

7.3 監(jiān)控前準(zhǔn)備工作

  • 1.安裝Jprofiler監(jiān)控程序(可監(jiān)控本地或遠(yuǎn)程)

  • 2.應(yīng)用程序添加如下參數(shù)

-Xloggc:/gc.log #GC日志記錄文件
-XX:+PrintGCDetails # 打印GC日志
-XX:+HeapDumpOnOutOfMemoryError 
-XX:HeapDumpPath=/dump.hprof
  • 3.安裝GCView 分析GC日志用
  • 4.優(yōu)化前堆棧配置
    使用jmap -heap pid 查看堆內(nèi)存信息如下
Heap Configuration:
   MinHeapFreeRatio         = 0
   MaxHeapFreeRatio         = 100
   MaxHeapSize              = 268435456 (256.0MB)
   NewSize                  = 89128960 (85.0MB)
   MaxNewSize               = 89128960 (85.0MB)
   OldSize                  = 179306496 (171.0MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

7.4分析案例

7.4.1 OutOfMemoryError

常見(jiàn)的OutOfMemoryError分別有一下幾類(lèi)

  • HeapOutOfMemoryError
    這個(gè)錯(cuò)誤表明 Java 堆內(nèi)存不足。Java 堆是 JVM 用于存儲(chǔ)對(duì)象實(shí)例的區(qū)域。當(dāng)應(yīng)用程序創(chuàng)建對(duì)象并且 JVM 無(wú)法在堆中分配足夠的空間時(shí),就會(huì)拋出此錯(cuò)誤。
  • GCOverheadLimitExceededError
    這個(gè)錯(cuò)誤發(fā)生在垃圾收集器花費(fèi)太多時(shí)間嘗試回收內(nèi)存,但實(shí)際上回收到的內(nèi)存很少。這通常意味著存在內(nèi)存泄漏,或者應(yīng)用程序需要更多的堆內(nèi)存。
  • Metaspace
    在 Java 8 及之后的版本中,元數(shù)據(jù)區(qū)(Metaspace)取代了永久代。如果 Metaspace 空間不足,就會(huì)拋出此錯(cuò)誤。
  • DirectBufferMemory
    這個(gè)錯(cuò)誤與直接緩沖區(qū)相關(guān),直接緩沖區(qū)是 Java NIO(New I/O)包中用于高效 I/O 操作的一種內(nèi)存區(qū)域。如果 JVM 無(wú)法為直接緩沖區(qū)分配更多內(nèi)存,就會(huì)拋出此錯(cuò)誤。

分析與定位
生成一般容易產(chǎn)生OutOfMemoryError異常的場(chǎng)景有,遞歸調(diào)用,導(dǎo)入導(dǎo)出,報(bào)表查詢等等,這些操作過(guò)程中容易將數(shù)據(jù)都放在內(nèi)存中,導(dǎo)致OutOfMemoryError,其次是業(yè)務(wù)量起來(lái)后目前jvm配置參數(shù)不能支持當(dāng)前業(yè)務(wù)并發(fā)量的時(shí)候也會(huì)產(chǎn)生OutOfMemoryError異常,所以,OutOfMemoryError分析與定位分下面兩方面。

  • 代碼導(dǎo)致
    OutOfMemoryError 一般會(huì)打印異常日志,日志中分析定位引發(fā)問(wèn)題的代碼進(jìn)行優(yōu)化
  • 并發(fā)量導(dǎo)致
    這種場(chǎng)景下,需要分析根據(jù)目前機(jī)器內(nèi)存和并發(fā)量大小配置什么樣比例的jvm內(nèi)存才合適,參考下面文章估算思路
    億級(jí)流量電商系統(tǒng)JVM模型參數(shù)預(yù)估方案

7.4.2 響應(yīng)延遲

從jvm的角度分析(默認(rèn)Parallel 收集器),響應(yīng)延遲一般都是因?yàn)樵诶厥者^(guò)程中標(biāo)記過(guò)程需要stop-the-world,沒(méi)辦法處理客戶端請(qǐng)求,而且如果發(fā)生full gc的話,年輕代,老年代,元空間都需要被回收,整個(gè)過(guò)程時(shí)間又被拉長(zhǎng),這就導(dǎo)致了響應(yīng)延遲,所以,這方面優(yōu)化主要是控制年輕代回收過(guò)程標(biāo)記過(guò)程時(shí)間,和避免full gc的頻繁發(fā)生

7.4.3 CPU飆升

排查步驟
1.通過(guò)top 命令查看占用cpu資源的進(jìn)程
2.通過(guò)ps -mp 命令把這個(gè)pid下的線程占用cpu情況查出來(lái)
3.通過(guò)printf “%x\n” tid 命令把這個(gè)id轉(zhuǎn)換成16進(jìn)制的數(shù)字
4.通過(guò)jstack pid grep tid 找到引起異常的java堆棧信息代碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容