您現在的位置是:首頁 > 棋牌

接上篇:大廠面試JVM虛擬機器18連環炮,你頂得住嗎?(下篇)

  • 由 Java開發仔 發表于 棋牌
  • 2021-12-23
簡介JVM 調優經驗總結JVM 配置方面,一般情況可以先用預設配置(基本的一些初始引數可以保證一般的應用跑得比較穩定了),在測試中根據系統執行狀況(會話併發情況、會話時間等),結合 gc 日誌、記憶體監控、使用的垃圾收集器等進行合理的調整,當老

連環炮數字解釋是什麼

如何設定Java虛擬機器棧的大小呢?

我們可以使用虛擬機器引數-Xss 選項來設定執行緒的最大棧空間,棧的大小直接決定了函式呼叫的最大可達深度;

-Xss size

設定執行緒堆疊大小(以位元組為單位)。附加字母k或K表示KB,m或M表示MB,和g或G表示GB。預設值取決於平臺:

Linux / x64(64位):1024 KB

macOS(64位):1024 KB

Oracle Solaris / x64(64位):1024 KB

Windows:預設值取決於虛擬記憶體

下面的示例以不同的單位將執行緒堆疊大小設定為1024 KB:

-Xss1m (1mb)-Xss1024k  (1024kb)-Xss1048576

回到上面的話題。

什麼是棧幀?

上面提到過,呼叫方法就生成一個棧幀,然後入棧。

看一段程式碼

