javagarbage-collectionjvmjdijdwp

Life-span of JDI mirrors of objects living in a remote JVM


I've been writing a Java client which uses JDI to create and modify objects in a remote JVM (by connecting to a JDWP agent-based server running in the remote JVM). One of the requirements of my project is that I can't suspend all of the threads in the remote JVM, which means that objects I create can be susceptible to garbage-collection before I can make them reachable within the JVM.

In some cases, I am creating objects in a remote JVM but they are randomly being garbage-collected. For example, if I create an array in the remote JVM via ArrayType.newInstance(int), sometimes the array will be garbage collected before I can make it "reachable" from another reachable object in the remote JVM.

(eg If I'm trying to store the new array into a field of an existing reachable object, the call to ObjectReference.setValue(Field, Value) may randomly throw an ObjectCollectedException.):

void createAndStoreArray(ObjectReference reachableObj, Field fieldOfObj, ArrayType type, int length)
{
    ArrayReference ref = type.newInstance(length);
    reachableObj.setValue(fieldOfObj, ref); // Sometimes throws ObjectCollectedException because ref's mirror garbage gets collected before I can store it on the reachable object
}

In theory, a ObjectReference's mirror could even get garbage-collected before I'm able to call ObjectReference.disableCollection() (which is a step I don't want to take for other reasons anyway).

So my question is, are any documented lifespan guarantees on JDI Values?

Thanks in advance for your help!


Solution

  • Although I can't find any documentation addressing things from exactly the angle I wanted, a combination of looking at the source code for the com.sun.tools.jdi package on GrepCode and the JDWP protocol spec on Oracle's website leads me to conclude that:

    1. "Mirrored" primitive values never die since the whole primitive value lives in the JDI client until it needs to be transmitted to the server using the JDWP protocol. (There wouldn't be an advantage to keeping a handle to a primitive value that exists only in the server in any event since it would take as many or more bytes to send the handle over the transport as it would to just send the primitive value!)
    2. Any mirror of a reference type, including a reference to a String created by VirtualMachine.mirrorOf(String) could be garbage-collected at any time.

    As support for #1, see for example the source for com.sun.tools.jdi.VirtualMachineImpl.mirrorOf(long): it just instantiates a new instance of LongValueImpl and returns:

    public LongValue mirrorOf(long value) {
        validateVM();
        return new LongValueImpl(this,value);
    }
    

    and you can see that neither LongValueImpl's constructor, nor any of its superclasses' constructors all the way up to MirrorImpl do anything that would transmit data over the JDWP transport and thus alter the server JVM's state.

    Contrast with #2. The starting point in the JDI JavaDoc is that any ObjectReference's mirrored object could be garbage collected at any time:

    Any method on ObjectReference or which directly or indirectly takes ObjectReference as parameter may throw ObjectCollectedException if the mirrored object has been garbage collected.

    This is corroborated by the source for com.sun.tools.jdi.VirtualMachineImpl.mirrorOf(String), it talks to the JDWP agent in the server. This is just confirmation that like any ObjectReference a StringReference created in this way is susceptible to GC at any time including immediately...

    public StringReference mirrorOf(String value) {
        validateVM();
        try {
            return (StringReference)JDWP.VirtualMachine.CreateString.
                process(vm, value).stringObject;
        } catch (JDWPException exc) {
            throw exc.toJDIException();
        }
    }
    

    So as far as I can tell, if you have any ObjectReference at all, you need to protect all attempts to interact with the mirrored object in the remote JVM in a loop which catches ObjectCollectedException unless all the threads in the remote JVM are suspended. For example, if you have a method in your JDI client that creates a String in the remote JVM and returns an non-garbage-collectable reference to it, you might do something along these lines:

    StringReference safeStringRef(VirtualMachine vm, String string) {
        ObjectCollectedException lastCause = null;
        for (int numTries = 0; numTries < SANE_TRY_LIMIT; ++numTries) {
            StringReference stringRef = vm.mirrorOf(string);
            try {
                stringRef.disableCollection();
                return stringRef;
            } catch (ObjectCollectedException e) {
                lastCause = e;
            }
        }
        throw new RuntimeException("Can't create safe string reference", lastCause);
    }