[转帖]JVM系列之:关于逃逸分析的学习

jvm,系列,关于,逃逸,分析,学习 · 浏览次数 : 0

小编点评

**Java 虚拟机即时编译器的逃逸分析** **简介** Java 虚拟机即时编译器逃逸分析是一种优化技术,通过分析编译器逃逸分析过程中的内存分配模式,并根据这些模式推断出对象逃逸的优化方法。 **逃逸分析** 逃逸分析是即时编译器逃逸分析的一个优化方法,通过在编译器逃逸分析过程中分析内存分配模式,推断出对象逃逸的优化方法。常见的面板优化方法包括同步消除、标量替换和栈上分配。 **优化的方法** * **同步消除**:通过在对象逃逸之前同步消除对象之间的内存分配,减少对象的逃逸。 * **标量替换**:通过在对象逃逸之前标量替换对象之间的内存分配,降低对象的逃逸。 * **栈上分配**:通过在对象逃逸之前在栈上分配对象之间的内存分配,降低对象的逃逸。 **性能提升** 通过优化对象的逃逸,Java虚拟机即时编译器能够在执行过程中进行内存分配优化,提升性能。 **示例** ```java //同步消除 synchronize() { objectA = new objectA(); objectB = new objectB(); } //标量替换 replace() { objectA = new objectA(); objectB = new objectB(); } //栈上分配 stack() { objectA = new objectA(); objectB = new objectB(); } ```

正文

本文为《深入学习 JVM 系列》第十九篇文章

上文讲解完方法内联后,JIT 即时编译还有一个最前沿的优化技术:逃逸分析Escape Analysis) 。废话少说,我们直接步入正题吧。

逃逸分析

首先我们需要知道,逃逸分析并不是直接的优化手段,而是通过动态分析对象的作用域,为其它优化手段提供依据的分析技术。具体而言就是:

逃逸分析是“一种确定指针动态范围的静态分析,它可以分析在程序的哪些地方可以访问到指针”。Java虚拟机的即时编译器会对新建的对象进行逃逸分析,判断对象是否逃逸出线程或者方法。即时编译器判断对象是否逃逸的依据有两种:

  1. 对象是否被存入堆中(静态字段或者堆中对象的实例字段),一旦对象被存入堆中,其他线程便能获得该对象的引用,即时编译器就无法追踪所有使用该对象的代码位置。

    简单来说就是,如类变量或实例变量,可能被其它线程访问到,这就叫做线程逃逸,存在线程安全问题。

  2. 对象是否被传入未知代码中,即时编译器会将未被内联的代码当成未知代码,因为它无法确认该方法调用会不会将调用者或所传入的参数存储至堆中,这种情况,可以直接认为方法调用的调用者以及参数是逃逸的。(未知代码指的是没有被内联的方法调用)

    比如说,当一个对象在方法中定义之后,它可能被外部方法所引用,作为参数传递到其它方法中,这叫做方法逃逸,

​ 方法逃逸我们可以用个案例来演示一下:

//StringBuffer对象发生了方法逃逸
public static StringBuffer createStringBuffer(String s1, String s2) {
    StringBuffer sb = new StringBuffer();
    sb.append(s1);
    sb.append(s2);
    return sb;
  }

