<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ǎn),速度撿包

          共 16720字,需瀏覽 34分鐘

           ·

          2021-01-13 16:10

          唐僧:龍不是你那么騎的

          想要提高程序員自身的內(nèi)功心法無非就是數(shù)據(jù)結(jié)構(gòu)跟算法 ?+ 操作系統(tǒng) + 計(jì)網(wǎng) + 底層,而所有的Java代碼都是在JVM上運(yùn)行的,了解了JVM好處就是:

          寫出更好更健壯的代碼。

          提高Java的性能,排除問題

          面試必問,要對知識有一定對深度。

          1、簡述JVM 內(nèi)存模型

          從宏觀上來說JVM 內(nèi)存區(qū)域 分為三部分線程共享區(qū)域、線程私有區(qū)域、直接內(nèi)存區(qū)域。

          1.1、線程共享區(qū)域

          1.1.1、堆區(qū)

          堆區(qū)Heap是JVM中最大的一塊內(nèi)存區(qū)域,基本上所有的對象實(shí)例都是在堆上分配空間。堆區(qū)細(xì)分為年輕代老年代,其中年輕代又分為Eden、S0、S1 三個部分,他們默認(rèn)的比例是8:1:1的大小。

          1.1.1、元空間

          方法區(qū)

          1. 在 《Java虛擬機(jī)規(guī)范》中只是規(guī)定了有方法區(qū)這么個概念跟它的作用。HotSpot在JDK8之前 搞了個永久代把這個概念實(shí)現(xiàn)了。用來主要存儲類信息、常量池、靜態(tài)變量、JIT編譯后的代碼等數(shù)據(jù)。
          2. PermGen(永久代)中類的元數(shù)據(jù)信息在每次FullGC的時候可能會被收集,但成績很難令人滿意。而且為PermGen分配多大的空間因?yàn)榇鎯ι鲜龆喾N數(shù)據(jù)很難確定大小。因此官方在JDK8剔除移除永久代。

          官方解釋移除永久代:

          1. This is part of the JRockit and Hotspot convergence effort. JRockit customers do not need to configure the permanent generation (since JRockit does not have a permanent generation) and are accustomed to not configuring the permanent generation.
          2. 即:移除永久代是為融合HotSpot JVM與 JRockit VM而做出的努力,因?yàn)镴Rockit沒有永久代,不需要配置永久代。

          元空間

          在Java中用永久代來存儲類信息,常量,靜態(tài)變量等數(shù)據(jù)不是好辦法,因?yàn)檫@樣很容易造成內(nèi)存溢出。同時對永久代的性能調(diào)優(yōu)也很困難,因此在JDK8中 把永久代去除了,引入了元空間metaspace,原先的class、field等變量放入到metaspace。

          總結(jié)

          元空間的本質(zhì)和永久代類似,都是對JVM規(guī)范中方法區(qū)的實(shí)現(xiàn)。不過元空間與永久代之間最大的區(qū)別在于:元空間并不在虛擬機(jī)中,而是使用本地內(nèi)存。因此,默認(rèn)情況下,元空間的大小僅受本地內(nèi)存限制,但可以通過參數(shù)來指定元空間的大小。

          1.2、直接內(nèi)存區(qū)域

          直接內(nèi)存

          一般使用Native函數(shù)操作C++代碼來實(shí)現(xiàn)直接分配堆外內(nèi)存,不是虛擬機(jī)運(yùn)行時數(shù)據(jù)區(qū)的一部分,也不是Java虛擬機(jī)規(guī)范中定義的內(nèi)存區(qū)域。這塊內(nèi)存不受Java堆空間大小的限制,但是受本機(jī)總內(nèi)存大小限制所以也會出現(xiàn)OOM異常。分配空間后避免了在Java堆區(qū)跟Native堆中來回復(fù)制數(shù)據(jù),可以有效提高讀寫效率,但它的創(chuàng)建、銷毀卻比普通Buffer慢。

          PS:如果使用了NIO,本地內(nèi)存區(qū)域會被頻繁的使用,此時 jvm內(nèi)存 ≈ 方法區(qū) + 堆 + 棧+ 直接內(nèi)存

          1.3、線程私有區(qū)域

          程序計(jì)數(shù)器、虛擬機(jī)棧、本地方法棧跟線程的聲明周期是一樣的。

          1.3.1、程序計(jì)數(shù)器

          課堂上比如你正在看小說《誅仙》,看到1412章節(jié)時,老師喊你回答問題,這個時候你肯定要先應(yīng)付老師的問題,回答完畢后繼續(xù)接著看,這個時候你可以用書簽也可以憑借記憶記住自己在看的位置,通過這樣實(shí)現(xiàn)繼續(xù)閱讀。

          落實(shí)到代碼運(yùn)行時候同樣道理,程序計(jì)數(shù)器用于記錄當(dāng)前線程下虛擬機(jī)正在執(zhí)行的字節(jié)碼的指令地址。它具有如下特性:

          1. 線程私有

          多線程情況下,在同一時刻所以為了讓線程切換后依然能恢復(fù)到原位,每條線程都需要有各自獨(dú)立的程序計(jì)數(shù)器。

          1. 沒有規(guī)定OutOfMemoryError

          程序計(jì)數(shù)器存儲的是字節(jié)碼文件的行號,而這個范圍是可知曉的,在一開始分配內(nèi)存時就可以分配一個絕對不會溢出的內(nèi)存。

          1. 執(zhí)行Native方法時值為空

          Native方法大多是通過C實(shí)現(xiàn)并未編譯成需要執(zhí)行的字節(jié)碼指令,也就不需要去存儲字節(jié)碼文件的行號了。

          1.3.2、虛擬機(jī)棧

          方法的出入棧:調(diào)用的方法會被打包成棧楨,一個棧楨至少需要包含一個局部變量表、操作數(shù)棧、楨數(shù)據(jù)區(qū)、動態(tài)鏈接。

          動態(tài)鏈接

          當(dāng)棧幀內(nèi)部包含一個指向運(yùn)行時常量池引用前提下,類加載時候會進(jìn)行符號引用到直接引用的解析跟鏈接替換。

          局部變量表

          局部變量表是棧幀重要組中部分之一。他主要保存函數(shù)的參數(shù)以及局部的變量信息。局部變量表中的變量作用域是當(dāng)前調(diào)用的函數(shù)。函數(shù)調(diào)用結(jié)束后,隨著函數(shù)棧幀的銷毀。局部變量表也會隨之銷毀,釋放空間。

          操作數(shù)棧

          保存著Java虛擬機(jī)執(zhí)行過程中數(shù)據(jù)

          方法返回地址

          方法被調(diào)用的位置,當(dāng)方法退出時候?qū)嶋H上等同于當(dāng)前棧幀出棧。

          比如執(zhí)行簡單加減法:

          public?class?ShowByteCode?{
          ????private?String?xx;
          ????private?static?final?int?TEST?=?1;
          ????public?ShowByteCode()?{
          ????}
          ????public?int?calc()?{
          ????????int?a?=?100;
          ????????int?b?=?200;
          ????????int?c?=?300;
          ????????return?(a?+?b)?*?c;
          ????}
          }

          執(zhí)行javap -c *.class

          1.3.3、本地方法棧

          跟虛擬機(jī)棧類似,只是為使用到的Native方法服務(wù)而已。

          2、判斷對象是否存活

          JVM空間不夠就需要Garbage Collection了,一般共享區(qū)的都要被回收比如堆區(qū)以及方法區(qū)。在進(jìn)行內(nèi)存回收之前要做的事情就是判斷那些對象是死的,哪些是活的。常用方法有兩種 引用計(jì)數(shù)法可達(dá)性分析。

          2.1、引用計(jì)數(shù)法

          思路是給 Java 對象添加一個引用計(jì)數(shù)器,每當(dāng)有一個地方引用它時,計(jì)數(shù)器 +1;引用失效則 -1,當(dāng)計(jì)數(shù)器不為 0 時,判斷該對象存活;否則判斷為死亡(計(jì)數(shù)器 = 0)。優(yōu)點(diǎn)

          實(shí)現(xiàn)簡單,判斷高效。

          缺點(diǎn)

          無法解決 對象間 相互循環(huán)引用 的問題

          class?GcObject?{
          ????public?Object?instance?=?null;
          }
          public?class?GcDemo?{
          ????public?static?void?main(String[]?args)?{
          ????????GcObject?object1?=?new?GcObject();?//?step?1?
          ????????GcObject?object2?=?new?GcObject();?//?step?2
          ????????
          ????????object1.instance?=?object2?;//step?3
          ????????object2.instance?=?object1;?//step?4
          ????????
          ????????object1?=?null;?//step?5
          ????????object2?=?null;?//?step?6
          ????}
          }

          step1: GcObject實(shí)例1的引用計(jì)數(shù)+1,實(shí)例1引用數(shù) = 1?

          step2: GcObject實(shí)例2的引用計(jì)數(shù)+1,實(shí)例2引用數(shù) = 1?

          step3: GcObject實(shí)例2的引用計(jì)數(shù)+1,實(shí)例2引用數(shù) = 2?

          step4: GcObject實(shí)例1的引用計(jì)數(shù)+1,實(shí)例1引用數(shù) = 2?

          step5: GcObject實(shí)例1的引用計(jì)數(shù)-1,結(jié)果為 1?

          step6: GcObject實(shí)例2的引用計(jì)數(shù)-1,結(jié)果為 1

          如上分析發(fā)現(xiàn)實(shí)例1跟實(shí)例2的引用數(shù)都不為0而又相互引用,這兩個實(shí)例所占有的內(nèi)存則無法釋放。

          2.2、可達(dá)性分析

          很多主流商用語言(如Java、C#)都采用引用鏈法判斷對象是否存活,大致的思路就是將一系列的 GC Roots 對象作為起點(diǎn),從這些起點(diǎn)開始向下搜索。在Java語言中,可作為 GC Roots 的對象包含以下幾種:

          1. 第一種是虛擬機(jī)棧中的引用的對象,在程序中正常創(chuàng)建一個對象,對象會在堆上開辟一塊空間,同時會將這塊空間的地址作為引用保存到虛擬機(jī)棧中,如果對象生命周期結(jié)束了,那么引用就會從虛擬機(jī)棧中出棧,因此如果在虛擬機(jī)棧中有引用,就說明這個對象還是有用的,這種情況是最常見的。

          2. 第二種是我們在類中定義了全局的靜態(tài)的對象,也就是使用了static關(guān)鍵字,由于虛擬機(jī)棧是線程私有的,所以這種對象的引用會保存在共有的方法區(qū)中,顯然將方法區(qū)中的靜態(tài)引用作為GC Roots是必須的。

          3. 第三種便是常量引用,就是使用了static final關(guān)鍵字,由于這種引用初始化之后不會修改,所以方法區(qū)常量池里的引用的對象也應(yīng)該作為GC Roots。

          4. 第四種是在使用JNI技術(shù)時,有時候單純的Java代碼并不能滿足我們的需求,我們可能需要在Java中調(diào)用C或C++的代碼,因此會使用Native方法,JVM內(nèi)存中專門有一塊本地方法棧,用來保存這些對象的引用,所以本地方法棧中引用的對象也會被作為GC Roots。

          GC Root步驟主要包含如下三步:

          2.1.1 可達(dá)性分析

          當(dāng)一個對象到 GC Roots 沒有任何引用鏈相連時,則判斷該對象不可達(dá)。注意: 可達(dá)性分析僅僅只是判斷對象是否可達(dá),但還不足以判斷對象是否存活 / 死亡。

          2.1.2 第一次標(biāo)記 & 篩選

          篩選的條件對象 如果沒有重寫finalize或者調(diào)用過finalize 則將該對象加入到F-Queue中

          2.1.3 第二次標(biāo)記 & 篩選

          當(dāng)對象經(jīng)過了第一次的標(biāo)記 & 篩選,會被進(jìn)行第二次標(biāo)記 & 準(zhǔn)備被進(jìn)行篩選。經(jīng)過F-Queue篩選后如果對象還沒有跟GC Root建立引用關(guān)系則被回收,屬于給個二次機(jī)會。

          2.3、四大引用類型

          2.3.1 強(qiáng)引用

          強(qiáng)引用(StrongReference)是使用最普遍的引用。垃圾回收器絕對不會回收它,內(nèi)存不足時寧愿拋出OOM導(dǎo)致程序異常,平常的new 對象就是。

          2.3.2 軟引用

          垃圾回收器在內(nèi)存充足時不會回收軟引用(SoftReference)對象,不足時會回收它,特別適合用于創(chuàng)建緩存。

          2.3.3 弱引用

          弱引用(WeakReference)是在掃描到該對象時無論內(nèi)存是否充足都會回收該對象。ThreadLocal 的Key就是弱引用。

          2.3.4 虛引用

          如果一個對象只具有虛引用(PhantomReference)那么跟沒有任何引用一樣,任何適合都可以被回收。主要用跟蹤對象跟垃圾回收器回收的活動。

          3、垃圾回收算法

          為了揮手回收垃圾操作系統(tǒng)一般會使用標(biāo)記清除、復(fù)制算法標(biāo)記整理三種算法,這三種各有優(yōu)劣,簡單介紹下:

          3.1、標(biāo)記清除

          原理:

          算法分為標(biāo)記清除兩個階段:首先標(biāo)記出所有需要回收的對象,在標(biāo)記完成后統(tǒng)一回收所有被標(biāo)記的對象。

          缺點(diǎn):

          標(biāo)記清除之后會產(chǎn)生大量不連續(xù)的內(nèi)存碎片,導(dǎo)致觸發(fā)GC。

          3.2、標(biāo)記復(fù)制

          原理:

          將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當(dāng)這一塊的內(nèi)存用完了,就將還存活著的對象復(fù)制到另外一塊上面,然后再把已使用過的內(nèi)存空間一次清理掉。

          缺點(diǎn):

          這種算法的代價是將內(nèi)存縮小為了原來的一半,還要來回移動數(shù)據(jù)。

          3.3、標(biāo)記整理

          原理:

          首先標(biāo)記出所有需要回收的對象,在標(biāo)記完成后,后續(xù)步驟是讓所有存活的對象都向一端移動,然后直接清理掉端邊界以外的內(nèi)存。

          缺點(diǎn):

          涉及到移動大量對象,效率不高。

          總結(jié)

          指標(biāo)標(biāo)記清理標(biāo)記整理標(biāo)記復(fù)制
          速度中等最慢
          空間開銷少(但會堆積碎片)少(不堆積碎片)通常需要活對象的2倍大?。ú欢逊e碎片)
          移動對象

          3.4 、三色標(biāo)記跟讀寫屏障

          前面說的三種回收算法都說到了先標(biāo)記,問題是如何標(biāo)記的呢?說話說一半,小心沒老伴!

          接下來的知識點(diǎn)個人感覺面試應(yīng)該問不到那么深了,但是為了裝逼必須Mark下!CMS、G1 標(biāo)記時候一般用的是三色標(biāo)記法,根據(jù)可達(dá)性分析從GC Roots開始進(jìn)行遍歷訪問,可達(dá)的則為存活對象,而最終不可達(dá)說明就是需要被GC對象。大致流程是把遍歷對象圖過程中遇到的對象,按是否訪問過這個條件標(biāo)記成以下三種顏色:

          白色:尚未訪問過。

          黑色:本對象已訪問過,而且本對象 引用到 的其他對象 也全部訪問過了。灰色:本對象已訪問過,但是本對象 引用到 的其他對象 尚未全部訪問完。全部訪問后會轉(zhuǎn)換為黑色。

          假設(shè)現(xiàn)在有白、灰、黑三個集合(表示當(dāng)前對象的顏色),遍歷訪問過程:

          1、初始時所有對象都在白色集合中。

          2、將GC Roots 直接引用到的對象挪到灰色集合中。?

          3、從灰色集合中獲取對象:第一步將本對象 引用到的 其他對象 全部挪到灰色集合中,第二步將本對象 挪到黑色集合里面。?

          4、重復(fù)步驟3,直至灰色集合為空時結(jié)束。?

          5、結(jié)束后仍在白色集合的對象即為GC Roots 不可達(dá),可以嘗試進(jìn)行回收。

          當(dāng)STW時對象間的引用是不會發(fā)生變化的,可以輕松完成標(biāo)記。當(dāng)支持并發(fā)標(biāo)記時,對象間的引用可能發(fā)生變化,多標(biāo)和漏標(biāo)的情況就有可能發(fā)生。

          3.4 .1、浮動垃圾

          狀況:GC線程遍歷到E(E是灰色),一個業(yè)務(wù)線程執(zhí)行了D.E = null,此時E應(yīng)該被回收的。但是GC線程已經(jīng)認(rèn)為E是灰色了會繼續(xù)遍歷,導(dǎo)致E沒有被回收。

          3.4 .2、漏標(biāo)

          GC線程遍歷到E(灰色了)。業(yè)務(wù)線程執(zhí)行了E-->G斷開,D-->G鏈接的操作。GC線程發(fā)現(xiàn)E無法到達(dá)G,因?yàn)槭呛谏粫俦闅v標(biāo)記了。最終導(dǎo)致漏標(biāo)G。漏標(biāo)的必備兩個條件:灰到白斷開,黑到白建立

          Object?G?=?E.G;????//?第一步?:讀
          Object?E.G?=?null;?//?第二步:寫
          Object?D.G?=?G;???//?第三步:寫

          漏標(biāo)解決方法

          將對象G存儲到特定集合中,等并發(fā)標(biāo)記遍歷完畢后再對集合中對象進(jìn)行重新標(biāo)記。

          3.4.2.1、CMS方案

          這里比如開始B指向C,但是后來B不指向C,A指向D,最簡單的方法是將A變成灰色,等待下次進(jìn)行再次遍歷。CMS中可能引發(fā)ABA問題:

          1、回收線程 m1 正在標(biāo)記A,屬性A.1標(biāo)記完畢,正在標(biāo)記屬性A.2。

          2、業(yè)務(wù)線程 m2 把屬性1指向了C,由于CMS方案此時回收線程 m3 把A標(biāo)記位灰色。?

          3、回收線程 m1 認(rèn)為所有屬性標(biāo)記完畢,將A設(shè)置為黑色,結(jié)果C漏標(biāo)。所以CMS階段需要重新標(biāo)記。

          3.4.2.2、讀寫屏障

          漏標(biāo)的實(shí)現(xiàn)是有三步的,JVM加入了讀寫屏障,其中讀屏障則是攔截第一步,寫屏障用于攔截第二和第三步。

          寫屏障 + SATB(原始快照) 來破壞 灰到白斷開。?

          寫屏障 + 增量更新 來破壞 黑到白建立。?

          讀屏障 一種保守方式來破壞灰到白斷開后白的存儲,此時用讀屏障OK的。

          現(xiàn)代使用可達(dá)性分析的垃圾回收器幾乎都借鑒了三色標(biāo)記的算法思想,盡管實(shí)現(xiàn)的方式不盡相同。對于讀寫屏障,以Java HotSpot VM為例,其并發(fā)標(biāo)記時對漏標(biāo)的處理方案如下:

          CMS寫屏障 + 增量更新

          G1寫屏障 + SATB

          ZGC讀屏障

          CMS中使用的增量更新,在重新標(biāo)記階段除了需要遍歷 寫屏障的記錄,還需要重新掃描遍歷GC Roots(標(biāo)記過的不用再標(biāo)記),這是由于CMS對于astore_x等指令不添加寫屏障的原因。

          4、GC流程

          核心思想就是根據(jù)各個年代的特點(diǎn)不同選用不同到垃圾收集算法

          1. 年輕代:使用復(fù)制算法
          2. 老年代:使用標(biāo)記整理或者標(biāo)記清除算法。

          為什么要有年輕代

          分代的好處就是優(yōu)化GC性能,如果沒有分代每次掃描所有區(qū)域能累死GC。因?yàn)楹芏鄬ο髱缀蹙褪?code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(40, 202, 113);">朝生夕死的,如果分代的話,我們把新創(chuàng)建的對象放到某一地方,當(dāng)GC的時候先把這塊存朝生夕死(80%以上)對象的區(qū)域進(jìn)行回收,這樣就會騰出很大的空間出來。

          4.1、 年輕代

          HotSpot JVM把年輕代分為了三部分:1個Eden區(qū)和2個Survivor區(qū)(分別叫fromto)。默認(rèn)比例為8:1:1。一般情況下,新創(chuàng)建的對象都會被分配到Eden區(qū)(一些大對象特殊處理),這些對象經(jīng)過第一次Minor GC后,如果仍然存活,將會被移到Survivor區(qū)。對象在Survivor區(qū)中每熬過一次Minor GC年齡就會增加1歲,當(dāng)它的年齡增加到一定次數(shù)(默認(rèn)15次)時,就會被移動到年老代中。年輕代的垃圾回收算法使用的是復(fù)制算法。

          年輕代GC過程:GC開始前,年輕代對象只會存在于Eden區(qū)和名為FromSurvivor區(qū),名為ToSurvivor區(qū)永遠(yuǎn)是空的。如果新分配對象在Eden申請空間發(fā)現(xiàn)不足就會導(dǎo)致GC。

          yang GCEden區(qū)中所有存活的對象都會被復(fù)制到To,而在From區(qū)中,仍存活的對象會根據(jù)他們的年齡值來決定去向。年齡達(dá)到一定值(年齡閾值可以通過-XX:MaxTenuringThreshold來設(shè)置)的對象會被移動到年老代中,沒有達(dá)到閾值的對象會被復(fù)制到To區(qū)域。經(jīng)過這次GC后,Eden區(qū)和From區(qū)已經(jīng)被清空。這個時候,FromTo會交換他們的角色,也就是新的To就是上次GC前的From,新的From就是上次GC前的To。不管怎樣都會保證名為To的Survivor區(qū)域是空的。Minor GC會一直重復(fù)這樣的過程,直到To區(qū)被填滿,To區(qū)被填滿之后,會將所有對象移動到年老代中。這里注意如果yang GC 后空間還是不夠用則會 空間擔(dān)保 機(jī)制將數(shù)據(jù)送到Old區(qū)

          卡表 Card Table

          1. 為了支持高頻率的新生代回收,虛擬機(jī)使用一種叫做卡表(Card Table)的數(shù)據(jù)結(jié)構(gòu),卡表作為一個比特位的集合,每一個比特位可以用來表示年老代的某一區(qū)域中的所有對象是否持有新生代對象的引用。
          2. 新生代GC時不用花大量的時間掃描所有年老代對象,來確定每一個對象的引用關(guān)系,先掃描卡表,只有卡表的標(biāo)記位為1時,才需要掃描給定區(qū)域的年老代對象。而卡表位為0的所在區(qū)域的年老代對象,一定不包含有對新生代的引用。

          4.2、 老年代

          老年代GC過程

          老年代中存放的對象是存活了很久的,年齡大于15的對象 或者 觸發(fā)了老年代的分配擔(dān)保機(jī)制存儲的大對象。在老年代觸發(fā)的gc叫major gc也叫full gcfull gc會包含年輕代的gc。full gc采用的是 標(biāo)記-清除標(biāo)記整理。在執(zhí)行full gc的情況下,會阻塞程序的正常運(yùn)行。老年代的gc比年輕代的gc效率上慢10倍以上。對效率有很大的影響。所以一定要盡量避免老年代GC!

          4.3、 元空間

          永久代的回收會隨著full gc進(jìn)行移動,消耗性能。每種類型的垃圾回收都需要特殊處理 元數(shù)據(jù)。將元數(shù)據(jù)剝離出來,簡化了垃圾收集,提高了效率。

          -XX:MetaspaceSize 初始空間的大小。達(dá)到該值就會觸發(fā)垃圾收集進(jìn)行類型卸載,同時GC會對該值進(jìn)行調(diào)整:

          如果釋放了大量的空間,就適當(dāng)降低該值;如果釋放了很少的空間,那么在不超過MaxMetaspaceSize時,適當(dāng)提高該值。

          -XX:MaxMetaspaceSize:

          最大空間,默認(rèn)是沒有限制的。

          4.4 、垃圾回收流程總結(jié)

          大致的GC回收流程如上圖,還有一種設(shè)置就是 大對象直接進(jìn)入老年代

          1. 如果在新生代分配失敗且對象是一個不含任何對象引用的大數(shù)組,可被直接分配到老年代。通過在老年代的分配避免新生代的一次垃圾回收。
          2. 設(shè)置了-XX:PretenureSizeThreshold 值,任何比這個值大的對象都不會嘗試在新生代分配,將在老年代分配內(nèi)存。

          內(nèi)存回收跟分配策略

          1. 優(yōu)先在Eden上分配對象,此區(qū)域垃圾回收頻繁速度還快。
          2. 大對象直接進(jìn)入老生代。
          3. 年長者(長期存活對象默認(rèn)15次)跟 進(jìn)入老生代。
          4. 在Survivor空間中相同年齡所有對象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對象會群體進(jìn)入老生代。
          5. 空間分配擔(dān)保(擔(dān)保minorGC),如果Minor GC后 Survivor區(qū)放不下新生代仍存活的對象,把Suvivor 無法容納的對象直接進(jìn)人老年代。

          5、垃圾收集器

          5.1、 垃圾收集器

          堆heap是垃圾回收機(jī)制的重點(diǎn)區(qū)域。我們知道垃圾回收機(jī)制有三種minor gc、major gcfull gc。針對于堆的就是前兩種。年輕代的叫 minor gc,老年代的叫major gc。

          1. JDK7、JDK8 默認(rèn)垃圾收集器 Parallel Scavenge(新生代)+ Parallel Old(老年代)
          2. JDK9 默認(rèn)垃圾收集器G1
          3. 服務(wù)端開發(fā)常見組合就是 ParNew + CMS

          工程化使用的時候使用指定的垃圾收集器組合使用,講解垃圾收集器前先普及幾個重要知識點(diǎn):

          STW

          java中Stop-The-World機(jī)制簡稱STW,是指執(zhí)行垃圾收集算法時Java應(yīng)用程序的其他所有線程都被掛起(除了垃圾收集幫助器之外)。是Java中一種全局暫?,F(xiàn)象,全局停頓,所有Java代碼停止,native代碼雖然可以執(zhí)行但不能與JVM交互,如果發(fā)生了STW 現(xiàn)象多半是由于gc引起。

          吞吐量

          吞吐量 = 運(yùn)行用戶代碼時間 / ( 運(yùn)行用戶代碼時間 + 垃圾收集時間 )。例如:虛擬機(jī)共運(yùn)行100分鐘,垃圾收集器花掉1分鐘,那么吞吐量就是99%

          垃圾收集時間

          垃圾回收頻率 * 單次垃圾回收時間

          并行收集

          指多條垃圾收集線程并行工作,但此時用戶線程仍處于等待狀態(tài)。

          并發(fā)收集

          用戶線程與垃圾收集線程同時工作(不一定是并行的可能會交替執(zhí)行)。用戶程序在繼續(xù)運(yùn)行,而垃圾收集程序運(yùn)行在另一個CPU上。

          5.2、 新生代

          新生代有SerialParNew、Parallel Scavenge三種垃圾收集器。

          名稱串行/并行/并發(fā)回收算法使用場景可以跟CMS配合
          Serial串行復(fù)制單CPU,Client模式下虛擬機(jī)
          ParNew并行(Serial的并行版)復(fù)制多CPU,常在Server模式
          Parallel Scavenge并行復(fù)制多CPU且關(guān)注吞吐量

          5.3、 老年代

          老年代有Serial OldParallel Old、CMS 三種垃圾收集器。

          名稱串行/并行/并發(fā)回收算法使用場景組合年輕代
          Serial Old串行標(biāo)記整理單CPUSerial ?、ParNew、Parallel Scavenge
          Parallel Old并行標(biāo)記整理多CPUParallel Scavenge
          CMS并發(fā)標(biāo)記清除多CPU且關(guān)注吞吐量,常用Server端Serial 、ParNew
          5.3.1、CMS

          CMS(Concurrent Mark Sweep)比較重要這里 重點(diǎn)說一下。

          CMS的初衷和目的

          為了消除Throught收集器和Serial收集器在Full GC周期中的長時間停頓。是一種以獲取最短回收停頓時間為目標(biāo)的收集器,具有自適應(yīng)調(diào)整策略,適合互聯(lián)網(wǎng)站 跟B/S 服務(wù)應(yīng)用。

          CMS的適用場景

          如果你的應(yīng)用需要更快的響應(yīng),不希望有長時間的停頓,同時你的CPU資源也比較豐富,就適合適用CMS收集器。比如常見的Server端任務(wù)。

          優(yōu)點(diǎn)

          并發(fā)收集、低停頓。

          缺點(diǎn)

          1. CMS收集器對CPU資源非常敏感:在并發(fā)階段,雖然不會導(dǎo)致用戶線程停頓,但是會占用CPU資源而導(dǎo)致引用程序變慢,總吞吐量下降。
          2. 無法處理浮動垃圾:由于CMS并發(fā)清理階段用戶線程還在運(yùn)行,伴隨程序的運(yùn)行自熱會有新的垃圾不斷產(chǎn)生,這一部分垃圾出現(xiàn)在標(biāo)記過程之后,CMS無法在本次收集中處理它們,只好留待下一次GC時將其清理掉。這一部分垃圾稱為浮動垃圾。如果內(nèi)存放不下浮動垃圾這時 JVM 啟動 Serial Old 替代 CMS。
          3. 空間碎片:CMS是基于標(biāo)記-清除算法實(shí)現(xiàn)的收集器,使用標(biāo)記-清除算法收集后,會產(chǎn)生大量碎片

          CMS回收流程

          1. 初始標(biāo)記引發(fā)STW, 僅僅只是標(biāo)記出GC ROOTS能直接關(guān)聯(lián)到的對象,速度很快。
          2. 并發(fā)標(biāo)記不引發(fā)STW,正常運(yùn)行 所有Old 對象是否可鏈到GC Roots
          3. 重新標(biāo)記引發(fā)STW,為了修正并發(fā)標(biāo)記期間,因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生改變的標(biāo)記。這個階段的停頓時間會被初始標(biāo)記階段稍長,但比并發(fā)標(biāo)記階段要短。
          4. 并發(fā)清除不引發(fā)STW,正常運(yùn)行,標(biāo)記清除算法來清理刪除掉標(biāo)記階段判斷的已經(jīng)死亡的對象。

          總結(jié)

          1. 并發(fā)標(biāo)記并發(fā)清除的耗時最長但是不需要停止用戶線程。初始標(biāo)記重新標(biāo)記的耗時較短,但是需要停止用戶線程,所以整個GC過程造成的停頓時間較短,大部分時候是可以和用戶線程一起工作的。

          5.4、G1

          之前的GC收集器對Heap的劃分:以前垃圾回收器是 新生代 + 老年代,用了CMS效果也不是很好,為了減少STW對系統(tǒng)的影響引入了G1(Garbage-First Garbage Collector),G1是一款面向服務(wù)端應(yīng)用的垃圾收集器,具有如下特點(diǎn):

          1、并行與并發(fā):G1能充分利用多CPU、多核環(huán)境下的硬件優(yōu)勢,可以通過并發(fā)的方式讓Java程序繼續(xù)執(zhí)行。

          2、分代收集:分代概念在G1中依然得以保留,它能夠采用不同的方式去處理新創(chuàng)建的對象和已經(jīng)存活了一段時間、熬過多次GC的舊對象來獲得更好的收集效果。?

          3、空間整合:G1從整體上看是基于標(biāo)記-整理算法實(shí)現(xiàn)的,從局部(兩個Region之間)上看是基于復(fù)制算法實(shí)現(xiàn)的,G1運(yùn)行期間不會產(chǎn)生內(nèi)存空間碎片。?

          4、可預(yù)測停頓:G1比CMS牛在能建立可預(yù)測的停頓時間模型,能讓使用者明確指定在一個長度為M毫秒的時間片段內(nèi),消耗在垃圾收集上的時間不得超過N毫秒。

          G1作為JDK9之后的服務(wù)端默認(rèn)收集器,不再區(qū)分年輕代和老年代進(jìn)行垃圾回收,G1默認(rèn)把堆內(nèi)存分為N個分區(qū),每個1~32M(總是2的冪次方)。并且提供了四種不同Region標(biāo)簽Eden、Survivor 、Old、 Humongous。H區(qū)可以認(rèn)為是Old區(qū)中一種特列專門用來存儲大數(shù)據(jù)的,關(guān)于H區(qū)數(shù)據(jù)存儲類型一般符合下面條件:

          當(dāng) 0.5 Region <= ?當(dāng)對象大小 <= 1 Region 時候?qū)?shù)據(jù)存儲到 H區(qū)?

          當(dāng)對象大小 > 1 Region 存儲到連續(xù)的H區(qū)。

          同時G1中引入了RememberSets、CollectionSets幫助更好的執(zhí)行GC 。

          1、RememberSets:?RSet 記錄了其他Region中的對象引用本Region中對象的關(guān)系,屬于points-into結(jié)構(gòu)(誰引用了我的對象)?

          2、CollectionSetsCsets 是一次GC中需要被清理的regions集合,注意G1每次GC不是全部region都參與的,可能只清理少數(shù)幾個,這幾個就被叫做Csets。在GC的時候,對于old -> young 和old -> old的跨代對象引用,只要掃描對應(yīng)的CSet中的RSet即可。

          G1進(jìn)行GC的時候一般分為Yang GCMixed GC。

          Young GCCSet 就是所有年輕代里面的Region

          Mixed GCCSet 是所有年輕代里的Region加上在全局并發(fā)標(biāo)記階段標(biāo)記出來的收益高的Region

          5.4.1、Yang GC

          標(biāo)準(zhǔn)的年輕代GC算法,整體思路跟CMS中類似。

          5.4.2、Mixed GC

          G1中是有Old GC的,有一個把老年代跟新生代同時GC的 Mixed GC,它的回收流程

          1、初始標(biāo)記是STW事件,其完成工作是標(biāo)記GC ROOTS 直接可達(dá)的對象。標(biāo)記位RootRegion。

          2、根區(qū)域掃描不是STW事件,拿來RootRegion,掃描整個Old區(qū)所有Region,看每個Region的Rset中是否有RootRegion。有則標(biāo)識出來。?

          3、并發(fā)標(biāo)記 :同CMS并發(fā)標(biāo)記 不需要STW,遍歷范圍減少,在此只需要遍歷 第二步 被標(biāo)記到引用老年代的對象 RSet。?

          4、最終標(biāo)記 :同 CMS 重新標(biāo)記 會STW ,用的SATB操作,速度更快。

          5、清除STW操作,用 復(fù)制清理算法,清點(diǎn)出有存活對象的Region和沒有存活對象的Region(Empty Region),更新Rset。把Empty Region收集起來到可分配Region隊(duì)列。

          回收總結(jié)

          1、經(jīng)過global concurrent marking,collector就知道哪些Region有存活的對象。并將那些完全可回收的Region(沒有存活對象)收集起來加入到可分配Region隊(duì)列,實(shí)現(xiàn)對該部分內(nèi)存的回收。對于有存活對象的Region,G1會根據(jù)統(tǒng)計(jì)模型找處收益最高、開銷不超過用戶指定的上限的若干Region進(jìn)行對象回收。這些選中被回收的Region組成的集合就叫做collection set 簡稱Cset!?

          2、在MIX GC中的Cset = 所有年輕代里的region + 根據(jù)global concurrent marking統(tǒng)計(jì)得出收集收益高的若干old region。?

          3、在YGC中的Cset = 所有年輕代里的region + 通過控制年輕代的region個數(shù)來控制young GC的開銷。?

          4、YGC 與 MIXGC 都是采用多線程復(fù)制清理,整個過程會STW。G1的低延遲原理在于其回收的區(qū)域變得精確并且范圍變小了。

          G1提速點(diǎn)

          1 重新標(biāo)記時X區(qū)域直接刪除。

          2 Rset降低了掃描的范圍,上題中兩點(diǎn)。?

          3 重新標(biāo)記階段使用SATB速度比CMS快。?

          4 清理過程為選取部分存活率低的Region進(jìn)行清理,不是全部,提高了清理的效率。

          總結(jié)

          就像你媽讓你把自己臥室打掃干凈,你可能只把顯眼而比較大的垃圾打掃了,犄角旮旯的你沒打掃。關(guān)于G1 還有很多細(xì)節(jié)其實(shí)沒看到也。

          一句話總結(jié)G1思維:每次選擇性的清理大部分垃圾來保證時效性跟系統(tǒng)的正常運(yùn)行。

          6、New個對象

          一個Java類從編碼到最終完成執(zhí)行,主要包括兩個過程,編譯、運(yùn)行。

          編譯:將我們寫好的.java文件通過Javac命令編譯成.class文件。?

          運(yùn)行:把編譯生成的.class文件交由JVM執(zhí)行。

          Jvm運(yùn)行class類的時候,并不是一次性將所有的類都加載到內(nèi)存中,而是用到哪個就加載哪個,并且只加載一次。

          6.1、類的生命周期

          6.1.1、 加載

          加載指的是把class字節(jié)碼文件從各個來源通過類加載器裝載入內(nèi)存中,這里有兩個重點(diǎn):

          1. 字節(jié)碼來源:一般的加載來源包括從本地路徑下編譯生成的.class文件,從jar包中的.class文件,從遠(yuǎn)程網(wǎng)絡(luò),以及動態(tài)代理實(shí)時編譯
          2. 類加載器:一般包括啟動類加載器,擴(kuò)展類加載器,應(yīng)用類加載器,以及用戶的自定義類加載器(加密解密那種)。
          6.1.2、 驗(yàn)證

          主要是為了保證加載進(jìn)來的字節(jié)流符合虛擬機(jī)規(guī)范,不會造成安全錯誤。文件格式驗(yàn)證、元數(shù)據(jù)驗(yàn)證、字節(jié)碼驗(yàn)證、符號引用驗(yàn)證。

          6.1.3、 準(zhǔn)備

          給類靜態(tài)變量分配內(nèi)存空間,僅僅是分配空間,比如 public static int age = 14,在準(zhǔn)備后age = 0,在初始化階段 age = 14,如果添加了final則在這個階段直接賦值為14。

          6.1.4、 解析

          將常量池內(nèi)的符號引用替換為直接引用

          6.1.5、 初始化

          前面在加載類階段用戶應(yīng)用程序可以通過自定義類加載器參與之外 其余動作完全由虛擬機(jī)主導(dǎo)和控制。此時才是真正開始執(zhí)行類中定義的代碼 :執(zhí)行static代碼塊進(jìn)行初始化,如果存在父類,先對父類進(jìn)行初始化。

          6.1.6、 使用

          類加載完畢后緊接著就是為對象分配內(nèi)存空間和初始化了:

          1. 為對象分配合適大小的內(nèi)存空間
          2. 為實(shí)例變量賦默認(rèn)值
          3. 設(shè)置對象的頭信息,對象hash碼、GC分代年齡、元數(shù)據(jù)信息等
          4. 執(zhí)行構(gòu)造函數(shù)(init)初始化。
          6.1.7、 卸載

          最終沒啥說等,就是通過GC算法回收對象了。

          6.2、 對象占據(jù)字節(jié)

          關(guān)于對象頭問題在 Synchronized 一文中已經(jīng)詳細(xì)寫過了,一個對象頭包含三部分對象頭(MarkWord、classPointer)實(shí)例數(shù)據(jù)Instance Data、對齊Padding,想看內(nèi)存詳細(xì)占用情況IDEA調(diào)用jol-core包即可。

          問題一:new Object()占多少字節(jié)

          1. markword 8字節(jié) + classpointer 4字節(jié)(默認(rèn)用calssPointer壓縮) + padding 4字節(jié) ?= 16字節(jié)
          2. 如果沒開啟classpointer壓縮:markword 8字節(jié) + classpointer 8字節(jié) = 16字節(jié)

          問題二:User (int id,String name) User u = new User(1,"李四")

          markword 8字節(jié) + 開啟classPointer壓縮后classpointer 4字節(jié) + instance data int 4字節(jié) + 開啟普通對象指針壓縮后String4字節(jié) + padding 4 ?= 24字節(jié)

          6.3、 對象訪問方式

          使用句柄

          使用句柄來訪問的最大好處就是reference中存儲的是穩(wěn)定的句柄地址,在對象被移動(垃圾收集時移動對象是非常普遍的行為)時只會改變句柄中的實(shí)例數(shù)據(jù)指針,而reference本身不需要修改。

          直接指針

          reference中存儲的直接就是對象地址。最大好處就是速度更快,它節(jié)省了一次指針定位的時間開銷,由于對象的訪問在Java中非常頻繁,因此這類開銷積少成多后也是一項(xiàng)非常可觀的執(zhí)行成本。

          Sun HotSpot 使用 直接指針訪問方式 進(jìn)行對象訪問的。

          7、對象一定創(chuàng)建在堆上嗎

          結(jié)論:不一定 看對象經(jīng)過了逃逸分析后發(fā)現(xiàn)該變量只是用到方法區(qū)時,則JVM會自動優(yōu)化,在棧上創(chuàng)建該對象。

          7.1、逃逸分析

          逃逸分析(Escape Analysis)簡單來講就是:Java Hotspot 虛擬機(jī)可以分析新創(chuàng)建對象的使用范圍,并決定是否在 Java 堆上分配內(nèi)存。

          7.2、標(biāo)量替換

          標(biāo)量替換:JVM通過逃逸分析確定該對象不會被外部訪問。那就通過將該對象標(biāo)量替換分解在棧上分配內(nèi)存,這樣該對象所占用的內(nèi)存空間就可以隨棧幀出棧而銷毀,就減輕了垃圾回收的壓力。

          標(biāo)量:不可被進(jìn)一步分解的量,而JAVA的基本數(shù)據(jù)類型就是標(biāo)量?

          聚合量:在JAVA中對象就是可以被進(jìn)一步分解的聚合量。

          7.3、棧上分配

          JVM對象分配在中,當(dāng)對象沒有被引用時,依靠GC進(jìn)行回收內(nèi)存,如果對象數(shù)量較多會給GC帶來較大壓力,也間接影響了應(yīng)用的性能。

          為了減少臨時對象在堆內(nèi)分配的數(shù)量,JVM通過逃逸分析確定該對象不會被外部訪問。那就通過將該對象標(biāo)量替換分解在棧上分配內(nèi)存,這樣該對象所占用的內(nèi)存空間就可以隨棧幀出棧而銷毀,就減輕了垃圾回收的壓力。

          7.4、同步消除

          同步消除是java虛擬機(jī)提供的一種優(yōu)化技術(shù)。通過逃逸分析,可以確定一個對象是否會被其他線程進(jìn)行訪問,如果對象沒有出現(xiàn)線程逃逸,那該對象的讀寫就不會存在資源的競爭,不存在資源的競爭,則可以消除對該對象的同步鎖。比如方法體內(nèi)調(diào)用StringBuffer。

          逃逸分析結(jié)論

          雖然經(jīng)過逃逸分析可以做標(biāo)量替換棧上分配、和鎖消除。但是逃逸分析自身也是需要進(jìn)行一系列復(fù)雜的分析的,這其實(shí)也是一個相對耗時的過程。如果對象經(jīng)過層層分析后發(fā)現(xiàn) 無法進(jìn)行逃逸分析優(yōu)化則反而耗時了,因此慎用。

          8、類加載器

          在連接階段一般是無法干預(yù)的,大部分干預(yù) 類加載階段,對于任意一個類,都需要由加載它的類加載器和這個類本身一同確立其在Java虛擬機(jī)中的唯一性,類加載時候重要三個方法:

          1、loadClass() :加載目標(biāo)類的入口,它首先會查找當(dāng)前 ClassLoader 以及它的雙親里面是否已經(jīng)加載了目標(biāo)類,找到直接返回?

          2、findClass() :如果沒有找到就會讓雙親嘗試加載,如果雙親都加載不了,就會調(diào)用 findClass() 讓自定義加載器自己來加載目標(biāo)類?

          3、defineClass() :拿到這個字節(jié)碼之后再調(diào)用 defineClass() 方法將字節(jié)碼轉(zhuǎn)換成 Class 對象。

          8.1、雙親委派機(jī)制

          定義

          當(dāng)某個類加載器需要加載某個.class文件時,首先把這個任務(wù)委托給他的上級類加載器,遞歸這個操作,如果上級的類加載器沒有加載,自己才會去加載這個類。

          作用

          1、可以防止重復(fù)加載同一個.class。通過委托去向上面問一問,加載過了,就不用再加載一遍。保證數(shù)據(jù)安全。?

          2、保證核心.class不能被篡改*,通過委托方式,不會去篡改核心.class?!?/p>

          類加載器

          1、BootstrapClassLoader(啟動類加載器):c++編寫,加載java核心庫 java.*,JAVA_HOME/lib

          2、ExtClassLoader (標(biāo)準(zhǔn)擴(kuò)展類加載器):java編寫的加載擴(kuò)展庫,JAVA_HOME/lib/ext

          3、AppClassLoader(系統(tǒng)類加載器):加載程序所在的目錄,如user.dir所在的位置的ClassPath

          4、CustomClassLoader(用戶自定義類加載器):用戶自定義的類加載器,可加載指定路徑的class文件

          8.2、關(guān)于加載機(jī)制

          雙親委派機(jī)制只是Java類加載的一種常見模式,還有別的加載機(jī)制哦,比如Tomcat 總是先嘗試去加載某個類,如果找不到再用上一級的加載器,跟雙親加載器順序正好相反。再比如當(dāng)使用第三方框架JDBC跟具體實(shí)現(xiàn)的時候,反而會引發(fā)錯誤,因?yàn)镴DK自帶的JDBC接口由啟動類加載,而第三方實(shí)現(xiàn)接口由應(yīng)用類加載。這樣相互之間是不認(rèn)識的,因此JDK引入了SPI機(jī)制 線程上下文加載器 來實(shí)現(xiàn)加載(跟DubboSPI不一樣哦)。

          9、OOM 、CPU100%

          系統(tǒng)性能分析常用指令:

          工具用途
          jps輸出JVM中運(yùn)行的進(jìn)程狀態(tài)信息
          jstack生成虛擬機(jī)當(dāng)前時刻的線程快照
          jstat虛擬機(jī)統(tǒng)計(jì)信息監(jiān)控工具
          jinfo實(shí)時地查看和調(diào)整虛擬機(jī)各項(xiàng)參數(shù)
          jmap生成虛擬機(jī)的內(nèi)存轉(zhuǎn)儲快照,heapdump文件
          JConsole可視化管理工具,常用

          9.1、OOM

          9.1.1、為啥OOM

          發(fā)生 OOM 簡單來說可總結(jié)為兩個原因:

          1. 分配給 JVM的?內(nèi)存不夠用
          2. 分配內(nèi)存夠用,但代碼寫的不好,多余的內(nèi)存?沒有釋放,導(dǎo)致內(nèi)存不夠用。
          9.1.2、三種類型OOM
          9.2.1、堆內(nèi)存溢出:

          此種情況最常見 Java heap space。一般是先通過內(nèi)存映像工具對Dump出來的堆轉(zhuǎn)儲快照然后辨別到底是內(nèi)存泄漏還是內(nèi)存溢出。內(nèi)存泄漏

          通過工具查看泄漏對象到GC Roots的引用鏈。找到泄漏的對象是通過怎么樣的路徑與GC Roots相關(guān)聯(lián)的導(dǎo)致垃圾回收機(jī)制無法將其回收,最終比較準(zhǔn)確地定位泄漏代碼的位置。

          不存在泄漏

          就是內(nèi)存中的對象確實(shí)必須存活著,那么此時就需要通過虛擬機(jī)的堆參數(shù),從代碼上檢查是否存在某些對象存活時間過長、持有時間過長的情況,嘗試減少運(yùn)行時內(nèi)存的消耗。

          9.2.2、虛擬機(jī)棧和本地方法棧溢出

          在HotSpot虛擬機(jī)上不區(qū)分虛擬機(jī)棧和本地方法棧,因此棧容量只能由**-Xss**參數(shù)設(shè)定。在Java虛擬機(jī)規(guī)范中描述了兩種異常:

          StackOverflowError :線程請求的棧深度超過了虛擬機(jī)所允許的最大深度,就會拋出該異常。OutOfMemoryError:虛擬機(jī)在拓展棧的時候無法申請到足夠的空間,就會拋出該異常。

          單線程環(huán)境下無論是由于棧幀太大還是虛擬機(jī)棧容量太小,當(dāng)內(nèi)存無法繼續(xù)分配的時候,虛擬機(jī)拋出的都是StackOverflowError 異常。

          多線程環(huán)境下為每個線程的棧分配的內(nèi)存越大,每個線程獲得空間大則可建立的線程數(shù)減少了反而越容易產(chǎn)生OOM異常,因此一般通過減少最大堆減少棧容量 來換取更多的線程數(shù)量。

          9.2.3、永久代溢出:

          PermGen space 即方法區(qū)溢出了。方法區(qū)用于存放Class的相關(guān)信息,如類名、訪問修飾符、常量池、字段描述、方法描述等。當(dāng)前的一些主流框架,如Spring、Hibernate,對于類進(jìn)行增強(qiáng)的時候都會使用到CGLib這類字節(jié)碼技術(shù),增強(qiáng)的類越多,就需要越大的方法區(qū)來保證動態(tài)生成Class可以加載入內(nèi)存,這樣的情況下可能會造成方法區(qū)的OOM異常。

          9.2.4、OOM查看指令
          1. 通過命令查看對應(yīng)的進(jìn)程號 ?:

          比如:jps ? ?或者 ? ps -ef | grep 需要的任務(wù)

          1. 輸入命令查看gc情況命令:

          jstat -gcutil 進(jìn)程號 刷新的毫秒數(shù) 展示的記錄數(shù)?

          比如:jstat -gcutil 1412 1000 10 ?(查看進(jìn)程號1412,每隔1秒獲取下,展示10條記錄)

          1. 查看具體占用情況:

          命令:jmap -histo 進(jìn)程號 | more ?(默認(rèn)展示到控制臺)?

          比如:jmap -histo 1412 | more ? ?查看具體的classname,是否有開發(fā)人員的類,也可以輸出到具體文件分析

          9.3 CPU 100%

          線上應(yīng)用導(dǎo)致 CPU 占用 100%, ?出現(xiàn)這樣問題一般情況下是代碼進(jìn)入了死循環(huán),分析步驟如下:

          1. 找出對應(yīng)服務(wù)進(jìn)程id :

          用 ps -ef | grep 運(yùn)行的服務(wù)名字,直接top命令也可以看到各個進(jìn)程CPU使用情況。

          1. 查詢目標(biāo)進(jìn)程下所有線程的運(yùn)行情況 :

          top -Hp pid, -H表示以線程的維度展示,默認(rèn)以進(jìn)程維度展示。

          1. 對目標(biāo)線程進(jìn)行10進(jìn)制到16進(jìn)制轉(zhuǎn)換:

          printf ?‘%x\n’ ?線程pid

          1. 用jstack 進(jìn)程id | grep 16進(jìn)制線程id 找到線程信息,具體分析:

          jstack 進(jìn)程ID | grep -A 20 16進(jìn)制線程id

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

          一般項(xiàng)目加個xms和xmx參數(shù)就夠了。在沒有全面監(jiān)控、收集性能數(shù)據(jù)之前,調(diào)優(yōu)就是瞎調(diào)。出現(xiàn)了問題先看自身代碼或者參數(shù)是否不合理,畢竟不是誰都能寫JVM底層代碼的。一般要減少創(chuàng)建對象的數(shù)量, 減少使用全局變量和大對象, GC 優(yōu)化是到最后不得已才采用的手段。日常 分析 GC 情況 優(yōu)化代碼比優(yōu)化 GC 參數(shù)要多得多。一般如下情況不用調(diào)優(yōu)的:

          1、minor GC 單次耗時 < 50ms,頻率10秒以上。說明年輕代OK。?

          2、Full GC 單次耗時 < 1秒,頻率10分鐘以上,說明年老代OK。

          GC調(diào)優(yōu)目的:GC時間夠少,GC次數(shù)夠少。

          調(diào)優(yōu)建議:

          1. -Xms5m設(shè)置JVM初始堆為5M,-Xmx5m設(shè)置JVM最大堆為5M。-Xms跟-Xmx值一樣時可以避免每次垃圾回收完成后JVM重新分配內(nèi)存。
          2. -Xmn2g:設(shè)置年輕代大小為2G,一般默認(rèn)為整個堆區(qū)的1/3 ~ 1/4。-Xss每個線程??臻g設(shè)置。
          3. -XX:SurvivorRatio,設(shè)置年輕代中Eden區(qū)與Survivor區(qū)的比值,默認(rèn)=8,比值為8:1:1。
          4. -XX:+HeapDumpOnOutOfMemoryError 當(dāng)JVM發(fā)生OOM時,自動生成DUMP文件。
          5. -XX:PretenureSizeThreshold 當(dāng)創(chuàng)建的對象超過指定大小時,直接把對象分配在老年代。
          6. -XX:MaxTenuringThreshold 設(shè)定對象在Survivor區(qū)最大年齡閾值,超過閾值轉(zhuǎn)移到老年代,默認(rèn)15。
          7. 開啟GC日志對性能影響很小且能幫助我們定位問題,-XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:gc.log
          瀏覽 43
          點(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>
                  国产乱伦无码高清 | 日韩AV网站在线观看 | 国产夫妻自拍在线观看 | 69av在线 | 亚洲成人情趣大香蕉 |