Java逃逸分析详解及代码示例

概念引入

我们都知道,Java 创建的对象都是被分配到堆内存上,但是事实并不是这么绝对,通过对Java对象分配的过程分析,可以知道有两个地方会导致Java中创建出来的对象并一定分别在所认为的堆上。这两个点分别是Java中的逃逸分析和TLAB(Thread Local Allocation Buffer)线程私有的缓存区。

基本概念介绍

逃逸分析,是一种可以有效减少Java程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法。通过逃逸分析,Java Hotspot编译器能够分析出一个新的对象的引用的使用范围从而决定是否要将这个对象分配到堆上。

在计算机语言编译器优化原理中,逃逸分析是指分析指针动态范围的方法,它同编译器优化原理的指针分析和外形分析相关联。当变量(或者对象)在方法中分配后,其指针有可能被返回或者被全局引用,这样就会被其他过程或者线程所引用,这种现象称作指针(或者引用)的逃逸(Escape)。通俗点讲,如果一个对象的指针被多个方法或者线程引用时,那么我们就称这个对象的指针发生了逃逸。

Java在Java SE 6u23以及以后的版本中支持并默认开启了逃逸分析的选项。Java的 HotSpot JIT编译器,能够在方法重载或者动态加载代码的时候对代码进行逃逸分析,同时Java对象在堆上分配和内置线程的特点使得逃逸分析成Java的重要功能。

代码示例

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 对象总是在堆中被分配的,因此 java 对象的创建和回收对系统的开销是很大的。java 语言被批评的一个地方,也是认为 java 性能慢的一个原因就是 java不支持栈上分配对象。JDK6里的 Swing内存和性能消耗的瓶颈就是由于 GC 来遍历引用树并回收内存的,如果对象的数目比较多,将给 GC 带来较大的压力,也间接得影响了性能。减少临时对象在堆内分配的数量,无疑是最有效的优化方法。java 中应用里普遍存在一种场景,一般是在方法体内,声明了一个局部变量,并且该变量在方法执行生命周期内未发生逃逸,按照 JVM内存分配机制,首先会在堆内存上创建类的实例(对象),然后将此对象的引用压入调用栈,继续执行,这是 JVM优化前的方式。当然,我们可以采用逃逸分析对 JVM 进行优化。即针对栈的重新分配方式,首先我们需要分析并且找到未逃逸的变量,将该变量类的实例化内存直接在栈里分配,无需进入堆,分配完成之后,继续调用栈内执行,最后线程执行结束,栈空间被回收,局部变量对象也被回收,通过这种方式的优化,与优化前的方案主要区别在于对象的存储介质,优化前是在堆中,而优化后的是在栈中,从而减少了堆中临时对象的分配(较耗时),从而优化性能。

使用逃逸分析进行性能优化(-XX:+DoEscapeAnalysis开启逃逸分析)

public void method() {
  Test test = new Test();
  //处理逻辑
  ......
  test = null;
}

这段代码,之所以可以在栈上进行内存分配,是因为没有发生指针逃逸,即是引用没有暴露出这个方法体。

栈和堆内存分配比较

package me.stormma.gc;
/**
 * <p>Created on 2017/4/21.</p>
 *
 * @author stormma
 * @description: <p>内存分配比较</p>
 */
public class EscapeAnalysisTest {
  public static void alloc() {
    byte[] b = new byte[2];
    b[0] = 1;
  }
  public static void main(String[] args) {
    long b = System.currentTimeMillis();
    for (int i = 0; i < 100000000; i++) {
      alloc();
    }
    long e = System.currentTimeMillis();
    System.out.println(e - b);
  }
}

JVM 参数为-server -Xmx10m -Xms10m -XX:-DoEscapeAnalysis -XX:+PrintGC, 运行结果

JVM 参数为-server -Xmx10m -Xms10m -XX:+DoEscapeAnalysis -XX:+PrintGC, 运行结果

性能测试

