细说逃逸分析及基于逃逸分析的优化

Java 中 Iterable 对象的 foreach 循环遍历是一个语法糖,Java 编译器会将该语法编译为调用 Itreable 对象的 iterator 方法,并用所返回的 Iterator 对象的 hasNext 以及 next 方法,来完成遍历。

public void forEach(ArrayList<Object> list, Consumer<Object> f) {
  for (Object obj : list) {
    f.accept(obj);
  }
}

举个例子,上面的 Java 代码将使用 foreach 循环来遍历一个 ArrayList 对象,其等价的代码如下所示:

public void forEach(ArrayList<Object> list, Consumer<Object> f) {
  Iterator<Object> iter = list.iterator();
  while (iter.hasNext()) {
    Object obj = iter.next();
    f.accept(obj);
  }
}

这里再列举一个所涉及 ArrayList 代码。可以看到ArrayList.iterator 方法将创建一个 ArrayList$Itr实例

public class ArrayList ... {
  public Iterator<E> iterator() {
    return new Itr();
  }
  private class Itr implements Iterator<E> {
    int cursor;       // index of next element to return
    int lastRet = -1; // index of last element returned; -1 if no such
    int expectedModCount = modCount;
    ...
    public boolean hasNext() {
      return cursor != size;
    }
    @SuppressWarnings("unchecked")
    public E next() {
      checkForComodification();
      int i = cursor;
      if (i >= size)
        throw new NoSuchElementException();
      Object[] elementData = ArrayList.this.elementData;
      if (i >= elementData.length)
        throw new ConcurrentModificationException();
      cursor = i + 1;
      return (E) elementData[lastRet = i];
    }
    ...
    final void checkForComodification() {
      if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
    }
  }
}

因此,可以认为应当避免再热点代码中使用 foreach 循环,并且直接使用基于 ArrayList.size 以及 ArrayList.get 的循环方式,以减少对 Java 堆的压力

public void forEach(ArrayList<Object> list, Consumer<Object> f) {
  for (int i = 0; i < list.size(); i++) {
    f.accept(list.get(i));
  }
}

实际上,Java 虚拟机中的即时编译器可以将 ArrayList.iterator 方法中的实例创建操作优化,不过,这需要方法内联以及逃逸分析的协作。

逃逸分析

逃逸分析是“一种确定指针动态范围的静态分析,它可以分析在程序的哪些地方可以访问到指针”。

在 Java 虚拟机的即时编译语境下,逃逸分析将判断新建的对象是否逃逸。即时编译器判断对象是否逃逸的依据,一时独享是否被存入堆中 (静态字段或者堆中对象的实例字段),二时对象是否被传入位置代码中。

前者比较好理解:一旦对象被存入堆中,其他线程便能获得该对象的引用。即时编译器也因此无法追踪所有使用该对象的代码位置。

关于后者,由于 Java 虚拟机的即时编译器时以方法为单位的,对于方法中未被内联的方法调用,即时编译器会将其当成未知代码,毕竟它无法确认该方法调用会不会将调用者或者传入的参数存储至堆中。因此,我们可以认为方法调用的调用者以及参数是逃逸的。

通常来说,即时编译器里逃逸分析是放在方法内敛之后的,以便消除这些 “未知代码” 入口。

基于逃逸分析的优化

即时编译器可以根据逃逸分析的结果进行诸如锁消除、栈上分配以及标量替换的优化。

首先看锁消除。如果即时编译其能够证明锁对象不逃逸,那么对该所对象的加锁、解锁操作没有意义。这是以内其他线程并不能获得该锁独享,因此也不可能对其尽享加锁。在这种情况下,即时编译器可以消除对该不逃逸锁对象那个的加锁、解锁操作。

实际上,传统编译器仅需证明锁对象那个不逃逸出线程,便可以进行锁消除。由于 Java 虚拟机即时编译器的限制,上述条件被强化未证明锁对象不逃逸出当前编译的方法。

我们知道 synchronized (new Object()) {} 会被完全优化掉。这正式因为基于逃逸分析的锁消除。由于其他线程不能获得锁对象,因此也无法基于该锁对象构造两个线程之间的 happens-before 规则。

synchronized (escapedObject) {} 则不然。由于其他线程可能会对逃逸了的对象 escapedObject 进行加锁操作,从而构造了两个线程之间的happens-before关系。因此即时编译器至少需要为这段diamagnetic生成一条刷新缓存的内存屏障指令。

