Search code examples
javaobjectreflectionreferencermi

Get Java Object Using A Reference


I have this question on my mind and I severely need to find an answer for it.

My problem: How to get a reference of the object (something like memory address) then get the object using the reference (note: I know where the object located (in an array)). I hear there are ways to do what I want to do using the Unsafe class, or even JNA (I lack knowledge of both of them, but happy to learn!)

What I'm doing:

I have made an application (app1) which is inserted into another application**(app2)** through Instrumentation and this application (app1) sends data fetched from (app2) through the Reflection API to another application of mine (app3) through RMI. The objects in the array change constantly in terms of values, so what I'm doing is fetching the objects and reading their data but my problem comes when I try to get updated data using the previously obtained object which is normally done by getDeclaredField(fieldName).get(PreviouslyObtainedObject); but I can't keep the previously obtained object on the current application (app1) due to it being Unserializable so I need to get a reference of this object so I can get it later on.

What I've tried:

I've tried doing it using HashCode but HashCode just keeps changing/dissppears whenever the object's values change and the hashCode might be the same with other objects so this creates another problem.

I've tried doing it using XStream,GSON,etc.. but using the xml/json it creates a new object and doesn't relate to the previously obtained object. so when I try to get updated info about the object using the JSON/XML created object it returns the old info as it does not equal the previously obtained object. So like getDeclaredField(fieldName).get(PreviouslyObtainedObject) returns updated information about the object but getDeclaredField(fieldName).get(JSON/XMLVersionOfPreviouslyObtainedObject) does not return new data.

I can't do Object -> byte Array due to the objects being unserializable.

There's no code in the question because I felt that the code won't serve a purpose to the question. to get more information about what I'm doing you could view a previous question of mine: Getting an Object by reference in Java Note: there's a difference between this question and my previous one as the previous one discussed ways of wrapping or turning the objects into JSON or XML format and this one is just for getting a reference.

Summary of what I'm trying to do (for more clarity):

I have 2 applications: first app (app1): Application which is instrumented into another application (to gather data and send)

second app (app2): Application which receives the data from the instrumented data and does logic..

app1 gets the data from the application its instrumented into via the Reflection API so like (getDeclaredField(fieldName).get(object/null);) app1 send the Object reference (memory address/hashCode/this is what I want) and the current values of the object to app2. app2 sends back the reference whenever it wants and gets the object updated data.

so like in code terms:

The first time I want to get an object, I access an object array using reflection then loop through it to find my perfect object like this:

Object[] obArray = (Object[]) getClassLoader().loadClass("className").getDeclaredField("fieldName").get(null);
for(Object ob : obArray) {
    if(ob is what I want) return ob;
}

then If I wanted to get updated information of ob (object that I fetched above). I do getClassLoader().loadClass(className).getDeclaredField(fieldName).get(ob);

Now due to the amount of objects I process I can't store the objects I fetched on the same application so I must get a reference to them (something like a memory address or hashCode) to send to app2 so that app2 can send it back to app1 at a later date to get the object back and get updated information about it.


