<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          面試官:你工作中做過 JVM 調(diào)優(yōu)嗎?怎么做的?

          共 11288字,需瀏覽 23分鐘

           ·

          2022-11-18 11:39

          最近很多小伙伴跟我說,自己學(xué)了不少JVM的調(diào)優(yōu)知識,但是在實(shí)際工作中卻不知道何時對JVM進(jìn)行調(diào)優(yōu)。今天,我就為大家介紹幾種JVM調(diào)優(yōu)的場景。

          在閱讀本文時,假定大家已經(jīng)了解了運(yùn)行時的數(shù)據(jù)區(qū)域和常用的垃圾回收算法,也了解了Hotspot支持的垃圾回收器。

          cpu占用過高

          cpu占用過高要分情況討論,是不是業(yè)務(wù)上在搞活動,突然有大批的流量進(jìn)來,而且活動結(jié)束后cpu占用率就下降了,如果是這種情況其實(shí)可以不用太關(guān)心,因?yàn)檎埱笤蕉啵枰幚淼木€程數(shù)越多,這是正常的現(xiàn)象。話說回來,如果你的服務(wù)器配置本身就差,cpu也只有一個核心,這種情況,稍微多一點(diǎn)流量就真的能夠把你的cpu資源耗盡,這時應(yīng)該考慮先把配置提升吧。

          第二種情況,cpu占用率長期過高,這種情況下可能是你的程序有那種循環(huán)次數(shù)超級多的代碼,甚至是出現(xiàn)死循環(huán)了。排查步驟如下:

          (1)用top命令查看cpu占用情況

          這樣就可以定位出cpu過高的進(jìn)程。在linux下,top命令獲得的進(jìn)程號和jps工具獲得的vmid是相同的:


          (2)用top -Hp命令查看線程的情況

          可以看到是線程id為7287這個線程一直在占用cpu

          (3)把線程號轉(zhuǎn)換為16進(jìn)制

          [root@localhost ~]# printf "%x" 7287
          1c77

          記下這個16進(jìn)制的數(shù)字,下面我們要用

          (4)用jstack工具查看線程棧情況

          [root@localhost ~]# jstack 7268 | grep 1c77 -A 10
          "http-nio-8080-exec-2" #16 daemon prio=5 os_prio=0 tid=0x00007fb66ce81000 nid=0x1c77 runnable [0x00007fb639ab9000]
             java.lang.Thread.State: RUNNABLE
           at com.spareyaya.jvm.service.EndlessLoopService.service(EndlessLoopService.java:19)
           at com.spareyaya.jvm.controller.JVMController.endlessLoop(JVMController.java:30)
           at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
           at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
           at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
           at java.lang.reflect.Method.invoke(Method.java:498)
           at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190)
           at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138)
           at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105)

          通過jstack工具輸出現(xiàn)在的線程棧,再通過grep命令結(jié)合上一步拿到的線程16進(jìn)制的id定位到這個線程的運(yùn)行情況,其中jstack后面的7268是第(1)步定位到的進(jìn)程號,grep后面的是(2)、(3)步定位到的線程號。

          從輸出結(jié)果可以看到這個線程處于運(yùn)行狀態(tài),在執(zhí)行com.spareyaya.jvm.service.EndlessLoopService.service這個方法,代碼行號是19行,這樣就可以去到代碼的19行,找到其所在的代碼塊,看看是不是處于循環(huán)中,這樣就定位到了問題。

          死鎖

          死鎖并沒有第一種場景那么明顯,web應(yīng)用肯定是多線程的程序,它服務(wù)于多個請求,程序發(fā)生死鎖后,死鎖的線程處于等待狀態(tài)(WAITING或TIMED_WAITING),等待狀態(tài)的線程不占用cpu,消耗的內(nèi)存也很有限,而表現(xiàn)上可能是請求沒法進(jìn)行,最后超時了。在死鎖情況不多的時候,這種情況不容易被發(fā)現(xiàn)。

          可以使用jstack工具來查看

          (1)jps查看java進(jìn)程

          [root@localhost ~]# jps -l
          8737 sun.tools.jps.Jps
          8682 jvm-0.0.1-SNAPSHOT.jar

          (2)jstack查看死鎖問題

          由于web應(yīng)用往往會有很多工作線程,特別是在高并發(fā)的情況下線程數(shù)更多,于是這個命令的輸出內(nèi)容會十分多。jstack最大的好處就是會把產(chǎn)生死鎖的信息(包含是什么線程產(chǎn)生的)輸出到最后,所以我們只需要看最后的內(nèi)容就行了

          Java stack information for the threads listed above:
          ===================================================
          "Thread-4":
           at com.spareyaya.jvm.service.DeadLockService.service2(DeadLockService.java:35)
           - waiting to lock <0x00000000f5035ae0> (a java.lang.Object)
           - locked <0x00000000f5035af0> (a java.lang.Object)
           at com.spareyaya.jvm.controller.JVMController.lambda$deadLock$1(JVMController.java:41)
           at com.spareyaya.jvm.controller.JVMController$$Lambda$457/1776922136.run(Unknown Source)
           at java.lang.Thread.run(Thread.java:748)
          "Thread-3":
           at com.spareyaya.jvm.service.DeadLockService.service1(DeadLockService.java:27)
           - waiting to lock <0x00000000f5035af0> (a java.lang.Object)
           - locked <0x00000000f5035ae0> (a java.lang.Object)
           at com.spareyaya.jvm.controller.JVMController.lambda$deadLock$0(JVMController.java:37)
           at com.spareyaya.jvm.controller.JVMController$$Lambda$456/474286897.run(Unknown Source)
           at java.lang.Thread.run(Thread.java:748)

          Found 1 deadlock.

          發(fā)現(xiàn)了一個死鎖,原因也一目了然。

          內(nèi)存泄漏

          我們都知道,java和c++的最大區(qū)別是前者會自動收回不再使用的內(nèi)存,后者需要程序員手動釋放。在c++中,如果我們忘記釋放內(nèi)存就會發(fā)生內(nèi)存泄漏。但是,不要以為jvm幫我們回收了內(nèi)存就不會出現(xiàn)內(nèi)存泄漏。

          程序發(fā)生內(nèi)存泄漏后,進(jìn)程的可用內(nèi)存會慢慢變少,最后的結(jié)果就是拋出OOM錯誤。發(fā)生OOM錯誤后可能會想到是內(nèi)存不夠大,于是把-Xmx參數(shù)調(diào)大,然后重啟應(yīng)用。這么做的結(jié)果就是,過了一段時間后,OOM依然會出現(xiàn)。最后無法再調(diào)大最大堆內(nèi)存了,結(jié)果就是只能每隔一段時間重啟一下應(yīng)用。

          內(nèi)存泄漏的另一個可能的表現(xiàn)是請求的響應(yīng)時間變長了。這是因?yàn)轭l繁發(fā)生的GC會暫停其它所有線程(Stop The World)造成的。

          為了模擬這個場景,使用了以下的程序

          import java.util.concurrent.ExecutorService;
          import java.util.concurrent.Executors;
          public class Main {
              public static void main(String[] args) {
                  Main main = new Main();
                  while (true) {
                      try {
                          Thread.sleep(1);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                      main.run();
                  }
              }

              private void run() {
                  ExecutorService executorService = Executors.newCachedThreadPool();
                  for (int i = 0; i < 10; i++) {
                      executorService.execute(() -> {
                          // do something...
                      });
                  }
              }
          }

          運(yùn)行參數(shù)是-Xms20m -Xmx20m -XX:+PrintGC,把可用內(nèi)存調(diào)小一點(diǎn),并且在發(fā)生gc時輸出信息,運(yùn)行結(jié)果如下

          ...
          [GC (Allocation Failure)  12776K->10840K(18432K), 0.0309510 secs]
          [GC (Allocation Failure)  13400K->11520K(18432K), 0.0333385 secs]
          [GC (Allocation Failure)  14080K->12168K(18432K), 0.0332409 secs]
          [GC (Allocation Failure)  14728K->12832K(18432K), 0.0370435 secs]
          [Full GC (Ergonomics)  12832K->12363K(18432K), 0.1942141 secs]
          [Full GC (Ergonomics)  14923K->12951K(18432K), 0.1607221 secs]
          [Full GC (Ergonomics)  15511K->13542K(18432K), 0.1956311 secs]
          ...
          [Full GC (Ergonomics)  16382K->16381K(18432K), 0.1734902 secs]
          [Full GC (Ergonomics)  16383K->16383K(18432K), 0.1922607 secs]
          [Full GC (Ergonomics)  16383K->16383K(18432K), 0.1824278 secs]
          [Full GC (Allocation Failure)  16383K->16383K(18432K), 0.1710382 secs]
          [Full GC (Ergonomics)  16383K->16382K(18432K), 0.1829138 secs]
          [Full GC (Ergonomics) Exception in thread "main"  16383K->16382K(18432K), 0.1406222 secs]
          [Full GC (Allocation Failure)  16382K->16382K(18432K), 0.1392928 secs]
          [Full GC (Ergonomics)  16383K->16382K(18432K), 0.1546243 secs]
          [Full GC (Ergonomics)  16383K->16382K(18432K), 0.1755271 secs]
          [Full GC (Ergonomics)  16383K->16382K(18432K), 0.1699080 secs]
          [Full GC (Allocation Failure)  16382K->16382K(18432K), 0.1697982 secs]
          [Full GC (Ergonomics)  16383K->16382K(18432K), 0.1851136 secs]
          [Full GC (Allocation Failure)  16382K->16382K(18432K), 0.1655088 secs]
          java.lang.OutOfMemoryError: Java heap space

          可以看到雖然一直在gc,占用的內(nèi)存卻越來越多,說明程序有的對象無法被回收。但是上面的程序?qū)ο蠖际嵌x在方法內(nèi)的,屬于局部變量,局部變量在方法運(yùn)行結(jié)果后,所引用的對象在gc時應(yīng)該被回收啊,但是這里明顯沒有。

          為了找出到底是哪些對象沒能被回收,我們加上運(yùn)行參數(shù)-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heap.bin,意思是發(fā)生OOM時把堆內(nèi)存信息dump出來。運(yùn)行程序直至異常,于是得到heap.dump文件,然后我們借助eclipse的MAT插件來分析,如果沒有安裝需要先安裝。

          然后File->Open Heap Dump... ,然后選擇剛才dump出來的文件,選擇Leak Suspects

          圖片

          MAT會列出所有可能發(fā)生內(nèi)存泄漏的對象

          可以看到居然有21260個Thread對象,3386個ThreadPoolExecutor對象,如果你去看一下java.util.concurrent.ThreadPoolExecutor的源碼,可以發(fā)現(xiàn)線程池為了復(fù)用線程,會不斷地等待新的任務(wù),線程也不會回收,需要調(diào)用其shutdown()方法才能讓線程池執(zhí)行完任務(wù)后停止。

          其實(shí)線程池定義成局部變量,好的做法是設(shè)置成單例。

          上面只是其中一種處理方法

          在線上的應(yīng)用,內(nèi)存往往會設(shè)置得很大,這樣發(fā)生OOM再把內(nèi)存快照dump出來的文件就會很大,可能大到在本地的電腦中已經(jīng)無法分析了(因?yàn)閮?nèi)存不足夠打開這個dump文件)。這里介紹另一種處理辦法:

          (1)用jps定位到進(jìn)程號

          C:\Users\spareyaya\IdeaProjects\maven-project\target\classes\org\example\net>jps -l
          24836 org.example.net.Main
          62520 org.jetbrains.jps.cmdline.Launcher
          129980 sun.tools.jps.Jps
          136028 org.jetbrains.jps.cmdline.Launcher

          因?yàn)橐呀?jīng)知道了是哪個應(yīng)用發(fā)生了OOM,這樣可以直接用jps找到進(jìn)程號135988

          (2)用jstat分析gc活動情況

          jstat是一個統(tǒng)計java進(jìn)程內(nèi)存使用情況和gc活動的工具,參數(shù)可以有很多,可以通過jstat -help查看所有參數(shù)以及含義

          C:\Users\spareyaya\IdeaProjects\maven-project\target\classes\org\example\net>jstat -gcutil -t -h8 24836 1000
          Timestamp         S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT
                     29.1  32.81   0.00  23.48  85.92  92.84  84.13     14    0.339     0    0.000    0.339
                     30.1  32.81   0.00  78.12  85.92  92.84  84.13     14    0.339     0    0.000    0.339
                     31.1   0.00   0.00  22.70  91.74  92.72  83.71     15    0.389     1    0.233    0.622

          上面是命令意思是輸出gc的情況,輸出時間,每8行輸出一個行頭信息,統(tǒng)計的進(jìn)程號是24836,每1000毫秒輸出一次信息。

          輸出信息是Timestamp是距離jvm啟動的時間,S0、S1、E是新生代的兩個Survivor和Eden,O是老年代區(qū),M是Metaspace,CCS使用壓縮比例,YGC和YGCT分別是新生代gc的次數(shù)和時間,F(xiàn)GC和FGCT分別是老年代gc的次數(shù)和時間,GCT是gc的總時間。雖然發(fā)生了gc,但是老年代內(nèi)存占用率根本沒下降,說明有的對象沒法被回收(當(dāng)然也不排除這些對象真的是有用)。

          (3)用jmap工具dump出內(nèi)存快照

          jmap可以把指定java進(jìn)程的內(nèi)存快照dump出來,效果和第一種處理辦法一樣,不同的是它不用等OOM就可以做到,而且dump出來的快照也會小很多。

          jmap -dump:live,format=b,file=heap.bin 24836

          這時會得到heap.bin的內(nèi)存快照文件,然后就可以用eclipse來分析了。

          總結(jié)

          以上三種嚴(yán)格地說還算不上jvm的調(diào)優(yōu),只是用了jvm工具把代碼中存在的問題找了出來。我們進(jìn)行jvm的主要目的是盡量減少停頓時間,提高系統(tǒng)的吞吐量。

          但是如果我們沒有對系統(tǒng)進(jìn)行分析就盲目去設(shè)置其中的參數(shù),可能會得到更壞的結(jié)果,jvm發(fā)展到今天,各種默認(rèn)的參數(shù)可能是實(shí)驗(yàn)室的人經(jīng)過多次的測試來做平衡的,適用大多數(shù)的應(yīng)用場景。

          如果你認(rèn)為你的jvm確實(shí)有調(diào)優(yōu)的必要,也務(wù)必要取樣分析,最后還得慢慢多次調(diào)節(jié),才有可能得到更優(yōu)的效果。

          推薦閱讀:

          世界的真實(shí)格局分析,地球人類社會底層運(yùn)行原理

          不是你需要中臺,而是一名合格的架構(gòu)師(附各大廠中臺建設(shè)PPT)

          企業(yè)IT技術(shù)架構(gòu)規(guī)劃方案

          論數(shù)字化轉(zhuǎn)型——轉(zhuǎn)什么,如何轉(zhuǎn)?

          華為干部與人才發(fā)展手冊(附PPT)

          企業(yè)10大管理流程圖,數(shù)字化轉(zhuǎn)型從業(yè)者必備!

          【中臺實(shí)踐】華為大數(shù)據(jù)中臺架構(gòu)分享.pdf

          華為的數(shù)字化轉(zhuǎn)型方法論

          華為如何實(shí)施數(shù)字化轉(zhuǎn)型(附PPT)

          超詳細(xì)280頁Docker實(shí)戰(zhàn)文檔!開放下載

          華為大數(shù)據(jù)解決方案(PPT)

          瀏覽 39
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  91啪啪视频 | 国产一区二区欧美 | 欧美精产国品一二三产品动漫 | 欧美美女做爱 | 婷婷伊人綜合中文字幕小说 |