1

Here is an example from Kishori Sharan's Learn JavaFX 8 book:

package sample;

import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.beans.value.WeakChangeListener;

public class WeakListener {
    public static IntegerProperty counter = new SimpleIntegerProperty(100);
    public static WeakChangeListener<Number> weakListener;
    public static ChangeListener<Number> changeListener;

    public static void main(String[] args) {
        // Add a weak change listener to the property
        addWeakListener();
        counter.set(300);
        System.gc();
        System.out.println("Garbage collected: " +
                weakListener.wasGarbageCollected());
        counter.set(400);
        changeListener = null;
        System.gc();
        System.out.println("Garbage collected: " +
                weakListener.wasGarbageCollected());
        counter.set(500);
    }

    public static void addWeakListener() {
        changeListener = WeakListener::changed;
        weakListener = new WeakChangeListener<>(changeListener);
        counter.addListener(weakListener);
        counter.set(200);
    }

    public static void changed(ObservableValue<? extends Number> prop,
                               Number oldValue,
                               Number newValue) {
        System.out.print("Counter changed: ");
        System.out.println("old = " + oldValue + ", new = " + newValue);
    }
}

The weak listener is supposed to be GCed after the strong listener is set to null:

enter image description here

but the result is not as expected:

Counter changed: old = 100, new = 200
Counter changed: old = 200, new = 300
Garbage collected: false
Counter changed: old = 300, new = 400
Garbage collected: false
Counter changed: old = 400, new = 500

I am using jdk 1.8_u51 on OSX yosemite.

qed
  • 22,298
  • 21
  • 125
  • 196
  • I don't see how setting `changeListener` to `null` would affect weakListener. I found the page of that book [here](https://books.google.com/books?id=Wb8ICAAAQBAJ&pg=PA52&lpg=PA52&dq=Java+wasGarbageCollected&source=bl&ots=_8eHOLh000&sig=edgWampiIcp-tPVXYh_xqdInI9M&hl=en&sa=X&ved=0CEQQ6AEwBmoVChMI96Oxgov-xgIVg1eSCh03_ApK#v=onepage&q=Java%20wasGarbageCollected&f=false) and they had the same result. What do you think is going wrong? – MC10 Jul 28 '15 at 15:00
  • Question edited. Please see the screenshot. – qed Jul 28 '15 at 15:05
  • It says "the change listener will be garbage collected", but nothing about the weak listener. – MC10 Jul 28 '15 at 15:09
  • Yeah. But "the last change in the counter property, ... did not fire a change event..." – qed Jul 28 '15 at 15:11
  • I believe they wrote it wrong, see @OldCurmudgeon's answer. – MC10 Jul 28 '15 at 15:20

4 Answers4

5

I'm afraid the article is wrong - here is the sentence corrected:

After you set it to null and then invoke the garbage collection again, the change listener may be garbage collected.

Essentially, there is no contract in Java for System.gc();. See the wording here where its states (my emphasis):

Calling the gc method suggests that the Java Virtual Machine expend effort toward recycling unused objects in order to make the memory they currently occupy available for quick reuse. When control returns from the method call, the Java Virtual Machine has made a best effort to reclaim space from all discarded objects.

Nothing there states that it will actually collect any garbage.

OldCurmudgeon
  • 64,482
  • 16
  • 119
  • 213
  • Also, it states that a change event would not be fired, but clearly `Counter changed: old = 400, new = 500` is in their output... – MC10 Jul 28 '15 at 15:19
  • Im able to get the desired behavior that the OP wants if I create a separate class implementing ChangeListener, instead of using a static method reference as in the example code. Though that might just be luck, as you say System.gc() doesn't give any guarantees. – nos Jul 28 '15 at 15:19
  • @nos - Interesting - perhaps method references are treated slightly differently in Java 8. Perhaps they are cached, that would make sense. – OldCurmudgeon Jul 28 '15 at 15:21
  • 1
    Exactly. Such a method reference, or more generally, all instances created for non-capturing lambda expressions are remembered and reused, see also [this answer](https://stackoverflow.com/a/27524543/2711488). As explained there, the behavior is intentionally unspecified, hence, implementation dependent. But even for unreachable listeners, your answer is correct, they are unreliable and not guaranteed to get collected. Even if gc happens frequently, it may be asynchronous, and delivering an event at the same time will keep the listener alive, as it is strongly reachable during event delivery. – Holger Jun 27 '18 at 09:14
  • @nos Thanks, that is a very nice catch. With the lambda approach I never saw any listener being garbage collected, even when creating a lot of other "garbage", forcing the GC to really run. With the an anonymous class the listener is collected as expected. Also thanks to Holger for the additional insights. – Patter Aug 05 '22 at 13:24
2

The simple but disappointing answer: System.gc() does not force garbage collection. All it does is tell the VM that now would be a good time to collect garbage. The VM now doesn't have to run garbage collection.

If the VM e.g. thinks, it has more than enough memory left, it may not run garbage collection at that time.

So if you really want to test the garbage collection, ramp up the memory usage and run System.gc() multiple times over a longer period. That way you might increase your chance of the listener being collected.

Dakkaron
  • 5,930
  • 2
  • 36
  • 51
1

same problem here.

I found that simply changing the creation of the ChangeListener from a method reference:

changeListener = WeakListener::changed;

to a plain old anonymous class

changeListener = new ChangeListener<Number>() {
            @Override
            public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
                System.out.print("Counter changed: ");
                System.out.println("old = " + oldValue + ", new = " + newValue);
            }
        };

does output that well:

Counter changed: old = 100, new = 200
Counter changed: old = 200, new = 300
Garbage collected: false
Counter changed: old = 300, new = 400
Garbage collected: true

Wow! Is this a lambda related bug?? Using win7 x64 with jdk8u60

mimmoz81
  • 41
  • 5
  • It’s not a bug, it’s a feature. The identity of objects created for method references is [intentionally unspecified](https://stackoverflow.com/a/27524543/2711488), hence, you can not draw any conclusions about their reachability. In this case, the instance is remembered and repeated execution of the `changeListener = WeakListener::changed;` statement will lead to the same object. However, even if changing it to an anonymous inner class happens to produce the intended behavior, it’s unreliable behavior, as there is no guaranty about when garbage collection will actually happen. – Holger Jun 27 '18 at 09:19
0

You are keeping a reference to the WeakChangeListener in the static variable weakListener.

An object is only garbage collected when there aren't any references to it.

When you nullify the changeListener, the internal reference that it keeps to weakListener is lost, but the object will remain in memory as long as there are at least one reference to it.

EDIT:

I wasn't aware of how a weak reference works on Java. If, like me, someone doesn't know about them, https://weblogs.java.net/blog/2006/05/04/understanding-weak-references is a good reading.

joanq
  • 1,416
  • 2
  • 10
  • 12
  • That is not the purpose of WeakChangeListener, it contains a weak reference to the changeListener, – nos Jul 28 '15 at 15:14