package me.stormma.gc;
/**
 * <p>Created on 2017/4/21.</p>
 *
 * @author stormma
 *
 * @description: <p>利用逃逸分析进行性能优化</p>
 */
public class EscapeAnalysisTest {
  private static class Foo {
    private int x;
    private static int counter;
    public Foo() {
      x = (++counter);
    }
  }
  public static void main(String[] args) {
    long start = System.nanoTime();
    for (int i = 0; i < 1000 * 1000 * 10; ++i) {
      Foo foo = new Foo();
    }
    long end = System.nanoTime();
    System.out.println("Time cost is " + (end - start));
  }
}

使用逃逸分析优化 JVM输出结果( -server -XX:+DoEscapeAnalysis -XX:+PrintGC)

Time cost is 11012345

未使用逃逸分析优化 JVM 输出结果( -server -Xmx10m -Xms10m -XX:-DoEscapeAnalysis -XX:+PrintGC)

[GC (Allocation Failure) 33280K->408K(125952K), 0.0010344 secs]
[GC (Allocation Failure) 33688K->424K(125952K), 0.0009799 secs]
[GC (Allocation Failure) 33704K->376K(125952K), 0.0007297 secs]
[GC (Allocation Failure) 33656K->456K(159232K), 0.0014817 secs]
Time cost is 68562263

分析结果,性能优化1/6。

总结

以上就是本文关于Java逃逸分析详解及代码示例的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站:Java实现生产者消费者问题与读者写者问题详解、java算法实现红黑树完整代码示例、Java编程接口调用的作用及代码分享等,有什么问题可以随时留言,小编会及时回复大家的。感谢朋友们对本站的支持。

(0)

