JVM調優


一、JVM類加載機制

1、類加載過程

  當我們用java命令加載某個類的main函數啟動程序時,首先需要通過類加載器把主類加載到JVM。

  具體步驟:1、調用底層的jvm.dll創建java虛擬機(C++)

       2、創建一個引導類加載器(C++)

       3、C++調用java代碼創建JVM啟動器實例com.misc.Lanucher(由引導類加載器加載)

       4、獲取自己的類加載器並加載

       5、加載完成時會執行主類的main方法入口

       6、程序運行結束時銷毀JVM

2、類加載過程的具體步驟

  • 加載:在硬盤上查找並通過IO讀入字節碼文件,使用到類時才會加載,例如調用類的main方法,new對象等,在加載階段會在內存中生成一個java.lang.Class對象,作為方法區這個類的各種數據的訪問入口
  • 驗證:校驗字節碼文件的正確性
  • 准備:給類的靜態變量分配內存並賦予默認值
  • 解析:將符號引用替換為直接引用,該階段會把一些靜態方法(符號引用,比如main()方法)替換為指向數據內存的指針或句柄(指針的指針),這個過程稱之為靜態鏈接過程,動態鏈接是在程序運行期間將符號引用替換為直接引用
  • 初始化:對類的靜態變量初始化值,執行靜態代碼塊

注意:主類在運行過程中如果引用到其他類會逐步加載,jar、war包里的類不是一次性全部加載的,是使用到時才會加載。

3、JAVA類加載器

  • 引導類加載器:負責加載lib目錄下的核心類庫,如rt.jar、charset.jar
  • 擴展類加載器:負責加載lib目錄下ext擴展目錄中的jar包
  • 應用程序類加載器:classPath路徑下的,就是加載你自己寫的那些類
  • 自定義加載器:負責加載自定義路徑下的類

4、雙親委派機制

  HOW?

  1. 首先,檢查指定類是否已經加載過,如果已經加載過了,就不需要再加載,直接返回。
  2. 如果沒有加載過,判斷一下是否有父加載器,如果有,則由父加載器加載
  3. 如果父加載器都沒有找到,則由當前加載器負責加載

  WHY?

  • 沙箱安全機制:防止核心類庫被隨意篡改
  • 避免類的重復加載:當父加載器已經加載類該類時,子ClassLoader沒必要再重新加載

  全盤委托機制

  “全盤負責”當一個classLoader加載一個類時,除非顯示的使用另外一個加載器,否則該類所依賴的引用類也由這個ClassLoader載入。

  自定義類加載器  

  java.lang.ClassLoader類有兩個核心方法

  loadCLass():實現了雙親委派機制

  findClass():默認是空,自定義加載器主要是重寫此方法

5、Tomcat幾個主要的類加載器

  1. commonLoader:Tomcat最基本的類加載器,加載的class可以被web容器及各個app所訪問
  2. catalinaLoader:Tomcat私有類加載器,加載路徑中的class對於webApp不可見
  3. sharedLoader:各個app共享的類加載器,加載路徑中的class對於所有Webapp可見
  4. WebappClassLoader:webapp私有的類加載器,比如加載war包里的相關類

6、Tomcat打破雙親委派機制

  1. 一個web容器可能需要部署兩個應用程序,不同的應用程序可能會依賴同一個第三方類庫的不同版本,因此要保證每個應用程序的類庫都是獨立的,是相互隔離的
  2. 部署在同一個web容器中的相同類庫的相同版本可以共享
  3. web容器也有自己的依賴類庫,不能與應用程序的類庫混淆
  4. web容器要支持jsp的熱加載,我們知道jsp也是翻譯成class文件后執行的,要支持jsp修改后不用重啟  

二、JVM整體結構及內存模型

 

 

 

 

 

關於元空間JVM參數:

XX:MaxMetaspaceSize:設置原空間最大值,默認是-1,即不限制,或者說只受限於本地內存大小

XX:MetaspaceSize:元空間觸發FullGc的初始伐值,默認是21M,達到該值就會觸發full gc進行卸載,同時收集器會對該值進行調整,如果釋放了大量空間就降低該值,如果釋放了較少空間就在不超過XX:MaxMetaspaceSize的情況下適當提高該值

由於調整元空間大小需要Full Gc,這是非常昂貴的操作,如果在剛啟動時就發生了Full Gc ,通常是由於永久代或元空間的大小發生了調整,一般建議XX:MaxMetaspaceSize和XX:MetaspaceSize 設置成一樣的值,並設置的比初始值要大,一般8G物理機內存將這兩個值都設置為256M