public class JavaStackDemo {    public static void main(String[] args) {        JavaStackDemo javaStackDemo = new JavaStackDemo();        javaStackDemo。getUserType(21);    }    public String getUserType(int age) {        int temp = 18;        if (age < temp) {            return “未成年人”;        }        //動態連結        //userService。xx();        return “成年人”;    } }

既然是和方法有關,那麼就可以聯想到方法裡都有些什麼

官網介紹

Each frame has its own array of local variables , its own operand stack (§2。6。2), and a reference to the run-time constant pool  of the class of the current method。

每個棧幀擁有自己的本地變數。比如上面程式碼裡的

int age、int temp

這些都是本地變數。

每個棧幀都有自己的運算元棧

透過javac編譯好JavaStackDemo,然後使用

javap -v JavaStackDemo。class >log。txt

將位元組碼匯入到log。txt中,開啟

接上篇:大廠面試JVM虛擬機器18連環炮,你頂得住嗎?(下篇)

對getUserType方法裡面的位元組碼做一個解釋。有時候本地變數透過javap看不到,可以在javac的時候新增一個引數

javac -g:vars XXX。class

這樣就可以把本地變量表給輸出來了。

指令bipush 18  將18壓入運算元棧istore_2 將棧頂int型數值存入第三個本地變數iload_1 將第二個int型本地變數推送至棧頂iload_2 將第三個int型本地變數推送至棧頂if_icmpge 比較棧頂兩int型數值大小, 當結果大於等於0時跳轉ldc 將int,float或String型常量值從常量池中推送至棧頂areturn 從當前方法返回物件引用

官網

https://docs。oracle。com/javase/specs/jvms/se8/html/

接上篇:大廠面試JVM虛擬機器18連環炮,你頂得住嗎?(下篇)

這些都是位元組碼指令。

LocalVariableTable 本地變量表

Start  Length  Slot  Name   Signature   0      14     0  this   Lcom/tian/demo/test/JavaStackDemo;   0      14     1   age   I   3      11     2  temp   I

自己this算一個本地變數,入參age算一個本地變數,方法中的臨時變數temp也算一個本地變數。

方法出口

return。如果方法不需要返回void的時候,其實方法裡是預設會為其加上一個return;

另外方法的返回分兩種:

正常程式碼執行完畢然後return。

遇到異常結束

棧幀總結

方法出口:return或者程式異常

區域性變量表:儲存區域性變數

運算元棧:儲存每次賦值、運算等資訊

動態連結:相對於C/C++的靜態連線而言,靜態連線是將所有類載入,不論是否使用到。而動態連結是要用到某個類的時候再載入到記憶體裡。靜態連線速度快,動態連結靈活性更高。

什麼是本地方法棧?

Native Method Stacks 翻譯過來就是本地方法棧,與Java虛擬機器棧一樣,但這裡的棧是針對native修飾的方法的,比如System、Unsafe、Object類中的相關native方法。

public class Object {    //native修飾的方法    private static native void registerNatives();    public final native Class<?> getClass();    public native int hashCode();    protected native Object clone() throws CloneNotSupportedException;    public final native void notify();    //……。}    public final class System {    //native修飾的方法    private static native void registerNatives();    static {        registerNatives();    }    public static native long currentTimeMillis();    private static native void setIn0(InputStream in);    private static native void setOut0(PrintStream out);    private static native void setErr0(PrintStream err);    //。。。。。}public final class Unsafe {    //native修飾的方法    private static native void registerNatives();    public native int getInt(Object var1, long var2);    public native void putInt(Object var1, long var2, int var4);    public native Object getObject(Object var1, long var2);    public native void putObject(Object var1, long var2, Object var4);    public native boolean getBoolean(Object var1, long var2);    //。。。}

面試常問:JVM執行時區哪些和執行緒有直接的關係和間接的關係,哪些區會發生OOM?

每個區域是否為執行緒共享,是否會發生OOM

接上篇:大廠面試JVM虛擬機器18連環炮,你頂得住嗎?(下篇)

如何判斷物件是垃圾物件?

引用計數法

給物件新增一個引用計數器,每當一個地方引用它object時計數加1,引用失去以後就減1,計數為0說明不再引用。

優點:實現簡單,判定效率高

缺點:無法解決物件相互迴圈引用的問題,物件A中引用了物件B,物件B中引用物件A。

接上篇:大廠面試JVM虛擬機器18連環炮,你頂得住嗎?(下篇)

public class A { public B b; }public class B {public C c; } public class C { public A a; }public class Test{  private void test(){      A a = new A();      B b = new B();      C c = new C();            a。b=b;      b。c=c;      c。a=a;  }}

可達性分析演算法

當一個物件到GC Roots沒有引用鏈相連,即就是GC Roots到這個物件不可達時,證明物件不可用。

接上篇:大廠面試JVM虛擬機器18連環炮,你頂得住嗎?(下篇)

GC Roots種類

Java 執行緒中,當前所有正在被呼叫的方法的引用型別引數、區域性變數、臨時值等。也就是與我們棧幀相關的各種引用。

所有當前被載入的 Java 類。

Java 類的引用型別靜態變數。

執行時常量池裡的引用型別常量(String 或 Class 型別)。

JVM 內部資料結構的一些引用,比如 sun。jvm。hotspot。memory。Universe 類。

用於同步的監控物件,比如呼叫了物件的 wait() 方法。

public class Test{    private void test(C c){        A a = new A();        B b = new B();        a。b=b;        //這裡的a/b/c都是GC Root;    }}

物件的引用型別有哪些?

強引用:User user=new User();我們開發中使用最多的物件引用方式。特點:我們平常典型編碼Object obj = new Object()中的obj就是強引用。透過關鍵字new建立的物件所關聯的引用就是強引用。當JVM記憶體空間不足,JVM寧願丟擲OutOfMemoryError執行時錯誤(OOM),使程式異常終止,也不會靠隨意回收具有強引用的“存活”物件來解決記憶體不足的問題。對於一個普通的物件,如果沒有其他的引用關係,只要超過了引用的作用域或者顯式地將相應(強)引用賦值為 null,就是可以被垃圾收集的了,具體回收時機還是要看垃圾收集策略。

軟引用:SoftReference object=new SoftReference(new Object()); 特點:軟引用透過SoftReference類實現。軟引用的生命週期比強引用短一些。只有當 JVM 認為記憶體不足時,才會去試圖回收軟引用指向的物件:即JVM 會確保在丟擲 OutOfMemoryError 之前,清理軟引用指向的物件。軟引用可以和一個引用佇列(ReferenceQueue)聯合使用,如果軟引用所引用的物件被垃圾回收器回收,Java虛擬機器就會把這個軟引用加入到與之關聯的引用佇列中。後續,我們可以呼叫ReferenceQueue的poll()方法來檢查是否有它所關心的物件被回收。如果佇列為空,將返回一個null,否則該方法返回佇列中前面的一個Reference物件。應用場景:軟引用通常用來實現記憶體敏感的快取。如果還有空閒記憶體,就可以暫時保留快取,當記憶體不足時清理掉,這樣就保證了使用快取的同時,不會耗盡記憶體。

弱引用:WeakReference object=new WeakReference (new Object();ThreadLocal中有使用。 弱引用透過WeakReference類實現。弱引用的生命週期比軟引用短。在垃圾回收器執行緒掃描它所管轄的記憶體區域的過程中,一旦發現了具有弱引用的物件,不管當前記憶體空間足夠與否,都會回收它的記憶體。由於垃圾回收器是一個優先順序很低的執行緒,因此不一定會很快回收弱引用的物件。弱引用可以和一個引用佇列(ReferenceQueue)聯合使用,如果弱引用所引用的物件被垃圾回收,Java虛擬機器就會把這個弱引用加入到與之關聯的引用佇列中。應用場景:弱應用同樣可用於記憶體敏感的快取。

虛引用:幾乎沒見過使用, ReferenceQueue 、PhantomReference

finalize()方法有什麼作用?這個方法就有點類似,某個人被判了死刑,但是不一定會死。即使在可達性分析演算法中不可達的物件,也並非一定是“非死不可”的,這時候他們暫時處於“緩刑”階段,真正宣告一個物件死亡至少要經歷兩個階段:1、如果物件在可達性分析演算法中不可達,那麼它會被第一次標記並進行一次篩選,刷選的條件是是否需要執行finalize()方法(當物件沒有覆蓋finalize()或者finalize()方法已經執行過了(物件的此方法只會執行一次)),虛擬機器將這兩種情況都會視為沒有必要執行)。2、如果這個物件有必要執行finalize()方法會將其放入F-Queue佇列中,稍後GC將對F-Queue佇列進行第二次標記,如果在重寫finalize()方法中將物件自己賦值給某個類變數或者物件的成員變數,那麼第二次標記時候就會將它移出“即將回收”的集合。

垃圾回收演算法有哪些?

標記-清除

第一步:就是找出活躍的物件。我們反覆強調 GC 過程是逆向的, 根據 GC Roots 遍歷所有的可達物件,這個過程,就叫作標記。第二步:除了上面標記出來的物件以外,其餘的都清楚掉。

缺點:標記和清除效率不高,標記和清除之後會產生大量不連續的記憶體碎片

接上篇:大廠面試JVM虛擬機器18連環炮,你頂得住嗎?(下篇)

複製

新生代使用,新生代分中Eden:S0:S1= 8:1:1,其中後面的1:1就是用來複制的。當其中一塊記憶體使用完了,就將還存活的物件複製到另外一塊上面,然後把已經使用過的記憶體空間一次清除掉。一般物件分配都是進入新生代的eden區,如果Minor GC還存活則進入S0區,S0和S1不斷物件進行復制。物件存活年齡最大預設是15,大物件進來可能因為新生代不存在連續空間,所以會直接接入老年代。任何使用都有新生代的10%是空著的。

缺點:物件存活率高時,複製效率會較低,浪費記憶體。

接上篇:大廠面試JVM虛擬機器18連環炮,你頂得住嗎?(下篇)

標記整理

它的主要思路,就是移動所有存活的物件,且按照記憶體地址順序依次排列,然後將末端記憶體地址以後的記憶體全部回收。 但是需要注意,這只是一個理想狀態。物件的引用關係一般都是非常複雜的,我們這裡不對具體的演算法進行描述。我們只需要瞭解,從效率上來說,一般整理演算法是要低於複製演算法的。這個演算法是規避了記憶體碎片和記憶體浪費。讓所有存活的物件都向一端移動,然後直接清理掉端邊界以外的記憶體。

接上篇:大廠面試JVM虛擬機器18連環炮,你頂得住嗎?(下篇)

從上面的三個演算法來看,其實沒有絕對最好的回收演算法,只有最適合的演算法。

垃圾收集器

垃圾收集器就是垃圾回收演算法的實現,下面就來聊聊現目前有哪些垃圾收集器。

新生代有哪些垃圾收集器

serial

Serial收集器是最基本、發展歷史最悠久的收集器,曾經(在JDK1。3。1之前)是虛擬機器新生代收集的唯一選擇。

它是一種單執行緒收集器,不僅僅意味著它只會使用一個CPU或者一條收集執行緒去完成垃圾收集工作,更重要的是其在進行垃圾收集的時候需要暫停其他執行緒。

優點:簡單高效,擁有很高的單執行緒收集效率

缺點:收集過程需要暫停所有執行緒

演算法:複製演算法

應用:Client模式下的預設新生代收集器

收集過程:

接上篇:大廠面試JVM虛擬機器18連環炮,你頂得住嗎?(下篇)

ParNew

可以把這個收集器理解為Serial收集器的多執行緒版本。

優點:在多CPU時,比Serial效率高。

缺點:收集過程暫停所有應用程式執行緒,單CPU時比Serial效率差。

演算法:複製演算法

應用:執行在Server模式下的虛擬機器中首選的新生代收集器

收集過程:

接上篇:大廠面試JVM虛擬機器18連環炮,你頂得住嗎?(下篇)

Parallel Scanvenge

Parallel Scavenge收集器是一個新生代收集器,它也是使用複製演算法的收集器,又是並行的多執行緒收集

器,看上去和ParNew一樣,但是Parallel Scanvenge更關注 系統的吞吐量 ;

吞吐量 = 執行使用者程式碼的時間 / (執行使用者程式碼的時間 + 垃圾收集時間)

比如虛擬機器總共運行了120秒,垃圾收集時間用了1秒,吞吐量=(120-1)/120=99。167%。

若吞吐量越大,意味著垃圾收集的時間越短,則使用者程式碼可以充分利用CPU資源,儘快完成程式的運算任務。

可設定引數:

-XX:MaxGCPauseMillis控制最大的垃圾收集停頓時間,

-XX:GC Time Ratio直接設定吞吐量的大小。

老年代有哪些垃圾收集器

CMS=Concurrent Mark Sweep

特點:最短回收停頓時間,回收

演算法:標記-清除

回收步驟:

初始標記:標記GC Roots直接關聯的物件,速度快

併發標記:GC Roots Tracing過程,耗時長,與使用者程序併發工作

重新標記:修正併發標記期間使用者程序執行而產生變化的標記,好似比初始標記長,但是遠遠小於併發標記

表發清除:清除標記的物件

缺點:對CPU資源非常敏感,CPU少於4個時,CMS對使用者程式的影響可能變得很大,有此虛擬機器提供了“增量式併發收集器”;無法回收浮動垃圾;採用標記清除演算法會產生記憶體碎片,不過可以透過引數開啟記憶體碎片的合併整理。

收集過程:

接上篇:大廠面試JVM虛擬機器18連環炮,你頂得住嗎?(下篇)

serial old

Serial Old收集器是Serial收集器的老年代版本,也是一個單執行緒收集器,不同的是採用“標記-整理算

法”,執行過程和Serial收集器一樣。

適用場景:JDK1。5前與Parallel Scanvenge配合使用,作為CMS的後備預案;

收集過程:

接上篇:大廠面試JVM虛擬機器18連環炮,你頂得住嗎?(下篇)

Parallel old

Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多執行緒和“標記-整理演算法”進行垃圾

回收,吞吐量優先;

回收演算法:標記-整理

適用場景:為了替代serial old與Parallel Scanvenge配合使用

收集過程:

接上篇:大廠面試JVM虛擬機器18連環炮,你頂得住嗎?(下篇)

G1 收集器

G1(Garbage first) 收集器是 jdk1。7 才正式引用的商用收集器,現在已經成為JDK9 預設的收集器。前面幾款收集器收集的範圍都是新生代或者老年代,G1 進行垃圾收集的範圍是整個堆記憶體,它採用 “ 化整為零 ” 的思路,把整個堆記憶體劃分為多個大小相等的獨立區域(Region),在 G1 收集器中還保留著新生代和老年代的概念,它們分別都是一部分 Region,如下圖:

接上篇:大廠面試JVM虛擬機器18連環炮,你頂得住嗎?(下篇)

每一個方塊就是一個區域,每個區域可能是 Eden、Survivor、老年代,每種區域的數量也不一定。JVM 啟動時會自動設定每個區域的大小(1M ~ 32M,必須是 2 的次冪),最多可以設定 2048 個區域(即支援的最大堆記憶體為 32M*2048 = 64G),假如設定 -Xmx8g -Xms8g,則每個區域大小為 8g/2048=4M。

為了在 GC Roots Tracing 的時候避免掃描全堆,在每個 Region 中,都有一個 Remembered Set 來實時記錄該區域內的引用型別資料與其他區域資料的引用關係(在前面的幾款分代收集中,新生代、老年代中也有一個 Remembered Set 來實時記錄與其他區域的引用關係),在標記時直接參考這些引用關係就可以知道這些物件是否應該被清除,而不用掃描全堆的資料。

G1 收集器可以 “ 建立可預測的停頓時間模型 ”,它維護了一個列表用於記錄每個 Region 回收的價值大小(回收後獲得的空間大小以及回收所需時間的經驗值),這樣可以保證 G1 收集器在有限的時間內可以獲得最大的回收效率。

如下圖所示,G1 收集器收集器收集過程有初始標記、併發標記、最終標記、篩選回收,和 CMS 收集器前幾步的收集過程很相似:

接上篇:大廠面試JVM虛擬機器18連環炮,你頂得住嗎?(下篇)

① 初始標記:標記出來 GC Roots 直接關聯的物件,這個階段速度較快,需要停止使用者執行緒,單執行緒執行。

② 併發標記:從 GC Root 開始對堆中的物件進行重新分析,找出存活物件,這個階段耗時較長,但可以和使用者執行緒併發執行。

③ 最終標記:修正在併發標記階段因使用者程式執行而產生變動的標記記錄。

④ 篩選回收:篩選回收階段會對各個 Region 回收價值和成本進行排序,根據使用者所期望的 GC 停頓時間來指定回收計劃(用最少的時間來回收包含垃圾最多的區域,這就是 Garbage First 的由來——第一時間清理垃圾最多的區塊),這裡為了提高回收效率,並沒有採用和使用者執行緒併發執行的方式,而是停頓使用者執行緒。

適用場景:要求儘可能可控 GC 停頓時間;記憶體佔用較大的應用。可以用 -XX:+UseG1GC 使用 G1 收集器,jdk9 預設使用

ZGC收集器

ZGC有什麼特點?

ZGC 是最新的 JDK1。11 版本中提供的高效垃圾回收演算法,ZGC 針對大堆記憶體設計可以支援 TB 級別的堆,ZGC 非常高效,能夠做到 10ms 以下的回收停頓時間。這麼快的響應,ZGC 是如何做到的呢?這是由於 ZGC 具有以下特點。

第一個:ZGC 使用了著色指標技術,我們知道 64 位平臺上,一個指標的可用位是 64 位,ZGC 限制最大支援 4TB 地堆,這樣定址只需要使用 42 位,那麼剩下 22 位就可以用來儲存額外的資訊,著色指標技術就是利用指標的額外資訊位,在指標上對物件做著色標記。

第二個:使用讀屏障,ZGC 使用讀屏障來解決 GC 執行緒和應用執行緒可能併發修改物件狀態的問題,而不是簡單粗暴地透過 STW 來進行全域性的鎖定。使用讀屏障只會在單個物件的處理上有機率被減速。

第三個:由於讀屏障的作用,進行垃圾回收的大部分時候都是不需要 STW 的,因此 ZGC 的大部分時間都是併發處理,也就是 ZGC 的第三個特點。

第四個:基於 Region,這與 G1 演算法一樣,不過雖然也分了 Region,但是並沒有進行分代。ZGC 的 Region 不像 G1 那樣是固定大小,而是動態地決定 Region 的大小,Region 可以動態建立和銷燬。這樣可以更好地對大物件進行分配管理。

第五個:壓縮整理。CMS 演算法清理物件時原地回收,會存在記憶體碎片問題。ZGC 和 G1 一樣,也會在回收後對 Region 中的物件進行移動合併,解決了碎片問題。

雖然 ZGC 的大部分時間是併發進行的,但是還會有短暫的停頓。來看一下 ZGC 的回收過程。

ZGC 是如何進行垃圾收集的?

ZGC(Z Garbage Collector)是一款由Oracle公司研發的,以低延遲為首要目標的一款垃圾收集器。它是基於動態Region記憶體佈局,(暫時)不設年齡分代,使用了讀屏障、染色指標和記憶體多重對映等技術來實現可併發的標記-整理演算法的收集器。

初始狀態時,整個堆空間被劃分為大小不等的許多 Region,即圖中綠色的方塊。

接上篇:大廠面試JVM虛擬機器18連環炮,你頂得住嗎?(下篇)

開始進行回收時,ZGC 首先會進行一個短暫的 STW(Stop The world),來進行 roots 標記。這個步驟非常短,因為 roots 總數通常比較小。

然後就開始進行併發標記,如上圖所示,透過對物件指標進行著色來進行標記,結合讀屏障來解決單個物件的併發問題。其實,這個階段在最後還是會有一個非常短的期限的 STW 停頓,用來處理一些邊緣情況,這個階段絕大部分時間是併發進行的,所以沒有明顯標出這個停頓。

下一個是清理階段,這個階段會把標記為不在使用的物件進行回收,如上圖所示,把橘色的不在使用的物件進行了回收。

最後一個階段是重定位,重定位就是對 GC 後面存活的物件進行移動,來釋放大塊的記憶體空間,解決碎片問題。

定位最開始會有一個短暫的變化 STW,用來重定位集合中的 root 物件。暫停時間取決於 root 的數量、重定位集與物件的總活動集的比率。

最後是併發重定位,這個過程也是透過讀屏障,與應用執行緒併發進行的。

效能調優

熟悉哪些JVM調優引數

X或者XX開頭的都是非轉標準化引數:

接上篇:大廠面試JVM虛擬機器18連環炮,你頂得住嗎?(下篇)

意思就是說準化引數不會變,非標準化引數可能在每個JDK版本中有所變化,但是就目前來看X開頭的非標準化的引數改變的也是非常少。

格式:-XX:[+-] 表示啟用或者禁用name屬性。

例如:-XX:+UseG1GC(表示啟用G1垃圾收集器)

堆設定

-Xms 初始堆大小,ms是memory start的簡稱 ,等價於-XX:InitialHeapSize

-Xmx 最大堆大小,mx是memory max的簡稱 引數等價於引數-XX:MaxHeapSize

注意:在通常情況下,伺服器專案在執行過程中,對空間會不斷地收縮與擴張,勢必會造成不必要的系統壓力。所以在生產環境中,JVM的Xms和Xmx要設定成一樣的,能夠避免GC在調整堆大小帶來的不必要的壓力。

-XX:NewSize=n 設定年輕代大小

-XX:NewRatio=n 設定年輕代和年老代的比值。如:-XX:NewRatio=3,表示年輕代與年老代比值為1:3,年輕代佔整個年輕代年老代和的1/4,預設新生代和老年代的比例=1:2。

-XX:SurvivorRatio=n 年輕代中Eden區與兩個Survivor區的比值。注意Survivor區有兩個,預設是8,表示

Eden:S0:S1=8:1:1

如:-XX:SurvivorRatio=3,表示Eden:Survivor=3:2,一個Survivor區佔整個年輕代的1/5。

-XX:MaxPermSize=n 設定持久帶大小

-XX:MetaspaceSize 設定元空間大小

收集器設定

-XX:+UseSerialGC 設定序列收集器

-XX:+UseParallelGC 設定並行收集器

-XX:+UseParalledlOldGC 設定並行年老代收集器

-XX:+UseConcMarkSweepGC 設定併發收集器

垃圾回收統計資訊

-XX:+PrintGC

-XX:+PrintGCDetails

-XX:+PrintGCTimeStamps

-Xloggc:filenameGC日誌輸出到檔案裡filename,比如:-Xloggc:/gc。log

並行收集器設定

-XX:ParallelGCThreads=n 設定並行收集器收集時使用的CPU數。並行收集執行緒數。

-XX:MaxGCPauseMillis=n 設定並行收集最大暫停時間

-XX:GCTimeRatio=n 設定垃圾回收時間佔程式執行時間的百分比。公式為1/(1+n)

-XX:MaxGCPauseMillis=n設定並行收集最大暫停時間

併發收集器設定

-XX:+CMSIncrementalMode 設定為增量模式。適用於單CPU情況。

-XX:ParallelGCThreads=n 設定併發收集器年輕代收集方式為並行收集時,使用的CPU數。並行收集執行緒數。

其他

-XX:+PrintCommandLineFlags檢視當前JVM設定過的相關引數

接上篇:大廠面試JVM虛擬機器18連環炮,你頂得住嗎?(下篇)

Dump異常快照

-XX:+HeapDumpOnOutOfMemoryError

-XX:HeapDumpPath

堆記憶體出現OOM的機率是所有記憶體耗盡異常中最高的,出錯時的堆內資訊對解決問題非常有幫助,所以給JVM設定這個引數(-XX:+HeapDumpOnOutOfMemoryError),讓JVM遇到OOM異常時能輸出堆內資訊,並透過(-XX:+HeapDumpPath)引數設定堆記憶體溢位快照輸出的檔案地址,這對於特別是對相隔數月才出現的OOM異常尤為重要。

-Xms10M -Xmx10M -Xmn2M -XX:SurvivorRatio=8 -XX:+HeapDumpOnOutOfMemoryError

-XX:HeapDumpPath=D:\study\log_hprof\gc。hprof

-XX:OnOutOfMemoryError

表示發生OOM後,執行jconsole。exe程式。這裡可以不用加“”,因為jconsole。exe路徑Program Files含有空格。利用這個引數,我們可以在系統OOM後,自定義一個指令碼,可以用來發送郵件告警資訊,可以用來重啟系統等等。

-XX:OnOutOfMemoryError=“C:\Program Files\Java\jdk1。8。0_151\bin\jconsole。exe”

JVM 調優常見目標

JVM 調優目標:使用較小的記憶體佔用來獲得較高的吞吐量或者較低的延遲。程式在上線前的測試或執行中有時會出現一些大大小小的 JVM 問題,比如 cpu load 過高、請求延遲、tps 降低等,甚至出現記憶體洩漏(每次垃圾收集使用的時間越來越長,垃圾收集頻率越來越高,每次垃圾收集清理掉的垃圾資料越來越少)、記憶體溢位導致系統崩潰,因此需要對 JVM 進行調優,使得程式在正常執行的前提下,獲得更高的使用者體驗和執行效率。這裡有幾個比較重要的指標:

記憶體佔用:程式正常執行需要的記憶體大小。

延遲:由於垃圾收集而引起的程式停頓時間。

吞吐量:使用者程式執行時間佔用戶程式和垃圾收集佔用總時間的比值。

當然,和 CAP 原則一樣,同時滿足一個程式記憶體佔用小、延遲低、高吞吐量是不可能的,程式的目標不同,調優時所考慮的方向也不同,在調優之前,必須要結合實際場景,有明確的的最佳化目標,找到效能瓶頸,對瓶頸有針對性的最佳化,最後進行測試,透過各種監控工具確認調優後的結果是否符合目標。

有哪些調優工具?JPS

用 jps(JVM process Status)可以檢視虛擬機器啟動的所有程序、執行主類的全名、JVM啟動引數,比如當執行了 JPSTest 類中的 main 方法後(main 方法持續執行),執行 jps -l可看到下面的JPSTest類的 pid 為 31354,加上 -v 引數還可以看到JVM啟動引數。

jstat

用 jstat(JVM Statistics Monitoring Tool)監視虛擬機器資訊 jstat -gc pid 500 10:每 500 毫秒列印一次 Java 堆狀況(各個區的容量、使用容量、gc 時間等資訊),列印 10 次。jstat 還可以以其他角度監視各區記憶體大小、監視類裝載資訊等,具體可以 google jstat 的詳細用法。

jmap

用 jmap(Memory Map for Java)檢視堆記憶體資訊 執行 jmap -histo pid 可以打印出當前堆中所有每個類的例項數量和記憶體佔用,如下,class name 是每個類的類名([B 是 byte 型別,[C是 char 型別,[I 是 int 型別),bytes 是這個類的所有示例佔用記憶體大小,instances 是這個類的例項數量。執行 jmap -dump 可以轉儲堆記憶體快照到指定檔案,比如執行:

jmap -dump:format=b,file=/data/jvm/dumpfile_jmap。hprof 3361

可以把當前堆記憶體的快照轉儲到 dumpfile_jmap。hprof 檔案中,然後可以對記憶體快照進行分析。

jconsole、jvisualvm

利用 jconsole、jvisualvm 分析記憶體資訊(各個區域 Eden、Survivor、Old 等記憶體變化情況),如果檢視的是遠端伺服器的 JVM,程式啟動需要加上如下引數:

“-Dcom。sun。management。jmxremote=true”

“-Djava。rmi。server。hostname=12。34。56。78”

“-Dcom。sun。management。jmxremote。port=18181”

“-Dcom。sun。management。jmxremote。authenticate=false”

“-Dcom。sun。management。jmxremote。ssl=false”

下圖是 jconsole 介面,概覽選項可以觀測堆記憶體使用量、執行緒數、類載入數和 CPU 佔用率;記憶體選項可以檢視堆中各個區域的記憶體使用量和左下角的詳細描述(記憶體大小、GC 情況等);執行緒選項可以檢視當前 JVM 載入的執行緒,檢視每個執行緒的堆疊資訊,還可以檢測死鎖;VM 概要描述了虛擬機器的各種詳細引數。

第三方工具

MAT、GChisto、GCViewer、JProfiler、arthas、async-profile。

JVM 調優經驗總結

JVM 配置方面,一般情況可以先用預設配置(基本的一些初始引數可以保證一般的應用跑得比較穩定了),在測試中根據系統執行狀況(會話併發情況、會話時間等),結合 gc 日誌、記憶體監控、使用的垃圾收集器等進行合理的調整,當老年代記憶體過小時可能引起頻繁 Full GC,當記憶體過大時 Full GC 時間會特別長。那麼 JVM 的配置比如新生代、老年代應該配置多大最合適呢?答案是不一定,調優就是找答案的過程,物理記憶體一定的情況下,新生代設定越大,老年代就越小,Full GC 頻率就越高,但 Full GC 時間越短;相反新生代設定越小,老年代就越大,Full GC 頻率就越低,但每次 Full GC 消耗的時間越大。建議如下:-Xms 和 -Xmx 的值設定成相等,對大小預設為 -Xms 指定的大小,預設空閒堆記憶體小於 40% 時,JVM 會擴大堆到 -Xmx 指定的大小;空閒堆記憶體大於 70% 時,JVM 會減小堆到 -Xms 指定的大小。如果在 Full GC 後滿足不了記憶體需求會動態調整,這個階段比較耗費資源。

新生代儘量設定大一些,讓物件在新生代多存活一段時間,每次 Minor GC 都要儘可能多地收集垃圾物件,防止或延遲物件進入老年代的機會,以減少應用程式發生 Full GC 的頻率。

老年代如果使用 CMS 收集器,新生代可以不用太大,因為 CMS 的並行收集速度也很快,收集過程比較耗時的併發標記和併發清除階段都可以與使用者執行緒併發執行。

方法區大小的設定,1。6 之前的需要考慮系統執行時動態增加的常量、靜態變數等,1。7 只要差不多能裝下啟動時和後期動態載入的類資訊就行。

程式碼實現方面,效能出現問題比如程式等待、記憶體洩漏除了 JVM 配置可能存在問題,程式碼實現上也有很大關係:

避免建立過大的物件及陣列:過大的物件或陣列在新生代沒有足夠空間容納時會直接進入老年代,如果是短命的大物件,會提前出發 Full GC。

避免同時載入大量資料,如一次從資料庫中取出大量資料,或者一次從 Excel 中讀取大量記錄,可以分批讀取,用完儘快清空引用。

當集合中有物件的引用,這些物件使用完之後要儘快把集合中的引用清空,這些無用物件儘快回收避免進入老年代。

可以在合適的場景(如實現快取)採用軟引用、弱引用,比如用軟引用來為 ObjectA 分配例項:SoftReferenceobjectA=new SoftReference(); 在發生記憶體溢位前,會將 objectA 列入回收範圍進行二次回收,如果這次回收還沒有足夠記憶體,才會丟擲記憶體溢位的異常。

避免產生死迴圈,產生死迴圈後,迴圈體內可能重複產生大量例項,導致記憶體空間被迅速佔滿。

儘量避免長時間等待外部資源(資料庫、網路、裝置資源等)的情況,縮小物件的生命週期,避免進入老年代,如果不能及時返回結果可以適當採用非同步處理的方式等。

總結

本文從認識JDK、JRE、JVM,到編譯,類載入,初始化,垃圾回收,效能調優。可以算的是把JVM的整個流程給過了一遍。希望對你有所幫助。

Top