How to find singleton object of class without having reference to it?

Viewed 352

Let's consider the following example:

import java.util.Map;
import java.util.Optional;

class Scratch {

    public static class UserService {

        private final Map<String, String> users = Map.of(
                "user1", "Max",
                "user2", "Ivan",
                "user3", "Leo");

        public Optional<String> findUserById(String userId) {
            return Optional.ofNullable(users.get(userId));
        }
    }

    // We save our singleton object to this filed
    // to protect from being garbage collected
    private volatile UserService userService = null;

    private UserService getUserService() {
        /// TODO we need to implement it
        /// Obviously, we cannot return Scratch#userService field
        return null;
    }

    public void doJobs() {
        // I need to get `userService` here, without explicitly passing it
        // to execute some service method

        UserService userService = getUserService();
        if (userService != null) {
            userService.findUserById("userId");
        }
    }

    public void startApplication() {
        userService = new UserService();

        doJobs();
    }

    public static void main(String[] args) {
        Scratch program = new Scratch();
        program.startApplication();
    }
}

So. we have simple java application without any frameworks, like spring. I need to find UserService object in doJobs() method, without explicitly passing it. Obviously, it is job interview question.

There are the following task preconditions:

  • UserService is not a spring bean or something like this. It is not about DI
  • You cannot explicitly pass UserService object to doJobs() method
  • You cannot set UserService object to some static/global variable/interface/method.
  • You cannot use javaagents.
  • You know, that there is only one object of UserService in current class loader.
  • You may use any reflection (included libraries), if you wish
  • You cannot create new object, you should use existed one
  • You cannot use Scratch#userService field for any purpose. It is introduced for protection from gc.

So, generally speaking we need to get somehow list of all objects and find needed one, using knowledge about Class name.

I did not solve this task on job interview. Could you help me to do it?

4 Answers

Maybe you missed some question details whose importance you did not realize. E.g., the following example works with HotSpot/OpenJDK and derived JREs:

import java.lang.ref.Reference;
import java.lang.reflect.*;
import java.util.*;

class Scratch {
    public static class UserService {
        private final Map<String, String> users = Map.of(
            "user1", "Max", "user2", "Ivan", "user3", "Leo");
        public Optional<String> findUserById(String userId) {
            return Optional.ofNullable(users.get(userId));
        }
        @Override
        protected void finalize() throws Throwable {
            System.out.println("everything went wrong");
        }
    }
    private volatile UserService userService; // never read

    private UserService getUserService() {
        try {
            Class<?> c = Class.forName("java.lang.ref.Finalizer");
            Field[] f={c.getDeclaredField("unfinalized"), c.getDeclaredField("next")};
            AccessibleObject.setAccessible(f, true);
            Reference r = (Reference)f[0].get(null);
            while(r != null) {
                Object o = r.get();
                if(o instanceof UserService) return (UserService)o;
                r = (Reference)f[1].get(r);
            }
        } catch(ReflectiveOperationException ex) {}
        throw new IllegalStateException("was never guaranteed to work anyway");
    }

    public void doJobs() {
        UserService userService = getUserService();
        System.out.println(userService);
        userService.findUserById("userId");
    }

    public void startApplication() {
        userService = new UserService();
        doJobs();
    }
    public static void main(String[] args) {
        Scratch program = new Scratch();
        program.startApplication();
    }
}
Scratch$UserService@9807454

The crucial aspect is that having a nontrivial finalize() method causes the creation of a special reference that allows to perform the finalization when no other reference to the object exists. The code above traverses these special references.

This does also provide a hint why no other solution (without reading the field) can exist. If the field contains the only reference to the object, only this reference makes the difference between an actual, existing object and, e.g. a chunk of memory that just happens to contain the same bitpattern by chance. Or garbage, i.e. a chunk of memory that happened to be an object in the past, but now is not different to memory that never contained an object.

Garbage collectors do not care about the unused memory, whether it contained objects in the past or not, they traverse the live references to determine the reachable objects. So even if you found a way to peek into the internals to piggyback on the garbage collector when it discovers the existing UserService instance, you just read the field Scratch.userService indirectly, as that’s what the garbage collector will do, to discover the existence of that object.