Xss:設置的count值越小,說明一個線程里能分配的棧幀就越少,但是對JVM來說能開啟的線程數就越多

JVM調優:就是盡可能讓對象在新生代里完成分配和回收,盡量別讓太多對象進入老年代,避免頻繁對老年代進行回收,給系統充足的內存大小,避免新生代頻繁的進行垃圾回收

 

三、JVM對象創建及內存分配機制

1、對象的創建

  1. 類加載檢查
    虛擬機遇到一條new指令時,首先將去檢查這個指令的參數是否能在常量池中定位一個類的符號引用,並檢查這個符號引用代表的類是否已經被加載、解析和初始化過。如果沒有,那必須先執行類的加載過程。分配內存
  2. 分配內存
    在類加載檢查通過后,接下來虛擬機將為新生對象分配內存,對象所需內存大小在類加載完成后便可完全確定,為對象分配空間的任務等同於把一塊確定大小的內存從Java堆中划分出來。

    划分內存的方法:
    指針碰撞:
    Java內存排列是絕對工整的,用過的內存放一邊,空閑的內存放另外一邊,中間放着一個指針作為分界點的指示器
    空閑列表:
    內存是不工整的,虛擬機維護一個列表,記錄哪些內存是可用的

    並發問題的解決辦法:
    CAS:虛擬機采用CAS配上失敗重試的方式保證更新操作的原子性來對分配內存空間的動作進行同步處理
    TLAB(本地線程緩沖):把內存分配的動作划分在不同的空間進行,即每個線程在Java堆中預先分配一小塊內存
    通過­XX:+/­UseTLAB參數來設定虛擬機是否使用TLAB(JVM會默認開啟­XX:+UseTLAB),­XX:TLABSize 指定TLAB大小。
     
  3. 初始化
    內存分配完成后,虛擬機將分配到的內存空間都初始化為零值,如果使用TLAB,這一過程也可提前至TLAB分配時進行,這一步驟保證了實例字段在JAVA代碼中可以不賦初始值就直接使用。設置對象頭初始化零值之后,虛擬機要對對象進行必要的設置,例如這個對象是哪個類的實例、如何才能找到類的元數據信息,對象的哈希碼,對象的GC分代年齡等信息,這些信息存放在對象頭之中。
  4. 對象在內存中的布局
    對象頭:對象自身的運行時數據,如哈希碼,GC分代年齡、線程持有的鎖、鎖狀態標志、偏向線程ID、偏向時間戳。另一部分是類型指針,指向類的元數據的指針,通過這個指針確定是哪一個類的實例。
    實例數據:
    對齊填充:

    什么是java對象的指針壓縮?
    啟用指針壓縮:­XX:+UseCompressedOops(默認開啟),禁止指針壓縮:­XX:­UseCompressedOops
    為什么要進行指針壓縮?
    1.在64位平台的HotSpot中使用32位指針,內存使用會多出1.5倍左右,使用較大指針在主內存和緩存之間移動數據,占用較大寬帶,同時GC也會承受較大壓力
    2.為了減少64位平台下內存的消耗,啟用指針壓縮功能
    3.在jvm中,32位地址最大支持4G內存(2的32次方),可以通過對對象指針的壓縮編碼、解碼方式進行優化,使得jvm只用32位地址就可以支持更大的內存配置(小於等於32G)
    4.堆內存小於4G時,不需要啟用指針壓縮,jvm會直接去除高32位地址,即使用低虛擬地址空間
    5.堆內存大於32G時,壓縮指針會失效,會強制使用64位(即8字節)來對java對象尋址,這就會出現1的問題,所以堆內存不要大於32G為好

