Skip to content

Instantly share code, notes, and snippets.

@TomasMikula
Last active March 2, 2019 22:51
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save TomasMikula/62c6e33863f2092f27c9 to your computer and use it in GitHub Desktop.
Save TomasMikula/62c6e33863f2092f27c9 to your computer and use it in GitHub Desktop.
Demonstration of a memory leak when relying on WeakInvalidationListener in cases when the value of ObservableValue it is attached to never changes. Two possible solutions are presented as well.
import javafx.beans.property.SimpleDoubleProperty;
public class Leaky {
public static void main(String[] args) throws InterruptedException {
SimpleDoubleProperty a = new SimpleDoubleProperty(0.0);
for(int i = 0; i < 1000000; ++i) {
a.add(5).multiply(10).dispose();
}
reportMemoryUsage();
a.add(0); // prevents a from being garbage collected until now
}
private static void reportMemoryUsage() {
System.gc();
Runtime runtime = Runtime.getRuntime();
long used = runtime.totalMemory() - runtime.freeMemory();
double mb = 1024*1024;
System.out.printf("Used Memory after GC: %.2f MB", used / mb);
}
}
import javafx.beans.binding.DoubleBinding;
import javafx.beans.property.SimpleDoubleProperty;
public class NonLeaky {
public static void main(String[] args) throws InterruptedException {
SimpleDoubleProperty a = new SimpleDoubleProperty(0.0);
for(int i = 0; i < 1000000; ++i) {
DoubleBinding x = a.add(5);
x.multiply(10);
x.dispose(); // dispose the first binding in the chain
}
reportMemoryUsage();
a.add(0); // prevents a from being garbage collected until now
}
private static void reportMemoryUsage() {
System.gc();
Runtime runtime = Runtime.getRuntime();
long used = runtime.totalMemory() - runtime.freeMemory();
double mb = 1024*1024;
System.out.printf("Used Memory after GC: %.2f MB", used / mb);
}
}
import org.reactfx.value.Var;
public class NonLeakyReactFX {
public static void main(String[] args) throws InterruptedException {
Var<Double> a = Var.newSimpleVar(0.0);
for(int i = 0; i < 1000000; ++i) {
a.map(x -> (x + 5) * 10);
}
reportMemoryUsage();
a.map(x -> x); // prevents a from being garbage collected until now
}
private static void reportMemoryUsage() {
System.gc();
Runtime runtime = Runtime.getRuntime();
long used = runtime.totalMemory() - runtime.freeMemory();
double mb = 1024*1024;
System.out.printf("Used Memory after GC: %.2f MB", used / mb);
}
}

Leaky.java:

Used Memory after GC: 52.73 MB

NonLeaky.java:

Used Memory after GC: 2.90 MB

NonLeakyReactFX.java:

Used Memory after GC: 0.98 MB

Discussion

The pure JavaFX NonLeaky solution breaks the fluency, because you have to store an intermediate binding in order to be able to dispose it later. The NonLeakyReactFX solution is fluent, but employs a third-party library ReactFX.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment