这段 Java 代码中的局部变量能够被提前回收吗?编译器或 VM 能够实现如下的人工优化吗?

public static void main(){ LargeObject lo = new LargeObject(); lo.doSomeThing(); while(true){ whatever(); } } 上面代码中的 lo 只是一次性使用,没有其他线程和对象引用。 lo 指向的对象能够在在循环过程中被回收吗? 前面的代码人工优化的结果是下面两种,编译器或VM能够实现这样的优化么?什么样的编译器优化技术能够实现? public static void main(){ LargeObject lo = new LargeObject(); lo.doSomeThing()…
关注者
291
被浏览
19163
以前写过一些,请参考传送门:答复: HotSpot VM 内存堆的两个Survivor区
下面稍微新写点字吧。不知道有多少人能耐心读完,反正我先写着 >_<

===================================================

简单回答题主的问题:
lo 指向的对象能够在在循环过程中被回收吗?
能。JVM规范没有说不能,所以具体实现有选择的自由。主流JVM在做完从Java字节码到机器码的编译后,都能做适当的优化来让题主例中的'lo'变量所指向的对象可以被回收。

做到这种效果的编译优化技术叫做“活跃分析”(liveness analysis)。一个变量只有被使用的地方才是“活跃”的;如果没有(继续)被使用,那么一个变量就“死”掉了。例子请参考这个传送门:如果变量在后面的代码中不再被引用, 在生存期内, 它的寄存器可以被编译器挪为他用吗? - RednaxelaFX 的回答
活跃分析是一种any-path backward data-flow analysis——信息是从后向前传播的,变量只要在任意路径上是活的那就是活的。

那么JVM的(JIT)编译器在做了活跃分析优化之后,是如何让GC知道某个引用类型的局部变量已经不重要了呢?很简单,通过一种叫做“GC map”的数据结构。请参考这个传送门:找出栈上的指针/引用

以HotSpot VM为具体例子看题主的代码例子。HotSpot VM里,解释执行的方法可以在任意字节码边界上进入GC,但JIT编译后的代码并不能在“任意位置”进入GC。可以进入GC的“特定位置”叫做“GC safepoint”,或者简称“safepoint”。这些位置是:
  • 主动safepoint:由方法里的代码通过主动轮询去发现需要进入safepoint。有两种情况:
    • 循环回跳处(loop backedge)
    • 临返回处(return)
  • 被动safepoint:调用别的方法的调用点。之所以叫做“被动”是因为并不是该方法主动发现要进入safepoint的,而是某个被调用的方法主动进入了safepoint,导致其整条调用链上的调用者都被动的进入了safepoint。
回头看题主的例子,假设代码里的调用点都没有被内联的话:
public static void main() {
  LargeObject lo = new LargeObject(); // safepoint 1/2: 被动safepoint
  lo.doSomeThing();                   // safepoint 3: 被动safepoint

  while (true) {
    whatever();                       // safepoint 4: 被动safepoint

    // 由于循环体里面有未内联的方法调用,也就是说已经有被动safepoint,HotSpot会优化掉循环回跳位置的主动safepoint。假设没有被优化掉的话,此处会有:
    // safepoint 5: 主动safepoint:循环回跳轮询(backedge poll)
  }
  // 上面循环是无限循环,所以下面如果有代码都属于不可到达的代码(unreachable code)

  // 如果上面的循环不是无限循环的话,则会有:
  // safepoint 6: 主动safepoint:返回前轮询(return poll)
}
简单讲解这6个safepoint分别是什么:
  • safepoint 1是由new带来的safepoint。new字节码要做的事情是给新对象分配空间,在HotSpot VM的JIT编译过的代码里,分配空间分快速路径和慢速路径两边:
    • 快速路径是通过TLAB分配,整个快速路径内联在JIT编译的代码里,不需要做额外的方法调用。如果能成功从TLAB分配到足够空间,则执行完快速路径就好了,否则会进一步进入慢速路径。
    • 慢速路径会调用到JVM runtime里,做若干不同尝试来分配空间。这会做一个VM runtime call,有一个函数调用——safepoint 1就在这个调用的位置上。VM里实现慢速路径的逻辑会尝试分配新的TLAB,或者触发GC清理掉无用对象后再尝试分配空间。由于做GC的决定并不是main()方法自己发现,而是调用进VM runtime里由JVM决定的,所以这个safepoint算被动而不是主动safepoint。
  • safepoint 2是由构造器调用的invokespecial带来的被动safepoint。要留意,Java语言层面的new Object()在字节码里是两条指令,new和invokespecial——new只负责分配空间和做默认初始化,invokespecial才会调用构造器。请参考传送门:实例构造器是不是静态方法?
  • safepoint 3、4都是方法调用的被动safepoint。跟safepoint 2一样,都是调用别的Java方法的调用点上有可能被动进入safepoint。例如说,如果main()方法本身在主动执行的时候并没有发现要进safepoint,而在lo.doSomeThing()执行的时候后者主动发现要进入safepoint的话,那调用者main()也必须进入safepoint,允许GC扫描其栈帧里的引用类型的局部变量。
  • safepoint 5是在循环末尾要跳回到循环开头处(backedge)的主动safepoint。这里HotSpot VM会生成代码检测VM是否发出了通知说要进入GC了,如果有通知的话就会在这个位置进入GC。
  • safepoint 6是在临返回前(return)的主动safepoint。跟上一个一样,也会有代码主动去检测是否要GC。

再多废话几句:大家都知道System.gc()可以让Java代码主动触发GC,但从HotSpot VM的角度看,讨论safepoint与OopMap时,System.gc()的调用点其实是个“被动safepoint”。
专门指出这个是为了避免读者误会这里说的主动/被动。强调一下,这里说的:
  • 主动:有显式代码轮询是否要GC;
  • 被动:在一个未内联的调用点,为被调用方法可能进入GC而做好准备,调用方并没有主动轮询是否要GC的代码。

既然只有这些safepoint的地方可能进入GC,就JVM只需要在这些地方提高足够信息让GC知道栈帧里什么位置有引用类型的局部变量。在HotSpot VM里,这种“信息”——前面说的“GC map”——通过名为OopMap的数据结构记录。关于OopMap具体是个怎样的东西,请参考前面提到过的传送门:找出栈上的指针/引用

回到题主的例子,6个safepoint对应的OopMap信息分别会是:
public static void main() {
  LargeObject lo = new LargeObject(); // safepoint 1/2: 被动safepoint
  // OopMap 1: 空。
  // 该栈帧里尚未有任何引用类型的局部变量是活跃的——new还没执行完
  // OopMap 2: 记录了一个变量:刚分配的对象的引用是活的,在OopMap里;不过这还不是局部变量lo而是求值栈上的一个slot
  lo.doSomeThing();                   // safepoint 3: 被动safepoint
  // OopMap 3: 空。局部变量lo的最后一次使用是作为上面方法的"this"参数传递出去;
  // 维持"this"的存活是被调用方法的责任而不是调用方法的责任。此后局部变量lo再也没有被使用过,所以对main()来说lo在此处已死。

  while (true) {
    whatever();                       // safepoint 4: 被动safepoint
    // OopMap 4: 空。

    // safepoint 5: 主动safepoint:循环回跳轮询(backedge poll)
    // OopMap 5: 空。
  }
  // 上面循环是无限循环,所以下面如果有代码都属于不可到达的代码(unreachable code)

  // 如果上面的循环不是无限循环的话,则:
  // safepoint 6: 主动safepoint:返回前轮询(return poll)
  // OopMap 6: 空
}
这样,编译器通过活跃分析得知lo具体被使用的范围,就可以向OopMap填入相应的信息,让局部变量'lo'只在需要存活的时候被GC扫描到——只要OopMap没有记录它的存在,GC就不会扫描它,而它所引用的对象就不会因为'lo'这个局部变量而被认为是活的,因而有机会被回收(假如没有其它活引用继续指向那个LargeObject对象的话。)

为了证明以上描述不是忽悠,下面给出HotSpot VM在上例各safepoint具体计算出的OopMap情况。测试代码、具体方法和完整日志附在本回答的最后,仅供参考。

safepoint 1:
087   	call,static  wrapper for: _new_instance_Java
        # LargeObject::main @ bci:0  L[0]=_
        # OopMap{off=140}
这里L[0]=_的意思是局部变量区slot 0还没有有效值(以'_'表示)。
OopMap里没有记录任何Java代码中能看到的引用,所以上面描述为“空”是正确的。off=140指的是该OopMap关联的指令在生成代码中的相对方法起始地址的偏移量。

safepoint 2:
05b   	call,static  LargeObject::<init>
        # LargeObject::main @ bci:4  L[0]=_ STK[0]=RBP
        # OopMap{rbp=Oop off=96}
这里L[0]仍然为'_',因为刚创建的对象的引用尚未赋值给局部变量'lo'。但是有一个活的引用需要记录在OopMap里:STK[0]是求值栈的栈顶,此时持有新创建的对象的引用。
OopMap里记录下了rbp=Oop,就是说rbp寄存器此时持有一个GC需要扫描的对象引用。

safepoint 3:
063   	call,static  LargeObject::doSomeThing
        # LargeObject::main @ bci:9  L[0]=_
        # OopMap{off=104}
不用多解释了。对JIT编译器来说,局部变量'lo'的生命期到此已结束,因而L[0]又是'_'。OopMap又是空的。

safepoint 4:
073   	call,static  LargeObject::whatever
        # LargeObject::main @ bci:12  L[0]=_
        # OopMap{off=120}
跟上一个safepoint一样。

safepoint 5和6实际并不存在,所以也没有对应的日志。

===================================================

上面的描述跟题主后面举的两种“优化”形式的代码例子其实都不一样。
public static void main() {
  LargeObject lo = new LargeObject();
  lo.doSomeThing();
  lo = null;

  while (true) {
    whatever();
  }
}
HotSpot VM的JIT编译器做的优化,硬要说的话效果跟这个类似。这个是显式把局部变量'lo'置为null从而切断其对LargeObject对象的引用,而实际发生的状况是JIT编译器在doSomething()调用之后就不把局部变量'lo'包含在OopMap里了,于是GC根本看不到这个变量,也不关心它引用了谁,自然的切断了引用。

这个显式置null版本的Java代码,在实际运行中能够可靠的切断局部变量'lo'对对象的引用,其实有时候还是可以推荐用的——虽然它对JIT编译的代码不会有任何额外好处——但并不是所有Java方法都会被JIT编译。如果有个Java方法没有被JIT编译但里面仍然有代码会执行比较长时间,那么在那段会执行长时间的代码前显式将不需要的引用类型局部变量置null是可取的,可以谨慎使用。

===================================================

而后面一个版本:
public static void main() {
  {
    LargeObject lo = new LargeObject();
    lo.doSomeThing();
  }

  while (true) {
    whatever();
  }
}
从Java语言层面的作用域看似乎跟前面说的“活跃分析”达到了一样的效果——限制了局部变量'lo'的可见范围。但在实际执行的层面上这里却有个陷阱:
JVM规范并没有对GC的行为、JVM与GC的交互做多少规定,也没有讨论过局部变量在离开作用域之后JVM要如何处理它。这些都给JVM的实现留下了自由度——换句话说,JVM实现并没有义务在一个引用类型的局部变量离开作用域之后把它置为null。

这就允许了HotSpot VM在解释模式里有略为奇怪的表现。请看下面例子:
public class TestGC {
  public static void main(String[] args) {
    {
      // 'o' in local variable slot 1
      Object o = new byte[4*1024*1024];
      o.hashCode();
    }
    // o is out of scope now, but...
    System.gc(); // the 4M byte[] is kept alive in this GC
    {
      // 'i' in local variable slot 1
      int i = 0; // "null out" local variable slot 1
    }
    System.gc(); // the 4M byte[] is freed in this GC
  }
}

用Oracle JDK8来运行它:
$ java -version
java version "1.8.0"
Java(TM) SE Runtime Environment (build 1.8.0-b132)
Java HotSpot(TM) 64-Bit Server VM (build 25.0-b70, mixed mode)
$ java -XX:+PrintGCDetails -XX:+UseSerialGC -Xmx20m -Xmn10m TestGC
[Full GC (System.gc()) [Tenured: 0K->4530K(10240K), 0.0041590 secs] 5098K->4530K(19456K), [Metaspace: 2716K->2716K(1056768K)], 0.0041940 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[Full GC (System.gc()) [Tenured: 4530K->434K(10240K), 0.0014000 secs] 4530K->434K(19456K), [Metaspace: 2716K->2716K(1056768K)], 0.0014180 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 9216K, used 246K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
  eden space 8192K,   3% used [0x00000007bec00000, 0x00000007bec3d8e0, 0x00000007bf400000)
  from space 1024K,   0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)
  to   space 1024K,   0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000)
 tenured generation   total 10240K, used 434K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
   the space 10240K,   4% used [0x00000007bf600000, 0x00000007bf66c928, 0x00000007bf66ca00, 0x00000007c0000000)
 Metaspace       used 2726K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 298K, capacity 386K, committed 512K, reserved 1048576K
可以看到第一次调用System.gc()的时候,例子里创建的4M大的byte[]对象并没有被GC掉,而是晋升到old gen了。
此时明明已经离开了局部变量'o'的作用域,而那个4M大的byte[]只被这个局部变量引用,为何HotSpot VM的GC没能回收它?
——因为HotSpot VM的解释器与GC之间的交互有点傻:当被解释执行的方法需要被GC时,计算OopMap的逻辑并没有彻底计算变量的liveness,导致已经无用的局部变量的生命期可能超过其在源码中的作用域范围。

看这个例子对应的字节码:
  public static void main(java.lang.String[]);
    Code:
      stack=1, locals=2, args_size=1
         0: ldc           #2                  // int 4194304
         2: newarray       byte
         4: astore_1      
         5: aload_1       
         6: invokevirtual #3                  // Method java/lang/Object.hashCode:()I
         9: pop           
        10: invokestatic  #4                  // Method java/lang/System.gc:()V
        13: iconst_0      
        14: istore_1      
        15: invokestatic  #4                  // Method java/lang/System.gc:()V
        18: return        
      LineNumberTable:
        line 4: 0
        line 5: 5
        line 7: 10
        line 9: 13
        line 11: 15
        line 12: 18
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               5       5     1     o   Ljava/lang/Object;
              15       0     1     i   I
               0      19     0  args   [Ljava/lang/String;
然后通过在一个debug build的HotSpot VM上用 -XX:+TraceNewOopMapGenerationDetailed 参数来跟踪解释器计算OopMap的结果:
java -XX:+TraceNewOopMapGenerationDetailed -XX:+PrintGCDetails -XX:+UseSerialGC -Xmx20m -Xmn10m TestGC
相关日志:
static void TestGC.main(jobject)

        0 vars     = ( r  |slot0)(   u|Top)    ldc
          stack    = 
          monitors = 
        2 vars     = ( r  |slot0)(   u|Top)    newarray
          stack    = (  v |Top)
          monitors = 
        4 vars     = ( r  |slot0)(   u|Top)    astore_1
          stack    = ( r  |line2)
          monitors = 
        5 vars     = ( r  |slot0)( r  |line2)    aload_1
          stack    = 
          monitors = 
        6 vars     = ( r  |slot0)( r  |line2)    invokevirtual()I
          stack    = ( r  |line2)
          monitors = 
        9 vars     = ( r  |slot0)( r  |line2)    pop
          stack    = (  v |Top)
          monitors = 
       10 vars     = ( r  |slot0)( r  |line2)    invokestatic()V
          stack    = 
          monitors = 
       13 vars     = ( r  |slot0)( r  |line2)    iconst_0
          stack    = 
          monitors = 
       14 vars     = ( r  |slot0)( r  |line2)    istore_1
          stack    = (  v |Top)
          monitors = 
       15 vars     = ( r  |slot0)(  v |Top)    invokestatic()V
          stack    = 
          monitors = 
       18 vars     = ( r  |slot0)(  v |Top)    return
          stack    = 
          monitors = 
其中这行日志:
10 vars     = ( r  |slot0)( r  |line2)    invokestatic()V
说明这个计算OopMap的逻辑认为在第一次调用System.gc()的地方,局部变量区的slot 0(String[] args)与slot 1(Object o)都还是活的引用类型变量(日志中的标记'r'表示reference)。前面提到了,HotSpot VM并不会在局部变量离开作用域之后对其做显式的清理动作,所以此时slot 1里的值还是指向4M byte[]的引用,导致该数组对象在第一次调用System.gc()时没能被回收。
而第二次调用System.gc()之前,例子通过对复用slot 1的int i变量赋值来达到了“清理引用”的效果,到真正第二次调用System.gc()的地方,计算出来的OopMap是:
15 vars     = ( r  |slot0)(  v |Top)    invokestatic()V
这里就只有slot 0还是活引用(标记'r')了,slot 1变为了GC不关心的内容(标记'v'表示void)。

上面例子演示了HotSpot VM在解释执行方法时可能会超越局部变量的静态作用域持有无用的引用,从而有可能隐式延长了对象的存活时间。这恐怕是题主以及许多用HotSpot VM跑Java程序的同学意想不到的行为吧。

===================================================

附录

测试代码:
public class LargeObject {
  public void doSomeThing() { }
  public static void whatever() { }

  public static void main() {
    LargeObject lo = new LargeObject();
    lo.doSomeThing();

    while (true) {
      whatever();
    }
  }

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

Java版本:Mac OS X上的Oracle JDK8 / OpenJDK8
java -version
java version "1.8.0"
Java(TM) SE Runtime Environment (build 1.8.0-b132)
OpenJDK 64-Bit Server VM (build 25.0-b66-internal-fastdebug, mixed mode)
命令行参数:
java -XX:+PrintOptoAssembly -XX:-TieredCompilation -Xcomp -XX:CompileCommand=compileonly,LargeObject,main -XX:CompileCommand=dontinline,LargeObject,main -XX:+PrintAssembly LargeObject

-XX:+PrintOptoAssembly的日志:
{method}
 - this oop:          0x0000000119686458
 - method holder:     'LargeObject'
 - constants:         0x0000000119686060 constant pool [35] {0x0000000119686060} for 'LargeObject' cache=0x0000000119686580
 - access:            0xc1000009  public static 
 - name:              'main'
 - signature:         '()V'
 - max stack:         3
 - max locals:        1
 - size of params:    0
 - method size:       12
 - vtable index:      -2
 - i2i entry:         0x000000010c7f16e0
 - adapters:          AHE@0x00007fad0a8600c8: 0x i2c: 0x000000010c8d9820 c2i: 0x000000010c8d9930 c2iUV: 0x000000010c8d9903
 - compiled entry     0x000000010c8d9930
 - code size:         18
 - code start:        0x0000000119686430
 - code end (excl):   0x0000000119686442
 - method data:       0x00000001196866f0
 - checked ex length: 0
 - linenumber start:  0x0000000119686442
 - localvar length:   1
 - localvar start:    0x000000011968644a
#
#  void (  )
#
# -- Old rsp -- Framesize: 32 --
#r191 rsp+28: in_preserve
#r190 rsp+24: return address
#r189 rsp+20: in_preserve
#r188 rsp+16: saved fp register
#r187 rsp+12: pad2, stack alignment
#r186 rsp+ 8: pad2, stack alignment
#r185 rsp+ 4: Fixed slot 1
#r184 rsp+ 0: Fixed slot 0
#
abababab   N1: #	B1 <- B13  Freq: 1
abababab
000   B1: #	B7 B2 <- BLOCK HEAD IS JUNK   Freq: 1
000   	# stack bang
	pushq   rbp	# Save rbp
	subq    rsp, #16	# Create frame

00c   	# TLS is in R15
00c   	movq    RAX, [R15 + #120 (8-bit)]	# ptr
010   	movq    R10, RAX	# spill
013   	addq    R10, #16	# ptr
017   	cmpq    R10, [R15 + #136 (32-bit)]	# raw ptr
01e   	jnb,us  B7  P=0.000100 C=-1.000000
01e
020   B2: #	B3 <- B1  Freq: 0.9999
020   	movq    [R15 + #120 (8-bit)], R10	# ptr
024   	PREFETCHNTA [R10 + #192 (32-bit)]	# Prefetch allocation to non-temporal cache for write
02c   	movl    R11, narrowklass: precise klass LargeObject: 0x00007fad0a8b7a08:Constant:exact *	# compressed klass ptr
032   	decode_klass_not_null R10,R11
040   	movq    R10, [R10 + #176 (32-bit)]	# ptr
047   	movq    [RAX], R10	# ptr
04a   	movl    [RAX + #8 (8-bit)], narrowklass: precise klass LargeObject: 0x00007fad0a8b7a08:Constant:exact *	# compressed klass ptr
051   	movl    [RAX + #12 (8-bit)], R12	# int (R12_heapbase==0)
051
055   B3: #	B11 B4 <- B8 B2  Freq: 1
055   	
055   	movq    RBP, RAX	# spill
058   	# checkcastPP of RBP
058   	movq    RSI, RBP	# spill
05b   	call,static  LargeObject::<init>
        # LargeObject::main @ bci:4  L[0]=_ STK[0]=RBP
        # OopMap{rbp=Oop off=96}
060
060   B4: #	B10 B5 <- B3  Freq: 0.99998
        # Block is sole successor of call
060   	movq    RSI, RBP	# spill
063   	call,static  LargeObject::doSomeThing
        # LargeObject::main @ bci:9  L[0]=_
        # OopMap{off=104}
      	nop 	# 8 bytes pad for loops and calls

070   B5: #	B12 B6 <- B4 B6 	Loop: B5-B6 inner  Freq: 99996
      	nop 	# 3 bytes pad for loops and calls
073   	call,static  LargeObject::whatever
        # LargeObject::main @ bci:12  L[0]=_
        # OopMap{off=120}
078
078   B6: #	B5 <- B5  Freq: 99994
        # Block is sole successor of call
078   	jmp,s   B5
078
07a   B7: #	B9 B8 <- B1  Freq: 0.000100017
07a   	movq    RSI, precise klass LargeObject: 0x00007fad0a8b7a08:Constant:exact *	# ptr
      	nop 	# 3 bytes pad for loops and calls
087   	call,static  wrapper for: _new_instance_Java
        # LargeObject::main @ bci:0  L[0]=_
        # OopMap{off=140}
08c
08c   B8: #	B3 <- B7  Freq: 0.000100015
        # Block is sole successor of call
08c   	jmp,s   B3
08c
08e   B9: #	B13 <- B7  Freq: 1.00017e-09
08e   	# exception oop is in rax; no code emitted
08e   	movq    RSI, RAX	# spill
091   	jmp,s   B13
091
093   B10: #	B13 <- B4  Freq: 9.9998e-06
093   	# exception oop is in rax; no code emitted
093   	movq    RSI, RAX	# spill
096   	jmp,s   B13
096
098   B11: #	B13 <- B3  Freq: 1e-05
098   	# exception oop is in rax; no code emitted
098   	movq    RSI, RAX	# spill
09b   	jmp,s   B13
09b
09d   B12: #	B13 <- B5  Freq: 0.99996
09d   	# exception oop is in rax; no code emitted
09d   	movq    RSI, RAX	# spill
09d
0a0   B13: #	N1 <- B9 B11 B10 B12  Freq: 0.99998
0a0   	addq    rsp, 16	# Destroy frame
	popq   rbp

0a5   	jmp     rethrow_stub
0a5
-XX:+PrintAssembly的日志:
Decoding compiled method 0x000000010c938950:
Code:
[Entry Point]
[Verified Entry Point]
[Constants]
  # {method} {0x0000000119686458} 'main' '()V' in 'LargeObject'
  #           [sp+0x20]  (sp of caller)
  ;; N1: #	B1 <- B13  Freq: 1

  ;; B1: #	B7 B2 <- BLOCK HEAD IS JUNK   Freq: 1

  0x000000010c938ac0: mov    %eax,-0x16000(%rsp)
  0x000000010c938ac7: push   %rbp
  0x000000010c938ac8: sub    $0x10,%rsp         ;*synchronization entry
                                                ; - LargeObject::main@-1 (line 6)

  0x000000010c938acc: mov    0x78(%r15),%rax
  0x000000010c938ad0: mov    %rax,%r10
  0x000000010c938ad3: add    $0x10,%r10
  0x000000010c938ad7: cmp    0x88(%r15),%r10
  0x000000010c938ade: jae    0x000000010c938b3a
  ;; B2: #	B3 <- B1  Freq: 0.9999

  0x000000010c938ae0: mov    %r10,0x78(%r15)
  0x000000010c938ae4: prefetchnta 0xc0(%r10)
  0x000000010c938aec: mov    $0xf800c006,%r11d  ;   {metadata('LargeObject')}
  0x000000010c938af2: movabs $0x0,%r10
  0x000000010c938afc: lea    (%r10,%r11,8),%r10
  0x000000010c938b00: mov    0xb0(%r10),%r10
  0x000000010c938b07: mov    %r10,(%rax)
  0x000000010c938b0a: movl   $0xf800c006,0x8(%rax)  ;   {metadata('LargeObject')}
  0x000000010c938b11: mov    %r12d,0xc(%rax)
  ;; B3: #	B11 B4 <- B8 B2  Freq: 1

  0x000000010c938b15: mov    %rax,%rbp          ;*new  ; - LargeObject::main@0 (line 6)

  0x000000010c938b18: mov    %rbp,%rsi
  0x000000010c938b1b: callq  0x000000010c8d8de0  ; OopMap{rbp=Oop off=96}
                                                ;*invokespecial <init>
                                                ; - LargeObject::main@4 (line 6)
                                                ;   {optimized virtual_call}
  ;; B4: #	B10 B5 <- B3  Freq: 0.99998

  0x000000010c938b20: mov    %rbp,%rsi
  0x000000010c938b23: callq  0x000000010c8d8de0  ; OopMap{off=104}
                                                ;*invokevirtual doSomeThing
                                                ; - LargeObject::main@9 (line 7)
                                                ;   {optimized virtual_call}
  0x000000010c938b28: nop
  0x000000010c938b29: nop
  0x000000010c938b2a: nop
  0x000000010c938b2b: nop
  0x000000010c938b2c: nop
  0x000000010c938b2d: nop
  0x000000010c938b2e: nop
  0x000000010c938b2f: nop
  ;; B5: #	B12 B6 <- B4 B6 	Loop: B5-B6 inner  Freq: 99996

  0x000000010c938b30: nop
  0x000000010c938b31: nop
  0x000000010c938b32: nop
  0x000000010c938b33: callq  0x000000010c8d91e0  ; OopMap{off=120}
                                                ;*invokestatic whatever
                                                ; - LargeObject::main@12 (line 10)
                                                ;   {static_call}
  ;; B6: #	B5 <- B5  Freq: 99994

  0x000000010c938b38: jmp    0x000000010c938b30
  ;; B7: #	B9 B8 <- B1  Freq: 0.000100017

  0x000000010c938b3a: movabs $0x7c0060030,%rsi  ;   {metadata('LargeObject')}
  0x000000010c938b44: nop
  0x000000010c938b45: nop
  0x000000010c938b46: nop
  0x000000010c938b47: callq  0x000000010c8d93e0  ; OopMap{off=140}
                                                ;*new  ; - LargeObject::main@0 (line 6)
                                                ;   {runtime_call}
  ;; B8: #	B3 <- B7  Freq: 0.000100015

  0x000000010c938b4c: jmp    0x000000010c938b15  ;*new
                                                ; - LargeObject::main@0 (line 6)

  ;; B9: #	B13 <- B7  Freq: 1.00017e-09

  0x000000010c938b4e: mov    %rax,%rsi
  0x000000010c938b51: jmp    0x000000010c938b60  ;*invokevirtual doSomeThing
                                                ; - LargeObject::main@9 (line 7)

  ;; B10: #	B13 <- B4  Freq: 9.9998e-06

  0x000000010c938b53: mov    %rax,%rsi
  0x000000010c938b56: jmp    0x000000010c938b60  ;*invokespecial <init>
                                                ; - LargeObject::main@4 (line 6)

  ;; B11: #	B13 <- B3  Freq: 1e-05

  0x000000010c938b58: mov    %rax,%rsi
  0x000000010c938b5b: jmp    0x000000010c938b60  ;*invokestatic whatever
                                                ; - LargeObject::main@12 (line 10)

  ;; B12: #	B13 <- B5  Freq: 0.99996

  0x000000010c938b5d: mov    %rax,%rsi          ;*new  ; - LargeObject::main@0 (line 6)

  ;; B13: #	N1 <- B9 B11 B10 B12  Freq: 0.99998

  0x000000010c938b60: add    $0x10,%rsp
  0x000000010c938b64: pop    %rbp
  0x000000010c938b65: jmpq   0x000000010c907da0  ;   {runtime_call}
  0x000000010c938b6a: hlt    
  0x000000010c938b6b: hlt    
  0x000000010c938b6c: hlt    
  0x000000010c938b6d: hlt    
  0x000000010c938b6e: hlt    
  0x000000010c938b6f: hlt    
  0x000000010c938b70: hlt    
  0x000000010c938b71: hlt    
  0x000000010c938b72: hlt    
  0x000000010c938b73: hlt    
  0x000000010c938b74: hlt    
  0x000000010c938b75: hlt    
  0x000000010c938b76: hlt    
  0x000000010c938b77: hlt    
  0x000000010c938b78: hlt    
  0x000000010c938b79: hlt    
  0x000000010c938b7a: hlt    
  0x000000010c938b7b: hlt    
  0x000000010c938b7c: hlt    
  0x000000010c938b7d: hlt    
  0x000000010c938b7e: hlt    
  0x000000010c938b7f: hlt    
[Stub Code]
  0x000000010c938b80: movabs $0x0,%rbx          ;   {no_reloc}
  0x000000010c938b8a: jmpq   0x000000010c938b8a  ;   {runtime_call}
  0x000000010c938b8f: movabs $0x0,%rbx          ;   {static_stub}
  0x000000010c938b99: jmpq   0x000000010c938b99  ;   {runtime_call}
  0x000000010c938b9e: movabs $0x0,%rbx          ;   {static_stub}
  0x000000010c938ba8: jmpq   0x000000010c938ba8  ;   {runtime_call}
[Exception Handler]
  0x000000010c938bad: jmpq   0x000000010c9076e0  ;   {runtime_call}
[Deopt Handler Code]
  0x000000010c938bb2: callq  0x000000010c938bb7
  0x000000010c938bb7: subq   $0x5,(%rsp)
  0x000000010c938bbc: jmpq   0x000000010c8d9da0  ;   {runtime_call}
  0x000000010c938bc1: hlt    
  0x000000010c938bc2: hlt    
  0x000000010c938bc3: hlt    
  0x000000010c938bc4: hlt    
  0x000000010c938bc5: hlt    
  0x000000010c938bc6: hlt    
  0x000000010c938bc7: hlt