0

I'd like a little help understanding how setting an object to null works in java. I have a situation where, seemingly, at first glance it appears that an object that is set to null, is suddenly not null, but obviously this can't be the case.

I have a class in which I create an object. This object is a scene. This is an Open GL ES 2.0 project, so this scene's render() and updateLogic() methods are called from onDrawFrame (this is controlled via a Scene Manager so we can easily switch scenes).

So, I might have something like this (code cut down for the purpose of the question):

public class MyGLRenderer implements GLSurfaceView.Renderer{

    MyScene myScene;
    SomeOtherScene someOtherScene;

    public void createScenes(){
        myScene = new MyScene(this);
        someOtherScene = new SomeOtherScene(this);
        SceneManager.getInstance().setCurrentScene(myScene);
    }

    public void cleanUp(){
        myScene = null;
        Log.v("tag","myScene (from MyGLRenderer) is: "+myScene);
        SceneManager.getInstance().setCurrentScene(someOtherScene);   //Scene changed but this won't take effect until the next 'tick'   
    } 

    @Override
    public void onDrawFrame(GL10 gl) {
        SceneManager.getInstance().getCurrentScene().updateLogic();
        SceneManager.getInstance().getCurrentScene().render();
    }

}

In the above situation, processing is turned over to myScene which would look something like this:

public class MyScene implements Scene{

    MyGLRenderer renderer;

    public myScene(MyGLRenderer renderer){     
        this.renderer = renderer;
    }

    @Override
    public void render(){
        //Render something here
    }

    @Override
    public void updateLogic(){
        doSomething();           
        //The condition here could be anything - maybe the user taps the sceen and a flag is set in onTouchEvent for example
        if (someConditionIsMet){
            renderer.cleanup();
        }            
         Log.v("tag","myScene (from within myScene) is: "+this);
    }
}

So, when I set the scene using my scene manager, processing is turned over to that scene and it's updateLogic and render methods get called from onDrawFrame continuously.

When I ran my code, I was suprised it didn't crash with a NullpointerException. The logs were like this:

myScene (from within myScene) is: com.program.name.MyScene@26354632
myScene (from within myScene) is: com.program.name.MyScene@26354632
myScene (from within myScene) is: com.program.name.MyScene@26354632
myScene (from within myScene) is: com.program.name.MyScene@26354632
myScene (from within myScene) is: com.program.name.MyScene@26354632
myScene (from within myScene) is: com.program.name.MyScene@26354632
myScene (from within myScene) is: com.program.name.MyScene@26354632
myScene (from within myScene) is: com.program.name.MyScene@26354632
myScene (from MyGLRenderer) is: null
myScene (from within myScene) is: com.program.name.MyScene@26354632

As you can see, 'myScene' is valid up until the cleanUp() method is called and sets it to null. But the code then returns to myScene to finish off, where it's still valid (not null).

I'd really like to understand how thing works in Java - why does it seem to be null one minute (or from one place) and then not (from a different place)?

Zippy
  • 3,826
  • 5
  • 43
  • 96

2 Answers2

1

You just clear out the reference to MyScene in renderer, but the object is still there and SceneManager probably still holds on to it.

The confusion seems to be about "setting an object to null". That's not really possible. You can only set variables pointing to objects to null. After this, the variable points to null, but the object may still be there (as in your case).

Specificially, you hand it over to the scene manager here:

SceneManager.getInstance().setCurrentScene(myScene);

The object will get garbage collected only if nothing holds a reference to it. In your case, this is probably when the scene change takes effect.

Stefan Haustein
  • 18,427
  • 3
  • 36
  • 51
  • Thanks @StefanHaustein , OK that kind of makes sense. I'm probably misunderstanding things. So because the 'object' is still in use (it's render() and updateLogic() methods still being called), it can't be GC'd and therefore stays alive until such a time that the OS deems it to be 'free' - even though the actual reference that was assigned to it when it was instatiated is now null (and no other actual 'references' exist ie, like this.myScene = myScene in some other class somewhere)? – Zippy Sep 07 '15 at 00:45
  • I think the scene manager is still holding on to it, clarified my answer accordingly. – Stefan Haustein Sep 07 '15 at 00:54
  • 1
    Theres still a reference to the object because the call stack has to return from an instance method on the object. Also, as stefan said, the object is nulled, the reference in renderer is. – D. Ben Knoble Sep 07 '15 at 01:25
0

Looks like you have run into a thread safety bug.

Other threads can see stale values of a variable unless you "safely publish" the change of value. (You have one CPU changing a value in its cache line, but another CPU is still seeing stale cache data - you need to force the cache to write up to main memory but this is expensive so Java does not do this until it is told to).

There are many ways to safely publish a value depending on your exact requirements. Looking at your code it seems that all you need to do is declare the myScene field to be volatile. It probably should be private too. So try:

private volatile Scene myScene;

If you want to properly understand thread safety in Java, then I highly recommend "the Train Book": http://www.amazon.com/Java-Concurrency-Practice-Brian-Goetz/dp/0321349601

  • I tried making it volatile, but I get the same results - no crash and it runs perfectly. Could this really be a thread-safety problem? I mean I'm running on the same thread (GL Rendering thread), and then calling the cleanUp(); method from the same thread too.... – Zippy Sep 07 '15 at 00:40