3

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?

Max
  • 1,803
  • 3
  • 25
  • 39
  • Comments are not for extended discussion; this conversation has been [moved to chat](https://chat.stackoverflow.com/rooms/221358/discussion-on-question-by-max-how-to-find-singleton-object-of-class-without-havi). – Machavity Sep 12 '20 at 01:51

4 Answers4

2

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.

Holger
  • 285,553
  • 42
  • 434
  • 765
1
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();
    }
Rony Nguyen
  • 1,067
  • 8
  • 18
  • U can use `private static volatile UserService userService = new UserService();` – Rony Nguyen Sep 12 '20 at 07:10
  • @ToànNguyễnHải I thought that `userService` field was for example. – Dmytro Mitin Sep 12 '20 at 08:42
  • @DmytroMitin, I saw that ` // We save our singleton object to this filed // to protect from being garbage collected ` So basically need to store userService instance to this variable (volatile variable) – Rony Nguyen Sep 12 '20 at 12:56
1

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);
    }
}
Bohemian
  • 412,405
  • 93
  • 575
  • 722
  • It will work, but we cannot use this field for any other purpose except protection from gc. – Max Sep 11 '20 at 22:43
  • 1
    @Max this does not "use the field" - there is no direct reference to the field, and IMHO this is the answer they were looking for. – Bohemian Sep 11 '20 at 22:46
  • @Max if it's an interview question, it's going to be about the standard java libraries. btw, it's a stupid esoteric question the solution of which has no commerial value IMHO. – Bohemian Sep 11 '20 at 23:01
  • yep, it has no commercial value. They give this task to check knowledge about JVM internals. – Max Sep 11 '20 at 23:04
  • 1
    @Max there is no way to get a list of all instances using only classes from the JDK. And since my interpretation of the problem is that external libraries may **not** be used (see previous comment), that leaves just this solution, which is about the right complexity for a job interview question. Actually, *this* kind of code I have used lots of times for a dynamic approach, but using a library to trawl the heap is has no value. – Bohemian Sep 11 '20 at 23:13
1

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;
}
Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • The field is not static, and I would expect that using external libraries and/or instrumentation etc is out of scope. – Bohemian Sep 11 '20 at 22:53
  • @Bohemian *"You may use any reflection (included libraries), if you wish"* – Dmytro Mitin Sep 11 '20 at 22:56
  • I interpret "included libraries" in *you may use any reflection (included libraries)* as a *clarification* meaning "libraries included in the JDK". Had it said "includ**ing** libraries", it may be interpreted as external libraries, but it didn't. – Bohemian Sep 11 '20 at 22:59
  • @Bohemian I guess we've already figured out that external libraries were allowed. – Dmytro Mitin Sep 11 '20 at 23:05
  • 1
    @Bohemian well, since the Reflection library consists of pure Java code, you could do yourself what the library does, to solve the problem without external libraries. However, it’s pointless to use a library to search for classes when the class is already known. The crucial part is the reflective access to the field that happens when the class is found, which is not different to the other answers using reflective access to the field. – Holger Sep 15 '20 at 09:35
  • 1
    @DmytroMitin I’m primarily talking to the user whose name appeared after the `@` in my comment. But it’s a public comment that everyone may derive insights from. – Holger Sep 15 '20 at 10:53
  • @Holger OP's question wasn't clear from the very beginning (and partially still is). The field `userService` appeared not from the very beginning. I understood *"In initial task this object is saved in some field, so it cannot be removed by gc"* (now this comment is transferred to the chat) as the field `userService` being just for example and in actual use case a field being arbitrary in arbitrary class. So surely I used `org.reflections` Reflections intentionally. Anyway upvoting your answer. – Dmytro Mitin Sep 15 '20 at 11:01
  • 2
    That makes sense. As an addendum, [code like this](https://stackoverflow.com/a/36021165/2711488) can be used to discover the existing classes without the Reflection library. – Holger Sep 15 '20 at 11:05