JVM(2): 逃逸分析和內存分配

  • 2019 年 10 月 4 日
  • 筆記

首先來說下為什麼會有逃逸分析

我們都知道Java對象都是分配在在堆上的,在過往的認識中,一直是以這樣的方式存在的,但是從Java7開始支持對象的棧分配和逃逸分析機制。

然後我們來說說具體什麼是逃逸分析

逃逸分析是一種能有效減少對象在堆上分配和同步負載的跨函數數據流分析算法,逃逸分析通俗的說就是一個對象的指針被多個線程和方法引用時,那我們就稱為這個對象發生了逃逸。

逃逸分析的基本行為就是分析對象動態作用域:當一個對象在方法中被定義後,它可能被外部方法所引用。逃逸分析只能在JIT里完成,不能在靜態編譯時進行。

逃逸分析又分為方法逃逸和線程逃逸。

方法逃逸:例如作為調用參數傳遞到其他方法中。 線程逃逸:有可能被外部線程訪問到,譬如賦值給類變量或可以在其他線程中訪問的實例變量。

主要涉及三種場景,分別是全局變量賦值、方法返回值、實例引用傳遞。

package me.stormma.gc;  /**   * <p>Created on 2017/4/21.</p>   *   * @author stormma   *   * @title <p>逃逸分析</p>   */  public class EscapeAnalysis {      public static B b;      /**       * <p>全局變量賦值發生指針逃逸</p>       */      public void globalVariablePointerEscape() {          b = new B();      }      /**       * <p>方法返回引用,發生指針逃逸</p>       * @return       */      public B methodPointerEscape() {          return new B();      }      /**       * <p>實例引用發生指針逃逸</p>       */      public void instancePassPointerEscape() {          methodPointerEscape().printClassName(this);      }      class B {          public void printClassName(EscapeAnalysis clazz) {              System.out.println(clazz.getClass().getName());          }      }  }

逃逸分析主要分為兩部分,一個是Java虛擬機進行的逃逸分析,一個是根據逃逸分析原理去優化自己的代碼

我們先來說下Java虛擬機的逃逸分析

1.堆對象變成棧對象,一個方法中的對象沒有發生逃逸,那麼該對象就很有可能被分配在棧上

2.同步消除,逃逸分析可以分析出某個對象是否只有一個線程訪問,如果是只有一個線程訪問,那麼對該對象的同步操作就可以消除,就樣就能大大提高並發性和性能。

3.矢量替代,逃逸分析如果發現對象的內存存儲結構不需要連續進行的話,就可以將對象的部分甚至全部都保存在CPU寄存器內

下面我們來說下對象的內存分配

為對象分配空間的任務等同於把一塊確定大小的內存從Java堆中劃分出來。

指針碰撞和空閑列表

指針碰撞對於垃圾收集算法為Serial,ParNew等帶compact過程的收集器,該分配算法是假設堆中內存是決對規整的,空閑的在一邊,非空閑的在另一邊,中間有個指針作為指示器,再要進行內存分配時,只是把指針向空閑的那邊移動一段和對象大小相等的空間。

空閑列表只是對於垃圾收集算法為CMS這種基於Mark-sweep算法的收集器,該分配算法是假設堆中的內存是縱橫交錯的,空閑的和非空閑的交錯在一起,對於這種虛擬機就必須維護一個列表,記錄那些塊是可用的,在要進行內存分配時,從列表中找出一塊分配給劃分給對象實例,並更新列表上的記錄。這種分配方式稱為空閑列表。

TLAB

線程本地分配緩衝區(Thread Local Allocation Buffer),由於Java對象一般分配在堆上,在有多個線程需要在堆上申請空間時,那麼需要對這些對象的申請進行同步,在有多個對象進行申請時,就會照成分配的效率非常低下,那麼使用TLAB就可以避免這種競爭。

TLAB本身佔用eden空間,默認是開啟的,在開啟TLAB的情況下,虛擬機會為每個線程分配一個TLAB空間,開啟的設置是:-XX:+UserTLAB,而-XX:+TLABWasteTargetPercent是設置TLAB空間佔用eden的空間大小