相关推荐

  • 简述Java编程语言中的逃逸分析

    大家一般认为new出来的对象都是被分配在堆上,但这并不是完全正确,通过对Java对象分配过程分析,我们发现对象除了可以被分配在堆上,还可以在栈或TLAB中分配空间.而栈上分配对象的技术基础是逃逸分析和标量替换,本文主要介绍下逃逸分析. 1.逃逸分析的定义 逃逸分析:是一种可以有效减少Java 程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法. 通过逃逸分析,Java Hotspot编译器能够分析出一个新的对象的引用的使用范围从而决定是否要将这个对象分配到堆上. Java在Java SE

  • Java逃逸分析详解及代码示例

    概念引入 我们都知道,Java 创建的对象都是被分配到堆内存上,但是事实并不是这么绝对,通过对Java对象分配的过程分析,可以知道有两个地方会导致Java中创建出来的对象并一定分别在所认为的堆上.这两个点分别是Java中的逃逸分析和TLAB(Thread Local Allocation Buffer)线程私有的缓存区. 基本概念介绍 逃逸分析,是一种可以有效减少Java程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法.通过逃逸分析,Java Hotspot编译器能够分析出一个新的对象的

  • Java中的静态内部类详解及代码示例

    1. 什么是静态内部类 在Java中有静态代码块.静态变量.静态方法,当然也有静态类,但Java中的静态类只能是Java的内部类,也称为静态嵌套类.静态内部类的定义如下: public class OuterClass { static class StaticInnerClass { ... } } 在介绍静态内部类之前,首先要弄清楚静态内部类与Java其它内部类的区别. 2. 内部类 什么是内部类?将一个类的定义放在另一个类的内部,就是内部类.Java的内部类主要分为成员内部类.局部内部类.

  • Java之dao模式详解及代码示例

    什么是dao模式? DAO(Data Access Object)顾名思义是一个为数据库或其他持久化机制提供了抽象接口的对象,在不暴露底层持久化方案实现细节的前提下提供了各种数据访问操作.在实际的开发中,应该将所有对数据源的访问操作进行抽象化后封装在一个公共API中.用程序设计语言来说,就是建立一个接口,接口中定义了此应用程序中将会用到的所有事务方法.在这个应用程序中,当需要和数据源进行交互的时候则使用这个接口,并且编写一个单独的类来实现这个接口,在逻辑上该类对应一个特定的数据存储.DAO模式实

  • Java中Volatile关键字详解及代码示例

    一.基本概念 先补充一下概念:Java内存模型中的可见性.原子性和有序性. 可见性: 可见性是一种复杂的属性,因为可见性中的错误总是会违背我们的直觉.通常,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根本不可能的事情.为了确保多个线程之间对内存写入操作的可见性,必须使用同步机制. 可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的.也就是一个线程修改的结果.另一个线程马上就能看到.比如:用volatile修饰的变量,就会具有可见性.volatile修饰的

  • Java中LinkedList详解和使用示例_动力节点Java学院整理

    第1部分 LinkedList介绍 LinkedList简介 LinkedList 是一个继承于AbstractSequentialList的双向链表.它也可以被当作堆栈.队列或双端队列进行操作. LinkedList 实现 List 接口,能对它进行队列操作. LinkedList 实现 Deque 接口,即能将LinkedList当作双端队列使用. LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆. LinkedList 实现java.io.Serial

  • 集合框架(Collections Framework)详解及代码示例

    简介 集合和数组的区别: 数组存储基础数据类型,且每一个数组都只能存储一种数据类型的数据,空间不可变. 集合存储对象,一个集合中可以存储多种类型的对象.空间可变. 严格地说,集合是存储对象的引用,每个对象都称为集合的元素.根据存储时数据结构的不同,分为几类集合.但对象不管存储到什么类型的集合中,既然集合能存储任何类型的对象,这些对象在存储时都必须向上转型为Object类型,也就是说,集合中的元素都是Object类型的对象. 既然是集合,无论分为几类,它都有集合的共性,也就是说虽然存储时数据结构不

  • SpringMVC中的拦截器详解及代码示例

    本文研究的主要是SpringMVC中的拦截器的介绍及实例代码,配置等内容,具体如下. Springmvc的处理器拦截器类似于Servlet 开发中的过滤器Filter,用于对处理器进行预处理和后处理.本文主要总结一下springmvc中拦截器是如何定义的,以及测试拦截器的执行情况和使用方法. 1. springmvc拦截器的定义和配置 1.1 springmvc拦截器的定义 在springmvc中,定义拦截器要实现HandlerInterceptor接口,并实现该接口中提供的三个方法,如下: /

  • java线程池详解及代码介绍

    目录 一.线程池简介 二.四种常见的线程池详解 三.缓冲队列BlockingQueue和自定义线程池ThreadPoolExecutor 总结 一.线程池简介 线程池的概念 线程池就是首先创建一些线程,它们的集合称为线程池,使用线程池可以很好的提高性能,线程池在系统启动时既创建大量空闲的线程,程序将一个任务传给线程池.线程池就会启动一条线程来执行这个任务,执行结束后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务. 线程池的工作机制 在线程池的编程模式下,任务是提交给整个

  • Java中SimpleDateFormat日期格式转换详解及代码示例

    SimpleDateFormat是处理日期格式转换的类. 官方API_1.8关于SimpleDateFormat继承于DateFormate截图: SimpleDateFormat的构造器如下: SimpleDateFormat中的格式定义,常用的用红色框圈出: 中文解释: y : 年 M : 年中的月份 D : 年中的天数 d : 月中的天数 w : 年中的周数 W : 月中的周数 a : 上下/下午 H : 一天中的小时数(0-23) h : 一天中的小时数(0-12) m : 小时中的分钟

  • Jmeter调用java脚本过程详解

    这篇文章主要介绍了Jmeter调用java脚本过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 jmeter为纯java编写,所以有三种方式使用java脚本,分别是:调用 .java 文件:调用 .class文件 :调用 .jar 文件 1. jmeter调用.java文件 1>. 新建一java脚本,内容如下: 2>. 新建线程组>>添加BeanShell Sampler.Debug PostProcessor.查看结果树

随机推荐