<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性能優(yōu)化

          共 10417字,需瀏覽 21分鐘

           ·

          2021-06-19 20:35

          點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號”

          優(yōu)質(zhì)文章,第一時間送達(dá)

            作者 |  IT王小二

          來源 |  urlify.cn/Vzu2Ij

          一、內(nèi)存溢出

          內(nèi)存溢出的原因:程序在申請內(nèi)存時,沒有足夠的空間。

          1. 棧溢出

          方法死循環(huán)遞歸調(diào)用(StackOverflowError)、不斷建立線程(OutOfMemoryError)。

          2. 堆溢出

          不斷創(chuàng)建對象,分配對象大于最大堆的大小(OutOfMemoryError)。

          3. 直接內(nèi)存

          JVM 分配的本地直接內(nèi)存大小大于 JVM 的限制,可以通過-XX:MaxDirectMemorySize 來設(shè)置(不設(shè)置的話默認(rèn)與堆內(nèi)存最大值一樣,也會出現(xiàn)OOM 異常)。

          4. 方法區(qū)溢出

          一個類要被垃圾收集器回收掉,判定條件是比較苛刻的,在經(jīng)常動態(tài)生產(chǎn)大量 Class 的應(yīng)用中,CGLIb 字節(jié)碼增強(qiáng),動態(tài)語言,大量 JSP(JSP 第一次運(yùn)行需要編譯成 Java 類),基于 OSGi 的應(yīng)用(同一個類,被不同的加載器加載也會設(shè)為不同的類),都可能會導(dǎo)致OOM。

          二、內(nèi)存泄露

          程序在申請內(nèi)存后,無法釋放已申請的內(nèi)存空間,導(dǎo)致這一部分的原因主要是代碼寫的不合理,比如以下幾種情況。

          1. 長生命周期的對象持有短生命周期對象的引用

          例如將 ArrayList 設(shè)置為靜態(tài)變量,然后不斷地向ArrayList中添加對象,則 ArrayList 容器中的對象在程序結(jié)束之前將不能被釋放,從而造成內(nèi)存泄漏。

          2. 連接未關(guān)閉

          如數(shù)據(jù)庫連接、網(wǎng)絡(luò)連接和 IO 連接等,只有連接被關(guān)閉后,垃圾回收器才會回收對應(yīng)的對象。

          3. 變量作用域不合理

          例如:

          • 一個變量的定義的作用范圍大于其使用范圍。

          • 如果沒有及時地把對象設(shè)置為 null。

          4. 內(nèi)部類持有外部類

          Java 的 非靜態(tài)內(nèi)部類 的這種創(chuàng)建方式,會隱式地持有外部類的引用,而且默認(rèn)情況下這個引用是強(qiáng)引用,因此,如果內(nèi)部類的生命周期長于外部類的生命周期,程序很容易就產(chǎn)生內(nèi)存泄露(可以理解為:垃圾回收器會回收掉外部類的實(shí)例,但由于內(nèi)部類持有外部類的引用,導(dǎo)致垃圾回收器不能正常工作)。

          解決辦法:將非靜態(tài)內(nèi)部類改為 靜態(tài)內(nèi)部類,即加上 static 修飾,例如:

          public class Jvm5 {
              private static String string = "SuunyBear";

              public static void show() {
                  System.out.println("show");
              }

              public static void main(String[] args) {
                  Jvm5 m = new Jvm5();
                  // 非靜態(tài)內(nèi)部類的構(gòu)造方式
                  // Child c=m.new Child();
                  Child c = new Child();
                  c.test();
              }

              /**
               * 內(nèi)部類Child --靜態(tài)的,防止內(nèi)存泄漏
               */
              static class Child {
                  public int i;

                  public void test() {
                      System.out.println("string:" + string);
                      show();
                  }
              }
          }

          5. Hash值改變

          在集合中,如果修改了對象中的那些參與計(jì)算哈希值的字段,會導(dǎo)致無法從集合中單獨(dú)刪除當(dāng)前對象,造成內(nèi)存泄露。

          使用例子來說明。

          public class Jvm6 {
              private int x;
              private int y;

              public Jvm6(int x, int y) {
                  super();
                  this.x = x;
                  this.y = y;
              }
              /**
               * 重寫HashCode的方法
               */
              @Override
              public int hashCode() {
                  final int prime = 31;
                  int result = 1;
                  result = prime * result + x;
                  result = prime * result + y;
                  return result;
              }
              /**
               * 改變y的值:同時改變hashcode
               */
              public void setY(int y) {
                  this.y = y;
              }

              public static void main(String[] args) {
                  HashSet<Jvm6> hashSet = new HashSet<Jvm6>();
                  Jvm6 data1 = new Jvm6(1, 3);
                  Jvm6 data2 = new Jvm6(3, 5);
                  hashSet.add(data1);
                  hashSet.add(data2);
                  data2.setY(7); // data2的Hash值改變
                  hashSet.remove(data2); // 刪掉data2節(jié)點(diǎn)
                  System.out.println(hashSet.size()); // 2
              }
          }

          三、內(nèi)存溢出和內(nèi)存泄漏辨析

          • 內(nèi)存溢出:實(shí)實(shí)在在的內(nèi)存空間不足導(dǎo)致。

          • 內(nèi)存泄漏:該釋放的對象沒有釋放,常見于使用容器保存元素的情況下。

          如何避免

          • 內(nèi)存溢出:檢查代碼以及設(shè)置足夠的空間。

          • 內(nèi)存泄漏:一定是代碼有問題,往往很多情況下,內(nèi)存溢出往往是內(nèi)存泄漏造成的。

          四、了解MAT

          mat是一個內(nèi)存泄露的分析工具。

          1. 淺堆和深堆

          • 淺堆(Shallow Heap):是指一個對象所消耗的內(nèi)存。

          • 深堆(Retained Heap):這個對象被 GC 回收后,可以真實(shí)釋放的內(nèi)存大小,也就是只能通過對象被直接或間接訪問到的所有對象的集合。通俗地說,就是一個對象包含(引用)的所有對象的大小,如圖:

          2. MAT的使用

          1、下載MAT工具:下載地址

          2、內(nèi)存溢出例子演示

          參數(shù)說明:

          • -Xms5m 堆初始大小5M

          • -Xmx5m 堆最大大小5M

          • -XX:+PrintGCDetails 打印gc日志詳情

          • -XX:+HeapDumpOnOutOfMemoryError 輸出內(nèi)存溢出文件

          • -XX:HeapDumpPath=D:/oomDump/dump.hprof 內(nèi)存溢出文件保存位置,此文件用于MAT分析

          /**
           * VM Args:-Xms5m -Xmx5m  -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:/oomDump/dump.hprof
           */
          public class Jvm7 {

              public static void main(String[] args) {
                  // 在方法執(zhí)行的過程中,它是GCRoots
                  List<Object> list = new LinkedList<>();
                  int i = 0;
                  while (true) {
                      i++;
                      if (i % 10000 == 0) {
                          System.out.println("i=" + i);
                      }
                      list.add(new Object());
                  }
              }
          }

          設(shè)置參數(shù)運(yùn)行后,內(nèi)存溢出,程序結(jié)束,然后我們就可以用下載好的MAT來分析了,當(dāng)然MAT也只是分析猜想,并不代表一定是這個原因?qū)е聝?nèi)存溢出。

          打開我們保存的文件目錄進(jìn)行分析。

          分析結(jié)果。

          此時可以查看詳情查看具體原因,當(dāng)然這個原因也只是一種猜想。

          五、JDK提供的一些工具

          分類屬性值描述
          命令行工具jps虛擬機(jī)進(jìn)程狀況工具
          jstat虛擬機(jī)統(tǒng)計(jì)信息監(jiān)視工具
          jinfoJava配置信息工具
          jmapJava內(nèi)存映像工具
          jhat虛擬機(jī)堆轉(zhuǎn)儲快照分析工具
          jstackJava堆棧跟蹤工具
          可視化工具JConsoleJava監(jiān)視與管理控制臺
          VisualVM多合一故障處理工具

          所有的工具都在jdk的安裝bin目錄下,比如我的在C:\My Program Files\Java\jdk1.8.0_201\bin

          其中一般情況命令行在線上服務(wù)器上使用,可視化工具在本地使用,當(dāng)然如果你的線上服務(wù)器允許遠(yuǎn)程的話也可以使用可視化工具。

          六、GC調(diào)優(yōu)

          1. GC調(diào)優(yōu)重要參數(shù)

          生產(chǎn)環(huán)境推薦開啟

          • -XX:+HeapDumpOnOutOfMemoryError

            • 輸出內(nèi)存溢出文件

          • -XX:HeapDumpPath=D:/oomDump/dump.hprof

            • 內(nèi)存溢出文件保存位置,此文件用于MAT分析

            • 當(dāng)然,一般Linux服務(wù)器可以設(shè)置為 ./java_pid<pid>.hprof 默認(rèn)為Java進(jìn)程啟動位置

          調(diào)優(yōu)之前開始,調(diào)優(yōu)之后關(guān)閉

          • -XX:+PrintGC

            • 調(diào)試跟蹤之 打印簡單的 GC 信息參數(shù):

          • -XX:+PrintGCDetails和-XX:+PrintGCTimeStamps

            • 打印詳細(xì)的 GC 信息

          • -Xlogger:logpath:log/gc.log

            • 設(shè)置 gc 的日志路,將 gc.log 的路徑設(shè)置到當(dāng)前目錄的 log 目錄下. 應(yīng)用場景:將 gc 的日志獨(dú)立寫入日志文件,將 GC 日志與系統(tǒng)業(yè)務(wù)日志進(jìn)行了分離,方便開發(fā)人員進(jìn)行追蹤分析

          考慮使用

          • -XX:+PrintHeapAtGC

            • 打印推信息,獲取 Heap 在每次垃圾回收前后的使用狀況

          • -XX:+TraceClassLoading

            • 在系統(tǒng)控制臺信息中看到 class 加載的過程和具體的 class 信息,可用以分析類的加載順序以及是否可進(jìn)行精簡操作

          • -XX:+DisableExplicitGC

            • 禁止在運(yùn)行期顯式地調(diào)用 System.gc()

          2. GC調(diào)優(yōu)的原則(很重要)

          • 大多數(shù)的 java 應(yīng)用不需要 GC 調(diào)優(yōu)

          • 大部分需要 GC 調(diào)優(yōu)的的,不是參數(shù)問題,是代碼問題

          • 在實(shí)際使用中,分析 GC 情況優(yōu)化代碼 比 優(yōu)化 GC 參數(shù) 要多得多

          • GC 調(diào)優(yōu)是最后的手段

          調(diào)優(yōu)的目的

          • GC 的時間夠小

          • GC 的次數(shù)夠少發(fā)生

          • Full GC 的周期足夠的長,時間合理,最好是不發(fā)生

          注: 如果滿足下面的指標(biāo),則一般不需要進(jìn)行 GC調(diào)優(yōu)

          • Minor GC 執(zhí)行時間不到 50ms

          • Minor GC 執(zhí)行不頻繁,約 10 秒一次

          • Full GC 執(zhí)行時間不到 1s

          • Full GC 執(zhí)行頻率不算頻繁,不低于 10 分鐘 1 次

          3. GC調(diào)優(yōu)步驟

          1、監(jiān)控 GC 的狀態(tài)使用各種 JVM 工具,查看當(dāng)前日志,分析當(dāng)前 JVM 參數(shù)設(shè)置,并且分析當(dāng)前堆內(nèi)存快照和 gc 日志,根據(jù)實(shí)際的各區(qū)域內(nèi)存劃分和 GC 執(zhí)行時間,覺得是否進(jìn)行優(yōu)化。

          2、分析結(jié)果,判斷是否需要優(yōu)化如果各項(xiàng)參數(shù)設(shè)置合理。

          • 系統(tǒng)沒有超時日志出現(xiàn),GC 頻率不高,GC 耗時不高,那么沒有必要進(jìn)行 GC 優(yōu)化。

          • 如果 GC 時間超過 1 秒,或者頻繁 GC,則必須優(yōu)化。

          3、調(diào)整 GC 類型和內(nèi)存分配如果內(nèi)存分配過大或過小,或者采用的 GC 收集器比較慢,則應(yīng)該優(yōu)先調(diào)整這些參數(shù),并且先找 1 臺或幾臺機(jī)器進(jìn)行 測試,然后比較優(yōu)化過的機(jī)器和沒有優(yōu)化的機(jī)器的性能對比,并有針對性的做出最后選擇。

          4、不斷的分析和調(diào)整通過不斷的試驗(yàn)和試錯,分析并找到最合適的參數(shù)5,全面應(yīng)用參數(shù)如果找到了最合適的參數(shù),則將這些參數(shù)應(yīng)用到所有服務(wù)器,并進(jìn)行后續(xù)跟蹤。

          分析GC日志

          主要關(guān)注 MinorGC 和 FullGC 的回收效率(回收前大小和回收比較)、回收的時間。

          1、-XX:+UseSerialGC

          • 以參數(shù)-Xms5m -Xmx5m -XX:+PrintGCDetails -XX:+UseSerialGC 為例詳細(xì)說明。

          • [DefNew: 1855K->1855K(1856K), 0.0000148 secs][Tenured: 2815K->4095K(4096K), 0.0134819 secs] 4671K。

          • DefNew 指明了收集器類型,而且說明了收集發(fā)生在新生代。

          • 1855K->1855K(1856K)表示,回收前 新生代占用 1855K,回收后占用 1855K,新生代大小 1856K

          • 0.0000148 secs 表明新生代回收耗時。

          • Tenured 表明收集發(fā)生在老年代。

          • 2815K->4095K(4096K), 0.0134819 secs:含義同新生代最后的 4671K 指明堆的大小。

          2、-XX:+UseParNewGC

          • 收集器參數(shù)變?yōu)?XX:+UseParNewGC。

          • 日志變?yōu)椋篬ParNew: 1856K->1856K(1856K), 0.0000107 secs][Tenured: 2890K->4095K(4096K), 0.0121148 secs]。

          • 收集器參數(shù)變?yōu)?XX:+ UseParallelGC 或 UseParallelOldGC。

          • 日志變?yōu)椋篬PSYoungGen: 1024K->1022K(1536K)] [ParOldGen: 3783K->3782K(4096K)] 4807K->4804K(5632K)。

          3、-XX:+UseConcMarkSweepGC 和 -XX:+UseG1GC

          使用這兩個收集器的日志會和UseParNewGC一樣有明顯的相關(guān)字樣。

          4. 項(xiàng)目啟動調(diào)優(yōu)

          開啟日志分析-XX:+PrintGCDetails,啟動項(xiàng)目時,通過分析日志,不斷地調(diào)整參數(shù),減少GC次數(shù)。

          例如:

          1、碰到 Metadata空間 不足發(fā)生GC,那么調(diào)整 Metadata空間 -XX:MetaspaceSize=64m 減少 FullGC 。
          2、碰到MinorGC,那么調(diào)整堆空間 
          -Xms1000m 大小減少FullGC 。
          3、如果還是有MinorGC,那么繼續(xù)增大堆空間大小,或者增大新生代比例 
          -Xmn900m GC,此時新生代空間為900m,老年代大小100m 。

          5. 項(xiàng)目運(yùn)行GC調(diào)優(yōu)

          使用 jmeter 工具 來進(jìn)行壓測,然后分析原因,進(jìn)行調(diào)優(yōu),當(dāng)然 正式上線的項(xiàng)目請謹(jǐn)慎操作 。

          jmeter工具安裝使用

          1、下載好對應(yīng)版本的jmeter,注意jdk版本。

          2、jmeter需要Java運(yùn)行時環(huán)境,所以如果報(bào)錯請先檢查你的Java環(huán)境變量設(shè)置,解壓到你想要的路徑,例如我解壓在C:\My Program Files\apache-jmeter-5.2.1,在bin目錄下有一個 jmeter.bat 文件,雙擊啟動。

          至于具體怎么使用就百度吧,基本拿到軟件就知道使用了,畢竟這個說來就浪費(fèi)篇幅了。

          聚合報(bào)告參數(shù)

          這里放出我本地 jmeter 測試一個項(xiàng)目之后的 聚合報(bào)告參數(shù)解釋

          6. 推薦策略(僅作參考)

          1、新生代大小選擇

          • 盡可能設(shè)大,直到接近系統(tǒng)的最低響應(yīng)時間限制(根據(jù)實(shí)際情況選擇).在此種情況下,新生代收集發(fā)生的頻率也是最小的.同時,減少到達(dá)老年代的對象。

          • 避免設(shè)置過小,當(dāng)新生代設(shè)置過小時會導(dǎo)致:MinorGC 次數(shù)更加頻繁、可能導(dǎo)致 MinorGC 對象直接進(jìn)入老年代,如果此時老年代滿了,會觸發(fā) FullGC。

          2、老年代大小選擇

          一般吞吐量優(yōu)先的應(yīng)用都有一個很大的新生代和一個較小的老年代.原因是,這樣可以盡可能回收掉大部分短期對象,減少中期的對象,而老年代盡存放長期存活對象

          七、逃逸分析

          補(bǔ)充知識,并非所有的對象都會在堆上面分配,而沒有在堆上分配的對象是因?yàn)榻?jīng)過逃逸分析,分析之后發(fā)現(xiàn)該對象的大小可以在棧上分配,不會造成棧溢出,這時,對象就可以在棧上分配。

          當(dāng)然,如果經(jīng)過逃逸分析,發(fā)現(xiàn)該對象在棧上分配會照成棧溢出,那么該對象就會在堆空間分配。

          參數(shù)jdk1.8默認(rèn)開啟

          • -XX:+DoEscapeAnalysis 啟用逃逸分析(默認(rèn)打開)

          • -XX:+EliminateAllocations 標(biāo)量替換(默認(rèn)打開)

          • -XX:+UseTLAB 本地線程分配緩沖(默認(rèn)打開)

          八、常用的性能評價(jià)/測試指標(biāo)

          一個 web 應(yīng)用不是一個孤立的個體,它是一個系統(tǒng)的部分,系統(tǒng)中的每一部分都會影響整個系統(tǒng)的性能。

          1、響應(yīng)時間:提交請求和返回該請求的響應(yīng)之間使用的時間,一般比較關(guān)注平均響應(yīng)時間。
          2、并發(fā)數(shù):同一時刻,對服務(wù)器有實(shí)際交互的請求數(shù),和網(wǎng)站在線用戶數(shù)的關(guān)聯(lián):1000 個同時在線用戶數(shù),可以估計(jì)并發(fā)數(shù)在 5%到 15%之間,也就是同時并發(fā)數(shù)在 50~150 之間。
          3、吞吐量:對單位時間內(nèi)完成的工作量(請求)的量度,例如1秒處理5萬個請求。









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

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  99精品人| 看一级黄片视频 | 国产又爽 又黄 A片免费观看 | 欧美日韩国产一区二区三区 | 国产美女啪啪视频 |