2、對象內存分配

  1. 內存分配流程
  2. 對象棧上分配(依賴於逃逸分析和標量替換
    JVM通過逃逸分析確定對象不會被外部訪問,如果不會逃逸可以將對象在棧上分配內存,這樣該對象鎖占用的內存空間就可以隨棧幀出棧而銷毀,減輕了垃圾回收的壓力。
    逃逸分析:當一個對象在方法中被定義后,可能被外部方法所引用,JDK7以后會默認開啟逃逸分析
    標量替換:通過逃逸分析后,確定不會被外部所引用,JVM不會創建該對象,而是將對象分解若干個被這個方法所使用的成員變量,這些代替的成員變量在棧幀或寄存器上分配空間
    聚合量:不可被進一步分解的量稱之為聚合量,例如java對象
  3. 對象在Eden區分配
    Minor GC/Young GC:新生代垃圾收集動作,minor GC 回收非常頻繁,速度也比較快
    Full GC/Major GC:回收老年代,年輕代的垃圾,回收速度比Minor GC慢十倍以上
    Eden與Survivor區默認8:1:1 
    注意:當Eden區被分配完了時,虛擬機將發起一次Minor GC,GC期間又發現Survivor區滿了,只好把新生代的對象提前移到老年代中去

  4. 大對象直接進入老年代(避免對大對象內存的復制操作而降低效率
    大對象就是需要大量連續內存空間的對象(比如:字符串,數組)。JVM參數-XX:PretenureSizeThreshold可以設置大對象的大小,超過這個大小會直接進入老年代
  5. 長期存活的對象將進入老年代
    對象在 Survivor 中每熬過一次 MinorGC,年齡就增加1歲,當它的年齡增加到一定程度(默認為15歲,CMS收集器默認6歲,不同的垃圾收集器會略微有點不同),就會被晉升到老年代中。對象晉升到老年代 的年齡閾值,可以通過參數 -XX:MaxTenuringThreshold 來設置。 
    年齡1+年齡2+年齡n的多個年齡對象總和超過了Survivor區域的50%,此時就會把年齡n(含)以上的對象都放入老年代。
  6. 老年代空間分配擔保機制
    年輕代每次minor gc之前,jvm都會計算下老年代的剩余可用空間,如果這個空間小於現有年輕代里所有對象大小之和(包括垃圾對象),就會看  -XX:-HandlePromotionFailure 參數是否設置(jdk1.8默認設置),就會看老年代可用大小是否大於之前每一次minor gc后進入老年代的對象平均大小,如果小於或者沒有設置,那么就會觸發一次Full GC對老年代和年輕代一起回收,如果還是沒有空間就會發生OOM,當然minor gc 后老年代還是沒有空間放minor gc 中存活的對象,也會觸發 Full GC ,也會發生OOM
  7. 如何判斷對象已經死亡
    引用計數法:
    給對象添加一個引用計數器,每當有引用到的地方,計數器+1,失去引用,計數器-1,當計數器為0時,對象已經死亡
    可達性分析算法:
    GC Roots對象作為起點,開始向下搜索引用的對象,找到的對象都標記為非垃圾對象,其余對象標記為垃圾對象
  8. 幾種常見的引用類型:
    強引用:普通的變量引用 

    public static User user = new User(); 
    軟引用:正常情況不會被回收,但是GC做完后發現釋放不出空間存放新的對象,則會把這些軟引用的對象回收掉。軟引用可用來實現內存敏感的高速緩存。 
    public static SoftReference<User> user = new SoftReference<User>(new User()); 
    弱引用:將對象用WeakReference軟引用類型的對象包裹,弱引用跟沒引用差不多,GC會直接回收掉,很少用 1 public static WeakReference<User> user = new WeakReference<User>(new User());
    虛引用:虛引用也稱為幽靈引用或者幻影引用,它是最弱的一種引用關系,幾乎不用
 
  finalize()方法判定對象是否存活:即使可達性分析中不可達的對象也並非是非死不可,真正宣告一個對象死亡至少要經歷再次標記的過程
    1、第一次標記:篩選此對象是否需要執行finalize()方法,如果沒有覆蓋finalize方法,對象直接被回收
    2、第二次標記:執行finalize方法,在finalize方法中添加一個引用便可拯救自己,注意finalize只能被執行一次
 

 如何判斷一個類是無用的類?

  • 該類所有的實例已經被回收,java堆中不存在類的任何實例
  • 加載該類的classLoader已經被回收
  • 該類對應的class對象沒有在任何地方被引用

四、垃圾收集算法

1、分代收集理論

  java堆一般分為年輕代和老年代,這樣我們就可以根據各塊的特點選擇合適的垃圾收集算法

2、復制算法

  為了解決效率問題,復制算法出現了,他可以將內存分為大小相同的兩塊,每次使用其中的一塊,當這一塊內存使用完以后,將還存活的對象復制到另一塊中去,然后把使用的空間一次清理掉,這樣每次回收都是堆一半的內存進行回收。

3、標記清除算法

  標記存活的對象,統一回收未標記的對象(也可以反過來)

  會產生兩個問題:1、標記的對象太多,效率不高
          2、標記清除后會產生大量的不連續碎片

4、標記整理算法

   第一步與標記清除算法一樣,后續讓所有存活的對象向一端移動,然后直接清理掉端邊界以外的內存。

五、垃圾收集器

1、Serial收集器(-XX:+UseSerialGC-XX:+UseSerialOldGC)

  單線程,並且會暫停其他所有線程(STW),新生代采用復制算法,老年代采用標記整理算法。

2、Parallel Scavenge收集器(-XX:+UseParallelGC(年輕代),-XX:+UseParallelOldGC(老年代))   

  Serial收集器的多線程版本

3、ParNew收集器(-XX:+UseParNewGC)

   ParNew收集器其實跟Parallel收集器很類似,區別主要在於它可以和CMS收集器配合使用。 

4、CMS收集器(-XX:+UseConcMarkSweepGC(old)) ()

  CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器。它非常符合在注重用戶體驗的應用上使用,它是HotSpot虛擬機第一款真正意義上的並發收集器,它第一次實現了讓垃圾收集線程與用戶線程(基本上)同時工作。

  步驟:
  1. 初始標記:STW,記錄gc roots 直接能引用的對象,速度很快
  2. 並發標記:是從GC Roots的直接關聯對象開始遍歷整個對象圖的過程, 這個過程耗時較長但是不需要停頓用戶線程, 可以與垃圾收集線程一起並發運行。因為用戶程序繼續運行,可能會有導致已經標記過的對象狀態發生改變。
  3. 重新標記:重新標記階段就是為了修正並發標記期間因為用戶程序繼續運行而導致標記產生變動的那一部分對象的標記記錄,這個階段的停頓時間一般會比初始標記階段的時間稍長,遠遠比並發標記階段時間短。
  4. 並發清理:開啟用戶線程,同時GC線程開始對未標記的區域做清掃。這個階段如果有新增對象會被標記為黑色不做任何處理
  5. 並發重置:重置本次GC過程中的標記數據。 
  6. 缺點
    對CPU資源敏感(會和服務搶資源);
    無法處理浮動垃圾(在並發標記和並發清理階段又產生垃圾,這種浮動垃圾只能等到下一次gc再清理了);
    它使用的回收算法-“標記-清除”算法會導致收集結束時會有大量空間碎片產生,當然通過參數-XX:+UseCMSCompactAtFullCollection可以讓jvm在執行完標記清除后再做整理
    執行過程中的不確定性,會存在上一次垃圾回收還沒執行完,然后垃圾回收又被觸發的情況,特別是在並發標記和並發清理階段會出現,一邊回收,系統一邊運行,也許沒回收完就再次觸發full gc,也就是"concurrent mode failure",此時會進入stop the world,用serial old垃圾收集器來回收 
  7. 核心參數
    1. -XX:+UseConcMarkSweepGC:啟用cms
    2. -XX:ConcGCThreads:並發的GC線程數
    3. -XX:+UseCMSCompactAtFullCollection:FullGC之后做壓縮整理(減少碎片)
    4. -XX:CMSFullGCsBeforeCompaction:多少次FullGC之后壓縮一次,默認是0,代表每次FullGC后都會壓縮一
    5. -XX:CMSInitiatingOccupancyFraction: 當老年代使用達到該比例時會觸發FullGC(默認是92,這是百分比)
    6. -XX:+UseCMSInitiatingOccupancyOnly:只使用設定的回收閾值(-XX:CMSInitiatingOccupancyFraction設
    定的值),如果不指定,JVM僅在第一次使用設定值,后續則會自動調整
    7. -XX:+CMSScavengeBeforeRemark:在CMS GC前啟動一次minor gc,目的在於減少老年代對年輕代的引
    用,降低CMS GC的標記階段時的開銷,一般CMS的GC耗時 80%都在標記階段
    8. -XX:+CMSParallellnitialMarkEnabled:表示在初始標記的時候多線程執行,縮短STW
    9. -XX:+CMSParallelRemarkEnabled:在重新標記的時候多線程執行,縮短STW; 
    只要年輕代參數設置合理,老年代CMS的參數設置基本都可以用默認值

5、G1收集器(-XX:+UseG1GC)

  是一款面向服務器的垃圾收集器,主要針對配備多顆處理器及大容量內存的機器. 以極高概率滿足GC停頓時間要求的同時,還具備高吞吐量性能特征. 
  G1將Java堆划分為多個大小相等的獨立區域(Region),JVM最多可以有2048個Region。一般Region大小等於堆大小除以2048,比如堆大小為4096M,則Region大小為2M,當然也可以用參數"-XX:G1HeapRegionSize"手動指定Region大小,但是推薦默認的計算方式。
  G1保留了年輕代和老年代的概念,但不再是物理隔閡了,它們都是(可以不連續)Region的集合。默認年輕代對堆內存的占比是5%,如果堆大小為4096M,那么年輕代占據200MB左右的內存,對應大概是100個Region,可以通過“-XX:G1NewSizePercent”設置新生代初始占比,在系統運行中,JVM會不停的給年輕代增加更多的Region,但是最多新生代的占比不會超過60%,可以通過“-XX:G1MaxNewSizePercent”調整。年輕代中的Eden和Survivor對應的region也跟之前一樣,默認8:1:1,假設年輕代現在有1000個region,eden區對應800個,s0對應100個,s1對應100個。一個Region可能之前是年輕代,如果Region進行了垃圾回收,之后可能又會變成老年代,也就是說Region的區域功能可能會動態變化。 

 

G1有專門分配大對象的Region叫Humongous區,而不是讓大對象直接進入老年代的Region中。在G1中,大對象的判定規則就是一個大對象超過了一個Region大小的50%,比如按照上面算的,每個Region是2M,只要一個大對象超過了1M,就會被放入Humongous中,而且一個大對象如果太大,可能會橫跨多個Region來存放。
Humongous區專門存放短期巨型對象,不用直接進老年代,可以節約老年代的空間,避免因為老年代空間不夠的GC開銷。
Full GC的時候除了收集年輕代和老年代之外,也會將Humongous區一並回收。 
 
步驟:
  1. 初始標記(initial mark,STW):暫停所有的其他線程,並記錄下gc roots直接能引用的對象,速度很快 ;  
  2. 並發標記(Concurrent Marking):同CMS的並發標記 
  3. 最終標記(Remark,STW):同CMS的重新標記
  4. 篩選回收(Cleanup,STW):篩選回收階段首先對各個Region的回收價值和成本進行排序,根據用戶所期望的GC停頓時間(可以用JVM參數 -XX:MaxGCPauseMillis指定)來制定回收計划
回收算法主要用的是復制算法,將一個region中的存活對象復制到另一個region中,這種不會像CMS那樣回收完因為有很多內存碎片還需要整理一次,G1采用復制算法回收幾乎不會有太多內存碎片。
G1收集器在后台維護了一個優先列表,每次根據允許的收集時間,優先選擇回收價值最大的Region(這也就是它的名字Garbage-First的由來),比如一個Region花200ms能回收10M垃圾,另外一個Region花50ms能回收20M垃圾,在回收時間有限情況下,G1當然會優先選擇后面這個Region回收。
 
具備以下特點:
1、 並行與並發:G1能充分利用CPU、多核環境下的硬件優勢,使用多個CPU(CPU或者CPU核心)來縮短Stop-The-World停頓時間。部分其他收集器原本需要停頓Java線程來執行GC動作,G1收集器仍然可以通過並發的方式讓java程序繼續執行。
2、 分代收集:雖然G1可以不需要其他收集器配合就能獨立管理整個GC堆,但是還是保留了分代的概念。空間整合:與CMS的“標記--清理”算法不同,G1從整體來看是基於“標記整理”算法實現的收集器;從局部上來看是基於“復制”算法實現的。
3、 可預測的停頓:這是G1相對於CMS的另一個大優勢,降低停頓時間是G1 和 CMS 共同的關注點,但G1 除了追求低停頓外,還能建立可預測的停頓時間模型,能讓使用者明確指定在一個長度為M毫秒的時間片段(通過參數"-XX:MaxGCPauseMillis"指定)內完成垃圾收集。 
 
G1收集器分類
YoungGC:YoungGC並不是說現有的Eden區放滿了就會馬上觸發,G1會計算下現在Eden區回收大概要多久時間,如果回收時間遠遠小於參數 -XX:MaxGCPauseMills 設定的值,那么增加年輕代的region,繼續給新對象存放,不會馬上做YoungGC,直到下一次Eden區放滿,G1計算回收時間接近參數       -XX:MaxGCPauseMills 設定的值,那么就會觸發Young GC 

 

MixedGC :不是FullGC,老年代的堆占有率達到參數(-XX:InitiatingHeapOccupancyPercent)設定的值則觸發,回收所有的Young和部分Old(根據期望的GC停頓時間確定old區垃圾收集的優先順序)以及大對象區,正常情況G1的垃圾收集是先做MixedGC,主要使用復制算法,
      需要把各個region中存活的對象拷貝到別的region里去,拷貝過程中如果發現沒有足夠的空region能夠承載拷貝對象就會觸發一次Full GC 
 
Full GC :停止系統程序,然后采用單線程進行標記、清理和壓縮整理,好空閑出來一批Region來供下一次MixedGC使用,這個過程是非常耗時的。(Shenandoah優化成多線程收集了) 

   

G1收集器參數設置
-XX:+UseG1GC:使用G1收集器
-XX:ParallelGCThreads:指定GC工作的線程數量
-XX:G1HeapRegionSize:指定分區大小(1MB~32MB,且必須是2的N次冪),默認將整堆划分為2048個分區
-XX:MaxGCPauseMillis:目標暫停時間(默認200ms)
-XX:G1NewSizePercent:新生代內存初始空間(默認整堆5%)
-XX:G1MaxNewSizePercent:新生代內存最大空間
-XX:TargetSurvivorRatio:Survivor區的填充容量(默認50%),Survivor區域里的一批對象(年齡1+年齡2+年齡n的多個年齡對象)總和超過了Survivor區域的50%,此時就會把年齡n(含)以上的對象都放入老年代
-XX:MaxTenuringThreshold:最大年齡閾值(默認15)
-XX:InitiatingHeapOccupancyPercent:老年代占用空間達到整堆內存閾值(默認45%),則執行新生代和老年代的混合收集(MixedGC),比如我們之前說的堆默認有2048個region,如果有接近1000個region都是老年代的region,則可能就要觸發MixedGC了
-XX:G1MixedGCLiveThresholdPercent(默認85%) region中的存活對象低於這個值時才會回收該region,如果超過這個值,存活對象過多,回收的的意義不大。
-XX:G1MixedGCCountTarget:在一次回收過程中指定做幾次篩選回收(默認8次),在最后一個篩選回收階段可以回收一會,然后暫停回收,恢復系統運行,一會再開始回收,這樣可以讓系統不至於單次停頓時間過長。
-XX:G1HeapWastePercent(默認5%): gc過程中空出來的region是否充足閾值,在混合回收的時候,對Region回收都是基於復制算法進行的,都是把要回收的Region里的存活對象放入其他Region,然后這個Region中的垃圾對象全部清理掉,這樣的話在回收過程就會不斷空出來新的Region,一旦空閑出來的Region數量達到了堆內存的5%,此時就會立即停止混合回收,意味着本次混合回收就結束了。 
 
G1垃圾收集器優化建議
里核心還是在於調節 -XX:MaxGCPauseMills 這個參數的值,在保證他的年輕代gc別太頻繁的同時,還得考慮每次gc過后的存活對象有多少,避免存活對象太多快速進入老年代,頻繁觸發mixed gc
 
什么場景適合使用G1(Kafka
1. 50%以上的堆被存活對象占用
2. 對象分配和晉升的速度變化非常大
3. 垃圾回收時間特別長,超過1秒
4. 8GB以上的堆內存(建議值)
5. 停頓時間是500ms以內 

 6、ZGC收集器(-XX:+UseZGC) 

  TB級別收集器,不分代

六、常用命令

1、jps:查看所有java進程

 

 

2、jmap -histo 15322     (查看內存信息:實例個數、內存大小、類名)

 

 

 3、jmap -heap 15322 (查看堆信息)

 

 

 

 

 

 

 

4、jmap‐dump:format=b,file=eureka.hprof 14660(內存很大的時候,可能會導不出來)

jvm 參數內存溢出自動導出
1.-XX:+HeapDumpOnOutOfMemoryError
2.-XX:HeapDumpPath=./ (路徑)
導出后用jvisualvm 分析dump文件
 

5、Jstack 找出占用cpu最高的線程堆棧信息

  1. top -p 15322  顯示java進程的內存情況

     

     

  2. 按H獲取每個每個線程的內存情況

     

     

  3. 找到內存和cpu占用最高的線程tid,假設是18929,轉換16進制49f1

     

     

  4. 查看對應對應堆棧信息找出可能存在問題的代碼

     

6、Jinfo 查看正在運行java程序的擴展參數

  1. jinfo -flags 15322  查看jvm參數


  2. jinfo -sysprops 15322 查看java系統參數

7、Jstat 查看堆內存各部分的使用量以及加載類的數量

  1. jstat -gc 15322 評估內存使用及GC壓力情況

     

     S0C:第個幸存區的大小,單位KB

    S1C:第二個幸存區的大小
    S0U:第 個幸存區的使用大小 S1U:第二個幸存區的使用大小
    EC:伊甸園區的大小
    EU:伊甸園區的使用大小
    OC:老年代大小
    OU:老年代使用大小
    MC:方法區大小(元空間)
    MU:方法區使用大小
    CCSC:壓縮類空間大小
    CCSU:壓縮類空間使用大小
    YGC:年輕代垃圾回收次數
    YGCT:年輕代垃圾回收消耗時間,單位s
    FGC:老年代垃圾回收次數
    FGCT:老年代垃圾回收消耗時間,單位s
    GCT:垃圾回收消耗總時間,單位s 
  2. jstat -gccapacity 15322 (堆內存統計)
    NGCMN:新生代最小容量
    NGCMX:新生代最大容量
    NGC:當前新生代容量
    S0C:第 個幸存區大小
    S1C:第二個幸存區的大小
    EC:伊甸園區的大小
    OGCMN:老年代最小容量
    OGCMX:老年代最大容量
    OGC:當前老年代大小
    OC:當前老年代大小
    MCMN:最小元數據容量
    MCMX:最大元數據容量
    MC:當前元數據空間大小
    CCSMN:最小壓縮類空間大小
    CCSMX:最大壓縮類空間大小
    CCSC:當前壓縮類空間大小
    YGC:年輕代gc次數
    FGC:老年代GC次數 
  3. jstat -gcnew 15322 (新生代垃圾回收統計)

    S0C:第 個幸存區的大小
    S1C:第二個幸存區的大小
    S0U:第 個幸存區的使用大小
    S1U:第二個幸存區的使用大小
    TT:對象在新生代存活的次數
    MTT:對象在新生代存活的最大次數
    DSS:期望的幸存區大小
    EC:伊甸園區的大小
    EU:伊甸園區的使用大小
    YGC:年輕代垃圾回收次數
    YGCT:年輕代垃圾回收消耗時間
  4. jstat -gcnewcapacity 15322 (新生代內存統計)

    NGCMN:新生代最小容量
    NGCMX:新生代最大容量
    NGC:當前新生代容量
    S0CMX:最大幸存1區大小
    S0C:當前幸存1區大小
    S1CMX:最大幸存2區大小
    S1C:當前幸存2區大小
    ECMX:最大伊甸園區大小
    EC:當前伊甸園區大小
    YGC:年輕代垃圾回收次數
    FGC:老年代回收次數 
  5. jstat -gcold 15322 (老年代垃圾回收統計)

    MC:方法區大小
    MU:方法區使用大小
    CCSC:壓縮類空間大小
    CCSU:壓縮類空間使用大小
    OC:老年代大小
    OU:老年代使用大小
    YGC:年輕代垃圾回收次數
    FGC:老年代垃圾回收次數
    FGCT:老年代垃圾回收消耗時間
    GCT:垃圾回收消耗總時間
  6. jstat -gcoldcapacity 15322 (老年代內存統計)

     

     OGCMN:老年代最小容量

    OGCMX:老年代最大容量
    OGC:當前老年代大小
    OC:老年代大小
    YGC:年輕代垃圾回收次數
    FGC:老年代垃圾回收次數
    FGCT:老年代垃圾回收消耗時間
    GCT:垃圾回收消耗總時間 

  7. jstat -gcmetacapacity 15322 (元空間垃圾回收統計)

     

     MCMN:最小元數據容量

    MCMX:最大元數據容量
    MC:當前元數據空間大小
    CCSMN:最小壓縮類空間大小
    CCSMX:最大壓縮類空間大小
    CCSC:當前壓縮類空間大小
    YGC:年輕代垃圾回收次數
    FGC:老年代垃圾回收次數
    FGCT:老年代垃圾回收消耗時間
    GCT:垃圾回收消耗總時間

  8. jstat -gcutil 15322
    S0:幸存1區當前使用比例
    S1:幸存2區當前使用比例
    E:伊甸園區使用比例
    O:老年代使用比例
    M:元數據區使用比例
    CCS:壓縮使用比例
    YGC:年輕代垃圾回收次數
    FGC:老年代垃圾回收次數
    FGCT:老年代垃圾回收消耗時間
    GCT:垃圾回收消耗總時間 

 七、如何調優?

1、JVM運行情況預估
用 jstat gc-pid 命令可以計算出如下些關鍵數據,有了這些數據就可以采用之前介紹過的優化思路,先給自己的系統設置些初始性的JVM參數,比如堆內存大小,年輕代大小,Eden和Survivor的比例,老年代的大小,大對象的閾值,大齡對象進入老年代的閾值等。
2、年輕代對象增長的速率
可以執行命令 jstat-gc pid 1000 10 (每隔1秒執行1次命令,共執行10次),通過觀察EU(eden區的使用)來估算每秒eden大概新增多少對象,如果系統負載不高,可以把頻率1秒換成1分鍾,甚至10分鍾來觀察整體情況。注意,般系統可能有高峰期和日常期,所以需要在不同的時間分別估算不同情況下對象增長速率。
3、Young GC的觸發頻率和每次耗時
知道年輕代對象增長速率我們就能推根據eden區的大小推算出Young GC大概多久觸發次,Young GC的平均耗時可以通過 YGCT/YGC公式算出,根據結果我們大概就能知道系統大概多久會因為Young GC的執行而卡頓多久。
4、每次Young GC后有多少對象存活和進入老年代
這個因為之前已經大概知道Young GC的頻率,假設是每5分鍾次,那么可以執行命令 jstat-gc pid 300000 10 ,觀察每次結果eden,survivor和老年代使用的變化情況,在每次gc后eden區使用 般會大幅減少,survivor和老年代都有可能增長,這些增長的對象就是每次Young GC后存活的對象,同時還可以看出每次Young GC后進去老年代大概多少對象,從而可以推算出老年代對象增長速率。
 
5、Full GC的觸發頻率和每次耗時
知道了老年代對象的增長速率就可以推算出Full GC的觸發頻率了,Full GC的每次耗時可以用公式 FGCT/FGC 計算得出。
優化思路
其實簡單來說就是盡量讓每次Young GC后的存活對象小於Survivor區域的50%,都留存在年輕代里。盡量別讓對象進入老年代。盡量減少Full GC的頻率,避免頻繁Full GC對JVM性能的影響。
 

full gc比minor gc還多的原因有哪些?

1、元空間不足導致頻繁full gc

2、顯示調用System.gc()造成多余full gc ,-XX:+DisableExplicitGC 參數金庸

3、老年代空間分配擔保機制

 

什么是內存泄漏?

常見的多級緩存架構redis+jvm緩存,很多程序員圖方便jvm緩存就只是適用一個hashmap,結果這個緩存map越來越大,一直占用老年代的空間,時間長了就會發生full gc,對於一些老舊數據沒有及時清理,時間長了除了會導致full gc 還會導致OOM。這種情況考慮使用一些成熟的JVM框架來解決,如ehcache等自帶的LRU數據淘汰算法的框架作為JVM級的緩存

 

三種字符串操作

String s = “abc”;// s指向常量池中的引用(用equals方法檢查常量池中有沒有這個常量,直接有返回引用,沒有創建一個返回對象引用)

String s1 = new(“abc”);// s1指向內存中的對象引用(equals方法檢查常量池中有沒有這個常量,沒有創建,然后在堆中在創建一個對象,返回引用)
String s2 = s1.intern(); // 如果池中已經包含一個等於s1對象的字符串,則返回池子中的字符串,否則直接指向s1

 

1 String s0="zhuge";
2 String s1="zhuge"; 
3 String s2="zhu" + "ge";
4 System.out.println( s0==s1 ); //true 
5 System.out.println( s0==s2 ); //true
都是字符串常量,在編譯時期就確定了

 

1 String s0="zhuge"; 
2 String s1=new String("zhuge"); 
3 String s2="zhu" + new String("ge"); 
4 System.out.println( s0==s1 ); // false 
5 System.out.println( s0==s2 ); // false 
6 System.out.println( s1==s2 ); // false

new()創建的字符串不是常量,在編譯時期不能確定

 

1 String a = "a1"; 
2 String b = "a" + 1; 
3 System.out.println(a == b); // true 
4 String a = "atrue"; 
5 String b = "a" + "true"; 
6 System.out.println(a == b); // true 
7 String a = "a3.4"; 
8 String b = "a" + 3.4; 
10 System.out.println(a == b); // true

1、true、3.4在字符串之后在編譯時期就確定為常量

 

 String a = "ab"; 
 String bb = "b"; 
 String b = "a" + bb; 
System.out.println(a == b); // false

bb作為變量在編譯時期不確定,在運行時才確定,會生成一個新的對象

 

 String a = "ab"; 
 final String bb = "b"; 
 String b = "a" + bb; 
 System.out.println(a == b); // true

final在編譯時期被解析為常量

 

String a = "ab"; 
 final String bb = getBB(); 
 String b = "a" + bb; 
 System.out.println(a == b); // false 
private static String getBB() {  return "b";  }

getBB()在編譯時期無法確定

 

 String s = "a" + "b" + "c"; //就等價於String s = "abc"; 
 String a = "a"; 
 String b = "b"; 
 String c = "c"; 
 String s1 = a + b + c; // 編譯時期a\b\c作為變量不確定

 

八種基本類型的包裝類和對象池

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM