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的空间大小