United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
JDK-6721588 : Server JIT optimization can cause objects to go out of scope prematurely

Details
Type:
Bug
Submit Date:
2008-07-02
Status:
Closed
Updated Date:
2010-04-02
Project Name:
JDK
Resolved Date:
2008-08-26
Component:
hotspot
OS:
solaris_10
Sub-Component:
compiler
CPU:
x86
Priority:
P3
Resolution:
Not an Issue
Affected Versions:
5.0u15
Fixed Versions:

Related Reports

Sub Tasks

Description
OPERATING SYSTEM:
Solaris 10, AMD64

FULL JDK VERSION:
Java HotSpot(TM) 64-Bit Server VM (build 1.5.0_15-b04, mixed mode)

DESCRIPTION from Licensee:
We have encountered a situation where objects can go out of scope unexpectedly. In our case this leads to crash due a SIGSEGV in our JNI code, because a native structure is cleared away by finalisation before the native code has finished with it.

Consider the following Java code:

  public void doStuff() {
    NativeThing nt = new NativeThing();
    evaluateNativeThing(nt);
  }

In our case, the constructor for NativeThing creates a new native structure, and stores a pointer to that structure in a long field.

The evaluateNativeThing() method takes the pointer from the NativeThing instance's long field and passes it to some native code via JNI, which then does some work on the native structure.

The NativeThing class implementation also contains a finalize() method to clear away the native structure associated with it prior to garbage collection.

Occasionally, we see a SIGSEGV in the native evaluateNativeThing() code because the native stucture has been cleared away before we've finished with it.

This should not be able to happen - the NativeThing instance should remain in scope until doStuff() returns. However, it seems that the NativeThing instance can actually go out of scope and be eligible for finalization before even the evaluateNativeThing() method has returned.

We suspect that this is happening due to a Server JIT optimization based on the fact that the NativeThing instance *effectively* goes out of scope as soon as evaluateNativeThing() is called. This optimization would not cause a problem in pure Java code - the NativeThing instance in doStuff() is no longer required.

However, in our case, the NativeThing instance passed to the implementation of evaluateNativeThing() points to the same native structure as the instance created in doStuff(), so when the instance in doStuff() is finalized, the instance in evaluateNativeThing() will point to a non-existent structure and we'll get a SIGSEGV when we attempt to dereference the pointer.

We understand the need to optimize the code is ways such as this, and from a pure Java perspective the optimization is arguably a valid one. However, the optimization can clearly break code that is backed up by native structures via JNI.

Moreover, the optimization conflicts with the JLS, which states:

 "The scope of a local variable declaration in a block is the rest of
  the block in which the declaration appears, starting with its
  own initializer"

  http://java.sun.com/docs/books/jls/second_edition/html/names.doc.html#103228

In the case we've highlighted here, the local variable "nt" clearly does NOT stay in scope for the whole of the block in which its declaration appears.

                                    

Comments
EVALUATION

Nowhere in either the JLS or the JVMS is there a suggestion that scope has anything to do with when a reference can be garbage collected.  The JLS says something along the line of a "a reference may be collected when it no longer participates in a computation" which gives a lot of leeway to GC implementations.  Scope is a notion that applies to resolution of names in source code.   There's no representation of program scopes in the bytecodes that the JVM could even pay attention to.  The JVM only worries about the lifetime of objects which ends after the last meaningful use of an object, where meaningful is an implementation dependent notion.  However, your native methods will always constitute meaningful uses and you should program to take advantage of that.  If you want the lifetime of a Java object to control the lifetime of a native piece of memory then you have to program very carefully.  One primary rule is that you never pass around the long that represents your native pointer separately from the Java object.  If you pass the long into your native method separately, then you must also pass the object that controls it's lifetime.  I'm assuming evaluateNativeThing looks something like this:

public void evaluateNativeThing(NativeThing nt) {
  myNativeMethod(nt.myLong);
}

It either needs to be myNativeMethod(nt) or myNativeMethod(nt, nt.myLong) if you want to guarantee their lifetimes are the same.
                                     
2008-08-13
EVALUATION

> I'm assuming an object cannot die *before* a local reference to it has gone out of scope.

This can not be assumed.  Neither the Java spec nor the JVM spec
guarantees this.

Just because a variable is in scope, doesn't mean the object it points
to is reachable.  Usually it is the case that an object pointed to by
an in-scope variable is reachable, but yours is a case where it is
not.  The compiler can determine at jit time which variables are dead
and does not include such variables in the oop-map.  Since the object
pointed to by "nt" can be reached from any live variable, it is eligible
for collection.

Rather than pass the "long" structure pointer to a native method via
JNI, your program should pass the actual Java object to the native
method. The native method can then fetch the structure pointer from
Java object via jni.  This parameter will acheive the effect you
desire because JNI will create a handle for the parameter that keeps
the object reachable until the native method returns.
                                     
2008-08-13
WORK AROUND

1. Exclude doStuff() from JIT compilation.
2. Add another line of code after the call to evaluateNativeThing(), to
   artificially keep the NativeThing instance in scope until
   evaluateNativeThing() returns. For example, modify the method to
   something like this:

   public void doStuff() {
     NativeThing nt = new NativeThing();
     evaluateNativeThing(nt);
     nt.toString();
   }
                                     
2008-07-02
WORK AROUND

Workarounds that keep an object alive may not be entirely reliable.

For example, with the above suggestion, inlining may find that toString() is devoid  of side effects, and escape analysis may show that "nt" is dead. Although it may not be capable today, a future JIT may eliminate the toString() code completely, re-exposing
the current bug.
                                     
2008-07-02



Hardware and Software, Engineered to Work Together