一文理清JVM和GC(上)

本文主要介紹JVM和GC解析
本文較長,分爲上下篇(可收藏,勿吃塵)
若有須要,能夠參考
若有幫助,不忘 點贊java

一文理清JVM和GC下篇web

1、前期預熱

1)JVM內存體系

其中方法區被JVM中多個線程共享,好比類的靜態常量就被存放在方法區,供類對象之間共享,虛擬機棧本地方法棧程序計數器是每一個線程獨立擁有的,不會與其餘線程共享。因此Java在經過new建立一個類對象實例的時候,一方面會在虛擬機棧中建立一個對該對象的引用,另外一方面會在堆上建立類對象的實例,而後將對象引用指向該對象的實例。對象引用存放在每個方法對應的棧幀中。算法

圖片來源於網上
圖片來源於網上
  • 虛擬機棧:虛擬機棧中執行每一個方法的時候,都會建立一個棧幀用於存儲局部變量表,操做數棧,動態連接,方法出口等信息。
  • 本地方法棧:與虛擬機棧發揮的做用類似,相比於虛擬機棧爲Java方法服務,本地方法棧爲虛擬機使用的Native方法服務,執行每一個本地方法的時候,都會建立一個棧幀用於存儲局部變量表,操做數棧,動態連接,方法出口等信息。
  • 方法區:它用於存儲已被虛擬機加載的類信息,常量,靜態變量,即時編譯器編譯後的代碼等數據,方法區在JDK1.7版本及以前稱爲永久代,從JDK1.8以後永久代被移除。
  • 堆:堆是Java對象的存儲區域,任何new字段分配的Java對象實例和數組,都被分配在了堆上,Java堆可以使用 - Xms / - Xmx 進行內存控制,從JDK1.7版本以後,運行時常量池從方法區移到了堆上。
  • 程序計數器:指示Java虛擬機下一條須要執行的字節碼指令。

2)JAVA8以後的JVM

從圖中咱們能夠看出JAVA8的JVM 用元空間取代了永久代
數組

---
---

---
---
---
---

3)GC做用域

---
---

4)常見垃圾回收算法

  • 引用計數法:

JVM的實現通常不採用這種方式緩存

---
---

缺點:
1. 每次對對象賦值時均要維護引用計數器,且計數器自己也有必定的消耗;
2. 較難處理循環引用;網絡

  • 複製算法:

Java 堆從GC的角度能夠細分爲:新生代(Eden區、From Survivor區 和 To Survivor區)和 老年代。
特色:
複製算法不會產生內存碎片,但會佔用空間。用於新生代。app

---
---

MinorGC的過程(複製 --> 清空 --> 互換)

  1. 複製: (Eden、SurvivorFrom 複製到 SurvivorTo,年齡加1)
    首先,當Eden區滿的時候會觸發第一次GC,把還活着的對象拷貝到SurvivorFrom區,當Eden區再次觸發GC的時候會掃描Eden區域和From區域,對這兩個區域進行垃圾回收,通過此次回收後還存活的對象,則直接複製到To區域(若是有對象的年齡已經到達了老年的標準,則複製到老年代區),同時把這些對象的年齡加1。
  2. 清空:(清空Eden、SurvivorFrom)
    清空Eden和SurvivorFrom中的對象,也即複製以後有交換,誰空誰是to。
  3. 互換:(SurvivorTo和SurvivorFrom 互換)
    最後,SurvivorTo和SurvivorFrom 互換,原SurvivorTo成爲下一次GC是的SurvivorFrom區。
  • `標記清除法`

算法分紅標記和清除兩個階段,先標記出要回收的對象,而後統一回收這些。
特色:
不會佔用額外空間,但會掃描兩次,耗時,容易產生碎片,用於老年代jvm

---
---
  • `標記壓縮法`

優勢:
沒有內存碎片,能夠利用bump
缺點:
須要移動對象的成本,用於老年代
原理:post

  1. 標記:與標記清除同樣
    ---
    ---

    2.壓縮:再次掃描,並往一段滑動存活對象
    ---
    ---

2、正文接入

1)判斷對象是否可回收

  • `引用計數法`

Java中,引用和對象是有關聯的。若是要操做對象則必須用引用進行。
所以,很顯然的一個方法就是經過引用計數來判斷一個對象是否能夠回收。簡單來講就是給對象添加一個引用計數器。每當有一個地方引用它,計數器的值加1,每當有一個引用失效時,計數器的值減1。
任什麼時候刻計數器值爲0的對象就是不可能再被使用的,那麼這個對象就是可回收對象。
缺點: 很難解決對象之間相互循環引用的問題性能

---
---
  • `枚舉根節點作可達性分析(根搜索路徑)`