public static String createString(String s1, String s2) {
StringBuffer sb = new StringBuffer();
sb.append(s1);
sb.append(s2);
return sb.toString();
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

关于逃逸分析技术,本人想过用代码展示对象是否发生了逃逸,比如说上述代码,根据理论知识可以认为 createStringBuffer 方法中发生了逃逸,但是具体是个什么情况,咱们都不清楚。虽然 JVM 有个参数 PrintEscapeAnalysis 可以显示分析结果,但是该参数仅限于 debug 版本的 JDK 才可以进行调试,多次尝试后,未能编译出 debug 版本的 JDK,暂且没什么思路,所以查看逃逸分析结果这件事先往后放一放,后续学习 JVM 调优再进一步来学习。

基于逃逸分析的优化

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

同步消除(锁消除)

线程同步本身比较耗费资源,JIT 编译器可以借助逃逸分析来判断,如果确定一个对象不会逃逸出线程,无法被其它线程访问到,那该对象的读写就不会存在竞争,则可以消除对该对象的同步锁,通过-XX:+EliminateLocks(默认开启)可以开启同步消除。 这个取消同步的过程就叫同步消除,也叫锁消除。

我们还是通过案例来说明这一情况,来看看何种情况需要线程同步。

首先构建一个 Worker 对象

@Getter
public class Worker {

private String name;
private double money;

public Worker() {
}

public Worker(String name) {
this.name = name;
}

public void makeMoney() {
money++;
}
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

测试代码如下:

public class SynchronizedTest {

public static void work(Worker worker) {
worker.makeMoney();
}

public static void main(String[] args) throws InterruptedException {
long start = System.currentTimeMillis();

<span class="token class-name">Worker</span> worker <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Worker</span><span class="token punctuation">(</span><span class="token string">"hresh"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-&gt;</span> <span class="token punctuation">{<!-- --></span>
  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">20000</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token function">work</span><span class="token punctuation">(</span>worker<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token string">"A"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-&gt;</span> <span class="token punctuation">{<!-- --></span>
  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">20000</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token function">work</span><span class="token punctuation">(</span>worker<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token string">"B"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">long</span> end <span class="token operator">=</span> <span class="token class-name">System</span><span class="token punctuation">.</span><span class="token function">currentTimeMillis</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>end <span class="token operator">-</span> start<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">Thread</span><span class="token punctuation">.</span><span class="token function">sleep</span><span class="token punctuation">(</span><span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>worker<span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"总共赚了"</span> <span class="token operator">+</span> worker<span class="token punctuation">.</span><span class="token function">getMoney</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

执行结果如下:

52
hresh总共赚了28224.0
  • 1
  • 2

可以看出,上述两个线程同时修改同一个 Worker 对象的 money 数据,对于 money 字段的读写发生了竞争,导致最后结果不正确。像上述这种情况,即时编译器经过逃逸分析后认定对象发生了逃逸,那么肯定不能进行同步消除优化。

换个对象不发生逃逸的情况试一下。

//JVM参数:-Xms60M -Xmx60M  -XX:+PrintGCDetails -XX:+PrintGCDateStamps
public class SynchronizedTest {

public static void lockTest() {
Worker worker = new Worker();
synchronized (worker) {
worker.makeMoney();
}
}

public static void main(String[] args) throws InterruptedException {
long start = System.currentTimeMillis();

<span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-&gt;</span> <span class="token punctuation">{<!-- --></span>
  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">500000</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token function">lockTest</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token string">"A"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">new</span> <span class="token class-name">Thread</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-&gt;</span> <span class="token punctuation">{<!-- --></span>
  <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">int</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> i <span class="token operator">&lt;</span> <span class="token number">500000</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span>
    <span class="token function">lockTest</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token string">"B"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token keyword">long</span> end <span class="token operator">=</span> <span class="token class-name">System</span><span class="token punctuation">.</span><span class="token function">currentTimeMillis</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token class-name">System</span><span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span>end <span class="token operator">-</span> start<span class="token punctuation">)</span><span class="token punctuation">;</span>

}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

输出结果如下:

56
Heap
 PSYoungGen      total 17920K, used 9554K [0x00000007bec00000, 0x00000007c0000000, 0x00000007c0000000)
  eden space 15360K, 62% used [0x00000007bec00000,0x00000007bf5548a8,0x00000007bfb00000)
  from space 2560K, 0% used [0x00000007bfd80000,0x00000007bfd80000,0x00000007c0000000)
  to   space 2560K, 0% used [0x00000007bfb00000,0x00000007bfb00000,0x00000007bfd80000)
 ParOldGen       total 40960K, used 0K [0x00000007bc400000, 0x00000007bec00000, 0x00000007bec00000)
  object space 40960K, 0% used [0x00000007bc400000,0x00000007bc400000,0x00000007bec00000)
 Metaspace       used 4157K, capacity 4720K, committed 4992K, reserved 1056768K
  class space    used 467K, capacity 534K, committed 640K, reserved 1048576K
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

在 lockTest 方法中针对新建的 Worker 对象加锁,并没有实际意义,经过逃逸分析后认定对象未逃逸,则会进行同步消除优化。JDK8 默认开启逃逸分析,我们尝试关闭它,再看看输出结果。

-Xms60M -Xmx60M  -XX:-DoEscapeAnalysis -XX:+PrintGCDetails -XX:+PrintGCDateStamps
  • 1

输出结果变为:

73
2022-03-01T14:51:08.825-0800: [GC (Allocation Failure) [PSYoungGen: 15360K->1439K(17920K)] 15360K->1447K(58880K), 0.0018940 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
Heap
 PSYoungGen      total 17920K, used 16340K [0x00000007bec00000, 0x00000007c0000000, 0x00000007c0000000)
  eden space 15360K, 97% used [0x00000007bec00000,0x00000007bfa8d210,0x00000007bfb00000)
  from space 2560K, 56% used [0x00000007bfb00000,0x00000007bfc67f00,0x00000007bfd80000)
  to   space 2560K, 0% used [0x00000007bfd80000,0x00000007bfd80000,0x00000007c0000000)
 ParOldGen       total 40960K, used 8K [0x00000007bc400000, 0x00000007bec00000, 0x00000007bec00000)
  object space 40960K, 0% used [0x00000007bc400000,0x00000007bc402000,0x00000007bec00000)
 Metaspace       used 4153K, capacity 4688K, committed 4864K, reserved 1056768K
  class space    used 466K, capacity 502K, committed 512K, reserved 1048576K
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

经过对比发现,关闭逃逸分析后,执行时间变长,且内存占用变大,同时发生了垃圾回收。

不过,基于逃逸分析的锁消除实际上并不多见。一般来说,开发人员不会直接对方法中新构造的对象进行加锁,如上述案例所示,lockTest 方法中的加锁操作没什么意义。

事实上,逃逸分析的结果更多被用于将新建对象操作转换成栈上分配或者标量替换。

标量替换

在讲解 Java 对象的内存布局时提到过,Java 虚拟机中对象都是在堆上分配的,而堆上的内容对任何线程大都是可见的(除开 TLAB)。与此同时,Java 虚拟机需要对所分配的堆内存进行管理,并且在对象不再被引用时回收其所占据的内存。

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

但是目前 Hotspot 并没有实现真正意义上的栈上分配,而是使用了标量替换这么一项技术。

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

若一个数据已经无法再分解成更小的数据来表示了,Java虚拟机中的原始数据类型(int、long等数值类型及reference类型等)都不能再进一步分解了,那么这些数据就可以被称为标量。相对的,如果一个数据可以继续分解, 那它就被称为聚合量(Aggregate),Java
中的对象就是典型的聚合量。

标量替换这项优化技术,可以看成将原本对对象的字段的访问,替换为一个个局部变量的访问。

如下述案例所示:

public class ScalarTest {

public static double getMoney() {
Worker worker = new Worker();
worker.setMoney(100.0);
return worker.getMoney() + 20;
}

public static void main(String[] args) {
getMoney();
}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

经过逃逸分析,Worker 对象未逃逸出 getMoney()的调用,因此可以对聚合量 worker 进行分解,得到局部变量 money,进行标量替换后的伪代码:

public class ScalarTest {

public static double getMoney() {
double money = 100.0;
return money + 20;
}

public static void main(String[] args) {
getMoney();
}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

对象拆分后,对象的成员变量改为方法的局部变量,这些字段既可以存储在栈上,也可以直接存储在寄存器中。标量替换因为不必创建对象,减轻了垃圾回收的压力。

另外,可以手动通过-XX:+EliminateAllocations可以开启标量替换(默认是开启的), -XX:+PrintEliminateAllocations(同样需要debug版本的JDK)查看标量替换情况。

栈上分配

故名思议就是在栈上分配对象,其实目前 Hotspot 并没有实现真正意义上的栈上分配,实际上是标量替换。

在一般情况下,对象和数组元素的内存分配是在堆内存上进行的。但是随着 JIT 编译器的日渐成熟,很多优化使这种分配策略并不绝对。JIT编译器就可以在编译期间根据逃逸分析的结果,来决定是否需要创建对象,是否可以将堆内存分配转换为栈内存分配。

部分逃逸分析

C2 的逃逸分析与控制流无关,相对来说比较简单。Graal 则引入了一个与控制流有关的逃逸分析,名为部分逃逸分析(partial escape analysis)。它解决了所新建的实例仅在部分程序路径中逃逸的情况。

如下代码所示:

public static void bar(boolean cond) {
  Object foo = new Object();
  if (cond) {
    foo.hashCode();
  }
}
// 可以手工优化为:
public static void bar(boolean cond) {
  if (cond) {
    Object foo = new Object();
    foo.hashCode();
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

假设 if 语句的条件成立的可能性只有 1%,那么在 99% 的情况下,程序没有必要新建对象。其手工优化的版本正是部分逃逸分析想要自动达到的成果。

部分逃逸分析将根据控制流信息,判断出新建对象仅在部分分支中逃逸,并且将对象的新建操作推延至对象逃逸的分支中。这将使得原本因对象逃逸而无法避免的新建对象操作,不再出现在只执行 if-else 分支的程序路径之中。

我们通过一个完整的测试案例来间接验证这一优化。

public class PartialEscapeTest {
  long placeHolder0;
  long placeHolder1;
  long placeHolder2;
  long placeHolder3;
  long placeHolder4;
  long placeHolder5;
  long placeHolder6;
  long placeHolder7;
  long placeHolder8;
  long placeHolder9;
  long placeHoldera;
  long placeHolderb;
  long placeHolderc;
  long placeHolderd;
  long placeHoldere;
  long placeHolderf;

public static void foo(boolean flag) {
PartialEscapeTest o = new PartialEscapeTest();
if (flag) {
o.hashCode();
}
}

public static void main(String[] args) {
for (int i = 0; i < 1000000; i++) {
foo(false);
}
}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

本次测试选用的是 JDK11,开启 Graal 编译器需要配置如下参数:

-XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+UseJVMCICompiler
  • 1

分别输出使用 C2 编译器或 Graal 编译器的 GC 日志,对应命令为:

java -Xlog:gc* PartialEscapeTest
java -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+UseJVMCICompiler -Xlog:gc* PartialEscapeTest
  • 1
  • 2

通过对比 GC 日志可以发现内存占用情况不一致,Graal 编译器下内存占用更小一点。

C2

[0.012s][info][gc,heap] Heap region size: 1M
[0.017s][info][gc     ] Using G1
[0.017s][info][gc,heap,coops] Heap address: 0x0000000700000000, size: 4096 MB, Compressed Oops mode: Zero based, Oop shift amount: 3
[0.345s][info][gc,heap,exit ] Heap
[0.345s][info][gc,heap,exit ]  garbage-first heap   total 262144K, used 21504K [0x0000000700000000, 0x0000000800000000)
[0.345s][info][gc,heap,exit ]   region size 1024K, 18 young (18432K), 0 survivors (0K)
[0.345s][info][gc,heap,exit ]  Metaspace       used 6391K, capacity 6449K, committed 6784K, reserved 1056768K
[0.345s][info][gc,heap,exit ]   class space    used 552K, capacity 571K, committed 640K, reserved 1048576K
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

Graal

[0.019s][info][gc,heap] Heap region size: 1M
[0.025s][info][gc     ] Using G1
[0.025s][info][gc,heap,coops] Heap address: 0x0000000700000000, size: 4096 MB, Compressed Oops mode: Zero based, Oop shift amount: 3
[0.611s][info][gc,start     ] GC(0) Pause Young (Normal) (G1 Evacuation Pause)
[0.612s][info][gc,task      ] GC(0) Using 6 workers of 10 for evacuation
[0.615s][info][gc,phases    ] GC(0)   Pre Evacuate Collection Set: 0.0ms
[0.615s][info][gc,phases    ] GC(0)   Evacuate Collection Set: 3.1ms
[0.615s][info][gc,phases    ] GC(0)   Post Evacuate Collection Set: 0.2ms
[0.615s][info][gc,phases    ] GC(0)   Other: 0.6ms
[0.615s][info][gc,heap      ] GC(0) Eden regions: 24->0(150)
[0.615s][info][gc,heap      ] GC(0) Survivor regions: 0->3(3)
[0.615s][info][gc,heap      ] GC(0) Old regions: 0->4
[0.615s][info][gc,heap      ] GC(0) Humongous regions: 5->5
[0.615s][info][gc,metaspace ] GC(0) Metaspace: 8327K->8327K(1056768K)
[0.615s][info][gc           ] GC(0) Pause Young (Normal) (G1 Evacuation Pause) 29M->11M(256M) 3.941ms
[0.615s][info][gc,cpu       ] GC(0) User=0.01s Sys=0.01s Real=0.00s
Cannot use JVMCI compiler: No JVMCI compiler found
[0.616s][info][gc,heap,exit ] Heap
[0.616s][info][gc,heap,exit ]  garbage-first heap   total 262144K, used 17234K [0x0000000700000000, 0x0000000800000000)
[0.616s][info][gc,heap,exit ]   region size 1024K, 9 young (9216K), 3 survivors (3072K)
[0.616s][info][gc,heap,exit ]  Metaspace       used 8336K, capacity 8498K, committed 8832K, reserved 1056768K
[0.616s][info][gc,heap,exit ]   class space    used 768K, capacity 802K, committed 896K, reserved 1048576K
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

查看 Graal 在 JDK11 上的编译结果,可以执行下述命令:

java -XX:+PrintCompilation -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI -XX:+UseJVMCICompiler -cp /Users/xxx/IdeaProjects/java_deep_learning/src/main/java/com/msdn/java/javac/escape ScalarTest > out-jvmci.txt
  • 1

总结

本文介绍了 Java 虚拟机中即时编译器的逃逸分析,以及基于逃逸分析的优化:同步消除、标量替换和栈上分配。另外还扩展了解了一下 Graal 编译器下的部分逃逸分析。

参考文献

极客时间 郑雨迪 《深入拆解Java虚拟机》 逃逸分析

文章知识点与官方知识档案匹配,可进一步学习相关知识
Java技能树首页概览98144 人正在系统学习中

与[转帖]JVM系列之:关于逃逸分析的学习相似的内容:

[转帖]JVM系列之:关于逃逸分析的学习

本文为《深入学习 JVM 系列》第十九篇文章 上文讲解完方法内联后,JIT 即时编译还有一个最前沿的优化技术:逃逸分析(Escape Analysis) 。废话少说,我们直接步入正题吧。 逃逸分析 首先我们需要知道,逃逸分析并不是直接的优化手段,而是通过动态分析对象的作用域,为其它优化手段提供依据的

[转帖]JVM系列之:关于即时编译器的那些事

本文为《深入学习 JVM 系列》第十六篇文章 我们在前文学习 Java 是如何执行的这篇文章中有提及即时编译器,这是一项用来提升应用程序运行效率的技术。通常而言,代码会先被 Java 虚拟机解释执行,之后反复执行的热点代码则会被即时编译成为机器码,直接运行在底层硬件之上。 那么问题来了,既然在 Ho

[转帖]JVM系列之:你知道Java有多少种内存溢出吗

本文为《深入学习 JVM 系列》第二十五篇文章 Java内存区域 关于这部分内容大多来源于《深入理解Java虚拟机》一书。 Java 运行时数据区域(JDK8)如下图所示: 关于上述提到的线程共享和线程隔离区域,下图做详细讲解: 程序计数器 程序计数器是一块较小的内存空间,可以看作是当前线程所执行的

[转帖]【JVM】关于 JVM,你需要掌握这些 | 一文彻底吃透 JVM 系列

【JVM】关于 JVM,你需要掌握这些 | 一文彻底吃透 JVM 系列 作者:冰河 2022-11-04 四川 本文字数:13519 字 阅读完需:约 44 分钟 写在前面 最近,一直有小伙伴让我整理下关于 JVM 的知识,经过十几天的收集与整理,初版算是整理出来了。希望对大家有所帮助。 JDK 是

[转帖]万字详解 JVM,让你一文吃透

https://my.oschina.net/u/4526289/blog/5588880 摘要:本文将带大家详细地了解关于 JVM 的一些知识点。 本文分享自华为云社区《【JVM】关于 JVM,你需要掌握这些 | 一文彻底吃透 JVM 系列》,作者: 冰 河 。 JDK 是什么? JDK 是用于支

[转帖]JVM系列之:深入学习方法内联

在前面多篇文章中多次提到方法内联,作为编译器最重要的优化技术,该技术不仅可以消除调用本身带来的性能开销,还能够触发更多的优化。本文将带领大家对该技术一探究竟。 方法内联 方法内联指的是:在编译过程中遇到方法调用时,将目标方法的方法体纳入编译范围之中,并取代原方法调用的优化手段。 以 getter/s

[转帖]JVM系列之:GC调优基础以及初识jstat命令

本文为《深入学习 JVM 系列》第二十二篇文章 影响垃圾收集性能有三个主要的属性,垃圾收集调优又有三个基本原则,以及垃圾收集调优时需要采集的信息。如果想要对垃圾收集进行调优,则需要根据实际场景对不同属性做出取舍,理解调优的原则以及收集什么信息。 性能属性 吞吐量 吞吐量是评价垃圾收集器能力的重要指标

[转帖]JVM系列之:深入学习方法内联

https://zhuanlan.zhihu.com/p/487044559 在前面多篇文章中多次提到方法内联,作为编译器最重要的优化技术,该技术不仅可以消除调用本身带来的性能开销,还能够触发更多的优化。本文将带领大家对该技术一探究竟。 方法内联 方法内联指的是:在编译过程中遇到方法调用时,将目标方

[转帖]小师妹学JVM之:深入理解JIT和编译优化-你看不懂系列

https://www.jianshu.com/p/3ad764e97b2a 简介 小师妹已经学完JVM的简单部分了,接下来要进入的是JVM中比较晦涩难懂的概念,这些概念是那么的枯燥乏味,甚至还有点惹人讨厌,但是要想深入理解JVM,这些概念是必须的,我将会尽量尝试用简单的例子来解释它们,但一定会有人

[转帖]JVM 系列 - 内存区域

一、对象在JVM中的表示: OOP-Klass模型 https://www.jianshu.com/p/424a920771a3 写的很赞。 注意:OOP-Klass是hotspot的JVM实现原理,其他JVM的实现可能不一样。、 OOP表示java实例,Klass表示class。 Klass: 包