0

Block Diagram

*The implementation module is not being garbage collected after it is de-reference in UI module. I have overridden finalize method in all the classes of implementation. finalize method of none of the objects being called after dereferencing. No Thread is running in implementation module when it is de-referenced.

The below code is only to provide a perspective of project. The Set of WeakReferences in BankConfig, Main class are not part of original project. The CoreConfig and CoreController belong to implementation module. *

    public class BankConfig {

    public final String uuid = UUID.randomUUID().toString();
    public final String name;
    public Controller   controller;

    public BankConfig(String name, Controller controller) {
        this.name       = name;
        this.controller = controller;
    }

    @SuppressWarnings("deprecation")
    @Override
    protected void finalize() throws Throwable {
        System.out.println("garbage collected : "+uuid);
        super.finalize();
    }

    @Override
    public String toString() {
        return "bank-"+uuid;
    }
}

public interface Controller {

    public abstract BankConfig getBankConfig();
}


public class CoreConfig extends BankConfig {

    public CoreConfig(String name,Controller controller) {
        super(name, controller);
    }

    public CoreConfig(BankConfig bankConfig) {
        super(bankConfig.name, bankConfig.controller);
    }

    public CoreConfig(BankConfig bankConfig, final Controller controller) {
        super(bankConfig.name, controller);
    }
    @Override
    public String toString() {
        return "core-"+uuid;
    }

}


public class CoreController implements Controller {

    public final CoreConfig config;

    public CoreController(BankConfig config) {
        this.config = new CoreConfig(config, this);
    }

    @Override
    public BankConfig getBankConfig() {
        return config;
    }
}



public class Main {

    private static final Set<WeakReference<BankConfig>> WEAK_REFERENCES = new HashSet<>();
    public static final ObservableList<BankConfig> banks = FXCollections.observableArrayList();

    static {
        banks.add(readConfigFromDatabase("krishna"));
        banks.add(readConfigFromDatabase("shankar"));
    }

    public static void main(String[] args) throws InterruptedException {
        banks.add(loadController(readConfigFromDatabase("krishna")).getBankConfig());
        banks.add(loadController(readConfigFromDatabase("shankar")).getBankConfig());

        for (BankConfig bankConfig : banks) {
            WEAK_REFERENCES.add(new WeakReference<BankConfig>(bankConfig));
        }
        banks.clear();

        for (int i = 0; i < 5; i++) {
            System.out.println("strong references : "+banks);
            System.out.println("weak references   : "+WEAK_REFERENCES.stream().filter(ref -> ref.get() != null).map(ref -> ref.get()).collect(Collectors.toSet()));
            System.gc();
            Thread.sleep(5000);
        }
    }

    public static final Controller loadController(final BankConfig config) {
        try {
            final List<String> moduleNames = List.of("implementation");
            final URLClassLoader loader          = new URLClassLoader(new URL[0]);
            final Configuration  configuration   = ModuleLayer.boot().configuration().resolveAndBind(ModuleFinder.of(Path.of(new URL("path to implementation jar").toURI())), ModuleFinder.of(), moduleNames);
            final ModuleLayer    moduleLayer     = ModuleLayer.boot().defineModulesWithOneLoader(configuration, loader);
            final Optional<Module> module        =  moduleLayer.findModule("implementation");
            final Class<?>       controllerClass = module.get().getClassLoader().loadClass("implementation.CoreController");
            final Controller controller = (Controller) controllerClass.getConstructors()[0].newInstance(config);
            return controller;
        } catch (Exception e) {e.printStackTrace();}
        return null;
    }

    public static BankConfig readConfigFromDatabase(final String name) {
        if("krishna".equals(name)) return new BankConfig("krishna", null);
        else if("shankar".equals(name)) return new BankConfig("shankar", null);
        return null;
    }
}
Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
krishna T
  • 425
  • 4
  • 14
  • 3
    1) Why are you using JavaFX artifacts in a program that is entirely unrelated to JavaFX? 2) The Garbage Collector is not required to reclaim or finalize all unreachable objects 3) You are holding an array of all old objects in `configs` in your `main` method 4) You are repeatedly iterating over the `weakReferences` set and resurrecting all contained referents via `reference.get()`, both in the finalizer and in main’s print statement, even when this is only for a tiny fraction, it may cause the referent to be treated as reachable by a concurrent garbage collection cycle – Holger Jan 27 '20 at 15:38
  • 3
    5) your entire cleanup is broken; `bankConfig == reference.get()` will never be `true` for an object under finalization, as the weak reference has been cleared at this point, so you never remove any `WeakReference` object from the set 6) `.filter(ref -> ref.get() != null).map(ref -> ref.get())` is an instance of the check-then-act anti-pattern, the condition you’ve checked in the first step can be false already in the next step as garbage collection may happen at any time – Holger Jan 27 '20 at 15:40
  • @Holger The given sample code is recreation of a larger project issue and i wanted not to miss any details that might be causing the issue so i included JavaFx. You are right about config array, but in original project the array is not use and the garbage collection of some of elements is not happening. The resurrected objects will become weak again after the iteration is completed. The given Main class is just for illustration of issue and not part of project. I am working to update the question for better clarity. Thanks. – krishna T Jan 27 '20 at 19:29
  • @Holger I agree the state of weakReferences might change while streaming, filtering and mapping, But as i can see while executing the weak references are still valid in overridden finalize method(bankConfig == reference.get() getting true for some of references). – krishna T Jan 27 '20 at 19:33
  • @Holger I have updated the question. I couldn't reproduce the issue in above small sample so i have removed log added earlier. – krishna T Jan 28 '20 at 06:20
  • 1
    It doesn’t help to remove the log when the issue that the code can’t reproduce your problem remains. You are right that the resurrected objects will become weak again after the iteration is completed, but that doesn’t help when the action overlaps with a concurrent gc cycle. Then, these objects are considered reachable for this cycle and when you do this repeatedly, it’s possible that it overlaps repeatedly, keeping the objects alive for a long time. When `bankConfig == reference.get()` is `true`, the weak reference must have been created and added after finalization started. – Holger Jan 28 '20 at 08:45
  • 1
    It may also be the result of a data race, as `weakReferences` is a `HashSet`, which is not thread safe but you’re manipulating it from the `finalize()` method which can be invoked by an arbitrary thread and even concurrently for multiple objects. – Holger Jan 28 '20 at 08:48
  • @Holger I have added block diagram and more details. I can share full source code analysis. I have also debug the program in eclipse. The object which is not being garbage collected has only references to and from all other object which are all eligible for garbage collection. None of active threads has reference to these object, Yet not being collected. – krishna T Jan 28 '20 at 14:37
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/206806/discussion-between-krishna-telgave-and-holger). – krishna T Jan 28 '20 at 16:17
  • 1
    When you see an object in the debugger, it has been already locked by the debugger itself. This can already disturb the analysis, I encountered such problem myself. It should also be mentioned that the presence of a nontrivial `finalize()` method causes a slowdown of an object’s reclamation. Then, mind the statement of my first comment “*The Garbage Collector is not required to reclaim or finalize all unreachable objects*”. Its job is to make enough room for subsequent allocations, not more. It doesn’t run when not needed and it may stop in the middle when it thinks it has done “enough”… – Holger Jan 30 '20 at 12:13

1 Answers1

0

The issue was caused by datakernel library used in implementation module caused due to JmxModule registering jmx-compatible components to the global jmx registry.

Reference : datakernel issue #17

krishna T
  • 425
  • 4
  • 14