JVM递归类初始化实现

huangapple go评论83阅读模式
英文:

JVM recursive class initialization implementation

问题

正在研究JVM规范/内部,并希望理解循环引用递归类初始化应该如何正确进行。看看这个例子:

class CA extends Object {
    public final int ivar = 1;
    public static CB other = new CB(); 
    public CA() {
        System.out.println("in CA.init, my ivar is " + this.ivar); 
    }	
}

class CB extends Object {
    public final int ivar = 2;
    public static CA other = new CA();  
    public CB() {
        System.out.println("in CB.init, my ivar is " + this.ivar); 
    }
    
    public static void main(String[] args) {
        CB cb = new CB();  
    }
}

执行这个代码会得到:

in CB.init, my ivar is 2
in CA.init, my ivar is 1
in CB.init, my ivar is 2

这些反映了实例的初始化,是合理的。然而,的初始化必须像这样运行:

  1. CB <clinit> 实例化一个 CA,应该触发...
  2. CA <clinit>,实例化一个 CB,然后尝试...
  3. 再次运行 CB <clinit>,但此时已经在进行中...

JVM规范在第5.5节“初始化”中说:

> 3. 如果类C的Class对象表明当前线程正在为C进行初始化,则这必须是对初始化的递归请求。释放LC并正常完成。

这意味着在上述的步骤3中,JVM会耸耸肩,然后返回完成步骤2。但是完成步骤2意味着调用新CB实例上的构造函数<init>。当类CB尚未完成其<clinit>时,它怎么能做到呢?

在这种情况下,因为这些对象并未“对它们持有的每个实例做任何操作”,所以没有什么坏处。但我应该如何思考这种行为以及潜在的风险?谢谢。

英文:

Am studying JVM spec/internals, and would like to understand how circularly-referenced recursive class initialization is supposed to happen correctly. Looking at this example:

 class CA extends Object {
	public final int ivar = 1;
	public static CB other = new CB(); 
	public CA() {
		System.out.println("in CA.init, my ivar is " + this.ivar); 
	}	
}
class CB extends Object {
	public final int ivar = 2;
	public static CA other = new CA();  
	public CB() {
		System.out.println("in CB.init, my ivar is " + this.ivar); 
	}
	
    public static void main(String[] args) {
		CB cb = new CB();  
	}
}

Executing this results in:

in CB.init, my svar is 2
in CA.init, my ivar is 1
in CB.init, my svar is 2

Those reflect the instance initializations and make sense. The class inits though, must run like this:

  1. CB <clinit> instantiates a CA, which should trigger...
  2. CA <clinit>, which instantiates a CB, which attempts a
  3. CB <clinit> again, which is already in-progress...

The JVM spec says under s5.5 Initialization:

> 3. If the Class object for C indicates that initialization is in progress for C by the current thread, then this must be a recursive request for initialization. Release LC and complete normally.

This implies that at my step 3 above, the JVM shrugs, and goes back to finish step 2. But completing step 2 means calling the constructor <init> on a new CB instance. How can it do that when class CB has not completed its <clinit>?

In this case, because the objects are not "doing anything" with the instances of each other they hold, no harm no foul. But how should I be thinking about the behavior and the potential pitfalls here? Thanks.

答案1

得分: 2

这仅在这些是 静态 字段(other)的情况下起作用,如果您移除该修饰符 - 您将会得到 StackOverflow 错误(因为对于实例字段,初始化被移动到构造函数中)。我觉得如果我向您展示编译器实际在做什么,事情可能会变得明显?

static class CA extends Object {

    public final int ivar = 1;
    public static CB other;

    static {
        System.out.println("running CA static block");
        other = new CB();
        System.out.println("CB done");
    }

    public CA() {
        System.out.println("in CA.init, my ivar is " + ivar);
    }
}

static class CB extends Object {

    public final int ivar = 2;
    public static CA other;

    static {
        System.out.println("running CB static block");
        other = new CA();
        System.out.println("CA done");
    }

    public CB() {
        System.out.println("in CB.init, my ivar is " + ivar);
    }
}

编辑:

在完全初始化类之前更改实例方法的调用确实是危险的。您可能会触及意想不到的东西:

static class CB {

    private static final CB ONLY = new CB();

    private static final Integer IVAR = 42;
    public final int ivar = IVAR;
}

public static void main(String[] args) {
    System.out.println(CB.ONLY.ivar);
}

这会抛出一个 NullPointerException。为什么?您可以反编译并查看,但用相对简化的话来说:

  • ivar 在构造函数中通过读取 IVAR 变量进行初始化
  • 静态成员按照代码中的出现顺序执行

因此,首先会执行 private static final CB ONLY = new CB();,因此必须调用构造函数,从而初始化 ivarivar 被设置为 IVAR,但后者只会在构造函数完成后才初始化。因此,在尝试设置 ivar 时,它将对 IVAR 的值进行拆箱,而此时(因为 CB 尚未完全初始化)它是 null

英文:

This only works because those are static fields (other), if you remove that modifier - you will get a StackOverflow (because for instance fields, the initialization is moved to the constructor). It seems to me that if I show you what the compiler is actually doing, things might get obvious?

static class CA extends Object {

    public final int ivar = 1;
    public static CB other;

    static {
        System.out.println("running CA static block");
        other = new CB();
        System.out.println("CB done");
    }

    public CA() {
        System.out.println("in CA.init, my ivar is " + ivar);
    }
}

static class CB extends Object {

    public final int ivar = 2;
    public static CA other;

    static {
        System.out.println("running CB static block");
        other = new CA();
        System.out.println("CA done");
    }

    public CB() {
        System.out.println("in CB.init, my ivar is " + ivar);
    }


}

EDIT

Messing with what instance methods are called, until the class is fully initialized is indeed dangerous. You might be stepping on things you would not expect:

 static class CB {

    private static final CB ONLY = new CB();

    private static final Integer IVAR = 42;
    public final int ivar = IVAR;

}

public static void main(String[] args) {
    System.out.println(CB.ONLY.ivar);
}

This throws a NullPointerException. Why? You can decompile yourself and see, but in rather simplified words:

  • ivar is initialized in the constructor by reading the IVAR variable

  • statics are executed in the order of how they appear in code

So, first private static final CB ONLY = new CB(); is executed, as such, constructor must be called and thus ivar initialized. ivar is set to IVAR, but the latter will be initializes only after the constructor finishes. So when trying to set ivar, it will unbox the value of IVAR, which at this point (because CB is not fully initialized) is null.

huangapple
  • 本文由 发表于 2020年10月22日 03:23:03
  • 转载请务必保留本文链接:https://go.coder-hub.com/64470359.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定