所謂「GC roots」 或者說tracing GC 的 "根集合" 就是一組必須活躍的引用。
基本思路就是經過一系列名爲「GC Roots」 的對象做爲起始點,從這個被稱爲GC Roots的對象開始向下搜索,如GC Roots沒有任何引用鏈相連是,則說明此對象不可用。也即給定一個集合的引用做爲根出發,經過引用關係

圖片來源於網上
圖片來源於網上

2)哪些能夠作GCRoots對象

  • 虛擬機棧(棧幀中的局部變量區,也叫作局部變量表)
  • 方法區中的類靜態屬性引用的對象
  • 方法區中常量引用的對象
  • 本地方法棧中N(Native方法)引用的對象

3)JVM的參數類型

  • 標配參數
    • java -version
    • java -help
      ---
      ---
  • X參數
    • java -Xint -version :解釋執行
    • java -Xcomp -version :第一次使用就編譯成本地代碼
    • java -Xmixed :混合模式
      ---
      ---
  • XX參數
    • Boolean類型
      > -XX:+ 或者 - 某個屬性值
      > +:表示開啓
      > -:表示關閉
      > `例子:`
      > -XX: +PrintGCDetails: 開啓打印GC收集細節
      > -XX: -PrintGCDetails: 關閉打印GC收集細節
      > -XX: +UseSerialGC: 開啓串行垃圾收集器
      > -XX: -UseSerialGC: 關閉串行垃圾收集器
    • KV設置類型
      > -XX: 屬性key = 屬性value
      > `例子:`
      > -XX: MetaspaceSize = 128m:設置元空間大小爲128m
      > -XX:MaxTenuringThreshold = 15: 控制新生代須要經歷多少次GC晉升到老年代中的最大閾值
    • jinfo-查看當前運行程序的配置
      > 公式: jinfo -flag 配置項 進程編號
      > `例子:`
      > 1. 查看初始堆大小:
      ---
      ---

      >2. 查看其餘參數
      ---
      ---

      > 3. 查看使用哪一種垃圾回收器
      ---
      ---
  • 兩個經典參數
    • -Xms 等價於 -XX: InitialHeapSize
    • -Xmx 等價於 -XX: MaxHeapSize

4)查看JVM默認值

  • -XX:+PrintFlagsInitial 查看默認初始值
  • java -XX: +PrintFlagsInitial -version
  • java -XX: +PrintFlagsInitial
    ---
    ---
  • -XX:+PrintFlagsFinal 查看修改更新
    • java -XX:+PrintFlagsFinal
    • java -XX:+PrintFlagsFinal -version
    • java -XX:+PrintCommandedLineFlags
      ---
      ---

5)經常使用的配置參數

經典案例設置:
-Xms128m -Xmx4096m -Xss1024k -XX:Metaspacesize=512m -XX:+PrintCommandLineFlags -XX:PrintGCDetails -XX:UseSerialGC

  • -Xms

初始化大小內存,默認爲物理內存1/64
等價於 -XX:InitialHeapSize

  • -Xmx

最大分配內存,默認爲物理內存1/4
等價於 -XX:MaxHeapSize

  • -Xss

設置單個線程的大小,通常默認爲5112K~1024K
等價於 -XX:ThreadStackSize

  • -Xmn

設置年輕代大小

  • -XX:MetaspaceSize

設置元空間大小

---
---
  • -XX:+PrintGCdetails

輸出詳細的GC收集日誌信息

---
---
---
---
  • -XX:SurvivorRatio

設置新生代中eden和S0/S1空間的比例
默認:
-XX:SurvivorRatio=8 --> Eden:S0:S1=8:1:1
修改:
-XX:SurvivorRatio=4 --> Eden:S0:S1=4:1:1
SurvivorRatio值就是設置eden區的比例佔多少,S0/S1相同

  • -XX:NewRatio

設置年輕代與老年代在堆結構的佔比
默認
-XX:NewRatio=2 新生代佔1,老年代佔2,年輕代佔整個堆的1/3
修改
-XX:NewRatio=4 新生代佔1,老年代佔4,年輕代佔整個堆的1/5
NewRatio值就是設置老年代的佔比,剩下的1給新生代

  • -XX:MaxTenuringThreshold

設置垃圾最大年齡
-XX:MaxTenuringThreshold=0:設置垃圾最大年齡。若是設置爲0的話,則年輕代對象不通過Survivor區,直接進入老年代。對於老年代比較多的應用,能夠提升效率。若是將此值設置爲一個較大值,則年輕代對象會在Survivor區進行屢次複製,這樣能夠增長對象在年輕代的存活時間,增長年輕代被回收的概論。

6)強軟弱虛

圖片來源於網絡
圖片來源於網絡

