<form id="dlljd"></form>
        <address id="dlljd"><address id="dlljd"><listing id="dlljd"></listing></address></address>

        <em id="dlljd"><form id="dlljd"></form></em>

          <address id="dlljd"></address>
            <noframes id="dlljd">

              聯系我們 - 廣告服務 - 聯系電話:
              您的當前位置: > 關注 > > 正文

              Java進階1-JVM虛擬機 JVM在字節碼上的使用方法

              來源:CSDN 時間:2023-04-06 10:06:28

              1. JVM簡述


              (資料圖片)

              JVM是Java Virtual Machine的縮寫。它是一種基于計算設備的規范,是一臺虛擬機,即虛構的計算機。 JVM屏蔽了具體操作系統平臺的信息(顯然,就像是我們在電腦上開了個虛擬機一樣),當然,JVM執行字節碼時實際上還是要解釋成具體操作平臺的機器指令的。 如類加載機制、運行時數據區、垃圾回收機制等; 官網:https://www.oracle.com/java/technologies/javase/vmoptions-jsp.html

              2. 字節碼

              2.1 字節碼由來

              Java 所有的指令有 200 個左右,一個字節( 8 位)可以存儲 256 種不同的指令信息,一個這樣的字節稱為字節碼( Bytecode )

              JVM 將字節碼解釋執行,屏蔽對底層操作系統的依賴,JVM也可以將字節碼編譯執行,如果是熱點代碼,會通過JIT 動態地編譯為機器碼,提高執行效率

              JVM在字節碼上也設計了一套操作碼助記符,使用特殊單詞來標記這些數字。 加載或存儲指令 、運算指令、類型轉換指令、對象創建與訪問指令、操作棧管理指令、方法調用與返回指令。

              2.2 源碼轉化成字節碼

              詞法解析是通過空格分隔出單詞 、操作符、控制符等信息 , 將其形成 token 信息流 ,傳遞給語法解析器。 在語法解析時,把詞法解析得到的 token 信息流按照 Java 語法規則組裝成一棵語法樹 , 如圖 4-2 虛線框所示。 在語義分析階段 , 需要檢查關鍵字的使用是否合理、類型是否匹配、作用域是否正確等。 當語義分析完成之后,即可生成字節碼。

              2.2 執行三種模式

              字節碼必須通過類加載過程加載到 JVM 環境后,才可以執行。執行有三種模式:第一,解釋執行;第二,JIT 編譯執行;第三, JIT 編譯與解釋混合執行(主流 JVM默認執行模式)?;旌蠄绦心J降膬瀯菰谟诮忉屍髟趩訒r先解釋執行,省去編譯時間。隨著時間推進 , JVM 通過熱點代碼統計分析 , 識別高頻的方法調用、循環體、公共模塊等,基于強大的 JlT 動態編譯技術,將熱點代碼轉換成機器碼,直接交給 CPU執行。 JIT 的作用是將 Java 字節碼動態地編譯成可以直接發送給處理器指令執行的機器碼。簡要流程如圖 4-3 所示。 注意解釋執行與編譯執行在線上環境微妙的辯證關系。機器在熱機狀態可以承受的負載要大于冷機狀態(剛啟動時 ),如果以熱機狀態時的流量進行切流 , 可能使處于冷機狀態的服務器因無法承載流量而假死。 剛啟動的 JVM 均是解釋執行,還沒有進行熱點代碼統計和 JIT 動態編譯。

              3. 類加載過程

              3.1 類加載流程

              1、Load加載(讀取二進制流轉特定數據結構) 2、Link鏈接( 驗證-詳細校驗如類型、常量、靜態變量等。 準備-為靜態變量分配內存。 解析-確保類與類之間的相互引用正確性,完成內存結構布局) 3、Init初始化(執行類構造器完成類的初始化) 4、解析執行直接給CPU、JIT動態編譯機器碼再給CPU 類加載是一個將 . class 字節碼文件實例化成 Class 對象并進行相關初始化的過程。 全小寫的 class 是關鍵字,用來定義類,而首字母大寫的 Class ,它是所有 class 的類。** 問題:**為什么這個抽象還是另外一個類 Class 的對象? 示例代碼如下:

              /** * @author cmy * @version 1.0 * @date 2022/6/29 0029 13:43 * @description 類加載過程測試 */public class ClassTest {//數組類型有一個魔法屬性:length來獲取數組長度    private static int[] array=new int[3];    private static int length=array.length;    //任何小寫class定義的類,也有一個魔法屬性:class,來獲取此類的大寫Class類對象    private static Classone=One.class;    private static Classanother=Another.class;    public static void main(String[] args) throws Exception {//通過newInstance方法創建One和 Another的類對象(第1處)        One oneObject=one.newInstance();        oneObject.call();        Another anotherObject=another.newInstance();        anotherObject.speak();                //通過one這個大寫的Class對象,反射方式獲取私有成員屬性對象Field(第2處)        Field privateFieldInOne = one.getDeclaredField("inner");                //設置私有對象可以訪問和修改(第3處)        privateFieldInOne.setAccessible(true);        privateFieldInOne.set(oneObject,"world changed");        //成功修改類的私有屬性inner變量值為world changed.        System.out.println(oneObject.getInner());    }}class One{private String inner="time files.";    public void call(){System.out.println("hello world");    }    public String getInner(){return inner;    }}class Another{public void speak(){System.out.println("easy coding");    }}

              第1處說明new 是強類型校驗 , 可以調用任何構造方法 , 在使用new 操作的時候,這個類可以沒有被加載過。 Class 類下的 newInstance是弱類型,只能調用無參數構造方法,如果沒有默認構造方法,就拋出InstantiationException 異常,如果此構造方法沒有權限訪問,則拋出IllegalAccessException 異常。 第2處說明使用類似的方式獲取其他聲明 , 如注解、方法等,如圖4.5所示。 第3處說明問題:private 成員在類外是否可以修改?通過 setAccessible(true) 操作,即可使用大寫 C lass 類的 set 方法修改其值。

              3.2 類加載器實現機制

              /**         * 查看本地類加載器的方式如下         */        //正在使用得類加載器sun.misc.Launcher$AppClassLoader@18b4aac2        ClassLoader c = One.class.getClassLoader();        //AppClassLoader的父類加載器是ExtensionClassLoader JDK1.9之前        ClassLoader c1 = c.getParent();        //ExtClassLoader父類加載器(最高一層)Bootstrap C++實現 返回null        ClassLoader c2 = c1.getParent();        System.out.println(c);        System.out.println(c1);        System.out.println(c2);

              低層次的當前類加載器,不能覆蓋更高層次類加載器已經加載的類。 如圖 4-6 所示,左側綠色箭頭向上逐級詢問是否已加載此類,直至 Bootstrap ClassLoader ,然后向下逐級嘗試是否能夠加載此類,如果都加載不了,則通知發起加載請求的當前類加載器 ,準予加載。 通過如下代碼可以查看 Bootstrap 所有已經加載的類庫:

              /**         * 通過如下代碼可以查看BootstrapClassLoader所有已經加載的類庫:         */        URL[] urLs = Launcher.getBootstrapClassPath().getURLs();        for (java.net.URL url:urLs) {System.out.println(url.toExternalForm());        }

              在JVM中增加如下啟動參數 , 則能通過 Class.forName 正常讀取到指定類 , 說明此參數可以增加Bootstrap 的類加載路徑:

              - Xbootclasspath/a:/Users/yangguanbao/book/easyCoding/byJdk11/src

              如果想在啟動時觀察加載了哪個jar包中的哪個類 ,可以增加-XX:+TraceClassLoading參數

              - XX:+TraceClassLoading

              3.3 自定義類加載器

              學習類加載器的實現機制后 , 雙親委派模型并非強制模型, 用戶可以自定義類加載器 。問題:在什么情況下需要自定義類加載器呢? ** ** 隔離加載類、修改類加載方式、擴展加載源、防止源碼泄露。

              實現自定義類加載器的步驟,繼承ClassLoader,重寫 findClass()方法,調用defineClass()方法。一個簡單的類加載器實現的示例代碼如下:

              /** * @author cmy * @version 1.0 * @date 2022/6/29 0029 15:34 * @description 自定義類加載器 * * 自定義類加載器步驟: * 繼承ClassLoader * 重寫findClass()方法 * 調用defineClass()方法 */public class CustomClassLoader extends ClassLoader {private String classpath;    public CustomClassLoader(String classpath) {this.classpath = classpath;    }    @Override    protected Class findClass(String name) throws ClassNotFoundException {try {byte[] bytes = getClassFromCustomPath(name);            return defineClass(name, bytes, 0, bytes.length);        } catch (IOException e) {e.printStackTrace();            throw new ClassNotFoundException();        }    }    private byte[] getClassFromCustomPath(String name) throws IOException {//從自定義路徑中加載指定類        FileInputStream fis = new FileInputStream(classpath + File.separator + name.replace(".", File.separator).concat(".class"));        byte[] bytes = new byte[fis.available()];        fis.read(bytes);        fis.close();        return bytes;    }    public static void main(String[] args) {CustomClassLoader customClassLoader = new CustomClassLoader("F:\\IDEA2022\\workmenu\\SoftwareTools\\src\\main\\java");        try {Class clazz = customClassLoader.loadClass("Test");            //調用的靜態方法            clazz.getDeclaredMethod("say").invoke(clazz);            Object o = clazz.newInstance();            Method print = clazz.getDeclaredMethod("print", String.class);            print.invoke(o, "調用的對象方法");            //自定義類加載器            System.out.println(clazz.getClassLoader());            //AppClassLoader            System.out.println(clazz.getClassLoader().getParent());            //ExtClassLoader            System.out.println(clazz.getClassLoader().getParent().getParent());            //BootstrapLoader            System.out.println(clazz.getClassLoader().getParent().getParent().getParent());        }catch (Exception e){e.printStackTrace();        }    }}//https://blog.csdn.net/weixin_42759726/article/details/114030153

              //javac編譯后,記得刪除Test.java比較路徑尋找和Test.class沖突public class Test {public static void say() {System.out.println("this is a static method!");    }     public void print(String s) {System.out.println("printing:"+s);    }}

              按某種規則 jar 包的版本被統←指定 ,導致某些類存在包路徑、類名相同 的情況 , 就會引起類沖突 ,導致應用程序出現異常。主流的容器類框架都會自定義類加載器,實現不同中間件之間的類隔離 , 有效避免了類沖突。

              4. 內存布局(運行時數據區域)

              前面回顧連接

              在類加載過程第二步Link鏈接的解析階段,解析類和方法確保類與類之間的相互引用正確性,完成內存 結構布局。

              4.1 內存布局簡介

              Java 程序在運行時,會為 JVM 單獨劃出一塊內存區域,而這塊內存區域又可以再次劃分出一塊運行時數據區,運行時數據區域大致可以分為五個部分:

              4.2 Heap堆區

              **Heap堆區作用:**Heap 是 OOM 故障最主要的發源地 , 它存儲著幾乎所有的實例對象, 堆由垃圾收集器自動回收 , 堆區由各子線程共享使用。 堆的內存空間既可以固定大小 , 也可以在運行時動態地調整。

              比如 -Xms256M -Xmxl024M ,其中 -X 表示它是 JVM 運行參數, ms 是 memory start 的簡 稱, mx 是 memory max 的簡稱,分別代表最小堆容量和最大堆容量。 一般最大和最小一樣。避免在GC后調整堆大小時帶來的額外壓力。

              堆分成兩大塊:新生代和老年代。新生代= 1 個 Eden 區+ 2 個Survivor 區。

              當 Eden區裝填滿的時候 , 會觸發 Young Garbage Collection , 即 YGC。垃圾回收的時候 , 在 Eden 區實現清除策略 , 沒有被引用的對象則直接回收。依然存活的對象會被移送到 Survivor 區。

              **問題:**Survivor 區分為 S0和 S1兩塊內存空間 , 送到哪塊空間呢? 每次 YGC 的時候, 它們將存活的對象復制到未使用的那塊空間,然后將當前正在使用的空間完全清除 , 交換兩塊空間的使用狀態。

              **問題:**如何防止對象沒有進取心? 每個對象都有一個計數器,每次 YGC 都會加1。 -XX:MaxTenuringThreshold 參數能配置計數器的值到達某個閥值的時候 , 對象從新生代晉升至老年代。

              對象分配與簡要GC流程圖如圖4-9所示。 圖 的 中,如果 Survivor 區無法放下,或者超大對象的鬧值超過上限,則嘗試在老年代中進行分配 ; 如果老年代也無法放下,則會觸發 Full Garbage Collection , 即FGC。如果依然無法放下, 則拋出 OOM。堆內存出現 OOM 的概率是所有內存耗盡異常中最高的。出錯時的堆內信息對解決問題非常有幫助 , 所以給JVM設置運行參數 -XX:+HeapDumpOnOutOfMemoryError,讓JVM遇到OOM 異常時能輸出堆內信息,特別是對相隔數月才出現的 OOM 異常尤為重要。

              4.3 Metaspace (元空間)

              元空間作用:存儲常量池、方法元信息、類元信息。字符串常量String存在堆內存。

              元空間發展:源碼解析和示例代碼基本采用 JDK11版本, JVM則為 Hotspot。在 JDK7 及之前的版本中,只有 Hotspot才有 Perm 區,譯為永久代 , 它在啟動時固定大小,很難進行調優。動態加載類過多,容易產生 Perm 區的 OOM。為了解決Perm 區的 OOM, 需要設定運行參數 -XX:MaxPermSize= 1280m。

              永久代在垃圾回收過程中還存在諸多問題。 JDK8 使用元空間替換永久代。在 JDK8 及以上版本中,設定 MaxPermSize 參數, JVM在啟動時并不會報錯。

              區別于永久代 , 元空間在本地內存中分配。在 JDK8 里, Perm 區 中的所有內容 中字符串常量移至堆內存,其他內容包括類元信息、字段、靜態屬性、方法、常量等 都移動至無空間內。

              圖 4-10 中顯示在常量池中的 String, 其實際對象是被保存在堆內存中的。

              4.4 JVM Stack (虛擬機棧)

              **虛擬機棧作用:描述JAVA方法執行的內存區域。**方法調用到執行完成~入棧到出棧的過程。棧頂的幀稱為棧幀,正在執行的方法稱為當前方法,棧幀是方法運行的基本結構。StackOverflowError表示請求的棧溢出,內存耗盡。 棧幀在整個 JVM 體系中的地位頗高, 包括局部變量表、操作棧、動態連接、方法返回地址等。

              1 局部變量表2 操作棧

              public int simpleMethod(){//將常量13壓入操作棧、保存到局部變量表的slot_1中    int x=13;     //將常量14壓入操作棧、保存到局部變量表的slot_2中    int y=14;     //將slot_1元素壓入操作棧,將slot_2元素壓入操作棧,    //再取出來到CPU中加法,并壓回操作棧,把棧頂結果保存到局部變量表的slot_3中    int z= x+y;     //返回棧頂元素值    return z;}

              ** 3 動態鏈接** 每個枝幀中包含一個在常量池中對當前方法的引用 , 目的是支持方法調用過程的動態連接。 4 方法返回地址

              4.5 Native Method Stacks (本地方法棧)

              本地方法棧作用:虛擬機?!爸鲀?”, 而本地方法?!爸魍狻?。本地方法棧為 Native 方法服務。本地方法可以通過 JNI ( Java Native Int rface )來訪問虛擬機運行時的數據區 ,甚至可以調用寄存器,具有和 JVM 相同的能力和權限。對于內存 不足的情況 本地方法枝還是會拋出 native heap OutOfMemory。

              4.6 Program Counter Register(程序計數寄存器)

              程序計數寄存器作用:CPU 只有把數據裝載到寄存器才能夠運行。保證在多線程并發執行過程中,保證分毫無差。**問題:**由于CPU時間片輪限制,眾多線程在并發執行過程中,導致經常中斷或恢復,如何保證分毫無差呢? 每個線程在創建后,都會產生 自己的程序計數器和棧幀,程序計數器用來存放執行指令的偏移量和行號指示器等, 線程執行或恢復都要依賴程序計數器。程序計數器在各個線程之間互不影響,此區域 也不會發生內存溢出異常。

              4.7 運行時數據區域總結

              1 Heap堆區作用:Heap 是 OOM 故障最主要的發源地 ,它存儲著幾乎所有的實例對象。堆由垃圾收集器自動回收 , 堆區由各子線程共享使用。 堆的內存空間既可以固定大小 , 也可以在運行時動態地調整。 2 元空間作用:存儲常量池、方法元信息、類元信息。字符串常量String存在堆內存。3 虛擬機棧作用:描述JAVA方法執行的內存區域。方法調用到執行完成~入棧到出棧的過程。棧頂的幀稱為棧幀,正在執行的方法稱為當前方法,棧幀是方法運行的基本結構。StackOverflowError表示請求的棧溢出,內存耗盡。 4 本地方法棧作用:虛擬機?!爸鲀?”, 而本地方法?!爸魍狻薄1镜胤椒?Native 方法服務。本地方法可以通過 JNI ( Java Native Int rface )來訪問虛擬機運行時的數據區 ,甚至可以調用寄存器。 5 程序計數寄存器作用:CPU 只有把數據裝載到寄存器才能夠運行。保證在多線程并發執行過程中,保證分毫無差。每個線程在創建后,都會產生 自己的程序計數器和棧幀,程序計數器用來存放執行指令的偏移量和行號指示器等。

              5. 對象實例化

              Java 是面向對象的靜態強類型語言,根據某個類聲明一個引用變量指向被創建的對象。 問題:在實例化對象的過程中,JVM會發生什么化學反應?

              5.1 從字節碼的進行分析

              1、NEW類加載 2、DUP棧頂復制引用變量 3、INVOKESPECIAL初始化

              5.2 從執行步驟的角度分析

              1、確認類元信息是否存在 2、分配對象內存 3、設定默認值 4、設置對象頭 5、執行init方法

              6. 垃圾回收

              垃圾回收( Garbage Collection, GC )。垃圾回收的主要目的是清除不再使用的對象,自動釋放內存。

              6.1 對象是否存活的標準

              問題:GC 是如何判斷對象是否可以被回收的呢?為了判斷對象是否存活 , JVM 引人了GC Roots。某個失去任何引用的對象,或者兩個互相環島狀循環引用的對象等,可以直接回收。 問題:什么對象可以作為 GC Roots 呢?比如類靜態屬性中引用的對象、常量引用的對象、虛擬機棧中尋引用的對象、本地方法棧中引用的對象等。

              6.2 垃圾回收的相關算法

              “標記-清除算法”。

              該算法從GC Roots出發,依次標記有引用關系的對象,將沒有被標記的對象清除。此算法會帶來大量的空間碎片,要分配較大連續空間容易出現FGC。

              “標記-整理算法”。

              該算法從GC Roots出現標記存活對象,然后將存活對象整理導內存空間的一端,形成已使用的連續空間,最后把已使用空間外的部分全部清理掉。

              “Mark-Copy算法”(主流YGC算法新生代垃圾回收)。也稱標記-復制

              為了并行地標記和整理,將空間分為兩塊,每次只激活一塊。垃圾回收時只需要把存活對象復制到另一塊未激活空間,將未激活空間標記為已激活,將已激活空間標記為未激活,然后清除原空間對象,如此反復置換清除。每次只使用堆區一塊Eden區和Survior區,減少了內存空間的浪費。

              6.2 垃圾回收器

              垃圾回收器( Garbage Collector )是實現垃圾回收算法并應用在 JVM 環境中的內存管理模塊 。

              Serial 回收器

              Serial 回收器是一個主要應用于 YGC 的垃圾回收器,采用串行單線程的方式完成 GC 任務。其中"Stop The World"簡稱STW,即垃圾回收的某個階段會暫停整個應用程序的執行。標記-復制算法YGC

              CMS 回收器

              CMS 回收器 (Concurrent Mark Sweep Collector) 是回收停頓時間比較短、目前比較常用的垃圾回收器,采用"標記-清除算法"。 通過初始標記(Initial Mark) 、并發標記(Concurrent Mark )、重新標記(Remark)、并發清除(Concurrent Sweep)四個步驟完成垃圾回收工作。

              G1回收器

              Hotspot 在 JDK7 中 推出了新一代 G1 ( Garbage-First Garbage Collector ) 垃圾回收 ,通過去-XX:+UseG1GC參數啟用。G1采用"Mark-Copy算法"。 GI 將 Java 堆空間分割成了若干相同大小的 區域,即 region ,包括 Eden 、Survivor 、 Old 、 Humongous 四種類型。

              7. JVM監控工具

              7.1 jconsole

              Jconsole(Java Monitoring and Management Console)是從 java5 開始,在 JD K中自帶的 java 監控和管理控制臺,用于對 JVM 中內存,線程和類等的監控,是一個基于 JMX(java management extensions)的 GUI 性能監測工具。

              7.2 VisualVM

              VisualVM(All-in-One Java Troubleshooting Tool)是功能最強大的運行監視和故障處理程序之一,曾經在很長一段時間內是 Oracle 官方主力發展的虛擬機故障處理工具。

              8. JVM 調優選擇

              8.1 選擇合適的垃圾回收器

              CPU 單核:那么毫無疑問 Serial 垃圾收集器是你唯一的選擇;CPU 多核:關注吞吐量 ,那么選擇 PS+PO 組合;JDK8默認CPU 多核:關注用戶停頓時間,JDK 版本 1.6 或者 1.7,那么選擇 CMS;CPU 多核:關注用戶停頓時間,JDK1.8 及以上,JVM 可用內存 6G 以上,那么選擇 G1。

              參數配置:

              //設置Serial垃圾收集器(新生代)開啟:-XX:+UseSerialGC//設置PS+PO,新生代使用功能Parallel Scavenge 老年代將會使用Parallel Old收集器開啟 -XX:+UseParallelOldGC//CMS垃圾收集器(老年代)開啟 -XX:+UseConcMarkSweepGC//設置G1垃圾收集器 開啟 -XX:+UseG1GC

              8.2 調整內存大小

              現象:垃圾收集頻率非常頻繁。 原因:如果內存太小,就會導致頻繁的需要進行垃圾收集才能釋放出足夠的空間來創建新的對象,所以增加堆內存大小的效果是非常顯而易見的。 注意:如果垃圾收集次數非常頻繁,但是每次能回收的對象非常少,那么這個時候并非內存太小,而可能是內存泄露導致對象無法回收,從而造成頻繁 GC。 參數配置:

              //設置堆初始值 指令1:-Xms2g 指令2:-XX:InitialHeapSize=2048m  //設置堆區最大值 指令1:`-Xmx2g`  指令2: -XX:MaxHeapSize=2048m  //新生代內存配置 指令1:-Xmn512m 指令2:-XX:MaxNewSize=512m

              8.3 設置符合預期的停頓時間

              現象:程序間接性的卡頓 原因:如果沒有確切的停頓時間設定,垃圾收集器以吞吐量為主,那么垃圾收集時間就會不穩定。 注意:不要設置不切實際的停頓時間,單次時間越短也意味著需要更多的 GC 次數才能回收完原有數量的垃圾. 參數配置:

              //GC停頓時間,垃圾收集器會嘗試用各種手段達到這個時間 -XX:MaxGCPauseMillis

              8.4 調整內存區域大小比率

              現象:某一個區域的GC頻繁,其他都正常。 原因:如果對應區域空間不足,導致需要頻繁GC來釋放空間,在JVM堆內存無法增加的情況下,可以調整對應區域的大小比率。 注意:也許并非空間不足,而是因為內存泄造成內存無法回收,從而導致 GC 頻繁。 參數配置:

              //survivor區和Eden區大小比率 指令:-XX:SurvivorRatio=6  //S區和Eden區占新生代比率為1:6,兩個S區2:6  //新生代和老年代的占比 -XX:NewRatio=4  //表示新生代:老年代 = 1:4 即老年代占整個堆的4/5;默認值=2

              8.5 調整對象升老年代的年齡

              現象:老年代頻繁 GC,每次回收的對象很多。 原因:如果升代年齡小,新生代的對象很快就進入老年代了,導致老年代對象變多,而這些對象其實在隨后的很短時間內就可以回收,這時候可以調整對象的升級代年齡,讓對象不那么容易進入老年代解決老年代空間不足頻繁 GC 問題。 注意:增加了年齡之后,這些對象在新生代的時間會變長可能導致新生代的 GC 頻率增加,并且頻繁復制這些對象新生的 GC 時間也可能變長。 配置參數:

              //進入老年代最小的GC年齡,年輕代對象轉換為老年代對象最小年齡值,默認值7 -XX:InitialTenuringThreshol=7

              8.6 調整大對象的標準

              現象:老年代頻繁 GC,每次回收的對象很多,而且單個對象的體積都比較大。 原因:如果大量的大對象直接分配到老年代,導致老年代容易被填滿而造成頻繁 GC,可設置對象直接進入老年代的標準。 注意:這些大對象進入新生代后可能會使新生代的 GC 頻率和時間增加。 配置參數:

              //新生代可容納的最大對象,大于則直接會分配到老年代,0代表沒有限制。  -XX:PretenureSizeThreshold=1000000

              8.7 調整GC的觸發時機

              現象:CMS,G1 經常 Full GC,程序卡頓嚴重。 原因:G1 和 CMS 部分 GC 階段是并發進行的,業務線程和垃圾收集線程一起工作,也就說明垃圾收集的過程中業務線程會生成新的對象,所以在 GC 的時候需要預留一部分內存空間來容納新產生的對象,如果這個時候內存空間不足以容納新產生的對象,那么JVM就會停止并發收集暫停所有業務線程(STW)來保證垃圾收集的正常運行。這個時候可以調整GC觸發的時機(比如在老年代占用 60% 就觸發 GC),這樣就可以預留足夠的空間來讓業務線程創建的對象有足夠的空間分配。 注意:提早觸發 GC 會增加老年代 GC 的頻率。 配置參數:

              //使用多少比例的老年代后開始CMS收集,默認是68%,如果頻繁發生SerialOld卡頓,應該調小 -XX:CMSInitiatingOccupancyFraction  //G1混合垃圾回收周期中要包括的舊區域設置占用率閾值。默認占用率為 65% -XX:G1MixedGCLiveThresholdPercent=65

              8.8 調整 JVM本地內存大小

              現象:GC 的次數、時間和回收的對象都正常,堆內存空間充足,但是報 OOM 原因:JVM 除了堆內存之外還有一塊堆外內存,這片內存也叫本地內存,可是這塊內存區域不足了并不會主動觸發 GC,只有在堆內存區域觸發的時候順帶會把本地內存回收了,而一旦本地內存分配不足就會直接報 OOM 異常。 注意:本地內存異常的時候除了上面的現象之外,異常信息可能是 OutOfMemoryError:Direct buffer memory。解決方式除了調整本地內存大小之外,也可以在出現此異常時進行捕獲,手動觸發 GC(System.gc())。 配置參數:

              XX:MaxDirectMemorySize

              9. JVM 調試實戰

              為什么要調整JVM

              JVM調優背景

              生產環境中的問題 生產環境發生了內存溢出該如何處理? 生產環境應該給服務器分配多少內存合適? 如何對垃圾回收器的性能進行調優? 生產環境 CPU 負載飆高該如何處理? 生產環境應該給應用分配多少線程合適? 不加 log,如何確定請求是否執行了某一行代碼? 不加 log,如何實時查看某個方法的入參與返回值?

              為什么要調優

              防止出現 OOM解決 OOM減少 Full GC 出現的頻率

              不同階段的考慮

              上線前項目運行階段線上出現 OOM

              JVM調優方案

              監控的依據

              運行日志異常堆棧GC 日志線程快照堆轉儲快照

              調優的大方向

              合理地編寫代碼充分并合理的使用硬件資源合理地進行 JVM 調優

              JVM性能優化的步驟

              第 1 步:性能監控

              GC 頻繁 cpu load 過高 OOM 內存泄露 死鎖 程序響應時間較長

              第 2 步:性能分析

              打印 GC 日志,通過 GCviewer 或者 Universal JVM GC analyzer - Java Garbage collection log analysis made easy 來分析異常信息 靈活運用命令行工具、jstack、jmap、jinfo 等 dump 出堆文件,使用內存分析工具分析文件 使用阿里 Arthas、jconsole、JVisualVM 來實時查看 JVM 狀態 jstack 查看堆棧信息

              第 3 步:性能調優

              適當增加內存,根據業務背景選擇垃圾回收器優化代碼,控制內存使用增加機器,分散節點壓力合理設置線程池線程數量使用中間件提高程序效率,比如緩存、消息隊列等其他……

              性能評價/測試指標

              **1 停頓時間(或響應時間):**提交請求和返回該請求的響應之間使用的時間,一般比較關注平均響應時間。常用操作的響應時間列表:

              2 垃圾回收環節:

              暫停時間:執行垃圾收集時,程序的工作線程被暫停的時間。-XX:MaxGCPauseMillis 表示每次GC最大的停頓毫秒

              3 吞吐量

              對單位時間內完成的工作量(請求)的量度在 GC 中:運行用戶代碼的事件占總運行時間的比例(總運行時間:程序的運行時間+內存回收的時間)吞吐量為 1-1/(1+n),其中-XX::GCTimeRatio=n

              4 并發數

              同一時刻,對服務器有實際交互的請求數

              5 內存占用

              Java 堆區所占的內存大小

              6 相互間的關系

              以高速公路通行狀況為例

              吞吐量:每天通過高速公路收費站的車輛的數據并發數:高速公路上正在行駛的車輛的數目響應時間:車速

              9.1 網站流量瀏覽量暴增后,網站反應頁面響很慢

              1、問題推測:在測試環境測速度比較快,但是一到生產就變慢,所以推測可能是因為垃圾收集導致的業務線程停頓。 2、定位:為了確認推測的正確性,在線上通過 jstat -gc 指令 看到 JVM 進行 GC 次數頻率非常高,GC 所占用的時間非常長,所以基本推斷就是因為 GC 頻率非常高,所以導致業務線程經常停頓,從而造成網頁反應很慢。 3、解決方案:因為網頁訪問量很高,所以對象創建速度非??欤瑢е露褍却嫒菀滋顫M從而頻繁 GC,所以這里問題在于新生代內存太小,所以這里可以增加 JVM 內存就行了,所以初步從原來的 2G 內存增加到 16G 內存。 4、第二個問題:增加內存后的確平常的請求比較快了,但是又出現了另外一個問題,就是不定期的會間斷性的卡頓,而且單次卡頓的時間要比之前要長很多。 5、問題推測:之前的優化加大了內存,所以推測可能是因為內存加大了,從而導致單次 GC 的時間變長從而導致間接性的卡頓。 6、定位:還是通過 jstat -gc 指令 查看到 的確 FGC 次數并不是很高,但是花費在 FGC 上的時間是非常高的,根據 GC 日志 查看到單次 FGC 的時間有達到幾十秒的。 7、解決方案:因為 JVM 默認使用的是 PS+PO 的組合,PS+PO 垃圾標記和收集階段都是 STW,所以內存加大了之后,需要進行垃圾回收的時間就變長了,所以這里要想避免單次 GC 時間過長,所以需要更換并發類的收集器,因為當前的 JDK 版本為 1.7,所以最后選擇 CMS 垃圾收集器,根據之前垃圾收集情況設置了一個預期的停頓的時間,上線后網站再也沒有了卡頓問題。

              9.2 后臺導出數據引發的 OOM

              問題描述:公司的后臺系統,偶發性的引發 OOM 異常,堆內存溢出。 1、因為是偶發性的,所以第一次簡單的認為就是堆內存不足導致,所以單方面的加大了堆內存從 4G 調整到 8G。 2、但是問題依然沒有解決,只能從堆內存信息下手,通過開啟了 -XX:+HeapDumpOnOutOfMemoryError 參數 獲得堆內存的 dump 文件。 3、VisualVM 對堆 dump 文件進行分析,通過 VisualVM 查看到占用內存最大的對象是 String 對象,本來想跟蹤著 String 對象找到其引用的地方,但 dump 文件太大,跟蹤進去的時候總是卡死,而 String 對象占用比較多也比較正常,最開始也沒有認定就是這里的問題,于是就從線程信息里面找突破點。 4、通過線程進行分析,先找到了幾個正在運行的業務線程,然后逐一跟進業務線程看了下代碼,發現有個引起我注意的方法,導出訂單信息。 5、因為訂單信息導出這個方法可能會有幾萬的數據量,首先要從數據庫里面查詢出來訂單信息,然后把訂單信息生成 excel,這個過程會產生大量的 String 對象。 6、為了驗證自己的猜想,于是準備登錄后臺去測試下,結果在測試的過程中發現到處訂單的按鈕前端居然沒有做點擊后按鈕置灰交互事件,結果按鈕可以一直點,因為導出訂單數據本來就非常慢,使用的人員可能發現點擊后很久后頁面都沒反應,結果就一直點,結果就大量的請求進入到后臺,堆內存產生了大量的訂單對象和 EXCEL 對象,而且方法執行非常慢,導致這一段時間內這些對象都無法被回收,所以最終導致內存溢出。 7、知道了問題就容易解決了,最終沒有調整任何 JVM 參數,只是在前端的導出訂單按鈕上加上了置灰狀態,等后端響應之后按鈕才可以進行點擊,然后減少了查詢訂單信息的非必要字段來減少生成對象的體積,然后問題就解決了。

              9.3 Window JVM調優

              查詢JDK所用虛擬機

              java version "1.8.0_91"Java(TM) SE Runtime Environment (build 1.8.0_91-b14)Java HotSpot(TM) 64-Bit Server VM (build 25.91-b14, mixed mode)//支持 Java 8, 使用的是 Oracle 的64位HotSpot虛擬機。//HotSpot VM 是 OracleJDK / SunJDK 以及 OpenJDK 里的 JVM 實現。使用最廣泛,JDK默認安裝的。

              Java問題診斷和排查工具(查看JVM參數、內存使用情況及分析等)

              常用查詢命令

              JPS (打印Java進程信息)

              使用場景 : 查看當前機器的所有Java進程信息(可追蹤到應用進程ID 、啟動類名、文件路徑。)。jps 顯示當前所有java進程pid的命令jps -v 輸出傳遞給JVM的參數

              jstack(JVM線程信息監控)

              使用場景: 查看JVM線程信息 和生成線程快照。jstack pid 主要用于生成指定進程當前時刻的線程快照,線程快照是當前java虛擬機每一條線程正在執行的方法堆棧的集合。分析線程棧

              Jmap(JVM內存占用信息和快照)

              使用場景: 監控堆內存使用情況和對象占用情況, 生成堆內存快照文件,查看堆內存區域配置信息。**jmap **打印指定java進程的共享對象內存映射或堆內存細節。堆Dump是反映堆使用情況的內存鏡像,其中主要包括系統信息、虛擬機屬性、完整的線程Dump、所有類和對象的狀態等。

              **jmap pid **共享對象的起始地址、映射大小、共享對象路徑的全程。 jmap -heap pid:查看堆使用情況jmap -histo pid:查看堆中對象數量和大小

              Jstat (JVM內存信息統計)

              使用場景 :用于查看各個功能和區域的統計信息(如:類加載、編譯相關信息統計,各個內存區域GC概況和統計)jstat-gc pid: 統計垃圾回收堆的行為

              Jinfo(JVM參數查看和修改)

              使用場景: 查看和調整JVM啟動和運行參數。

              Jinfo pid 查看JVM整個系統參數信息jinfo -flag [參數名] pid 查看某個具體參數jinfo -flag啟動某個配置

              java查詢JVM配置參數

              查詢JVM配置參數java -XX:+PrintCommandLineFlags

              C:\Program Files\Java\jdk1.8.0_91\bin>java -XX:+PrintCommandLineFlags-XX:InitialHeapSize=199690240  //初始堆大小bytes 這里23M-XX:MaxHeapSize=3195043840     //最大堆大小bytes 這里380M-XX:+PrintCommandLineFlags     //PrintCommandLineFlags 是打印那些被新值覆蓋的項-XX:+UseCompressedClassPointers //UseCompressedClassPointers:類指針壓縮-XX:+UseCompressedOops //UseCompressedOops:普通對象指針壓縮-XX:-UseLargePagesIndividualAllocation //關閉減少處理器 TLB 緩存壓力的技術-XX:+UseParallelGC //設置并行收集器 “Parallel Scavenge” + "Parallel Old"組合

              查詢JVM配置參數java -XX:+PrintFlagsFinal -version |FINDSTR /i “:”

              C:\Program Files\Java\jdk1.8.0_91\bin>java -XX:+PrintFlagsFinal -version |FINDSTR /i ":"    intx CICompilerCount                          := 3                                   {product}    uintx InitialHeapSize                          := 201326592 //初始堆大小bytes                          {product}    uintx MaxHeapSize                              := 3196059648 //最大堆大小bytes                         {product}    uintx MaxNewSize                               := 1065353216 //新生代分配內存最大上限,小于-Xmx的值;                         {product}    uintx MinHeapDeltaBytes                        := 524288 //要擴容或者縮容最小擴/縮多少                             {product}    uintx NewSize                                  := 67108864  //新生代初始內存的大小,應該小于-Xms的值;                         {product}    uintx OldSize                                  := 134217728  //老年代的默認大小                         {product}     bool PrintFlagsFinal                          := true //打印所有的默認參數設置                             {product}     bool UseCompressedClassPointers               := true                                {lp64_product}     bool UseCompressedOops                        := true                                {lp64_product}     bool UseLargePagesIndividualAllocation        := false                               {pd product}     bool UseParallelGC                            := true                                {product}java version "1.8.0_91"Java(TM) SE Runtime Environment (build 1.8.0_91-b14)Java HotSpot(TM) 64-Bit Server VM (build 25.91-b14, mixed mode)

              配置Windows JVM參數

              1、系統環境中配置(推薦)

              虛擬機內存的大小除了在web容器中設置,我們還可以通過系統環境變量來設置,下面看看設置步驟: 打開windows系統環境變量,在系統變量中,新建變量JAVA_OPTS,值設置為:

              8G物理內存JVM虛擬機配置idea優化命令-XX:+UseG1GC -XX:+UseStringDeduplication -XX:MaxGCPauseMillis=95 -Xms5120m -Xmx5120m -Xmn1024m -Xss128k -XX:MaxTenuringThreshold=0jdk8 使用G1垃圾回收器-XX:-UseParallelGC -Xms3550m -Xmx3550m -Xmn1024m -Xss128k -XX:+UseG1GC -XX:+UnlockExperimentalVMOptions -XX:+UseStringDeduplication -XX:MaxGCPauseMillis=95     -XX:NewRatio=4         -XX:SurvivorRatio=4             -XX:MatespaceSize=6

              優化前啟動優化前jmeter測試

              優化后啟動優化后jmeter測試

              jvm中常用的參數含義:

              每個對象都有一個計數器,每次YGC 都會加1,配置計數器的值到達某個闡值的時候, 對象從 新生代晉升至老年。 -XX:MaxTenuringThreshold

              為功能點比較多,在運行過程中,要不斷動態加載很多的類,經常出現致命錯誤。為了解決該問題, 需要設定運行參數 -XX: MaxPermSize= 1280m ,

              以給NM 設置運行參數讓JVM 遇到OOM 異常時能輸出堆內信息。 -XX:+HeapDumpOnOutOfMemoryError

              1: -Xmx指定 jvm 的最大內存大小 , 如 :-Xmx=2048M(根據設備物理內存以及實際情況設定,建議為物理內存的80%) 2: -Xms指定 jvm 的初始內存大小 , 如 :-Xms=2048M, 高并發應用, 建議和-Xmx一樣, 防止因為內存收縮/突然增大帶來的性能影響. 3: -Xmn指定 jvm 中 New Generation (堆空間的新生代空間)的大小 , 如 :-Xmn=256m。 這個參數很影響性能, 如果你的程序需要比較多的臨時內存, 建議設置到512M, 如果用的少, 盡量降低這個數值, 一般來說128/256足以使用了。 4: -XX:PermSize (java7,java8移除)指定 jvm 中 Perm Generation (永久存儲區)的最小值 , 如 :-XX:PermSize=32m。 這個參數需要看你的實際情況??梢酝ㄟ^jmap 命令看看到底需要多少。 5: -XX:MaxPermSize(java7,java8移除)指定 Perm Generation 的最大值 , 如 :-XX:MaxPermSize=64m 6: -Xss指定線程桟大小 , 如 :-Xss=128k, 一般來說,webx框架下的應用需要256K。 如果程序中有大規模的遞歸行為,請考慮設置到512K/1M。 這個需要全面的測試才能知道。 不過,256K已經很大了。 這個參數對性能的影響比較大的。 7:-XX:MatespaceSize(java8)和-XX:MatespaceSize(java8)JVM加載類的時候,需要記錄類的元數據,這些數據會保存在一個單獨的內存區域內,在Java 7里,這個空間被稱為永久代(Permgen),在Java 8里,使用元空間(Metaspace)代替了永久代。由于調整元空間的大小需要Full GC,這是非常昂貴的操作,如果應用在啟動的時候發生大量Full GC,通常都是由于永久代或元空間發生了大小調整,基于這種情況一般建議在JVM參數中將MetaspaceSize和MaxMetaspaceSize設置成一樣的值,并設置得比初始值要大,對于8G物理內存的機器來說,一般我會將這兩個值都設置為512M。

              2、使用命令配置

              java命令配置

              8G物理內存JVM虛擬機配置java -XX:+UseG1GC-Xms3550m -Xmx3550m -Xmn1024m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MatespaceSize=512m -XX:MaxMetaspaceSize=512m -XX:MaxTenuringThreshold=0

              使用 -Xms 設置堆的初始空間大小 java -Xms20m -Xmx30m GCDemo

              JVM 提供了參數 -Xmn 來設置年輕代內存的大小 java -Xms20m -Xmn10M GCDemo

              使用 -XX:SurvivorRatio 這個參數,該參數設置 eden / from 空間的比例關系 -XX:SurvivorRatio = eden/from = eden/to java -Xms20m -Xmn10M -XX:SurvivorRatio=2 -XX:+PrintGCDetails GCDemo

              永久代(JDK1.7)所加載的類信息都放在永久代中。用 -XX:PermSize 設置永久代初始大小,用 -XX:MaxPermSize 設置永久代最大大小。 java -XX:PermSize=10m -XX:MaxPermSize=50m -XX:+PrintGCDetails GCDemo

              元空間(JDK1.8)在 JDK1.8 之時,永久代被移除,取而代之的是元空間 java -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=50m -XX:+PrintGCDetails GCDemo

              棧空間是每個線程各自有的一塊區域,如果??臻g太小,也會導致 StackOverFlow 異常。而要設置棧空間大小,只需要使用 -Xss 參數就可以。 java -Xss2m GCDemo

              在 JVM 中還有一塊內存,它獨立于 JVM 的堆內存,它就是:直接內存。 java -XX:MaxDirectMemorySize=50m GCDemo

              實時修改JVM參數:jinfo -flag name = value PID 如果要對參數進行實時調整:則需要看到參數后面有manageable的才能被實時調整

              9.4 Linux JVM調優

              基本同windows,只是命令上大同小異。 使用命令配置

              8G物理內存JVM虛擬機配置java -XX:+UseG1GC-Xms3550m -Xmx3550m -Xmn1024m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MatespaceSize=512m -XX:MaxMetaspaceSize=512m -XX:MaxTenuringThreshold=0

              參考鏈接

              jmeter

              https://cloud.tencent.com/developer/article/1633626 https://blog.csdn.net/wust_lh/article/details/86095924 https://blog.csdn.net/qq_37453279/article/details/107990659 https://zhuanlan.zhihu.com/p/142897766

              獲取jvm日志

              https://blog.csdn.net/zlzlei/article/details/46471627 https://blog.csdn.net/S1124654/article/details/125467265 https://www.jianshu.com/p/0167a0fc8063 [https://zhuanlan.zhihu.com/p/355765061[https://blog.csdn.net/qq_35925558/article/details/116464460

              jvm監控調優關注參數指標

              [https://blog.csdn.net/weixin_41605937/article/details/121704836https://blog.csdn.net/weixin_43935927/article/details/109233111 [https://zhuanlan.zhihu.com/p/269597178[https://zhuanlan.zhihu.com/p/267381560

              jvm調優

              https://blog.csdn.net/augsm/article/details/109272205 https://tianweichang.blog.csdn.net/article/details/109712806 https://heapdump.cn/ https://mp.weixin.qq.com/s?__biz=MzkwNjMwMTgzMQ==&mid=2247495919&idx=1&sn=71010d91e376270afc31bbe61c8326aa&chksm=c0e82807f79fa111d7c339832f48c9542d5fc1540c1a619c1a3281040fc1631616e016d0dc36&mpshare=1&scene=23&srcid=0630pKVRvfxq3GMy1Oa2Wxda&sharer_sharetime=1656582521218&sharer_shareid=4b447a3cb6ab5d3443a5fc9771951705#rd https://www.cnblogs.com/z-sm/p/6745375.html https://www.cnblogs.com/zhi-leaf/p/10629033.html https://blog.csdn.net/weixin_43474695/article/details/108248756

              https://blog.csdn.net/weixin_28864057/article/details/119661743 https://blog.csdn.net/qq_40902067/article/details/106003880

              idea中設置JVM參數

              https://zhuanlan.zhihu.com/p/117627812

              Java自定義類加載器

              https://blog.csdn.net/weixin_42759726/article/details/114030153

              查詢JVM配置參數分析參考

              https://blog.csdn.net/m0_45406092/article/details/110766297 https://www.cnblogs.com/milton/p/6134251.html https://blog.csdn.net/qq13933506749/article/details/120187991 https://www.cnblogs.com/yidaixiaohui/p/10216180.html

              JVM關鍵參數

              https://www.cnblogs.com/bapiera/p/13592525.html https://www.cnblogs.com/GtShare/p/9767768.html https://jishuin.proginn.com/p/763bfbd66a1c https://blog.csdn.net/a469517790/article/details/104916916 https://blog.csdn.net/a469517790/article/details/104916916 https://blog.csdn.net/m0_45406092/article/details/110766297

              JVM參數之堆??臻g配置

              https://blog.csdn.net/qq_40902067/article/details/106003880 https://blog.csdn.net/weixin_28864057/article/details/119661743

              JDK8使用G1無效

              https://blog.csdn.net/weixin_42629445/article/details/120717860 https://bbs.huaweicloud.com/blogs/247828 https://www.jianshu.com/p/7dd309cc3442 https://www.cnblogs.com/frankyou/p/15048531.html https://blog.csdn.net/Megustas_JJC/article/details/105470675 https://zhuanlan.zhihu.com/p/458098236

              java程序輸出jvm參數

              https://blog.csdn.net/sjl110/article/details/41516745

              JDK1.8默認垃圾回收器

              https://blog.csdn.net/qq_27500493/article/details/108063108

              JVM垃圾收集器【Serial、ParNew、PS、CMS、Serial Old、PO、G1】總結

              https://blog.csdn.net/qq_43661709/article/details/122793803

              windows配置jVM

              https://blog.csdn.net/qq_38776582/article/details/109738067 https://blog.csdn.net/mhqyr422/article/details/79691042

              責任編輯:

              標簽:

              相關推薦:

              精彩放送:

              新聞聚焦
              Top 中文字幕在线观看亚洲日韩