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 Value
s?
VirtualMachine
. mirror*()
methods documents says anything.)ObjectReference
can be GC'd at any time unless you manage to disable GC on it before?Thanks in advance for your help!
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:
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 takesObjectReference
as parameter may throwObjectCollectedException
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);
}