-
Bug
-
Resolution: Not an Issue
-
P3
-
None
-
8u25
-
Win 7
If you have a listener on a Service property but don't explicitly hold a reference to it, the Service can be garbage collected, and notifications lost, before the Task completes.
In the code below, there is a listener on Service's State property. If the class MyTask is static, the notifications of RUNNING and SUCCEEDED are never delivered.
If MyTask is made a normal inner class (and thus it holds a reference to the Service), then the RUNNING and SUCCEEDED notifications are delivered.
When there is a listener on the State property, all state transitions should be delivered.
What is happening is that Service attaches the listener using a Weak Reference, so the listener doesn't hold the Service in memory. Under memory pressure the Service gets GC'ed & stops delivering notifications even though the underlying Task succeeds.
This was a really hard bug to track down.
import javafx.application.Application;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import java.util.ArrayList;
public class ServiceGCTest extends Application
{
@Override
public void start(Stage primaryStage) throws Exception
{
startService();
Label lab = new Label("Hello");
StackPane root = new StackPane();
root.getChildren().add(lab);
primaryStage.setScene(new Scene(root, 200, 250));
primaryStage.show();
ArrayList<String> al = new ArrayList<>(); // apply pressure to the GC
for (int i = 0; i < 100_000; i++)
al.add("A really long long long really long long long really long long long really long long long String " + System.currentTimeMillis());
Thread.sleep(1_000);
}
private void startService()
{
Service s = new MyService();
s.stateProperty().addListener((observable, oldValue, newValue) -> System.out.println("new value " + newValue));
s.start();
}
public static class MyService extends Service
{
@Override
protected Task createTask()
{
return new MyTask();
}
static class MyTask extends Task<Void> // if static is removed, the bug goes away!
{
@Override
protected Void call() throws Exception
{
return null;
}
}
}
}
In the code below, there is a listener on Service's State property. If the class MyTask is static, the notifications of RUNNING and SUCCEEDED are never delivered.
If MyTask is made a normal inner class (and thus it holds a reference to the Service), then the RUNNING and SUCCEEDED notifications are delivered.
When there is a listener on the State property, all state transitions should be delivered.
What is happening is that Service attaches the listener using a Weak Reference, so the listener doesn't hold the Service in memory. Under memory pressure the Service gets GC'ed & stops delivering notifications even though the underlying Task succeeds.
This was a really hard bug to track down.
import javafx.application.Application;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import java.util.ArrayList;
public class ServiceGCTest extends Application
{
@Override
public void start(Stage primaryStage) throws Exception
{
startService();
Label lab = new Label("Hello");
StackPane root = new StackPane();
root.getChildren().add(lab);
primaryStage.setScene(new Scene(root, 200, 250));
primaryStage.show();
ArrayList<String> al = new ArrayList<>(); // apply pressure to the GC
for (int i = 0; i < 100_000; i++)
al.add("A really long long long really long long long really long long long really long long long String " + System.currentTimeMillis());
Thread.sleep(1_000);
}
private void startService()
{
Service s = new MyService();
s.stateProperty().addListener((observable, oldValue, newValue) -> System.out.println("new value " + newValue));
s.start();
}
public static class MyService extends Service
{
@Override
protected Task createTask()
{
return new MyTask();
}
static class MyTask extends Task<Void> // if static is removed, the bug goes away!
{
@Override
protected Void call() throws Exception
{
return null;
}
}
}
}
- duplicates
-
JDK-8095762 Service does not change to State.SUCCEEDED, even though its Task does
-
- Closed
-
- relates to
-
JDK-8095188 Service docs should indicate that a running Task does not prevent it from being GCed
-
- Resolved
-