The only exception is finalization, as it will effectively resurrect the object to invoke the finalize() method when no other reference to it exists, which requires the special reference, the code above exploited. This additional reference has been created when the UserService instance was constructed, which is one of the reasons why actively using finalization makes the memory management less effecient, so also How does Java GC call finalize() method? and why allocation phase can be increased if we override finalize method?


That said, we have to clarify another point:

In this particular scenario, the field userService does not prevent garbage collection.

This may contradict intuition, but as elaborated in Can java finalize an object when it is still in scope?, having an object referenced by a local variable does not prevent garbage collection per se. If the variable is not subsequently used, the referenced object may get garbage collected, but the language specification even explicitly allows code optimization to reduce the reachability, which may lead to issues like this, that, or yet another.

In the example, the Scratch instance is only referenced by local variables and, after writing the reference to the userService field, entirely unused, even without runtime optimizations. It’s even a requirement that the field is not read, in other words, unused. So in principle, the Scratch instance is eligible to garbage collection. Note that the due to the local nature of the Scratch instance, the volatile modifier has no meaning. Even if the object was not purely local, the absence of any read made it meaningless, though this is hard to recognize by optimizers. So, since the Scratch instance is eligible to garbage collection, the UserService instance only referenced by the collectible object is too.

The above example still works because it doesn’t run long enough to make runtime code optimizations or garbage collection happen. But it’s important to understand that there is no guaranty that the object persists in memory, even with the field, so the assumption that there must be a way to find it in heap memory, is wrong in general.

public static class UserService {

        private final Map<String, String> users = Map.of(
                "user1", "Max",
                "user2", "Ivan",
                "user3", "Leo");

        public Optional<String> findUserById(String userId) {
            return Optional.ofNullable(users.get(userId));
        }
    }
// We save our singleton object to this filed
    // to protect from being garbage collected
    private volatile UserService userService = new UserService();

    private UserService getUserService() {
        /// TODO we need to implement it
        /// Obviously, we cannot return Scratch#userService field
        return userService;
    }

    public void doJobs() {
        // I need to get `userService` here, without explicitly passing it
        // to execute some service method

        UserService userService = getUserService();
        if (userService != null) {
            System.out.println(userService.findUserById("user1"));
        }
    }

    public void startApplication() {
        userService = new UserService();

        doJobs();
    }

    public static void main(String[] args) {
        Scratch program = new Scratch();
        program.startApplication();
    }

The task asks for you to implement this method without referring to the field directly.

private UserService getUserService() {
    return ...;
}

Being an instance method you have a reference to the instance of Scratch via the this keyword. Using reflection, you can get a reference to the userService field, then use the field to get the value of that field for this:

Field field = getClass().getField("userService");
UserService userService = (UserService)field.get(this);

The complete method is then:

private UserService getUserService() {
    try {
        return (UserService)getClass().getField("userService").get(this);
    } catch (ReflectiveOperationException e ) {
        throw new RuntimeException(e);
    }
}

If it's known that an instance of UserService is saved in a static field you can try Reflections

private UserService getUserService() {
    Reflections reflections = new Reflections("", new SubTypesScanner(false));
    UserService userService = null;
    for (String name: reflections.getAllTypes()) {
        try {
            for (Field field: Class.forName(name).getDeclaredFields()) {
                if (field.getType().equals(UserService.class)) {
                    userService = (UserService) field.get(null);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    return userService;
}

If the field is not static then you'll have to create an instance of declaring class (and feed it instead of null)

private UserService getUserService() {
    Reflections reflections = new Reflections("", new SubTypesScanner(false));
    UserService userService = null;
    for (String name: reflections.getAllTypes()) {
        try {
            for (Field field: Class.forName(name).getDeclaredFields()) {
                if (field.getType().equals(UserService.class)) {
                    Object obj = (Modifier.isStatic(field.getModifiers())) ? null : field.getDeclaringClass().newInstance();
                    userService = (UserService) field.get(obj);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    return userService;
}
Related