How are object dependencies between static blocks resolved?

血红的双手。 提交于 2019-11-29 04:51:24

问题


I recently came across this at work. While I am not sure it is really a good idea, I don't understand how static blocks are handled by the compiler.

Here is an example:

Consider that you have classes A and B:

public class A {

    public final static List<Integer> list;
    static {
        list = new ArrayList<>();
    }
}

public class B {

    public final static int dependsOnA;
    static {
        dependsOnA = A.list.size();
    }
}

And a main class that just reads B.dependsOnA.

The static block in B is dependent of the one in A, since it uses the list static variable.

Now, the code executes properly and no NullPointerException is raised at runtime. But what is the mechanism that ensures that list is initialized before it is potentially used elsewhere?


回答1:


The mechanism is described in detail here, but the five most important points are:

  1. Before a class is referenced, it needs to be initialised.
  2. If initialisation of a class has already begun (or if it's finished), it isn't attempted again.
  3. Before a class is initialised, all its superclasses and superinterfaces need to be initialised first.
  4. Static initialisers within a single class are executed in textual order.
  5. Implemented interfaces are initialised in the order in which they appear in the implements clause.

These rules completely define the order in which static blocks are executed.

Your case is rather simple: before you access B.dependsOnA, B needs to be initialised (rule 1), the static initialiser is then trying to access A.list, which triggers the initialisation of class A (again rule 1).

Note that there's nothing stopping you from creating circular dependencies this way, which will cause interesting things to happen:

public class Bar {
    public static int X = Foo.X+1;

    public static void main(String[] args) {
        System.out.println( Bar.X+" "+Foo.X); // 
    }

}

class Foo {
    public static int X = Bar.X+1;
}

The result here is 2 1 because the way the initialisation happens is this:

  1. Bars initialisation begins.
  2. Bar.Xs initial value is evaluated, which requires initialising Foo first
  3. Foos initialisation begins.
  4. Foo.Xs initial value is evaluated, but since Bars initialisation is already in progress, it isn't initialised again, Bar.Xs "current" value is used, which is 0, thus Foo.X is initialised to 1.
  5. We're back to evaluating Bar.Xs value, Foo.X is 1 so Bar.X becomes 2.

This works even if both fields were declared final.

The moral of the story is to be careful with static initialisers referring to other classes in the same library or application (referring to classes in a third party library or standard class library is safe as they won't be referring back to your class).




回答2:


The "mechanism" is the JVM's classloader, which will ensure that a class' initalization blocks are executed (with a global lock across the whole JVM) before returning control flow to where the class was first referenced. It will first load class A only after referenced, in this case when B's init block references A.list.




回答3:


During execution of the static block of B, the runtime encounters A for the first time, and it will invoke the static block of A before it accesses A.list.




回答4:


No matter how you write the code, a static block is a static block and it will execute as part of the JVM loading a class.

When you say B.dependsOnA, B class starts geting loaded by the JVM and the static block in B gets called somewhere during this process. When you say dependsOnA = A.list.size();, class A starts getting loaded by the JVM and the static block in A will execute somewhere during this process which initializes the list. The statment list.size() will only execute after class A has been loaded completely by the JVM. Subsequently, the JVM can only finish loading class B after the static block in B completes.




回答5:


This is the job of the class loader. The class loading in java starts with the bootstrap classloader. This class loader first loads all the classes in the standard java library, the rt.jar.

Then the extension classloader is invoked. This loads all the classes from the extension jar files installed in a JVM ext directory. Now finally the classpath classloader is invoked.

The classpath classloader starts loading classes from the main class, the class that has the main method defined. Once it is loaded, it executes any static initializers in that class. While in the execution of the initializer, if it encounters any class that is not loaded, it will pause the execution of the static block, loads the class first, and finally resumes the execution of that static block.

So, there is no chance that any calls to non-loaded classes to occur. Let's see this with your own example, whose code is like this:

class A
{
    public final static List<Integer> list;
    static
    {
        System.out.println("Loaded Class A");
        list = new ArrayList<>();
    }
}

class B
{
    public final static int dependsOnA;
    static
    {
        System.out.println("Loaded Class B");
        dependsOnA = A.list.size();
    }
}

Here in this example, there is actually no main method, so these classes won't actually be loaded into the memory. Suppose, let's add the following main class to the above code.

class C
{
    static
    {
        System.out.println("Loaded Class C");
    }

    public static void main(String[] args)
    {
        System.out.println(B.dependsOnA);
    }
}

Let's see what this would produce in the output: http://ideone.com/pLg3Uh

Loaded Class C
Loaded Class B
Loaded Class A
0

That is, first the class C is loaded, because it had the main method. Once it is loaded, the static initializer of class C is invoked. Notice, however, that the main method is invoked after the static block of class C is loaded.

Now the main method, we printed the value of dependsOnA of class B. Now, the class loader stops executing that statement, and loads the class B, and executes it's static block, which in turn, assigns the dependsOnA variable with the value of the number of elements in the list of class A, which is not loaded.

So the class loader jumps from there, loads the class now, and invokes the static block of the class A, and a list is created. Now since there are no more classes to load, the class loader comes back to the static block of class B, and assignment is complete. Now finally, the control is now with the main method, and the value of dependsOnA is printed to the console.

Hope this helps.




回答6:


Here we have some explaination Static Block in Java

If you call the A class first, the A static is called and A.list exists and will when B will call it.

If you call the B class first, the B static is called, cascading to the A call, calling its static block, where A.list is created.

We could see is its trickiest way: B > B.static > A > A.static > A.list exists




回答7:


The working is very simple JVM class loader, which will ensure that a class static blocks are executed when the class is first referenced.
1.If you have executable statements in the static block, JVM will automatically execute these statements when the class is loaded into JVM.
2.If you’re referring some static variables/methods from the static blocks, these statements will be executed after the class is loaded into JVM same as above i.e., now the static variables/methods referred and the static block both will be executed.



来源:https://stackoverflow.com/questions/30192250/how-are-object-dependencies-between-static-blocks-resolved

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!