By: Gabriele Svelto (gabriele.svelto.delete@this.gmail.com), October 20, 2006 1:26 am
Room: Moderated Discussions
Tzvetan Mikov (tmikov@gmail.com) on 10/19/06 wrote:
---------------------------
>
>I see a huge problem with this code, which, I repeat, is probably used in every
>commercial JVM out there (I presume they all reuse most of Sun's code).
>
>Another thread could see the write to m_cachedResult before it has seen the writes
>to newObject. So from that thread, m_cachesResult will point to an uninitialized object.
>
>This could be viewed as a programming error, but the bigger problem is that m_cachedResult
>could actually be seen as pointing to uninitialized memory from a second
>thread. This is no longer a programming error - it violates the safety invariants
>of the language and it could cause the JVM to crash.
This should be a problem only on processors allowing out-of-order writes but it doesn't work exactly like in your example if I understand the Java spec correctly (which I hope I do after over a year spent over it).
Initialization in Java is what you call inside the constructor. After a NEW opcode is seen the reference on the Java operand stack is considered uninitialized (and all of its copies) until the constructor has walked over it so the VM must ensure that the constructor does its job. Whatever happens after that works on initialized data. From a safety POV the verifier will check that you don't touch data pointed by an uninitialized object. So the expensiveResultInitialization(newObject) does not really initialize that memory from the GC POV, the constructor does it.
Now Java does not really enforce a strong memory ordering model (see http://java.sun.com/docs/books/vmspec/2nd-edition/html/Threads.doc.html#23974) however another thread can never access uninitialized memory because references to uninitialized objects can only live on the operand stack which is visible by only one thread so the only real constraint is that all the writes issued inside a constructor must be visible before a write which makes the object reference visible to other threads is. So in the case of a processor with out-of-order store visibility you will probably need to add a memory barrier at the exit point of every constructor or before the first time you copy the new object reference to memory. A JVM failing to do so should be considered not complaint with the SPEC and thus buggy.
---------------------------
>
public Result getResult ()
>{
>/*
>We don't need to use synchronization here =>
>in the worst case m_cachedResult will be initialized
>twice.
>*/
>if (m_cachedResult == null)
>{
>Result newObject = new Result();
>expensiveResultInitialization(newObject);
>m_cachedResult = newObject;
>}
>return m_cachedResult;
>}
>I see a huge problem with this code, which, I repeat, is probably used in every
>commercial JVM out there (I presume they all reuse most of Sun's code).
>
>Another thread could see the write to m_cachedResult before it has seen the writes
>to newObject. So from that thread, m_cachesResult will point to an uninitialized object.
>
>This could be viewed as a programming error, but the bigger problem is that m_cachedResult
>could actually be seen as pointing to uninitialized memory from a second
>thread. This is no longer a programming error - it violates the safety invariants
>of the language and it could cause the JVM to crash.
This should be a problem only on processors allowing out-of-order writes but it doesn't work exactly like in your example if I understand the Java spec correctly (which I hope I do after over a year spent over it).
Initialization in Java is what you call inside the constructor. After a NEW opcode is seen the reference on the Java operand stack is considered uninitialized (and all of its copies) until the constructor has walked over it so the VM must ensure that the constructor does its job. Whatever happens after that works on initialized data. From a safety POV the verifier will check that you don't touch data pointed by an uninitialized object. So the expensiveResultInitialization(newObject) does not really initialize that memory from the GC POV, the constructor does it.
Now Java does not really enforce a strong memory ordering model (see http://java.sun.com/docs/books/vmspec/2nd-edition/html/Threads.doc.html#23974) however another thread can never access uninitialized memory because references to uninitialized objects can only live on the operand stack which is visible by only one thread so the only real constraint is that all the writes issued inside a constructor must be visible before a write which makes the object reference visible to other threads is. So in the case of a processor with out-of-order store visibility you will probably need to add a memory barrier at the exit point of every constructor or before the first time you copy the new object reference to memory. A JVM failing to do so should be considered not complaint with the SPEC and thus buggy.