圖片來源於網絡
圖片來源於網絡
  • 強引用
    • 當內存不足,JVM開始垃圾回收,對於強引用的對象,就算出現了OOM也不會對該對象進行回收,死都不收
    • 強引用是咱們最多見的普通對象引用,只要還有強引用指向一個對象,就能表名對象還「活着」,垃圾收集器不會碰這種對象。在Java中最多見的就是強引用,把一個對象賦給一個引用變量,這個引用變量就是一個強引用。當一個對象被強引用變量引用時,它處於可達狀態,它是不可能被垃圾回收機制回收的。即便該對象之後永遠都不會被用到,JVM也不會回收。 所以強引用是形成Java內存泄漏的主要緣由之一。
    • 對於一個普通的對象,若是沒有其餘的引用關係,只要超過了引用的做用域或者顯式地將相應(強)引用賦值爲null,通常就是認爲能夠被垃圾收集(具體看垃圾收集策略)
public static void main(String[] args) {
        Object o1 = new Object();   //默認爲強引用
        Object o2 = o1;     //引用賦值
        o1 = null;          //置空 讓垃圾收集
        System.gc();
        System.out.println(o1);     // null
        System.out.println(o2);     // java.lang.Object@1540e19d
    }
複製代碼
  • 軟引用
    • 軟引用就是一種相對強引用弱化了一些的引用。須要用java.lang.ref.SoftReference類來實現,可讓對象豁免一些垃圾收集。
    • 系統內存充足 -> 不會回收
    • 系統內存不足 -> 會回收
    • 軟引用一般用在對內存敏感的程序中,好比高速緩存就有用到軟引用,內存夠用的時候就保留,不夠用就回收
    public static void main(String[] args) {
        Object o1 = new Object();
        SoftReference softReference = new SoftReference(o1);
        o1 = null;
        System.gc();
        System.out.println(o1);
        System.out.println(softReference.get());
    }
複製代碼
  • 弱引用
    • 弱引用須要用java.lang.ref.WeakReference類來實現,它比軟引用的生存期更短
    • 對於弱引用的對象,只要垃圾回收機制一運行,無論JVM的內存空間是否足夠,都會回收該對象佔用的內存。
    public static void main(String[] args) {
        Object o1 = new Object();
        WeakReference weakReference = new WeakReference(o1);
        o1 = null;
        System.gc();
        System.out.println(o1);                     //null
        System.out.println(weakReference.get());    //null
    }
複製代碼
  • 虛引用
    • 虛引用須要java.lang.ref.PhantomReference類來實現。
    • 形如虛設,它不會決定對象的生命週期。
    • 若是一個對象持有虛引用,那麼它就和沒有任何同樣,在任什麼時候候均可能被垃圾回收器回收, 它不能單獨使用也不能經過它來訪問對象,虛引用必須和引用隊列(ReferenceQueue)聯合使用。
    • 虛引用的主要做用是跟蹤對象被垃圾回收的狀態,僅僅是提供了一種確保對象被finalize之後,作某些事情的機制。PhantomReference的get方法老是返回null,所以沒法訪問對應的引用對象。其意義在於說明一個對象已經進入finalization階段,能夠被gc回收,用來實現比finalization機制更靈活的回收操做。
public static void main(String[] args) {
        Object o1 = new Object();
        ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
        PhantomReference<Object> phantomReference = new PhantomReference<>(o1,referenceQueue);
        System.out.println(o1);                         //java.lang.Object@1540e19d
        System.out.println(phantomReference.get());     //null
        System.out.println(referenceQueue.poll());      //null
    }
複製代碼
  • 擴展
    • 軟弱引用適用場景 > 假若有一個引用須要讀取大量的本地圖片 >存在問題:
      1. 若是每次讀取圖片都從硬盤讀取則會嚴重影響性能。
      2. 若是一次性所有加載到內存中有可能形成內存溢出。
        >解決思路:
        > 用一個HashMap來保存圖片的路徑和相應圖片對象關聯的軟引用之間的映射關係,在內存不足時,JVM會自動回收這些緩存圖片對象所佔用的空間,從而有效地避免了OOM的問題。
        Map imgMap = new HashMap();
    • WeakHashMap
public static void main(String[] args) {
        WeakHashMap<Integer,String> weakHashMap = new WeakHashMap<>();
        Integer key = new Integer(1);
        weakHashMap.put(key,"測試1");
        System.out.println(weakHashMap);    //{1=測試1}
        key=null;
        System.out.println(weakHashMap);    //{1=測試1}
        System.gc();
        System.out.println(weakHashMap+"\t"+weakHashMap.size());    //{} 0
    }
複製代碼
看完不讚,都是壞蛋
看完不讚,都是壞蛋

本文較長,能看到這裏的都是好樣的,成長之路學無止境
今天的你多努力一點,明天的你就能少說一句求人的話!
好久好久以前,有個傳說,聽說:
看完不讚,都是壞蛋

相關文章
相關標籤/搜索