简体   繁体   English

引擎盖下的Java类构造

[英]Java class construction under the hood

Consider we have classes like this: 考虑我们有这样的类:

class A {
    public B b;

    public void someFunc() { // called sometime
        b = new B();
    }
}

Class B 's constructor assigns some inner variables. B类的构造函数分配一些内部变量。

Field b is not thread-safe in the sense another thread can view b not- null when B constructor hasn't finished. 字段b不是线程安全的,因为B构造函数尚未完成时,另一个线程可以查看b null (during someFunc execution) (在someFunc执行期间)

My question is the following: how can it be (from the logic perspective) that the constructor hasn't finished yet? 我的问题是:从逻辑角度看,构造函数还没有完成怎么办?

For me reordering of such kind is magic. 对我来说,这种重新排序是不可思议的。

In the context of thread safety, this usually happens because of just-in-time (JIT) compilers. 在线程安全的上下文中,这通常是由于即时(JIT)编译器引起的。 A JIT compiler takes Java byte code and translates it in to machine code to make it run faster. JIT编译器采用Java字节码并将其转换为机器码,以使其运行更快。 During translation, it's able to make a lot of optimizations, such as inlining various methods and constructors. 在翻译过程中,它可以进行很多优化,例如内联各种方法和构造函数。

Supposing B had a constructor like this: 假设B有一个这样的构造函数:

class B {
    int x;
    B(int x) { this.x = x; }
}

When a constructor is inlined, it takes Java code that's something like this: 内联构造函数时,它将使用类似于以下内容的Java代码:

b = new B(1);

And translates it to machine code that takes steps similar to the following: 并将其转换为采用类似于以下步骤的机器代码:

  1. Allocate space for a B object somehow. 以某种方式为B对象分配空间。
  2. Store the pointer to that memory in to b . 将指向该内存的指针存储在b
  3. Store 1 in bx . 1存储在bx

In other words, code which is analogous to this (in terms of ordering): 换句话说,与此类似的代码(在排序方面):

b = new B();
b.x = 1;

But we don't actually call a constructor at all. 但是我们实际上根本不调用构造函数。 We'd just allocate a B , however the JVM does it internally, and assign bx directly. 我们只是分配一个B ,但是JVM在内部进行分配,然后直接分配bx Calling the constructor would involve jump instructions, so it's a bit faster to inline it. 调用构造函数将涉及跳转指令,因此内联它要快一些。

There's an example like that in the famous "Double-Checked Locking is Broken" Declaration . 著名的“双重检查锁定已损坏”声明中有一个例子。

A regular Java compiler would be allowed to inline constructors too, but regular Java compilers don't typically perform many optimizations. 常规Java编译器也可以内联构造函数,但是常规Java编译器通常不执行许多优化。

Instance of object can "escape" from constructor, like that: 对象的实例可以从构造函数中“退出”,如下所示:

public class EscapeDemo {
    static void escape(B b) {
        System.out.println(b.strA);
        System.out.println(b.strB); // still null in this example, even if field is final and initialized to non-null value.
    }

    public static void main(String[] args) {
        System.out.println(new B());
    }
}
class B {
   final String strA;
   final String strB;
    B() {
        strA = "some operations";
        EscapeDemo.escape(this);
        strB = "here";
    }
}

prints: 印刷品:

some operations
null
B@hashcode

And in similar way that reference could escape to some code that will use it from other thread. 并且以类似的方式,该引用可以转义到将在其他线程中使用它的某些代码。

Like Andy Guibert added in comment: this is a bad practice to write such code - as it might be source of many weird errors and hard to trace bug - like here we have something that should not be a null, but it is a null. 就像安迪·吉伯特(Andy Guibert)在评论中添加的那样:写这样的代码是一种不好的做法-因为它可能是许多奇怪的错误的源头,并且很难跟踪错误-像这里,我们有一些不应该为null的东西,但是它为null。
And if you want to do something with object instance on creation it is much better idea to create static factory method that will create instance and then do something with (like add to some collection/registry) it and then return it. 而且,如果您想在创建对象实例时执行某些操作,最好创建静态工厂方法,该方法将创建实例,然后对其进行某些操作(例如添加到某个集合/注册表),然后将其返回。

Also if you include usage of weird code nad hacks - in bytecode java object creation is separated from constructor call, so it is possible from bytecode level to create an object, pass it somewhere and call constructor in some other place. 另外,如果您包括怪异的代码使用技巧-在字节码中,java对象的创建与构造函数的调用是分开的,因此可以从字节码级别创建对象,将其传递到某个地方,然后在其他地方调用构造函数。

But otherwise field is assigned after right side expression is executed so for code 但是否则,在执行右侧表达式后会分配字段,因此对于代码

b = new B(); 

field b can be only null or B instance after constructor is called. 调用构造函数后,字段b只能为nullB实例。 Unless you would set that field from inside of B constructor like in my escape example. 除非像我的转义示例那样从B构造函数内部设置该字段。

If I understand your question correctly. 如果我正确理解您的问题。 You are asking about if you create an instance of Object A which has a field b of type B and the field b is not initialized when A is created but only some other object calls someFunc() . 您正在询问是否创建对象A的实例,该实例的字段b为类型B并且在创建A时未初始化字段b ,只有其他一些对象调用someFunc() What will happen when some other thread tries to access this field b ? 当其他线程尝试访问该字段b时会发生什么?

If so, when you create a new object of type B the JVM will allocate some memory for this object and then will return a reference which will be held in the field b . 如果是这样,当您创建类型B的新对象时,JVM将为此对象分配一些内存,然后将返回引用,该引用将保留在字段b If other thread tries to access the field b before it got the reference of the new object, it will return null otherwise will return the reference to the newly created object. 如果其他线程在获取新对象的引用之前尝试访问字段b ,它将返回null否则将返回对新创建对象的引用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM