How do I make the "current directory" of the dialogs persist across
  different invocations?
You could modify the Singleton Pattern approach for this
Whereby you would only use one FileChooser and monitor/control the initial directory there, yet not directly exposing the instance to modifications outside of the class
For example:
public class RetentionFileChooser {
    private static FileChooser instance = null;
    private static SimpleObjectProperty<File> lastKnownDirectoryProperty = new SimpleObjectProperty<>();
    private RetentionFileChooser(){ }
    private static FileChooser getInstance(){
        if(instance == null) {
            instance = new FileChooser();
            instance.initialDirectoryProperty().bindBidirectional(lastKnownDirectoryProperty);
            //Set the FileExtensions you want to allow
            instance.getExtensionFilters().setAll(new ExtensionFilter("png files (*.png)", "*.png"));
        }
        return instance;
    }
    public static File showOpenDialog(){
        return showOpenDialog(null);
    }
    public static File showOpenDialog(Window ownerWindow){
        File chosenFile = getInstance().showOpenDialog(ownerWindow);
        if(chosenFile != null){
            //Set the property to the directory of the chosenFile so the fileChooser will open here next
            lastKnownDirectoryProperty.setValue(chosenFile.getParentFile());
        }
        return chosenFile;
    }
    public static File showSaveDialog(){
        return showSaveDialog(null);
    }
    public static File showSaveDialog(Window ownerWindow){
        File chosenFile = getInstance().showSaveDialog(ownerWindow);
        if(chosenFile != null){
            //Set the property to the directory of the chosenFile so the fileChooser will open here next
            lastKnownDirectoryProperty.setValue(chosenFile.getParentFile());
        }
        return chosenFile;
    }
}
This will set the initial directory of the FileChooser to be the directory of the file the user last opened/saved when it is re-used
Example usage:
File chosenFile = RetentionFileChooser.showOpenDialog();
However there is a limitation here:
//Set the FileExtensions you want to allow 
instance.getExtensionFilters().setAll(new ExtensionFilter("png files (*.png)", "*.png"));
As without providing any ExtensionFilter's the FileChooser becomes less user-friendly, requiring the user to manually append the file type, yet providing the filters when creating the instance with no way of updating them limits it to those same filters
One way to improve upon this is to expose optional filters within RetentionFileChooser that can be provided using varargs, whereby you can choose when to modify the filters as you display the dialogs
For example, building on the previous:
public class RetentionFileChooser {
    public enum FilterMode {
        //Setup supported filters
        PNG_FILES("png files (*.png)", "*.png"),
        TXT_FILES("txt files (*.txt)", "*.txt");
        private ExtensionFilter extensionFilter;
        FilterMode(String extensionDisplayName, String... extensions){
            extensionFilter = new ExtensionFilter(extensionDisplayName, extensions);
        }
        public ExtensionFilter getExtensionFilter(){
            return extensionFilter;
        }
    }
    private static FileChooser instance = null;
    private static SimpleObjectProperty<File> lastKnownDirectoryProperty = new SimpleObjectProperty<>();
    private RetentionFileChooser(){ }
    private static FileChooser getInstance(FilterMode... filterModes){
        if(instance == null) {
            instance = new FileChooser();
            instance.initialDirectoryProperty().bindBidirectional(lastKnownDirectoryProperty);
        }
        //Set the filters to those provided
        //You could add check's to ensure that a default filter is included, adding it if need be
        instance.getExtensionFilters().setAll(
                Arrays.stream(filterModes)
                        .map(FilterMode::getExtensionFilter)
                        .collect(Collectors.toList()));
        return instance;
    }
    public static File showOpenDialog(FilterMode... filterModes){
        return showOpenDialog(null, filterModes);
    }
    public static File showOpenDialog(Window ownerWindow, FilterMode...filterModes){
        File chosenFile = getInstance(filterModes).showOpenDialog(ownerWindow);
        if(chosenFile != null){
            lastKnownDirectoryProperty.setValue(chosenFile.getParentFile());
        }
        return chosenFile;
    }
    public static File showSaveDialog(FilterMode... filterModes){
        return showSaveDialog(null, filterModes);
    }
    public static File showSaveDialog(Window ownerWindow, FilterMode... filterModes){
        File chosenFile = getInstance(filterModes).showSaveDialog(ownerWindow);
        if(chosenFile != null){
            lastKnownDirectoryProperty.setValue(chosenFile.getParentFile());
        }
        return chosenFile;
    }
}
Example usage:
//Note the previous example still holds
File chosenFile = RetentionFileChooser.showOpenDialog();
File file = RetentionFileChooser.showSaveDialog(FilterMode.PNG_FILES);
but this does not work if the dialog is closed or canceled.
Unfortunately FileChooser doesn't expose information on what directory was being examined before it was closed / terminated. If you de-compile the class and trace through, it eventually hits a native call. So even if FileChooser wasn't final allowing it to be subclassed, it is just as unlikely to be able to get this information
The only gain the above approach provides around this is that it doesn't change the initial directory if this scenario is hit
If you're interested, there are some very good answers on the native key word within this question and those linked: