Analysis of the New Object Process in Java

1. Write in front


"[JVM Anatomy Park][1]" is a continuously updated series of mini-blogs. It usually takes 5 to 10 minutes to read each article. Due to space limitations, only an in-depth explanation of a topic in accordance with questions, tests, benchmark procedures, and observation results. Therefore, the data and discussions here can be viewed as anecdotes, without checking for errors in writing style, syntax and semantics, duplication or consistency. If you choose to adopt the content of the article, you are at your own risk.


Aleksey Shipilёv, JVM performance geek   

推 特 [@shipilev] [2]   

Questions, comments, suggestions are sent to [[email protected]][3]


[1]:https://shipilev.net/jvm-anatomy-park

[2]:http://twitter.com/shipilev

[3]: [email protected]


2. Problem


I heard that allocation is different from initialization. Java has a constructor. Does it perform allocation or initialization?


3. Theory


If you open [GC Handbook][4], it will tell you that creating a new object usually involves three stages:


> Annotation: GC Handbook Chinese version "Garbage Collection Algorithm Handbook"


  1. "Allocation": Allocate instance data from the process space.

  2. "System Initialization": Initialize according to the Java language specification. In C language, allocating new objects does not need to be initialized; in Java, all newly created objects must undergo system initialization, assign default values, set complete object headers, and so on.

  3. "Secondary initialization (user initialization)": Execute all initialization statements and constructors associated with the object type.


We have discussed this in the previous [TLAB allocation][5], and now we will introduce the detailed initialization process. If you are familiar with Java bytecode, you will know that the `new` statement corresponds to several bytecode instructions. E.g:


```java
public Object t()
{
 return new Object();
}
```


Will be compiled as:


```java
 public java.lang.Object t();
   descriptor: ()Ljava/lang/Object;
   flags: (0x0001) ACC_PUBLIC
   Code:
     stack=2, locals=1, args_size=1
        0: new           #4                  // java/lang/Object 类
        3: dup
        4: invokespecial #1                  // java/lang/Object."<init>":()V 方法
        7: areturn
```


[4]:http://gchandbook.org/

[5]:https://shipilev.net/jvm/anatomy-quarks/4-tlab-allocation/


看起来 `new` 会执行分配和系统初始化,同时调用构造函数(`<init>`)执行用户初始化。然而,智能的 Hotspot 虚拟机会不会优化?比如在构造函数执行完成以前查看对象使用情况,优化可以合并的任务。接下来,让我们做个实验。


4. 实验


要解除这个疑问,可以编写下面这样的测试。初始化两个不同的类,每个类只包含一个 `int` 属性:


```java
import org.openjdk.jmh.annotations.*;
import java.util.concurrent.TimeUnit;

@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(value = 3)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Benchmark)
public class UserInit {

   @Benchmark
   public Object init()
{
       return new Init(42);
   }

   @Benchmark
   public Object initLeaky()
{
       return new InitLeaky(42);
   }

   static class Init {
       private int x;
       public Init(int x) {
           this.x = x;
       }
   }

   static class InitLeaky {
       private int x;
       public InitLeaky(int x) {
           doSomething();
           this.x = x;
       }

       @CompilerControl(CompilerControl.Mode.DONT_INLINE)
       void doSomething() {
           // 此处留白
       }
   }
}
```


设计测试时,为防止编译器对 `doSomething()` 空方法进行内联优化加上了限制,迫使优化程序认为接下来可能有代码访问 `x`。换句话说,这样就无法判断 `doSomething()` 是否真的泄露了对象,从而可以有效地把对象暴露给某些外部代码。


建议启用 `-XX:+UseParallelGC -XX:-TieredCompilation -XX:-UseBiasedLocking` 参数运行测试,这样生成的代码更容易理解。JMH `-prof perfasm` 参数可以完美地转储测试生成的代码。


下面是 `Init` 测试结果:


```asm
0x00007efdc466d4cc: mov    0x60(%r15),%rax          ; 下面是 TLAB 分配
0x00007efdc466d4d0: mov    %rax,%r10
0x00007efdc466d4d3: add    $0x10,%r10
0x00007efdc466d4d7: cmp    0x70(%r15),%r10
0x00007efdc466d4db: jae    0x00007efdc466d50a
0x00007efdc466d4dd: mov    %r10,0x60(%r15)
0x00007efdc466d4e1: prefetchnta 0xc0(%r10)
                                                 ; ------- /分配 ---------
                                                 ; ------- 系统初始化 ---------
0x00007efdc466d4e9: movq   $0x1,(%rax)              ; header 设置 mark word
0x00007efdc466d4f0: movl   $0xf8021bc4,0x8(%rax)    ; header 设置 class word
                                                 ; ...... 系统/用户初始化 .....
0x00007efdc466d4f7: movl   $0x2a,0xc(%rax)          ; x = 42.
                                                 ; -------- /用户初始化 ---------
```


上面生成的代码中可以看到 TLAB 分配、对象元数据初始化,然后对字段执行系统+用户初始化。`InitLeaky` 的测试结果有很大区别:


```asm
                                                 ; ------- 分配 ----------
0x00007fc69571bf4c: mov    0x60(%r15),%rax
0x00007fc69571bf50: mov    %rax,%r10
0x00007fc69571bf53: add    $0x10,%r10
0x00007fc69571bf57: cmp    0x70(%r15),%r10
0x00007fc69571bf5b: jae    0x00007fc69571bf9e
0x00007fc69571bf5d: mov    %r10,0x60(%r15)
0x00007fc69571bf61: prefetchnta 0xc0(%r10)
                                                 ; ------- /分配 ---------
                                                 ; ------- 系统初始化 ---------
0x00007fc69571bf69: movq   $0x1,(%rax)              ; header 设置 mark word
0x00007fc69571bf70: movl   $0xf8021bc4,0x8(%rax)    ; header 设置 class word
0x00007fc69571bf77: mov    %r12d,0xc(%rax)          ; x = 0 (%r12 的值恰好是 0)
                                                 ; ------- /系统初始化 --------
                                                 ; -------- 用户初始化 ----------
0x00007fc69571bf7b: mov    %rax,%rbp
0x00007fc69571bf7e: mov    %rbp,%rsi
0x00007fc69571bf81: xchg   %ax,%ax
0x00007fc69571bf83: callq  0x00007fc68e269be0       ; call doSomething()
0x00007fc69571bf88: movl   $0x2a,0xc(%rbp)          
; x = 42
                                                 ; ------ /用户初始化 ------
```


由于优化程序无法确定是否需要 `x` 值,因此这里必须假定出现最坏的情况,先执行系统初始化,然后再完成用户初始化。


5. 观察


虽然教科书的定义很完美,而且生成的字节码也提供了佐证,但只要不出现奇怪的结果,优化程序还是会做一些不为人知的优化。从编译器的角度看,这只是一种简单优化。但从概念上说,这个结果已经超出了“阶段”的范畴。



Guess you like

Origin blog.51cto.com/15082395/2590353