On 1/14/16 8:09 PM, Jim Graham wrote: > How often are listeners added and removed? It might make more sense to > make them copy-on-write instead...?
That is a good point. As far as I can see, every window adds a stage listener when it is shown and removes it if it is closed. Every scene adds/removes a scene listener. So the listener maps are changed rarely. The post scene pulse listener seems to be used for the snapshot functionality. Although I can only see that such a listener is added, but it seems it will never be removed again. I will try to revise my patch and open an RFE as suggested by Kevin as soon as I handed in my OCA. Regards, Michael >On 1/14/16 4:52 AM, Doswald Michael wrote: >> While profiling a JavaFX application that runs on embedded hardware, I have >> found that the Toolkit.firePulse method creates more garbage than necessary. >> In an application that simply animates an object without doing much else, I >> see that the firePulse method allocates a fair amount of objects (compared >> to the overall number of allocated objects). I have written a small patch >> that decreases the allocation rate in said method. Since I'm not sure if >> such tweaks are desired in the JavaFX codebase, I didn't open a JIRA issue. >> I'm happy to do so if they are. >> >> The following small application animates a single circle on screen. The >> allocation rate of the 'JavaFX Application Thread' is as follows (measured >> with jvisualvm): >> openjfx 8u HEAD: ~ 65 KB/s after start, ~ 49 KB/s after JIT >> compilation kicked in >> with patch: ~ 25 KB/s, doesn't seem to change when JIT >> kicks in >> >> public class AllocateApp extends Application { >> public static void main(String... args) { >> Application.launch(args); >> } >> >> @Override >> public void start(Stage primaryStage) throws Exception { >> Circle circle = new Circle(10, 25, 10); >> >> Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(2), >> new KeyValue(circle.centerXProperty(), 490))); >> timeline.setCycleCount(Timeline.INDEFINITE); >> timeline.setAutoReverse(true); >> timeline.play(); >> >> primaryStage.setScene(new Scene(new Pane(circle), 500, 50)); >> primaryStage.show(); >> } >> } >> >> >> The patch below only creates a single List object instead of three >> WeakHashMaps to make a local copy of the TKPulseListeners. It also uses the >> number of listeners in the maps to estimate the size of the list. The patch >> uses WeakReference objects in the list, which I doubt is necessary, but it >> emulates the previous behaviour more accurately. I believe it would be >> possible to change that to a strong reference for the following reasons: >> a) The list is local and the strong references would only be there for the >> time the firePulse method is run >> b) The code would become more readable (less generics parameters, null-guard >> not necessary in loop) >> c) Getting rid of the WeakReference objects would decrease garbage >> generation even more (down to ~ 21 KB/s) >> >> >> Regards >> Michael >> >> diff -r f1c3eb85af4d >> modules/graphics/src/main/java/com/sun/javafx/tk/Toolkit.java >> --- a/modules/graphics/src/main/java/com/sun/javafx/tk/Toolkit.java Fri >> Jan 08 08:11:51 2016 -0800 >> +++ b/modules/graphics/src/main/java/com/sun/javafx/tk/Toolkit.java Wed >> Jan 13 10:43:27 2016 +0100 >> @@ -51,7 +51,6 @@ >> import javafx.scene.shape.StrokeType; >> import javafx.stage.FileChooser.ExtensionFilter; >> import javafx.stage.Modality; >> -import javafx.stage.Stage; >> import javafx.stage.StageStyle; >> import javafx.stage.Window; >> import java.io.File; >> @@ -94,6 +93,8 @@ >> import com.sun.scenario.effect.Color4f; >> import com.sun.scenario.effect.FilterContext; >> import com.sun.scenario.effect.Filterable; >> +import java.lang.ref.WeakReference; >> +import javafx.util.Pair; >> >> >> public abstract class Toolkit { >> @@ -362,32 +363,36 @@ >> // and those changes propogated to scene before it gets its pulse >> to update >> >> // Copy of listener map >> - final Map<TKPulseListener,AccessControlContext> stagePulseList = >> - new WeakHashMap<TKPulseListener,AccessControlContext>(); >> - final Map<TKPulseListener,AccessControlContext> scenePulseList = >> - new WeakHashMap<TKPulseListener,AccessControlContext>(); >> - final Map<TKPulseListener,AccessControlContext> postScenePulseList = >> - new WeakHashMap<TKPulseListener,AccessControlContext>(); >> + final >> List<Pair<WeakReference<TKPulseListener>,AccessControlContext>> >> listenersList; >> >> synchronized (this) { >> - stagePulseList.putAll(stagePulseListeners); >> - scenePulseList.putAll(scenePulseListeners); >> - postScenePulseList.putAll(postScenePulseListeners); >> + listenersList = new >> ArrayList<>(stagePulseListeners.size()+scenePulseListeners.size()+postScenePulseListeners.size()); >> + copyListeners(stagePulseListeners, listenersList); >> + copyListeners(scenePulseListeners, listenersList); >> + copyListeners(postScenePulseListeners, listenersList); >> } >> - for (Map.Entry<TKPulseListener,AccessControlContext> entry : >> stagePulseList.entrySet()) { >> - runPulse(entry.getKey(), entry.getValue()); >> - } >> - for (Map.Entry<TKPulseListener,AccessControlContext> entry : >> scenePulseList.entrySet()) { >> - runPulse(entry.getKey(), entry.getValue()); >> - } >> - for (Map.Entry<TKPulseListener,AccessControlContext> entry : >> postScenePulseList.entrySet()) { >> - runPulse(entry.getKey(), entry.getValue()); >> + >> + for (int idx = 0, max = listenersList.size(); idx < max; idx++) { >> + Pair<WeakReference<TKPulseListener>, AccessControlContext> >> listenerEntry = listenersList.get(idx); >> + TKPulseListener pulseListener = listenerEntry.getKey().get(); >> + if (pulseListener != null) { >> + runPulse(pulseListener, listenerEntry.getValue()); >> + } >> } >> >> if (lastTkPulseListener != null) { >> runPulse(lastTkPulseListener, lastTkPulseAcc); >> } >> } >> + >> + private void copyListeners(Map<TKPulseListener, AccessControlContext> >> listenerMap, List<Pair<WeakReference<TKPulseListener>,AccessControlContext>> >> listenersList) { >> + if (!listenerMap.isEmpty()) { >> + for (Map.Entry<TKPulseListener,AccessControlContext> entry : >> listenerMap.entrySet()) { >> + listenersList.add(new Pair<>(new >> WeakReference<>(entry.getKey()), entry.getValue())); >> + } >> + } >> + } >> + >> public void addStageTkPulseListener(TKPulseListener listener) { >> if (listener == null) { >> return; >> >> >> >> >> >> >> >>