我们知道,Java 虚拟机中对象都是在堆上分配的,而堆上的内容对任何线程都是可见的。与此同时,Java 虚拟机需要对所分配的堆内存进行管理,并且在对象不再被引用时回收其所占据的内存。

如果逃逸分析能够证明某些新建的对象不逃逸,那么 Java 虚拟机完全可以将其分配至栈上,并且在 new 语句所在的方法退出时,通过弹出当前方法的栈桢来自动回收所分配的内存空间。这样一来,我们便无须借助垃圾回收器来处理不再被引用的对象。

不过,由于实现起来需要更改大量假设了“对象只能堆分配”的代码,因此 HotSpot 虚拟机并没有采用栈上分配,而是使用了标量替换这么一项技术。

所谓的标量,就是仅能存储一个值的变量,比如 Java 代码中的局部变量。与之相反,聚合量则可能同时存储多个值,其中一个典型的例子便是 Java 对象。

标量替换这项优化技术,可以看成将原本对对象的字段的访问,替换为一个个局部变量的访问。举例来说,前面经过内联之后的 forEach 代码可以被转换为如下代码:

public void forEach(ArrayList<Object> list, Consumer<Object> f) {
  // Itr iter = new Itr; // 经过标量替换后该分配无意义,可以被优化掉
  int cursor = 0;     // 标量替换
  int lastRet = -1;   // 标量替换
  int expectedModCount = list.modCount; // 标量替换
  while (cursor < list.size) {
    if (list.modCount != expectedModCount)
      throw new ConcurrentModificationException();
    int i = cursor;
    if (i >= list.size)
      throw new NoSuchElementException();
    Object[] elementData = list.elementData;
    if (i >= elementData.length)
      throw new ConcurrentModificationException();
    cursor = i + 1;
    lastRet = i;
    Object obj = elementData[i];
    f.accept(obj);
  }
}

可以看到,原本需要在内存中连续分布的对象,现已被拆散为一个个单独的字段cursor,lastRet,以及expectedModCount。这些字段既可以存储在栈上,也可以直接存储在寄存器中。而该对象的对象头信息则直接消失了,不再被保存至内存之中。

由于该对象没有被实际分配,因此和栈上分配一样,它同样可以减轻垃圾回收的压力。与栈上分配相比,它对字段的内存连续性不做要求,而且,这些字段甚至可以直接在寄存器中维护,无须浪费任何内存空间。

小结

以上主要学习了 Java 虚拟机中即时编译其的逃逸分析,以及基于逃逸分析的优化。

在 Java 虚拟机的即时编译预警下,逃逸分析将判断新建的对象是否会发生逃逸,即时编译器判断对象逃逸的依据有两个:一是看对象是否被存入堆中,二是看独享死否作为方法调用的调用者或者参数。

即时编译器会根据逃逸分析的结果进行优化,如锁消除以及标量替换。后者指的是将原本连续分配的独享查三位一个个单独的字段,分布在栈上或者寄存器中。

部分逃逸分许是一种附带了控制流信息的逃逸分析。它将判断新建对象真正逃逸的分治,并且支持将新建操纵推延至逃逸分支。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
基于Spring Boot的宿舍管理系统技术可行性分析主要包括以下几个方面: 1. 技术方面:Spring Boot是一个快速开发框架,具有简单、易用、快速开发等特点。同时,Spring Boot还集成了很多常用的技术,如Spring MVC、Spring Data等,可以大大提高开发效率。此外,Spring Boot还提供了很多方便的工具,如自动化配置、热部署等,可以让开发者更加专注于业务逻辑的实现。 2. 数据库方面:宿舍管理系统需要处理大量的数据,如学生信息、宿舍信息、维修记录等,因此需要使用一个可靠、高效的数据库。MySQL是一个开源的关系型数据库,具有成熟的技术、稳定性高等优点,非常适合作为宿舍管理系统的数据库。 3. 安全方面:宿舍管理系统需要保证用户信息的安全性,防止信息泄露、非法访问等问题。Spring Security是一个强大的安全框架,可以提供基于角色的访问控制、密码加密、会话管理等功能,可以有效地保护用户信息的安全。 4. 用户体验方面:宿舍管理系统需要考虑用户体验,提供友好、易用的界面。前端技术可以选择一些成熟的框架,如Bootstrap、jQuery等,可以快速构建出美观、易用的界面。 综合以上几个方面的考虑,基于Spring Boot的宿舍管理系统技术可行性很高。开发者可以利用Spring Boot的优势,快速实现宿舍管理系统的各个功能,同时可以保证系统的安全性、性能、用户体验等方面的要求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值