Solution

  • When you are using RMI, you are already using a framework maintaining references across JVMs. But it does not support retrieving the original local object reference after passing a reference to another JVM and back, as it only allows communicating with the referenced object through the remote interface.

    But since your actual goal is to allow reading the fields of a referenced object, you can export this operation through the remote interface.

    Here’s a sketch for such an access. Note that it bears several things you wouldn’t do in the producing code (like setting sun.rmi.dgc.server.gcInterval to an insanely small value), for demonstration purposes.

    public class RmiApp implements RmiAppB {
        public static void main(String[] args) throws Exception {
            System.setProperty("sun.rmi.dgc.server.gcInterval", "1000");
            if(args.length == 0) runAppB(); else runAppA();
        }
    
        private static void runAppA() throws Exception {
            String me = String.format("App A (pid%6d) ", ProcessHandle.current().pid());
            System.out.println(me + "started");
            Registry r = LocateRegistry.getRegistry(12348);
            RmiAppB appB = (RmiAppB)r.lookup("foo");
            InnocuousObject obj = new InnocuousObject("a local string", 42, 1.234f);
            System.out.println(me + "sending reference");
            appB.passTheReference((WrappingReference)
                UnicastRemoteObject.exportObject(new WrappingReferenceImpl(obj), 0));
    
            LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
            System.out.println(me + "changing fields");
            obj.field1 = "another string";
            obj.field2 = 0;
            obj.field3 = 0.1f;
            obj = null; // we don't need to keep a strong reference
    
            LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(5));
    
            System.exit(0);
        }
    
        private static void runAppB() throws Exception {
            System.out.printf("App B (pid%6d) started%n", ProcessHandle.current().pid());
            Registry r = LocateRegistry.createRegistry(12348);
            r.bind("foo", UnicastRemoteObject.exportObject(new RmiApp(), 0));
            //UnicastRemoteObject.exportObject(new RmiApp(), 12345);
            new ProcessBuilder(
                Paths.get(System.getProperty("java.home"), "bin", "java").toString(),
                "-cp", System.getProperty("java.class.path"),
                RmiApp.class.getName(), "runA")
                .inheritIO().start().waitFor();
            System.exit(0);
        }
    
        @Override
        public void passTheReference(WrappingReference o) throws RemoteException {
            String me = String.format("App B (pid%6d) ", ProcessHandle.current().pid());
            System.out.println(me + "received object");
            System.out.println(me + "querying field1 " + o.getField("field1"));
            System.out.println(me + "querying field2 " + o.getField("field2"));
            System.out.println(me + "querying field3 " + o.getField("field3"));
            CompletableFuture.runAsync(() -> {
                LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(3));
                try {
                    System.out.println(me + "querying field1 " + o.getField("field1"));
                    System.out.println(me + "querying field2 " + o.getField("field2"));
                    System.out.println(me + "querying field3 " + o.getField("field3"));
                } catch(RemoteException ex) {
                    ex.printStackTrace();
                }
            });
        }
    }
    
    interface RmiAppB extends Remote {
        void passTheReference(WrappingReference o) throws RemoteException;
    }
    
    interface WrappingReference extends Remote {
        Object getField(String nra) throws RemoteException;
    }
    
    class WrappingReferenceImpl implements WrappingReference {
        InnocuousObject referent;
    
        WrappingReferenceImpl(InnocuousObject referent) {
            this.referent = referent;
        }
    
        @Override
        public Object getField(String nra) throws RemoteException {
            try {
                return referent.getClass().getDeclaredField(nra).get(referent);
            } catch(NoSuchFieldException|IllegalAccessException ex) {
                throw new RemoteException(null, ex);
            }
        }
    }
    
    class InnocuousObject {
        String field1;
        int field2;
        float field3;
    
        public InnocuousObject(String field1, int field2, float field3) {
            this.field1 = field1;
            this.field2 = field2;
            this.field3 = field3;
        }
    
        @Override
        protected void finalize() throws Throwable {
            System.out.println("InnocuousObject.finalize()");
        }
    
        @Override
        public String toString() {
            return field1;
        }
    }
    

    In one run, it printed

    App B (pid  8924) started
    App A (pid  9132) started
    App A (pid  9132) sending reference
    App B (pid  8924) received object
    App B (pid  8924) querying field1 a local string
    App B (pid  8924) querying field2 42
    App B (pid  8924) querying field3 1.234
    App A (pid  9132) changing fields
    App B (pid  8924) querying field1 another string
    App B (pid  8924) querying field2 0
    App B (pid  8924) querying field3 0.1
    InnocuousObject.finalize()
    

    showing how the remote reference kept the object alive as long as the other JVM held a remote reference, but allowed its garbage collection when no remote reference existed.

    Further note the important point that the target InnocuousObject has no awareness about the remote access via the encapsulating WrappingReferenceImpl that performed the reflective access. This seems to support your scenario of having app2 instrumented by app1 whereas only app1 is aware of the remote access.