8203929: Limit amount of data for JFR.dump
authoregahlin
Mon, 25 Jun 2018 02:07:42 +0200
changeset 50745 a390cbb82d47
parent 50744 6c306d54366d
child 50746 85789fb05154
8203929: Limit amount of data for JFR.dump Reviewed-by: mgronlun
src/hotspot/share/jfr/dcmd/jfrDcmds.cpp
src/hotspot/share/jfr/dcmd/jfrDcmds.hpp
src/hotspot/share/jfr/utilities/jfrLogTagSets.hpp
src/hotspot/share/logging/logTag.hpp
src/jdk.jfr/share/classes/jdk/jfr/FlightRecorder.java
src/jdk.jfr/share/classes/jdk/jfr/FlightRecorderPermission.java
src/jdk.jfr/share/classes/jdk/jfr/Recording.java
src/jdk.jfr/share/classes/jdk/jfr/internal/LogTag.java
src/jdk.jfr/share/classes/jdk/jfr/internal/OldObjectSample.java
src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecorder.java
src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecording.java
src/jdk.jfr/share/classes/jdk/jfr/internal/PrivateAccess.java
src/jdk.jfr/share/classes/jdk/jfr/internal/Repository.java
src/jdk.jfr/share/classes/jdk/jfr/internal/ShutdownHook.java
src/jdk.jfr/share/classes/jdk/jfr/internal/Utils.java
src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/AbstractDCmd.java
src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdCheck.java
src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdConfigure.java
src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdDump.java
src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdStart.java
src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdStop.java
src/jdk.management.jfr/share/classes/jdk/management/jfr/MBeanUtils.java
test/jdk/jdk/jfr/jcmd/JcmdAsserts.java
test/jdk/jdk/jfr/jcmd/TestJcmdDump.java
test/jdk/jdk/jfr/jcmd/TestJcmdDumpGeneratedFilename.java
test/jdk/jdk/jfr/jcmd/TestJcmdDumpLimited.java
test/jdk/jdk/jfr/jcmd/TestJcmdLegacy.java
test/jdk/jdk/jfr/jcmd/TestJcmdStartStopDefault.java
--- a/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp	Sun Jun 24 16:25:47 2018 +0100
+++ b/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp	Mon Jun 25 02:07:42 2018 +0200
@@ -179,11 +179,19 @@
 
 JfrDumpFlightRecordingDCmd::JfrDumpFlightRecordingDCmd(outputStream* output,
                                                        bool heap) : DCmdWithParser(output, heap),
-  _name("name", "Recording name, e.g. \\\"My Recording\\\"", "STRING", true, NULL),
-  _filename("filename", "Copy recording data to file, i.e \\\"" JFR_FILENAME_EXAMPLE "\\\"", "STRING", true),
+  _name("name", "Recording name, e.g. \\\"My Recording\\\"", "STRING", false, NULL),
+  _filename("filename", "Copy recording data to file, e.g. \\\"" JFR_FILENAME_EXAMPLE "\\\"", "STRING", false),
+  _maxage("maxage", "Maximum duration to dump, in (s)econds, (m)inutes, (h)ours, or (d)ays, e.g. 60m, or 0 for no limit", "NANOTIME", false, "0"),
+  _maxsize("maxsize", "Maximum amount of bytes to dump, in (M)B or (G)B, e.g. 500M, or 0 for no limit", "MEMORY SIZE", false, "0"),
+  _begin("begin", "Point in time to dump data from, e.g. 09:00, 21:35:00, 2018-06-03T18:12:56.827Z, 2018-06-03T20:13:46.832, -10m, -3h, or -1d", "STRING", false),
+  _end("end", "Point in time to dump data to, e.g. 09:00, 21:35:00, 2018-06-03T18:12:56.827Z, 2018-06-03T20:13:46.832, -10m, -3h, or -1d", "STRING", false),
   _path_to_gc_roots("path-to-gc-roots", "Collect path to GC roots", "BOOLEAN", false, "false") {
   _dcmdparser.add_dcmd_option(&_name);
   _dcmdparser.add_dcmd_option(&_filename);
+  _dcmdparser.add_dcmd_option(&_maxage);
+  _dcmdparser.add_dcmd_option(&_maxsize);
+  _dcmdparser.add_dcmd_option(&_begin);
+  _dcmdparser.add_dcmd_option(&_end);
   _dcmdparser.add_dcmd_option(&_path_to_gc_roots);
 };
 
@@ -225,6 +233,26 @@
     filepath = JfrJavaSupport::new_string(_filename.value(), CHECK);
   }
 
+  jobject maxage = NULL;
+  if (_maxage.is_set()) {
+    maxage = JfrJavaSupport::new_java_lang_Long(_maxage.value()._nanotime, CHECK);
+  }
+
+  jobject maxsize = NULL;
+  if (_maxsize.is_set()) {
+    maxsize = JfrJavaSupport::new_java_lang_Long(_maxsize.value()._size, CHECK);
+  }
+
+  jstring begin = NULL;
+  if (_begin.is_set() && _begin.value() != NULL) {
+    begin = JfrJavaSupport::new_string(_begin.value(), CHECK);
+  }
+
+  jstring end = NULL;
+  if (_end.is_set() && _end.value() != NULL) {
+    end = JfrJavaSupport::new_string(_end.value(), CHECK);
+  }
+
   jobject path_to_gc_roots = NULL;
   if (_path_to_gc_roots.is_set()) {
     path_to_gc_roots = JfrJavaSupport::new_java_lang_Boolean(_path_to_gc_roots.value(), CHECK);
@@ -232,7 +260,7 @@
 
   static const char klass[] = "jdk/jfr/internal/dcmd/DCmdDump";
   static const char method[] = "execute";
-  static const char signature[] = "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)Ljava/lang/String;";
+  static const char signature[] = "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;)Ljava/lang/String;";
 
   JfrJavaArguments execute_args(&result, klass, method, signature, CHECK);
   execute_args.set_receiver(h_dcmd_instance);
@@ -240,6 +268,10 @@
   // arguments
   execute_args.push_jobject(name);
   execute_args.push_jobject(filepath);
+  execute_args.push_jobject(maxage);
+  execute_args.push_jobject(maxsize);
+  execute_args.push_jobject(begin);
+  execute_args.push_jobject(end);
   execute_args.push_jobject(path_to_gc_roots);
 
   JfrJavaSupport::call_virtual(&execute_args, THREAD);
@@ -247,7 +279,7 @@
 }
 
 JfrCheckFlightRecordingDCmd::JfrCheckFlightRecordingDCmd(outputStream* output, bool heap) : DCmdWithParser(output, heap),
-  _name("name","Recording text, e.g. \\\"My Recording\\\" or omit to see all recordings","STRING",false, NULL),
+  _name("name","Recording name, e.g. \\\"My Recording\\\" or omit to see all recordings","STRING",false, NULL),
   _verbose("verbose","Print event settings for the recording(s)","BOOLEAN",
            false, "false") {
   _dcmdparser.add_dcmd_option(&_name);
--- a/src/hotspot/share/jfr/dcmd/jfrDcmds.hpp	Sun Jun 24 16:25:47 2018 +0100
+++ b/src/hotspot/share/jfr/dcmd/jfrDcmds.hpp	Mon Jun 25 02:07:42 2018 +0200
@@ -31,6 +31,10 @@
  protected:
   DCmdArgument<char*> _name;
   DCmdArgument<char*> _filename;
+  DCmdArgument<NanoTimeArgument> _maxage;
+  DCmdArgument<MemorySizeArgument> _maxsize;
+  DCmdArgument<char*> _begin;
+  DCmdArgument<char*> _end;
   DCmdArgument<bool>  _path_to_gc_roots;
 
  public:
--- a/src/hotspot/share/jfr/utilities/jfrLogTagSets.hpp	Sun Jun 24 16:25:47 2018 +0100
+++ b/src/hotspot/share/jfr/utilities/jfrLogTagSets.hpp	Mon Jun 25 02:07:42 2018 +0200
@@ -55,7 +55,8 @@
   JFR_LOG_TAG(jfr, system, metadata) \
   JFR_LOG_TAG(jfr, metadata) \
   JFR_LOG_TAG(jfr, event) \
-  JFR_LOG_TAG(jfr, setting)
+  JFR_LOG_TAG(jfr, setting) \
+  JFR_LOG_TAG(jfr, dcmd)
   /* NEW TAGS, DONT FORGET TO UPDATE JAVA SIDE */
 
 #endif // SHARE_VM_JFR_UTILITIES_JFRLOGTAGSETS_HPP
--- a/src/hotspot/share/logging/logTag.hpp	Sun Jun 24 16:25:47 2018 +0100
+++ b/src/hotspot/share/logging/logTag.hpp	Mon Jun 25 02:07:42 2018 +0200
@@ -44,6 +44,7 @@
   LOG_TAG(blocks) \
   LOG_TAG(bot) \
   LOG_TAG(breakpoint) \
+  LOG_TAG(bytecode) \
   LOG_TAG(cds) \
   LOG_TAG(census) \
   LOG_TAG(class) \
@@ -60,11 +61,13 @@
   LOG_TAG(cset) \
   LOG_TAG(data) \
   LOG_TAG(datacreation) \
+  LOG_TAG(dcmd) \
   LOG_TAG(decoder) \
   LOG_TAG(defaultmethods) \
   LOG_TAG(director) \
   LOG_TAG(dump) \
   LOG_TAG(ergo) \
+  LOG_TAG(event) \
   LOG_TAG(exceptions) \
   LOG_TAG(exit) \
   LOG_TAG(fingerprint) \
@@ -81,6 +84,7 @@
   LOG_TAG(inlining) \
   LOG_TAG(interpreter) \
   LOG_TAG(itables) \
+  LOG_TAG(jfr) \
   LOG_TAG(jit) \
   LOG_TAG(jni) \
   LOG_TAG(jvmti) \
@@ -105,6 +109,7 @@
   LOG_TAG(normalize) \
   LOG_TAG(objecttagging) \
   LOG_TAG(obsolete) \
+  LOG_TAG(oldobject) \
   LOG_TAG(oom) \
   LOG_TAG(oopmap) \
   LOG_TAG(oops) \
@@ -126,10 +131,13 @@
   LOG_TAG(region) \
   LOG_TAG(reloc) \
   LOG_TAG(remset) \
+  LOG_TAG(parser) \
   LOG_TAG(purge) \
   LOG_TAG(resolve) \
   LOG_TAG(safepoint) \
+  LOG_TAG(sampling) \
   LOG_TAG(scavenge) \
+  LOG_TAG(setting) \
   LOG_TAG(smr) \
   LOG_TAG(stacktrace) \
   LOG_TAG(stackwalk) \
@@ -143,6 +151,7 @@
   LOG_TAG(subclass) \
   LOG_TAG(survivor) \
   LOG_TAG(sweep) \
+  LOG_TAG(system) \
   LOG_TAG(table) \
   LOG_TAG(task) \
   DEBUG_ONLY(LOG_TAG(test)) \
@@ -160,15 +169,7 @@
   LOG_TAG(vmoperation) \
   LOG_TAG(vmthread) \
   LOG_TAG(vtables) \
-  LOG_TAG(workgang) \
-  LOG_TAG(jfr) \
-  LOG_TAG(system) \
-  LOG_TAG(parser) \
-  LOG_TAG(bytecode) \
-  LOG_TAG(setting) \
-  LOG_TAG(oldobject) \
-  LOG_TAG(sampling) \
-  LOG_TAG(event)
+  LOG_TAG(workgang)
   LOG_TAG_LIST_EXT
 
 #define PREFIX_LOG_TAG(T) (LogTag::_##T)
--- a/src/jdk.jfr/share/classes/jdk/jfr/FlightRecorder.java	Sun Jun 24 16:25:47 2018 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/FlightRecorder.java	Mon Jun 25 02:07:42 2018 +0200
@@ -34,7 +34,6 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.Map;
 import java.util.Objects;
 
 import jdk.jfr.internal.JVM;
@@ -77,8 +76,8 @@
      */
     public List<Recording> getRecordings() {
         List<Recording> recs = new ArrayList<>();
-        for (PlatformRecording internal : internal.getRecordings()) {
-            recs.add(internal.getRecording());
+        for (PlatformRecording r : internal.getRecordings()) {
+            recs.add(r.getRecording());
         }
         return Collections.unmodifiableList(recs);
     }
@@ -112,7 +111,10 @@
      * @return a snapshot of all available recording data, not {@code null}
      */
     public Recording takeSnapshot() {
-        return internal.newSnapshot();
+        Recording snapshot = new Recording();
+        snapshot.setName("Snapshot");
+        internal.fillWithRecordedData(snapshot.getInternal(), null);
+        return snapshot;
     }
 
     /**
@@ -345,9 +347,7 @@
         return initialized;
     }
 
-    // package private
-    PlatformRecording newInternalRecording(Map<String, String> settings) {
-        return internal.newRecording(settings);
+    PlatformRecorder getInternal() {
+        return internal;
     }
-
 }
--- a/src/jdk.jfr/share/classes/jdk/jfr/FlightRecorderPermission.java	Sun Jun 24 16:25:47 2018 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/FlightRecorderPermission.java	Mon Jun 25 02:07:42 2018 +0200
@@ -31,6 +31,7 @@
 import java.util.Objects;
 
 import jdk.jfr.internal.PlatformEventType;
+import jdk.jfr.internal.PlatformRecorder;
 import jdk.jfr.internal.PlatformRecording;
 import jdk.jfr.internal.PrivateAccess;
 import jdk.jfr.internal.Type;
@@ -185,6 +186,11 @@
         public boolean isUnsigned(ValueDescriptor v) {
             return v.isUnsigned();
         }
+
+        @Override
+        public PlatformRecorder getPlatformRecorder() {
+            return FlightRecorder.getFlightRecorder().getInternal();
+        }
     }
 
     /**
--- a/src/jdk.jfr/share/classes/jdk/jfr/Recording.java	Sun Jun 24 16:25:47 2018 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/Recording.java	Mon Jun 25 02:07:42 2018 +0200
@@ -31,11 +31,11 @@
 import java.nio.file.Path;
 import java.time.Duration;
 import java.time.Instant;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
 
+import jdk.jfr.internal.PlatformRecorder;
 import jdk.jfr.internal.PlatformRecording;
 import jdk.jfr.internal.Type;
 import jdk.jfr.internal.Utils;
@@ -93,11 +93,14 @@
 
     private final PlatformRecording internal;
 
-    private Recording(PlatformRecording internal) {
-        this.internal = internal;
-        this.internal.setRecording(this);
-        if (internal.getRecording() != this) {
-            throw new InternalError("Internal recording not properly setup");
+    public Recording(Map<String, String> settings) {
+        PlatformRecorder r = FlightRecorder.getFlightRecorder().getInternal();
+        synchronized (r) {
+            this.internal = r.newRecording(settings);
+            this.internal.setRecording(this);
+            if (internal.getRecording() != this) {
+                throw new InternalError("Internal recording not properly setup");
+            }
         }
     }
 
@@ -115,8 +118,8 @@
      *         FlightRecorderPermission "accessFlightRecorder" is not set.
      */
     public Recording() {
-        this(FlightRecorder.getFlightRecorder().newInternalRecording(new HashMap<String, String>()));
-    }
+        this(new HashMap<String, String>());
+     }
 
     /**
      * Creates a recording with settings from a configuration.
@@ -145,7 +148,7 @@
      * @see Configuration
      */
     public Recording(Configuration configuration) {
-        this(FlightRecorder.getFlightRecorder().newInternalRecording(configuration.getSettings()));
+        this(configuration.getSettings());
     }
 
     /**
@@ -370,7 +373,8 @@
      */
     public void dump(Path destination) throws IOException {
         Objects.requireNonNull(destination);
-        internal.copyTo(new WriteableUserPath(destination), "Dumped by user", Collections.emptyMap());
+        internal.dump(new WriteableUserPath(destination));
+
     }
 
     /**
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/LogTag.java	Sun Jun 24 16:25:47 2018 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/LogTag.java	Mon Jun 25 02:07:42 2018 +0200
@@ -73,7 +73,12 @@
     /**
      * Covers setting (for users of the JDK)
      */
-    JFR_SETTING(9);
+    JFR_SETTING(9),
+    /**
+     * Covers usage of jcmd with JFR
+     */
+    JFR_DCMD(10);
+
     /* set from native side */
     private volatile int tagSetLevel = 100; // prevent logging if JVM log system has not been initialized
 
@@ -86,4 +91,8 @@
     public boolean shouldLog(int level) {
         return level >= tagSetLevel;
     }
+
+    public boolean shouldLog(LogLevel logLevel) {
+        return shouldLog(logLevel.level);
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/OldObjectSample.java	Mon Jun 25 02:07:42 2018 +0200
@@ -0,0 +1,73 @@
+package jdk.jfr.internal;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import jdk.jfr.Enabled;
+import jdk.jfr.RecordingState;
+import jdk.jfr.internal.settings.CutoffSetting;
+import jdk.jfr.internal.test.WhiteBox;
+
+// The Old Object event could have been implemented as a periodic event, but
+// due to chunk rotations and how settings are calculated when multiple recordings
+// are running at the same time, it would lead to unacceptable overhead.
+//
+// Instead, the event is only emitted before a recording stops and
+// if that recording has the event enabled.
+//
+// This requires special handling and the purpose of this class is to provide that
+//
+public final class OldObjectSample {
+
+    private static final String EVENT_NAME = Type.EVENT_NAME_PREFIX + "OldObjectSample";
+    private static final String OLD_OBJECT_CUTOFF = EVENT_NAME + "#" + Cutoff.NAME;
+    private static final String OLD_OBJECT_ENABLED = EVENT_NAME + "#" + Enabled.NAME;
+
+    // Emit if old object is enabled in recoding with cutoff for that recording
+    public static void emit(PlatformRecording recording) {
+        if (isEnabled(recording)) {
+            long nanos = CutoffSetting.parseValueSafe(recording.getSettings().get(OLD_OBJECT_CUTOFF));
+            long ticks = Utils.nanosToTicks(nanos);
+            JVM.getJVM().emitOldObjectSamples(ticks, WhiteBox.getWriteAllObjectSamples());
+        }
+    }
+
+    // Emit if old object is enabled for at least one recording, and use the largest
+    // cutoff for an enabled recoding
+    public static void emit(List<PlatformRecording> recordings, Boolean pathToGcRoots) {
+        boolean enabled = false;
+        long cutoffNanos = Boolean.TRUE.equals(pathToGcRoots) ? Long.MAX_VALUE : 0L;
+        for (PlatformRecording r : recordings) {
+            if (r.getState() == RecordingState.RUNNING) {
+                if (isEnabled(r)) {
+                    enabled = true;
+                    long c = CutoffSetting.parseValueSafe(r.getSettings().get(OLD_OBJECT_CUTOFF));
+                    cutoffNanos = Math.max(c, cutoffNanos);
+                }
+            }
+        }
+        if (enabled) {
+            long ticks = Utils.nanosToTicks(cutoffNanos);
+            JVM.getJVM().emitOldObjectSamples(ticks, WhiteBox.getWriteAllObjectSamples());
+        }
+    }
+
+    public static void updateSettingPathToGcRoots(Map<String, String> s, Boolean pathToGcRoots) {
+        if (pathToGcRoots != null) {
+            s.put(OLD_OBJECT_CUTOFF, pathToGcRoots ? "infinity" : "0 ns");
+        }
+    }
+
+    public static Map<String, String> createSettingsForSnapshot(PlatformRecording recording, Boolean pathToGcRoots) {
+        Map<String, String> settings = new HashMap<>(recording.getSettings());
+        updateSettingPathToGcRoots(settings, pathToGcRoots);
+        return settings;
+    }
+
+    private static boolean isEnabled(PlatformRecording r) {
+        Map<String, String> settings = r.getSettings();
+        String s = settings.get(OLD_OBJECT_ENABLED);
+        return "true".equals(s);
+    }
+}
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecorder.java	Sun Jun 24 16:25:47 2018 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecorder.java	Mon Jun 25 02:07:42 2018 +0200
@@ -38,6 +38,7 @@
 import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -46,7 +47,6 @@
 import java.util.TimerTask;
 import java.util.concurrent.CopyOnWriteArrayList;
 
-import jdk.jfr.Enabled;
 import jdk.jfr.EventType;
 import jdk.jfr.FlightRecorder;
 import jdk.jfr.FlightRecorderListener;
@@ -56,8 +56,6 @@
 import jdk.jfr.events.ActiveSettingEvent;
 import jdk.jfr.internal.SecuritySupport.SecureRecorderListener;
 import jdk.jfr.internal.instrument.JDKEvents;
-import jdk.jfr.internal.settings.CutoffSetting;
-import jdk.jfr.internal.test.WhiteBox;
 
 public final class PlatformRecorder {
 
@@ -111,7 +109,16 @@
         return newRecording(settings, ++recordingCounter);
     }
 
-    public synchronized PlatformRecording newRecording(Map<String, String> settings, long id) {
+    // To be used internally when doing dumps.
+    // Caller must have recorder lock and close recording before releasing lock
+    public PlatformRecording newTemporaryRecording() {
+        if(!Thread.holdsLock(this)) {
+            throw new InternalError("Caller must have recorder lock");
+        }
+        return newRecording(new HashMap<>(), 0);
+    }
+
+    private synchronized PlatformRecording newRecording(Map<String, String> settings, long id) {
         PlatformRecording recording = new PlatformRecording(this, id);
         if (!settings.isEmpty()) {
             recording.setSettings(settings);
@@ -249,7 +256,7 @@
         RequestEngine.doChunkBegin();
     }
 
-    synchronized void stop(PlatformRecording recording, WriteableUserPath alternativePath) {
+    synchronized void stop(PlatformRecording recording) {
         RecordingState state = recording.getState();
 
         if (Utils.isAfter(state, RecordingState.RUNNING)) {
@@ -270,7 +277,7 @@
                 }
             }
         }
-        emitOldObjectSamples(recording);
+        OldObjectSample.emit(recording);
 
         if (endPhysical) {
             RequestEngine.doChunkEnd();
@@ -282,7 +289,7 @@
                 }
             } else {
                 // last memory
-                dumpMemoryToDestination(recording, alternativePath);
+                dumpMemoryToDestination(recording);
             }
             jvm.endRecording_();
             disableEvents();
@@ -306,26 +313,13 @@
         recording.setState(RecordingState.STOPPED);
     }
 
-    // Only dump event if the recording that is being stopped
-    // has OldObjectSample enabled + cutoff from recording, not global value
-    private void emitOldObjectSamples(PlatformRecording recording) {
-        Map<String, String> settings = recording.getSettings();
-        String s = settings.get(Type.EVENT_NAME_PREFIX + "OldObjectSample#" + Enabled.NAME);
-        if ("true".equals(s)) {
-            long nanos = CutoffSetting.parseValueSafe(settings.get(Type.EVENT_NAME_PREFIX + "OldObjectSample#" + Cutoff.NAME));
-            long ticks = Utils.nanosToTicks(nanos);
-            JVM.getJVM().emitOldObjectSamples(ticks, WhiteBox.getWriteAllObjectSamples());
-        }
-    }
-
-    private void dumpMemoryToDestination(PlatformRecording recording, WriteableUserPath alternativePath) {
-        WriteableUserPath dest = alternativePath != null ? alternativePath : recording.getDestination();
+    private void dumpMemoryToDestination(PlatformRecording recording)  {
+        WriteableUserPath dest = recording.getDestination();
         if (dest != null) {
             MetadataRepository.getInstance().setOutput(dest.getText());
             recording.clearDestination();
         }
     }
-
     private void disableEvents() {
         MetadataRepository.getInstance().disableEvents();
     }
@@ -504,47 +498,47 @@
         return newRec;
     }
 
-    public synchronized Recording newSnapshot() {
+    public synchronized void fillWithRecordedData(PlatformRecording target, Boolean pathToGcRoots) {
         boolean running = false;
         boolean toDisk = false;
+
         for (PlatformRecording r : recordings) {
             if (r.getState() == RecordingState.RUNNING) {
                 running = true;
+                if (r.isToDisk()) {
+                    toDisk = true;
+                }
             }
-            if (r.isToDisk()) {
-                toDisk = true;
-            }
-
         }
         // If needed, flush data from memory
         if (running) {
             if (toDisk) {
+                OldObjectSample.emit(recordings, pathToGcRoots);
                 rotateDisk();
             } else {
-                try (Recording snapshot = new Recording()) {
-                    PlatformRecording internal = PrivateAccess.getInstance().getPlatformRecording(snapshot);
-                    internal.setShouldWriteActiveRecordingEvent(false);
+                try (PlatformRecording snapshot = newTemporaryRecording()) {
+                    snapshot.setToDisk(true);
+                    snapshot.setShouldWriteActiveRecordingEvent(false);
                     snapshot.start();
-                    snapshot.stop();
-                    return makeRecordingWithDiskChunks();
+                    OldObjectSample.emit(recordings, pathToGcRoots);
+                    snapshot.stop("Snapshot dump");
+                    fillWithDiskChunks(target);
                 }
+                return;
             }
         }
-        return makeRecordingWithDiskChunks();
+        fillWithDiskChunks(target);
     }
 
-    private Recording makeRecordingWithDiskChunks() {
-        Recording snapshot = new Recording();
-        snapshot.setName("Snapshot");
-        PlatformRecording internal = PrivateAccess.getInstance().getPlatformRecording(snapshot);
+    private void fillWithDiskChunks(PlatformRecording target) {
         for (RepositoryChunk c : makeChunkList(null, null)) {
-            internal.add(c);
+            target.add(c);
         }
-        internal.setState(RecordingState.STOPPED);
+        target.setState(RecordingState.STOPPED);
         Instant startTime = null;
         Instant endTime = null;
 
-        for (RepositoryChunk c : makeChunkList(null, null)) {
+        for (RepositoryChunk c : target.getChunks()) {
             if (startTime == null || c.getStartTime().isBefore(startTime)) {
                 startTime = c.getStartTime();
             }
@@ -559,9 +553,8 @@
         if (endTime == null) {
             endTime = now;
         }
-        internal.setStartTime(startTime);
-        internal.setStopTime(endTime);
-        internal.setInternalDuration(Duration.between(startTime, endTime));
-        return snapshot;
+        target.setStartTime(startTime);
+        target.setStopTime(endTime);
+        target.setInternalDuration(Duration.between(startTime, endTime));
     }
 }
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecording.java	Sun Jun 24 16:25:47 2018 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecording.java	Mon Jun 25 02:07:42 2018 +0200
@@ -41,7 +41,6 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
-import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.LinkedList;
 import java.util.List;
@@ -54,6 +53,7 @@
 import jdk.jfr.FlightRecorderListener;
 import jdk.jfr.Recording;
 import jdk.jfr.RecordingState;
+import jdk.jfr.internal.SecuritySupport.SafePath;
 
 public final class PlatformRecording implements AutoCloseable {
 
@@ -70,6 +70,7 @@
     private boolean toDisk = true;
     private String name;
     private boolean dumpOnExit;
+    private SafePath dumpOnExitDirectory = new SafePath(".");
     // Timestamp information
     private Instant stopTime;
     private Instant startTime;
@@ -89,7 +90,7 @@
         // when you call dump(Path) or setDdestination(Path),
         // but if no destination is set and dumponexit=true
         // the control context of the recording is taken when the
-        // Recording object is constructed.  This works well for
+        // Recording object is constructed. This works well for
         // -XX:StartFlightRecording and JFR.dump
         this.noDestinationDumpOnExitAccessControlContext = AccessController.getContext();
         this.id = id;
@@ -114,45 +115,37 @@
             Logger.log(LogTag.JFR, LogLevel.INFO, () -> {
                 // Only print non-default values so it easy to see
                 // which options were added
-                    StringJoiner options = new StringJoiner(", ");
-                    if (!toDisk) {
-                        options.add("disk=false");
-                    }
-                    if (maxAge != null) {
-                        options.add("maxage=" + Utils.formatTimespan(maxAge, ""));
-                    }
-                    if (maxSize != 0) {
-                        options.add("maxsize=" + Utils.formatBytes(maxSize, ""));
-                    }
-                    if (dumpOnExit) {
-                        options.add("dumponexit=true");
-                    }
-                    if (duration != null) {
-                        options.add("duration=" + Utils.formatTimespan(duration, ""));
-                    }
-                    if (destination != null) {
-                        options.add("filename=" + destination.getText());
-                    }
-                    String optionText = options.toString();
-                    if (optionText.length() != 0) {
-                        optionText = "{" + optionText + "}";
-                    }
-                    return "Started recording \"" + getName() + "\" (" + getId() + ") " + optionText;
-                });
+                StringJoiner options = new StringJoiner(", ");
+                if (!toDisk) {
+                    options.add("disk=false");
+                }
+                if (maxAge != null) {
+                    options.add("maxage=" + Utils.formatTimespan(maxAge, ""));
+                }
+                if (maxSize != 0) {
+                    options.add("maxsize=" + Utils.formatBytes(maxSize, ""));
+                }
+                if (dumpOnExit) {
+                    options.add("dumponexit=true");
+                }
+                if (duration != null) {
+                    options.add("duration=" + Utils.formatTimespan(duration, ""));
+                }
+                if (destination != null) {
+                    options.add("filename=" + destination.getText());
+                }
+                String optionText = options.toString();
+                if (optionText.length() != 0) {
+                    optionText = "{" + optionText + "}";
+                }
+                return "Started recording \"" + getName() + "\" (" + getId() + ") " + optionText;
+            });
             newState = getState();
         }
         notifyIfStateChanged(oldState, newState);
     }
 
     public boolean stop(String reason) {
-        return stop(reason, null);
-    }
-
-    public boolean stop(String reason, WriteableUserPath alternativePath) {
-        return stop(reason, alternativePath, Collections.emptyMap());
-    }
-
-    boolean stop(String reason, WriteableUserPath alternativePath, Map<String, String> overlaySettings) {
         RecordingState oldState;
         RecordingState newState;
         synchronized (recorder) {
@@ -161,23 +154,21 @@
                 stopTask.cancel();
                 stopTask = null;
             }
-            recorder.stop(this, alternativePath);
+            recorder.stop(this);
             String endText = reason == null ? "" : ". Reason \"" + reason + "\".";
-            Logger.log(LogTag.JFR, LogLevel.INFO, "Stopped recording \"" + getName() + "\" (" + getId()+ ")" + endText);
+            Logger.log(LogTag.JFR, LogLevel.INFO, "Stopped recording \"" + getName() + "\" (" + getId() + ")" + endText);
             this.stopTime = Instant.now();
             newState = getState();
         }
         WriteableUserPath dest = getDestination();
-        if (dest == null && alternativePath != null) {
-            dest = alternativePath;
-        }
+
         if (dest != null) {
             try {
-                copyTo(dest, reason, overlaySettings);
-                Logger.log(LogTag.JFR, LogLevel.INFO, "Wrote recording \"" + getName() + "\" (" + getId()+ ") to " + dest.getText());
+                dumpStopped(dest);
+                Logger.log(LogTag.JFR, LogLevel.INFO, "Wrote recording \"" + getName() + "\" (" + getId() + ") to " + dest.getText());
                 notifyIfStateChanged(newState, oldState);
                 close(); // remove if copied out
-            } catch (IOException e) {
+            } catch(IOException e) {
                 // throw e; // BUG8925030
             }
         } else {
@@ -195,7 +186,7 @@
             setState(RecordingState.DELAYED);
             startTask = createStartTask();
             recorder.getTimer().schedule(startTask, delay.toMillis());
-            Logger.log(LogTag.JFR, LogLevel.INFO, "Scheduled recording \"" + getName() + "\" (" + getId()+ ") to start at " + now);
+            Logger.log(LogTag.JFR, LogLevel.INFO, "Scheduled recording \"" + getName() + "\" (" + getId() + ") to start at " + now);
         }
     }
 
@@ -271,7 +262,6 @@
         }
     }
 
-
     public RecordingState getState() {
         synchronized (recorder) {
             return state;
@@ -296,79 +286,67 @@
                 }
                 chunks.clear();
                 setState(RecordingState.CLOSED);
-                Logger.log(LogTag.JFR, LogLevel.INFO, "Closed recording \"" + getName() + "\" (" + getId()+ ")");
+                Logger.log(LogTag.JFR, LogLevel.INFO, "Closed recording \"" + getName() + "\" (" + getId() + ")");
             }
             newState = getState();
         }
         notifyIfStateChanged(newState, oldState);
     }
 
-    public void copyTo(WriteableUserPath path, String reason, Map<String, String> dumpSettings) throws IOException {
-        synchronized (recorder) {
-            RecordingState state = getState();
-            if (state == RecordingState.CLOSED) {
-                throw new IOException("Recording \"" + name + "\" (id=" + id + ") has been closed, no contents to write");
+    // To be used internally when doing dumps.
+    // Caller must have recorder lock and close recording before releasing lock
+    public PlatformRecording newSnapshotClone(String reason, Boolean pathToGcRoots) throws IOException {
+        if(!Thread.holdsLock(recorder)) {
+            throw new InternalError("Caller must have recorder lock");
+        }
+        RecordingState state = getState();
+        if (state == RecordingState.CLOSED) {
+            throw new IOException("Recording \"" + name + "\" (id=" + id + ") has been closed, no contents to write");
+        }
+        if (state == RecordingState.DELAYED || state == RecordingState.NEW) {
+            throw new IOException("Recording \"" + name + "\" (id=" + id + ") has not started, no contents to write");
+        }
+        if (state == RecordingState.STOPPED) {
+            PlatformRecording clone = recorder.newTemporaryRecording();
+            for (RepositoryChunk r : chunks) {
+                clone.add(r);
             }
-            if (state == RecordingState.DELAYED || state == RecordingState.NEW) {
-                throw new IOException("Recording \"" + name + "\" (id=" + id + ") has not started, no contents to write");
-            }
-            if (state == RecordingState.STOPPED) {
-                // have all we need, just write it
-                dumpToFile(path, reason, getId());
-                return;
-            }
+            return clone;
+        }
 
-            // Recording is RUNNING, create a clone
-            try(PlatformRecording clone = recorder.newRecording(Collections.emptyMap(), 0)) {
-                clone.setShouldWriteActiveRecordingEvent(false);
-                clone.setName(getName());
-                clone.setDestination(path);
-                clone.setToDisk(true);
-                // We purposely don't clone settings, since
-                // a union a == a
-                if (!isToDisk()) {
-                    // force memory contents to disk
-                    clone.start();
-                } else {
-                    // using existing chunks on disk
-                    for (RepositoryChunk c : chunks) {
-                        clone.add(c);
-                    }
-                    clone.setState(RecordingState.RUNNING);
-                    clone.setStartTime(getStartTime());
-                }
-                if (dumpSettings.isEmpty()) {
-                    clone.setSettings(getSettings());
-                    clone.stop(reason); // dumps to destination path here
-                } else {
-                    // Risk of violating lock order here, since
-                    // clone.stop() will take recorder lock inside
-                    // metadata lock, but OK if we already
-                    // have recorder lock when we entered metadata lock
-                    Thread.holdsLock(recorder);
-                    synchronized(MetadataRepository.getInstance()) {
-                        Thread.holdsLock(recorder);
-                        Map<String, String> oldSettings = getSettings();
-                        Map<String, String> newSettings = new HashMap<>(oldSettings);
-                        // replace with dump settings
-                        newSettings.putAll(dumpSettings);
-                        clone.setSettings(newSettings);
-                        clone.stop(reason);
-                    }
-                }
+        // Recording is RUNNING, create a clone
+        PlatformRecording clone = recorder.newTemporaryRecording();
+        clone.setShouldWriteActiveRecordingEvent(false);
+        clone.setName(getName());
+        clone.setDestination(this.destination);
+        clone.setToDisk(true);
+        // We purposely don't clone settings here, since
+        // a union a == a
+        if (!isToDisk()) {
+            // force memory contents to disk
+            clone.start();
+        } else {
+            // using existing chunks on disk
+            for (RepositoryChunk c : chunks) {
+                clone.add(c);
             }
-            return;
+            clone.setState(RecordingState.RUNNING);
+            clone.setStartTime(getStartTime());
         }
-    }
-
-    private void dumpToFile(WriteableUserPath userPath, String reason, long id) throws IOException {
-        userPath.doPriviligedIO(() -> {
-            try (ChunksChannel cc = new ChunksChannel(chunks); FileChannel fc = FileChannel.open(userPath.getReal(), StandardOpenOption.WRITE, StandardOpenOption.APPEND)) {
-                cc.transferTo(fc);
-                fc.force(true);
+        if (pathToGcRoots == null) {
+            clone.setSettings(getSettings()); // needed for old object sample
+            clone.stop(reason); // dumps to destination path here
+        } else {
+            // Risk of violating lock order here, since
+            // clone.stop() will take recorder lock inside
+            // metadata lock, but OK if we already
+            // have recorder lock when we entered metadata lock
+            synchronized (MetadataRepository.getInstance()) {
+                clone.setSettings(OldObjectSample.createSettingsForSnapshot(this, pathToGcRoots));
+                clone.stop(reason);
             }
-            return null;
-        });
+        }
+        return clone;
     }
 
     public boolean isToDisk() {
@@ -387,7 +365,7 @@
         }
     }
 
-   public void setDestination(WriteableUserPath userSuppliedPath) throws IOException {
+    public void setDestination(WriteableUserPath userSuppliedPath) throws IOException {
         synchronized (recorder) {
             if (Utils.isState(getState(), RecordingState.STOPPED, RecordingState.CLOSED)) {
                 throw new IllegalStateException("Destination can't be set on a recording that has been stopped/closed");
@@ -479,7 +457,7 @@
             TreeMap<String, String> ordered = new TreeMap<>(settings);
             Logger.log(LogTag.JFR_SETTING, LogLevel.INFO, "New settings for recording \"" + getName() + "\" (" + getId() + ")");
             for (Map.Entry<String, String> entry : ordered.entrySet()) {
-                String text =  entry.getKey() + "=\"" + entry.getValue() + "\"";
+                String text = entry.getKey() + "=\"" + entry.getValue() + "\"";
                 Logger.log(LogTag.JFR_SETTING, LogLevel.INFO, text);
             }
         }
@@ -491,7 +469,6 @@
         }
     }
 
-
     private void notifyIfStateChanged(RecordingState newState, RecordingState oldState) {
         if (oldState == newState) {
             return;
@@ -580,12 +557,12 @@
     private void added(RepositoryChunk c) {
         c.use();
         size += c.getSize();
-        Logger.log(JFR, DEBUG,  ()-> "Recording \"" + name + "\" (" + id + ") added chunk " + c.toString() + ", current size=" + size);
+        Logger.log(JFR, DEBUG, () -> "Recording \"" + name + "\" (" + id + ") added chunk " + c.toString() + ", current size=" + size);
     }
 
     private void removed(RepositoryChunk c) {
         size -= c.getSize();
-        Logger.log(JFR, DEBUG,  ()->  "Recording \"" + name + "\" (" + id + ") removed chunk " + c.toString() + ", current size=" + size);
+        Logger.log(JFR, DEBUG, () -> "Recording \"" + name + "\" (" + id + ") removed chunk " + c.toString() + ", current size=" + size);
         c.release();
     }
 
@@ -661,7 +638,7 @@
                     stop("End of duration reached");
                 } catch (Throwable t) {
                     // Prevent malicious user to propagate exception callback in the wrong context
-                    Logger.log(LogTag.JFR, LogLevel.ERROR, "Could not stop recording. " + t.getMessage());
+                    Logger.log(LogTag.JFR, LogLevel.ERROR, "Could not stop recording.");
                 }
             }
         };
@@ -678,7 +655,7 @@
     }
 
     void clearDestination() {
-       destination = null;
+        destination = null;
     }
 
     public AccessControlContext getNoDestinationDumpOnExitAccessControlContext() {
@@ -686,10 +663,116 @@
     }
 
     void setShouldWriteActiveRecordingEvent(boolean shouldWrite) {
-       this.shuoldWriteActiveRecordingEvent = shouldWrite;
+        this.shuoldWriteActiveRecordingEvent = shouldWrite;
     }
 
     boolean shouldWriteMetadataEvent() {
         return shuoldWriteActiveRecordingEvent;
     }
+
+    // Dump running and stopped recordings
+    public void dump(WriteableUserPath writeableUserPath) throws IOException {
+        synchronized (recorder) {
+            try(PlatformRecording p = newSnapshotClone("Dumped by user", null))  {
+                p.dumpStopped(writeableUserPath);
+            }
+        }
+    }
+
+    public void dumpStopped(WriteableUserPath userPath) throws IOException {
+        synchronized (recorder) {
+                userPath.doPriviligedIO(() -> {
+                    try (ChunksChannel cc = new ChunksChannel(chunks); FileChannel fc = FileChannel.open(userPath.getReal(), StandardOpenOption.WRITE, StandardOpenOption.APPEND)) {
+                        cc.transferTo(fc);
+                        fc.force(true);
+                    }
+                    return null;
+                });
+        }
+    }
+
+    public void filter(Instant begin, Instant end, Long maxSize) {
+        synchronized (recorder) {
+            List<RepositoryChunk> result = removeAfter(end, removeBefore(begin, new ArrayList<>(chunks)));
+            if (maxSize != null) {
+                if (begin != null && end == null) {
+                    result = reduceFromBeginning(maxSize, result);
+                } else {
+                    result = reduceFromEnd(maxSize, result);
+                }
+            }
+            int size = 0;
+            for (RepositoryChunk r : result) {
+                size += r.getSize();
+                r.use();
+            }
+            this.size = size;
+            for (RepositoryChunk r : chunks) {
+                r.release();
+            }
+            chunks.clear();
+            chunks.addAll(result);
+        }
+    }
+
+    private static List<RepositoryChunk> removeBefore(Instant time, List<RepositoryChunk> input) {
+        if (time == null) {
+            return input;
+        }
+        List<RepositoryChunk> result = new ArrayList<>(input.size());
+        for (RepositoryChunk r : input) {
+            if (!r.getEndTime().isBefore(time)) {
+                result.add(r);
+            }
+        }
+        return result;
+    }
+
+    private static List<RepositoryChunk> removeAfter(Instant time, List<RepositoryChunk> input) {
+        if (time == null) {
+            return input;
+        }
+        List<RepositoryChunk> result = new ArrayList<>(input.size());
+        for (RepositoryChunk r : input) {
+            if (!r.getStartTime().isAfter(time)) {
+                result.add(r);
+            }
+        }
+        return result;
+    }
+
+    private static List<RepositoryChunk> reduceFromBeginning(Long maxSize, List<RepositoryChunk> input) {
+        if (maxSize == null || input.isEmpty()) {
+            return input;
+        }
+        List<RepositoryChunk> result = new ArrayList<>(input.size());
+        long total = 0;
+        for (RepositoryChunk r : input) {
+            total += r.getSize();
+            if (total > maxSize) {
+                break;
+            }
+            result.add(r);
+        }
+        // always keep at least one chunk
+        if (result.isEmpty()) {
+            result.add(input.get(0));
+        }
+        return result;
+    }
+
+    private static List<RepositoryChunk> reduceFromEnd(Long maxSize, List<RepositoryChunk> input) {
+        Collections.reverse(input);
+        List<RepositoryChunk> result = reduceFromBeginning(maxSize, input);
+        Collections.reverse(result);
+        return result;
+    }
+
+    public void setDumpOnExitDirectory(SafePath directory) {
+       this.dumpOnExitDirectory = directory;
+    }
+
+    public SafePath getDumpOnExitDirectory()  {
+        return this.dumpOnExitDirectory;
+    }
 }
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/PrivateAccess.java	Sun Jun 24 16:25:47 2018 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/PrivateAccess.java	Mon Jun 25 02:07:42 2018 +0200
@@ -92,4 +92,6 @@
     public abstract void setAnnotations(SettingDescriptor s, List<AnnotationElement> a);
 
     public abstract boolean isUnsigned(ValueDescriptor v);
+
+    public abstract PlatformRecorder getPlatformRecorder();
 }
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/Repository.java	Sun Jun 24 16:25:47 2018 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/Repository.java	Mon Jun 25 02:07:42 2018 +0200
@@ -41,7 +41,7 @@
     private static final JVM jvm = JVM.getJVM();
     private static final Repository instance = new Repository();
 
-    static DateTimeFormatter REPO_DATE_FORMAT = DateTimeFormatter
+    public final static DateTimeFormatter REPO_DATE_FORMAT = DateTimeFormatter
             .ofPattern("yyyy_MM_dd_HH_mm_ss");
 
     private final Set<SafePath> cleanupDirectories = new HashSet<>();
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/ShutdownHook.java	Sun Jun 24 16:25:47 2018 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/ShutdownHook.java	Mon Jun 25 02:07:42 2018 +0200
@@ -26,12 +26,10 @@
 package jdk.jfr.internal;
 
 import java.io.IOException;
-import java.nio.file.Paths;
 import java.security.AccessControlContext;
 import java.security.AccessController;
 import java.security.PrivilegedActionException;
 import java.security.PrivilegedExceptionAction;
-import java.time.LocalDateTime;
 
 import jdk.jfr.RecordingState;
 
@@ -67,9 +65,10 @@
             WriteableUserPath dest = recording.getDestination();
             if (dest == null) {
                 dest = makeDumpOnExitPath(recording);
+                recording.setDestination(dest);
             }
             if (dest != null) {
-                recording.stop("Dump on exit", dest);
+                recording.stop("Dump on exit");
             }
         } catch (Exception e) {
             Logger.log(LogTag.JFR, LogLevel.DEBUG, () -> "Could not dump recording " + recording.getName() + " on exit.");
@@ -78,23 +77,21 @@
 
     private WriteableUserPath makeDumpOnExitPath(PlatformRecording recording) {
         try {
-            String pid = JVM.getJVM().getPid();
-            String dfText = Repository.REPO_DATE_FORMAT.format(LocalDateTime.now());
-            String name = "hotspot-" + "pid-" + pid + "-id-" + recording.getId() + "-" + dfText + ".jfr";
+            String name = Utils.makeFilename(recording.getRecording());
             AccessControlContext acc = recording.getNoDestinationDumpOnExitAccessControlContext();
             return AccessController.doPrivileged(new PrivilegedExceptionAction<WriteableUserPath>() {
                 @Override
                 public WriteableUserPath run() throws Exception {
-                    return new WriteableUserPath(Paths.get(".", name));
+                    return new WriteableUserPath(recording.getDumpOnExitDirectory().toPath().resolve(name));
                 }
             }, acc);
         } catch (PrivilegedActionException e) {
             Throwable t = e.getCause();
             if (t instanceof SecurityException) {
-                Logger.log(LogTag.JFR, LogLevel.WARN, "Not allowed to create dump path for recording " + recording.getId() + " on exit. " + e.getMessage());
+                Logger.log(LogTag.JFR, LogLevel.WARN, "Not allowed to create dump path for recording " + recording.getId() + " on exit.");
             }
             if (t instanceof IOException) {
-                Logger.log(LogTag.JFR, LogLevel.WARN, "Could not dump " + recording.getId() + " on exit. " + e.getMessage());
+                Logger.log(LogTag.JFR, LogLevel.WARN, "Could not dump " + recording.getId() + " on exit.");
             }
             return null;
         }
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/Utils.java	Sun Jun 24 16:25:47 2018 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/Utils.java	Mon Jun 25 02:07:42 2018 +0200
@@ -43,6 +43,7 @@
 import java.lang.reflect.Modifier;
 import java.nio.file.Path;
 import java.time.Duration;
+import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -55,6 +56,7 @@
 import jdk.internal.org.objectweb.asm.util.CheckClassAdapter;
 import jdk.jfr.Event;
 import jdk.jfr.FlightRecorderPermission;
+import jdk.jfr.Recording;
 import jdk.jfr.RecordingState;
 import jdk.jfr.internal.handlers.EventHandler;
 import jdk.jfr.internal.settings.PeriodSetting;
@@ -484,13 +486,6 @@
         return Collections.unmodifiableList(list);
     }
 
-    public static void updateSettingPathToGcRoots(Map<String, String> settings, Boolean pathToGcRoots) {
-        if (pathToGcRoots != null) {
-            settings.put(Type.EVENT_NAME_PREFIX + "OldObjectSample#cutoff", pathToGcRoots ? "infinity" : "0 ns" );
-        }
-    }
-
-
     public static String upgradeLegacyJDKEvent(String eventName) {
         if (eventName.length() <= LEGACY_EVENT_NAME_PREFIX.length()) {
             return eventName;
@@ -503,4 +498,11 @@
         }
         return eventName;
     }
+
+    public static String makeFilename(Recording recording) {
+        String pid = JVM.getJVM().getPid();
+        String date = Repository.REPO_DATE_FORMAT.format(LocalDateTime.now());
+        String idText = recording == null ? "" :  "-id-" + Long.toString(recording.getId());
+        return "hotspot-" + "pid-" + pid + idText + "-" + date + ".jfr";
+    }
 }
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/AbstractDCmd.java	Sun Jun 24 16:25:47 2018 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/AbstractDCmd.java	Mon Jun 25 02:07:42 2018 +0200
@@ -27,8 +27,10 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.StringWriter;
+import java.nio.file.Files;
 import java.nio.file.InvalidPathException;
 import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -63,15 +65,19 @@
         return result.toString();
     }
 
-    protected final SafePath resolvePath(String path, String errorMsg) throws DCmdException {
-        if (path == null) {
-            return null;
+    protected final SafePath resolvePath(Recording recording, String filename) throws InvalidPathException {
+        if (filename == null) {
+             return makeGenerated(recording, Paths.get("."));
         }
-        try {
-            return new SafePath(path);
-        } catch (InvalidPathException e) {
-            throw new DCmdException(e, errorMsg, ", invalid path \"" + path + "\".");
+        Path path = Paths.get(filename);
+        if (Files.isDirectory(path)) {
+            return makeGenerated(recording, path);
         }
+        return new SafePath(path.toAbsolutePath().normalize());
+    }
+
+    private SafePath makeGenerated(Recording recording, Path directory) {
+        return new SafePath(directory.toAbsolutePath().resolve(Utils.makeFilename(recording)).normalize());
     }
 
     protected final Recording findRecording(String name) throws DCmdException {
@@ -83,10 +89,12 @@
         }
     }
 
-    protected final void reportOperationComplete(String actionPrefix, Recording r, SafePath file) {
+    protected final void reportOperationComplete(String actionPrefix, String name, SafePath file) {
         print(actionPrefix);
-        print(" recording ");
-        print("\"" + r.getName() + "\"");
+        print(" recording");
+        if (name != null) {
+            print(" \"" + name + "\"");
+        }
         if (file != null) {
             print(",");
             try {
@@ -136,7 +144,7 @@
     }
 
     protected final void printBytes(long bytes, String separation) {
-       print(Utils.formatBytes(bytes, separation));
+        print(Utils.formatBytes(bytes, separation));
     }
 
     protected final void printTimespan(Duration timespan, String separator) {
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdCheck.java	Sun Jun 24 16:25:47 2018 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdCheck.java	Mon Jun 25 02:07:42 2018 +0200
@@ -36,6 +36,9 @@
 import jdk.jfr.EventType;
 import jdk.jfr.Recording;
 import jdk.jfr.SettingDescriptor;
+import jdk.jfr.internal.LogLevel;
+import jdk.jfr.internal.LogTag;
+import jdk.jfr.internal.Logger;
 
 /**
  * JFR.check - invoked from native
@@ -59,13 +62,17 @@
         return getResult();
     }
 
-    private void executeInternal(String recordingText, Boolean verbose) throws DCmdException {
+    private void executeInternal(String name, Boolean verbose) throws DCmdException {
+        if (LogTag.JFR_DCMD.shouldLog(LogLevel.DEBUG)) {
+            Logger.log(LogTag.JFR_DCMD, LogLevel.DEBUG, "Executing DCmdCheck: name=" + name + ", verbose=" + verbose);
+        }
+
         if (verbose == null) {
             verbose = Boolean.FALSE;
         }
 
-        if (recordingText != null) {
-            printRecording(findRecording(recordingText), verbose);
+        if (name != null) {
+            printRecording(findRecording(name), verbose);
             return;
         }
 
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdConfigure.java	Sun Jun 24 16:25:47 2018 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdConfigure.java	Mon Jun 25 02:07:42 2018 +0200
@@ -70,6 +70,19 @@
             Boolean sampleThreads
 
     ) throws DCmdException {
+        if (LogTag.JFR_DCMD.shouldLog(LogLevel.DEBUG)) {
+            Logger.log(LogTag.JFR_DCMD, LogLevel.DEBUG, "Executing DCmdConfigure: repositorypath=" + repositoryPath +
+                    ", dumppath=" + dumpPath +
+                    ", stackdepth=" + stackDepth +
+                    ", globalbuffercount=" + globalBufferCount +
+                    ", globalbuffersize=" + globalBufferSize +
+                    ", thread_buffer_size" + threadBufferSize +
+                    ", memorysize" + memorySize +
+                    ", maxchunksize=" + maxChunkSize +
+                    ", samplethreads" + sampleThreads);
+        }
+
+
         boolean updated = false;
         if (repositoryPath != null) {
             try {
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdDump.java	Sun Jun 24 16:25:47 2018 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdDump.java	Mon Jun 25 02:07:42 2018 +0200
@@ -26,10 +26,21 @@
 
 import java.io.IOException;
 import java.nio.file.InvalidPathException;
-import java.util.HashMap;
-import java.util.Map;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeParseException;
 
+import jdk.jfr.FlightRecorder;
 import jdk.jfr.Recording;
+import jdk.jfr.internal.LogLevel;
+import jdk.jfr.internal.LogTag;
+import jdk.jfr.internal.Logger;
+import jdk.jfr.internal.PlatformRecorder;
 import jdk.jfr.internal.PlatformRecording;
 import jdk.jfr.internal.PrivateAccess;
 import jdk.jfr.internal.SecuritySupport.SafePath;
@@ -40,41 +51,155 @@
  * JFR.dump
  *
  */
-//Instantiated by native
+// Instantiated by native
 final class DCmdDump extends AbstractDCmd {
     /**
      * Execute JFR.dump.
      *
-     * @param recordingText name or id of the recording to dump, or
-     *        <code>null</code>
+     * @param name name or id of the recording to dump, or <code>null</code> to dump everything
      *
-     * @param textPath file path where recording should be written.
+     * @param filename file path where recording should be written, not null
+     * @param maxAge how far back in time to dump, may be null
+     * @param maxSize how far back in size to dump data from, may be null
+     * @param begin point in time to dump data from, may be null
+     * @param end point in time to dump data to, may be null
+     * @param pathToGcRoots if Java heap should be swept for reference chains
      *
      * @return result output
      *
      * @throws DCmdException if the dump could not be completed
      */
-    public String execute(String recordingText, String textPath,  Boolean pathToGcRoots) throws DCmdException {
-        if (textPath == null) {
-            throw new DCmdException("Failed to dump %s, missing filename.", recordingText);
+    public String execute(String name, String filename, Long maxAge, Long maxSize, String begin, String end, Boolean pathToGcRoots) throws DCmdException {
+        if (LogTag.JFR_DCMD.shouldLog(LogLevel.DEBUG)) {
+            Logger.log(LogTag.JFR_DCMD, LogLevel.DEBUG,
+                    "Executing DCmdDump: name=" + name +
+                    ", filename=" + filename +
+                    ", maxage=" + maxAge +
+                    ", maxsize=" + maxSize +
+                    ", begin=" + begin +
+                    ", end" + end +
+                    ", path-to-gc-roots=" + pathToGcRoots);
+        }
+
+        if (FlightRecorder.getFlightRecorder().getRecordings().isEmpty()) {
+            throw new DCmdException("No recordings to dump from. Use JFR.start to start a recording.");
+        }
+
+        if (maxAge != null) {
+            if (end != null || begin != null) {
+                throw new DCmdException("Dump failed, maxage can't be combined with begin or end.");
+            }
+
+            if (maxAge < 0) {
+                throw new DCmdException("Dump failed, maxage can't be negative.");
+            }
+            if (maxAge == 0) {
+                maxAge = Long.MAX_VALUE / 2; // a high value that won't overflow
+            }
         }
-        Recording recording = findRecording(recordingText);
-        try {
-            SafePath dumpFile = resolvePath(textPath, "Failed to dump %s");
-            // create file for JVM
-            Utils.touch(dumpFile.toPath());
-            PlatformRecording r = PrivateAccess.getInstance().getPlatformRecording(recording);
-            WriteableUserPath wup = new WriteableUserPath(dumpFile.toPath());
+
+        if (maxSize!= null) {
+            if (maxSize < 0) {
+                throw new DCmdException("Dump failed, maxsize can't be negative.");
+            }
+            if (maxSize == 0) {
+                maxSize = Long.MAX_VALUE / 2; // a high value that won't overflow
+            }
+        }
+
+        Instant beginTime = parseTime(begin, "begin");
+        Instant endTime = parseTime(end, "end");
 
-            Map<String, String> overlay = new HashMap<>();
-            Utils.updateSettingPathToGcRoots(overlay, pathToGcRoots);
+        if (beginTime != null && endTime != null) {
+            if (endTime.isBefore(beginTime)) {
+                throw new DCmdException("Dump failed, begin must preceed end.");
+            }
+        }
 
-            r.copyTo(wup, "Dumped by user", overlay);
-            reportOperationComplete("Dumped", recording, dumpFile);
-        } catch (IOException | InvalidPathException e) {
-            throw new DCmdException("Failed to dump %s. Could not copy recording for dump. %s", recordingText, e.getMessage());
+        Duration duration = null;
+        if (maxAge != null) {
+            duration = Duration.ofNanos(maxAge);
+            beginTime = Instant.now().minus(duration);
+        }
+        Recording recording = null;
+        if (name != null) {
+            recording = findRecording(name);
+        }
+        PlatformRecorder recorder = PrivateAccess.getInstance().getPlatformRecorder();
+        synchronized (recorder) {
+            dump(recorder, recording, name, filename, maxSize, pathToGcRoots, beginTime, endTime);
         }
         return getResult();
     }
 
+    public void dump(PlatformRecorder recorder, Recording recording, String name, String filename, Long maxSize, Boolean pathToGcRoots, Instant beginTime, Instant endTime) throws DCmdException {
+        try (PlatformRecording r = newSnapShot(recorder, recording, pathToGcRoots)) {
+            r.filter(beginTime, endTime, maxSize);
+            if (r.getChunks().isEmpty()) {
+                throw new DCmdException("Dump failed. No data found in the specified interval.");
+            }
+            SafePath dumpFile = resolvePath(recording, filename);
+
+            // Needed for JVM
+            Utils.touch(dumpFile.toPath());
+            r.dumpStopped(new WriteableUserPath(dumpFile.toPath()));
+            reportOperationComplete("Dumped", name, dumpFile);
+        } catch (IOException | InvalidPathException e) {
+            throw new DCmdException("Dump failed. Could not copy recording data. %s", e.getMessage());
+        }
+    }
+
+    private Instant parseTime(String time, String parameter) throws DCmdException {
+        if (time == null) {
+            return null;
+        }
+        try {
+            return Instant.parse(time);
+        } catch (DateTimeParseException dtp) {
+            // fall through
+        }
+        try {
+            LocalDateTime ldt = LocalDateTime.parse(time);
+            return ZonedDateTime.of(ldt, ZoneId.systemDefault()).toInstant();
+        } catch (DateTimeParseException dtp) {
+            // fall through
+        }
+        try {
+            LocalTime lt = LocalTime.parse(time);
+            LocalDate ld = LocalDate.now();
+            Instant instant = ZonedDateTime.of(ld, lt, ZoneId.systemDefault()).toInstant();
+            Instant now = Instant.now();
+            if (instant.isAfter(now) && !instant.isBefore(now.plusSeconds(3600))) {
+                // User must have meant previous day
+                ld = ld.minusDays(1);
+            }
+            return ZonedDateTime.of(ld, lt, ZoneId.systemDefault()).toInstant();
+        } catch (DateTimeParseException dtp) {
+            // fall through
+        }
+
+        if (time.startsWith("-")) {
+            try {
+                long durationNanos = Utils.parseTimespan(time.substring(1));
+                Duration duration = Duration.ofNanos(durationNanos);
+                return Instant.now().minus(duration);
+            } catch (NumberFormatException nfe) {
+                // fall through
+            }
+        }
+        throw new DCmdException("Dump failed, not a valid %s time.", parameter);
+    }
+
+    private PlatformRecording newSnapShot(PlatformRecorder recorder, Recording recording, Boolean pathToGcRoots) throws DCmdException, IOException {
+        if (recording == null) {
+            // Operate on all recordings
+            PlatformRecording snapshot = recorder.newTemporaryRecording();
+            recorder.fillWithRecordedData(snapshot, pathToGcRoots);
+            return snapshot;
+        }
+
+        PlatformRecording pr = PrivateAccess.getInstance().getPlatformRecording(recording);
+        return pr.newSnapshotClone("Dumped by user", pathToGcRoots);
+    }
+
 }
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdStart.java	Sun Jun 24 16:25:47 2018 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdStart.java	Mon Jun 25 02:07:42 2018 +0200
@@ -25,19 +25,26 @@
 package jdk.jfr.internal.dcmd;
 
 import java.io.IOException;
+import java.nio.file.Files;
 import java.nio.file.InvalidPathException;
+import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.text.ParseException;
 import java.time.Duration;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
 
 import jdk.jfr.FlightRecorder;
 import jdk.jfr.Recording;
 import jdk.jfr.internal.JVM;
+import jdk.jfr.internal.LogLevel;
+import jdk.jfr.internal.LogTag;
+import jdk.jfr.internal.Logger;
+import jdk.jfr.internal.OldObjectSample;
+import jdk.jfr.internal.PrivateAccess;
 import jdk.jfr.internal.SecuritySupport.SafePath;
 import jdk.jfr.internal.Type;
-import jdk.jfr.internal.Utils;
 import jdk.jfr.internal.jfc.JFC;
 
 /**
@@ -51,7 +58,7 @@
      * Execute JFR.start.
      *
      * @param name optional name that can be used to identify recording.
-     * @param configurations names of settings files to use, i.e. "default" or
+     * @param settings names of settings files to use, i.e. "default" or
      *        "default.jfc".
      * @param delay delay before recording is started, in nanoseconds. Must be
      *        at least 1 second.
@@ -73,7 +80,19 @@
      * @throws DCmdException if recording could not be started
      */
     @SuppressWarnings("resource")
-    public String execute(String name, String[] configurations, Long delay, Long duration, Boolean disk, String path, Long maxAge, Long maxSize, Boolean dumpOnExit, Boolean pathToGcRoots) throws DCmdException {
+    public String execute(String name, String[] settings, Long delay, Long duration, Boolean disk, String path, Long maxAge, Long maxSize, Boolean dumpOnExit, Boolean pathToGcRoots) throws DCmdException {
+        if (LogTag.JFR_DCMD.shouldLog(LogLevel.DEBUG)) {
+            Logger.log(LogTag.JFR_DCMD, LogLevel.DEBUG, "Executing DCmdStart: name=" + name +
+                    ", settings=" + Arrays.asList(settings) +
+                    ", delay=" + delay +
+                    ", duration=" + duration +
+                    ", disk=" + disk+
+                    ", filename=" + path +
+                    ", maxage=" + maxAge +
+                    ", maxsize=" + maxSize +
+                    ", dumponexit =" + dumpOnExit +
+                    ", path-to-gc-roots=" + pathToGcRoots);
+        }
         if (name != null) {
             try {
                 Integer.parseInt(name);
@@ -86,25 +105,23 @@
         if (duration == null && Boolean.FALSE.equals(dumpOnExit) && path != null) {
             throw new DCmdException("Filename can only be set for a time bound recording or if dumponexit=true. Set duration/dumponexit or omit filename.");
         }
-        if (dumpOnExit == null && path != null) {
-            dumpOnExit = Boolean.TRUE;
-        }
+
 
         Map<String, String> s = new HashMap<>();
 
-        if (configurations == null || configurations.length == 0) {
-            configurations = new String[] { "default" };
+        if (settings == null || settings.length == 0) {
+            settings = new String[] { "default" };
         }
 
-        for (String configName : configurations) {
+        for (String configName : settings) {
             try {
                 s.putAll(JFC.createKnown(configName).getSettings());
             } catch (IOException | ParseException e) {
-                throw new DCmdException("Could not parse setting " + configurations[0], e);
+                throw new DCmdException("Could not parse setting " + settings[0], e);
             }
         }
 
-        Utils.updateSettingPathToGcRoots(s, pathToGcRoots);
+        OldObjectSample.updateSettingPathToGcRoots(s, pathToGcRoots);
 
         if (duration != null) {
             if (duration < 1000L * 1000L * 1000L) {
@@ -133,10 +150,24 @@
             recording.setToDisk(disk.booleanValue());
         }
         recording.setSettings(s);
+        SafePath safePath = null;
 
         if (path != null) {
             try {
-                recording.setDestination(Paths.get(path));
+                if (dumpOnExit == null) {
+                    // default to dumponexit=true if user specified filename
+                    dumpOnExit = Boolean.TRUE;
+                }
+                Path p = Paths.get(path);
+                if (Files.isDirectory(p) && Boolean.TRUE.equals(dumpOnExit)) {
+                    // Decide destination filename at dump time
+                    // Purposely avoid generating filename in Recording#setDestination due to
+                    // security concerns
+                    PrivateAccess.getInstance().getPlatformRecording(recording).setDumpOnExitDirectory(new SafePath(p));
+                } else {
+                    safePath = resolvePath(recording, path);
+                    recording.setDestination(safePath.toPath());
+                }
             } catch (IOException | InvalidPathException e) {
                 recording.close();
                 throw new DCmdException("Could not start recording, not able to write to file %s. %s ", path, e.getMessage());
@@ -175,10 +206,10 @@
             recording.setMaxSize(250*1024L*1024L);
         }
 
-        if (path != null && duration != null) {
+        if (safePath != null && duration != null) {
             println(" The result will be written to:");
             println();
-            printPath(new SafePath(path));
+            printPath(safePath);
         } else {
             println();
             println();
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdStop.java	Sun Jun 24 16:25:47 2018 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdStop.java	Mon Jun 25 02:07:42 2018 +0200
@@ -29,13 +29,16 @@
 import java.nio.file.Paths;
 
 import jdk.jfr.Recording;
+import jdk.jfr.internal.LogLevel;
+import jdk.jfr.internal.LogTag;
+import jdk.jfr.internal.Logger;
 import jdk.jfr.internal.SecuritySupport.SafePath;
 
 /**
  * JFR.stop
  *
  */
-//Instantiated by native
+// Instantiated by native
 final class DCmdStop extends AbstractDCmd {
 
     /**
@@ -43,35 +46,42 @@
      *
      * Requires that either <code>name or <code>id</code> is set.
      *
-     * @param recordingText name or id of the recording to stop.
+     * @param name name or id of the recording to stop.
      *
-     * @param textPath file path where data should be written after recording
-     *        has been stopped, or <code>null</code> if recording shouldn't be
-     *        written to disk.
+     * @param filename file path where data should be written after recording has
+     *        been stopped, or <code>null</code> if recording shouldn't be written
+     *        to disk.
      * @return result text
      *
      * @throws DCmdException if recording could not be stopped
      */
-    public String execute(String recordingText, String textPath) throws DCmdException {
+    public String execute(String name, String filename) throws DCmdException {
+        if (LogTag.JFR_DCMD.shouldLog(LogLevel.DEBUG)) {
+            Logger.log(LogTag.JFR_DCMD, LogLevel.DEBUG, "Executing DCmdStart: name=" + name + ", filename=" + filename);
+        }
+
         try {
-            SafePath path = resolvePath(textPath, "Failed to stop %s");
-            Recording recording = findRecording(recordingText);
-            if (textPath != null) {
+            SafePath safePath = null;
+            Recording recording = findRecording(name);
+            if (filename != null) {
                 try {
-                    recording.setDestination(Paths.get(textPath));
-                } catch (IOException e) {
-                    throw new DCmdException("Failed to stop %s. Could not set destination for \"%s\" to file %s", recording.getName(), textPath, e.getMessage());
+                    // Ensure path is valid. Don't generate safePath if filename == null, as a user may
+                    // want to stop recording without a dump
+                    safePath = resolvePath(null, filename);
+                    recording.setDestination(Paths.get(filename));
+                } catch (IOException | InvalidPathException  e) {
+                    throw new DCmdException("Failed to stop %s. Could not set destination for \"%s\" to file %s", recording.getName(), filename, e.getMessage());
                 }
             }
             recording.stop();
-            reportOperationComplete("Stopped", recording, path);
+            reportOperationComplete("Stopped", recording.getName(), safePath);
             recording.close();
             return getResult();
         } catch (InvalidPathException | DCmdException e) {
-            if (textPath != null) {
-                throw new DCmdException("Could not write recording \"%s\" to file. %s", recordingText, e.getMessage());
+            if (filename != null) {
+                throw new DCmdException("Could not write recording \"%s\" to file. %s", name, e.getMessage());
             }
-            throw new DCmdException(e, "Could not stop recording \"%s\".", recordingText, e.getMessage());
+            throw new DCmdException(e, "Could not stop recording \"%s\".", name, e.getMessage());
         }
     }
 }
--- a/src/jdk.management.jfr/share/classes/jdk/management/jfr/MBeanUtils.java	Sun Jun 24 16:25:47 2018 +0100
+++ b/src/jdk.management.jfr/share/classes/jdk/management/jfr/MBeanUtils.java	Mon Jun 25 02:07:42 2018 +0200
@@ -122,7 +122,7 @@
         }
         int size = Integer.parseInt(string);
         if (size <1)  {
-            throw new IllegalArgumentException("Block size msut be at least 1 byte");
+            throw new IllegalArgumentException("Block size must be at least 1 byte");
         }
         return size;
     }
--- a/test/jdk/jdk/jfr/jcmd/JcmdAsserts.java	Sun Jun 24 16:25:47 2018 +0100
+++ b/test/jdk/jdk/jfr/jcmd/JcmdAsserts.java	Mon Jun 25 02:07:42 2018 +0200
@@ -47,7 +47,7 @@
         output.shouldMatch("Flight Recorder has been used");
     }
 
-    public static void assertRecordingDumpedToFile(OutputAnalyzer output, String name, File recording) {
+    public static void assertRecordingDumpedToFile(OutputAnalyzer output, File recording) {
         output.shouldContain("Dumped recording");
         output.shouldContain(recording.getAbsolutePath());
     }
--- a/test/jdk/jdk/jfr/jcmd/TestJcmdDump.java	Sun Jun 24 16:25:47 2018 +0100
+++ b/test/jdk/jdk/jfr/jcmd/TestJcmdDump.java	Mon Jun 25 02:07:42 2018 +0200
@@ -26,8 +26,16 @@
 package jdk.jfr.jcmd;
 
 import java.io.File;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Predicate;
 
-import jdk.test.lib.jfr.FileHelper;
+import jdk.jfr.Event;
+import jdk.jfr.Recording;
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.jfr.consumer.RecordingFile;
+import jdk.test.lib.jfr.EventNames;
 import jdk.test.lib.process.OutputAnalyzer;
 
 /*
@@ -35,24 +43,129 @@
  * @summary The test verifies JFR.dump command
  * @key jfr
  * @library /test/lib /test/jdk
- * @run main/othervm jdk.jfr.jcmd.TestJcmdDump
+ * @run main/othervm -XX:FlightRecorderOptions:maxchunksize=1M jdk.jfr.jcmd.TestJcmdDump
  */
 public class TestJcmdDump {
 
+    static class StoppedEvent extends Event {
+    }
+    static class RunningEvent extends Event {
+    }
+
+    private static final String[] names = { null, "r1" };
+    private static final boolean booleanValues[] = { true, false };
+
     public static void main(String[] args) throws Exception {
-        String name = "TestJcmdDump";
-        File recording = new File(name + ".jfr");
+
+        // Create a stopped recording in the repository to complicate things
+        Recording r = new Recording();
+        r.start();
+        StoppedEvent de = new StoppedEvent();
+        de.commit();
+        r.stop();
 
+        // The implementation of JFR.dump touch code that can't be executed using the
+        // Java API. It is therefore important to try all combinations. The
+        // implementation is non-trivial and depends on the combination
+        for (String name : names) {
+            for (boolean disk : booleanValues) {
+                try (Recording r1 = new Recording(); Recording r2 = new Recording()) {
+                    System.out.println();
+                    System.out.println();
+                    System.out.println("Starting recordings with disk=" + disk);
+                    r1.setToDisk(disk);
+                    // To complicate things, only enable OldObjectSample for one recording
+                    r1.enable(EventNames.OldObjectSample).withoutStackTrace();
+                    r1.setName("r1");
+                    r2.setToDisk(disk);
+                    r2.setName("r2");
+                    r1.start();
+                    r2.start();
+
+                    // Expect no path to GC roots
+                    jfrDump(Boolean.FALSE, name, disk, rootCount -> rootCount == 0);
+                    // Expect path to GC roots
+                    jfrDump(null, name, disk, rootCount -> rootCount == 0);
+                    // Expect at least one path to a GC root
+                    jfrDump(Boolean.TRUE, name, disk, rootCount -> rootCount > 0);
+                }
+            }
+        }
+        r.close(); // release recording data from the stopped recording
+    }
 
-        OutputAnalyzer output = JcmdHelper.jcmd("JFR.start", "name=" + name);
-        JcmdAsserts.assertRecordingHasStarted(output);
-        JcmdHelper.waitUntilRunning(name);
+    private static void jfrDump(Boolean pathToGCRoots, String name, boolean disk, Predicate<Integer> successPredicate) throws Exception {
+        List<Object> leakList = new ArrayList<>();
+        leakList.add(new Object[1000_0000]);
+        System.gc();
+        while (true) {
+            RunningEvent re = new RunningEvent();
+            re.commit();
+            leakList.add(new Object[1000_0000]);
+            leakList.add(new Object[1000_0000]);
+            leakList.add(new Object[1000_0000]);
+            System.gc(); // This will shorten time for object to be emitted.
+            File recording = new File("TestJCMdDump.jfr");
+            String[] params = buildParameters(pathToGCRoots, name, recording);
+            OutputAnalyzer output = JcmdHelper.jcmd(params);
+            JcmdAsserts.assertRecordingDumpedToFile(output, recording);
+            int rootCount = 0;
+            int oldObjectCount = 0;
+            int stoppedEventCount = 0;
+            int runningEventCount = 0;
+            for (RecordedEvent e : RecordingFile.readAllEvents(recording.toPath())) {
+                if (e.getEventType().getName().equals(EventNames.OldObjectSample)) {
+                    if (e.getValue("root") != null) {
+                        rootCount++;
+                    }
+                    oldObjectCount++;
+                }
+                if (e.getEventType().getName().equals(StoppedEvent.class.getName())) {
+                    stoppedEventCount++;
+                }
+                if (e.getEventType().getName().equals(RunningEvent.class.getName())) {
+                    runningEventCount++;
+                }
+            }
+            System.out.println("Name: " + name);
+            System.out.println("Disk: " + disk);
+            System.out.println("Path to GC roots: " + pathToGCRoots);
+            System.out.println("Old Objects: " + oldObjectCount);
+            System.out.println("Root objects: "+ rootCount);
+            System.out.println("Stopped events: "+ stoppedEventCount);
+            System.out.println("Running events: "+ runningEventCount);
 
-        output = JcmdHelper.jcmd("JFR.dump",
-                "name=" + name,
-                "filename=" + recording.getAbsolutePath());
-        JcmdAsserts.assertRecordingDumpedToFile(output, name, recording);
-        JcmdHelper.stopAndCheck(name);
-        FileHelper.verifyRecording(recording);
+            System.out.println();
+            if (runningEventCount == 0) {
+                throw new Exception("Missing event from running recording");
+            }
+            if (name == null && stoppedEventCount == 0) {
+                throw new Exception("Missing event from stopped recording");
+            }
+            if (name != null && stoppedEventCount > 0) {
+                throw new Exception("Stopped event should not be part of dump");
+            }
+            if (oldObjectCount != 0 && successPredicate.test(rootCount)) {
+                return;
+            }
+            System.out.println();
+            System.out.println();
+            System.out.println();
+            System.out.println("************* Retrying! **************");
+            Files.delete(recording.toPath());
+        }
+    }
+
+    private static String[] buildParameters(Boolean pathToGCRoots, String name, File recording) {
+        List<String> params = new ArrayList<>();
+        params.add("JFR.dump");
+        params.add("filename=" + recording.getAbsolutePath());
+        if (pathToGCRoots != null) { // if path-to-gc-roots is omitted, default is used (disabled).
+            params.add("path-to-gc-roots=" + pathToGCRoots);
+        }
+        if (name != null) { // if name is omitted, all recordings will be dumped
+            params.add("name=" + name);
+        }
+        return params.toArray(new String[0]);
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/jcmd/TestJcmdDumpGeneratedFilename.java	Mon Jun 25 02:07:42 2018 +0200
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.jfr.jcmd;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Iterator;
+
+import jdk.jfr.Configuration;
+import jdk.jfr.Recording;
+import jdk.test.lib.jfr.FileHelper;
+import jdk.test.lib.process.OutputAnalyzer;
+
+/*
+ * @test
+ * @summary The test verifies JFR.dump command
+ * @key jfr
+ * @library /test/lib /test/jdk
+ * @run main/othervm jdk.jfr.jcmd.TestJcmdDumpGeneratedFilename
+ */
+public class TestJcmdDumpGeneratedFilename {
+
+    public static void main(String[] args) throws Exception {
+        // Increase the id for a recording
+        for (int i = 0; i < 300; i++) {
+            new Recording();
+        }
+        try (Recording r = new Recording(Configuration.getConfiguration("default"))) {
+            r.start();
+            r.stop();
+            testDumpFilename();
+            testDumpFilename(r);
+            testDumpDiectory();
+            testDumpDiectory(r);
+        }
+    }
+
+    private static void testDumpFilename() throws Exception {
+        OutputAnalyzer output = JcmdHelper.jcmd("JFR.dump");
+        verifyFile(readFilename(output), null);
+    }
+
+    private static void testDumpFilename(Recording r) throws Exception {
+        OutputAnalyzer output = JcmdHelper.jcmd("JFR.dump", "name=" + r.getId());
+        verifyFile(readFilename(output), r.getId());
+    }
+
+    private static void testDumpDiectory() throws Exception {
+        Path directory = Paths.get(".").toAbsolutePath().normalize();
+        OutputAnalyzer output = JcmdHelper.jcmd("JFR.dump", "filename=" + directory);
+        String filename = readFilename(output);
+        verifyFile(filename, null);
+        verifyDirectory(filename, directory);
+    }
+
+    private static void testDumpDiectory(Recording r) throws Exception {
+        Path directory = Paths.get(".").toAbsolutePath().normalize();
+        OutputAnalyzer output = JcmdHelper.jcmd("JFR.dump", "name=" + r.getId(), "filename=" + directory);
+        String filename = readFilename(output);
+        verifyFile(filename, r.getId());
+        verifyDirectory(filename, directory);
+    }
+
+    private static void verifyDirectory(String filename, Path directory) throws Exception {
+        if (!filename.contains(directory.toAbsolutePath().normalize().toString())) {
+            throw new Exception("Expected dump to be at " + directory);
+        }
+    }
+
+    private static void verifyFile(String filename, Long id) throws Exception {
+        String idText = id == null ? "" : "-id-" + Long.toString(id);
+        String expectedName = "hotspot-pid-" + ProcessHandle.current().pid() + idText;
+        if (!filename.contains(expectedName)) {
+            throw new Exception("Expected filename to contain " + expectedName);
+        }
+        FileHelper.verifyRecording(new File(filename));
+    }
+
+    private static String readFilename(OutputAnalyzer output) throws Exception {
+        Iterator<String> it = output.asLines().iterator();
+        while (it.hasNext()) {
+            String line = it.next();
+            if (line.contains("written to")) {
+                line = it.next(); // blank line
+                return it.next();
+            }
+        }
+        throw new Exception("Could not find filename of dumped recording.");
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/jcmd/TestJcmdDumpLimited.java	Mon Jun 25 02:07:42 2018 +0200
@@ -0,0 +1,291 @@
+/*
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.jfr.jcmd;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZoneOffset;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import jdk.jfr.Event;
+import jdk.jfr.Recording;
+import jdk.test.lib.Asserts;
+import jdk.test.lib.process.OutputAnalyzer;
+
+/*
+ * @test
+ * @summary The test verifies JFR.dump command
+ * @key jfr
+ * @library /test/lib /test/jdk
+ * @run main/othervm -XX:FlightRecorderOptions jdk.jfr.jcmd.TestJcmdDumpLimited
+ */
+public class TestJcmdDumpLimited {
+
+    static class TestEvent extends Event {
+        int id;
+        int number;
+    }
+
+    static class TestRecording {
+        Instant time;
+        final Recording r;
+        Path path;
+        int size;
+        int total;
+        int id;
+        Instant now;
+
+        TestRecording(int id, int events) throws IOException, InterruptedException {
+            r = new Recording();
+            r.start();
+            for (int i = 0; i < events; i++) {
+                TestEvent event = new TestEvent();
+                event.id = id;
+                event.number = i;
+                event.commit();
+                if (i == events / 2) {
+                    time = Instant.now();
+                }
+            }
+            r.stop();
+            Thread.sleep(1);
+            path = Paths.get("dump-" + id + ".jfr");
+            r.dump(path);
+            size = (int) Files.size(path);
+            this.id = id;
+            this.now = Instant.now();
+        }
+
+        public void close() {
+            r.close();
+        }
+    }
+
+    private static long totalSize;
+    private static long lastFiveSize;
+    private static long firstFiveSize;
+    private static long middleSize;
+    private static long centerSize;
+    private static long lastSize;
+
+    private static Instant middle;
+    private static Instant centerLeft;
+    private static Instant centerRight;
+
+    public static void main(String[] args) throws Exception {
+
+        List<TestRecording> recs = new ArrayList<>();
+
+        for (int i = 0; i < 9; i++) {
+            recs.add(new TestRecording(i, 100));
+        }
+        int last = 0;
+        List<TestRecording> reversed = new ArrayList<>(recs);
+        Collections.reverse(reversed);
+        for (TestRecording r : reversed) {
+            r.total = r.size + last;
+            last += r.size;
+        }
+
+        for (TestRecording r : recs) {
+            System.out.println("Recording " + r.id + ": size=" + r.size + " (total=" + r.total + ", time=" + r.now + ")");
+        }
+
+        centerLeft = recs.get(3).time;
+        middle = recs.get(4).time;
+        centerRight = recs.get(5).time;
+
+        totalSize = size(recs, 0, 9);
+        lastFiveSize = size(recs, 4, 5);
+        firstFiveSize = size(recs, 0, 5);
+        middleSize = size(recs, 4, 1);
+        centerSize = size(recs, 3, 3);
+        lastSize =  size(recs, 8, 1);
+
+        testDump();
+        testDumpMaxSize();
+        testDumpMaxSizeSmall();
+        testDumpBegin();
+        testDumpEnd();
+        testDumpBeginEndInstant();
+        testDumpBeginEndLocalDateTime();
+        testDumpBeginEndLocalTime();
+        testDumpBeginEndSame();
+        testDumpMaxAge();
+        testDumpBeginEndRelative();
+        testDumpTooEarly();
+        testDumpTooLate();
+        testDumpBeginMaxAge();
+        TestDumpEndMaxage();
+        testDumpEndBegin();
+        testDumpInvalidTime();
+    }
+
+    private static int size(List<TestRecording> recs, int skip, int limit) {
+        return recs.stream().skip(skip).limit(limit).mapToInt(r -> r.size).sum();
+    }
+
+    private static void testDumpEndBegin() throws Exception {
+        Path testEndBegin = Paths.get("testEndBegin.jfr");
+        OutputAnalyzer output = JcmdHelper.jcmd("JFR.dump", "filename=" + testEndBegin.toFile().getAbsolutePath(), "begin=" + Instant.now(), "end=" + Instant.now().minusSeconds(200));
+        output.shouldContain("Dump failed, begin must preceed end.");
+        assertMissingFile(testEndBegin);
+    }
+
+    private static void TestDumpEndMaxage() throws Exception {
+        Path testEndMaxAge = Paths.get("testEndMaxAge.jfr");
+        OutputAnalyzer output = JcmdHelper.jcmd("JFR.dump", "filename=" + testEndMaxAge.toFile().getAbsolutePath(), "end=" + Instant.now(), "maxage=2h");
+        output.shouldContain("Dump failed, maxage can't be combined with begin or end.");
+        assertMissingFile(testEndMaxAge);
+    }
+
+    private static Path testDumpBeginMaxAge() throws Exception {
+        Path testBeginMaxAge = Paths.get("testBeginMaxAge.jfr");
+        OutputAnalyzer output = JcmdHelper.jcmd("JFR.dump", "filename=" + testBeginMaxAge.toFile().getAbsolutePath(), "begin=" + Instant.now().minusSeconds(100), "maxage=2h");
+        output.shouldContain("Dump failed, maxage can't be combined with begin or end.");
+        assertMissingFile(testBeginMaxAge);
+        return testBeginMaxAge;
+    }
+
+    private static void testDumpTooLate() throws Exception {
+        Path missing = Paths.get("missing2.jfr");
+        OutputAnalyzer output = JcmdHelper.jcmd("JFR.dump", "filename=" + missing.toFile().getAbsolutePath(), "begin=" + Instant.now().plus(Duration.ofHours(1)),
+                "end=" + Instant.now().plus(Duration.ofHours(2)));
+        output.shouldContain("Dump failed. No data found in the specified interval.");
+        assertMissingFile(missing);
+    }
+
+    private static void testDumpTooEarly() throws Exception {
+        Path missing = Paths.get("missing.jfr");
+        OutputAnalyzer output = JcmdHelper.jcmd("JFR.dump", "filename=" + missing.toFile().getAbsolutePath(), "end=" + Instant.now().minus(Duration.ofHours(1)));
+        output.shouldContain("Dump failed. No data found in the specified interval.");
+        assertMissingFile(missing);
+    }
+
+    private static void testDumpBeginEndRelative() throws IOException {
+        Path testBeginEndRelative = Paths.get("testBeginEndRelative.jfr");
+        JcmdHelper.jcmd("JFR.dump", "filename=" + testBeginEndRelative.toFile().getAbsolutePath(), "begin=-3h", "end=-0s");
+        Asserts.assertEquals(totalSize, Files.size(testBeginEndRelative), "Expected dump with begin=-3h end=0s to contain data from all recordings");
+        Files.delete(testBeginEndRelative);
+    }
+
+    private static void testDumpMaxAge() throws IOException {
+        Path testMaxAge = Paths.get("testMaxAge.jfr");
+        JcmdHelper.jcmd("JFR.dump", "filename=" + testMaxAge.toFile().getAbsolutePath(), "maxage=2h");
+        Asserts.assertEquals(totalSize, Files.size(testMaxAge), "Expected dump with maxage=2h  to contain data from all recordings");
+        Files.delete(testMaxAge);
+    }
+
+    private static void testDumpBeginEndSame() throws IOException {
+        Path testBeginEnd = Paths.get("testBeginEndSame.jfr");
+        JcmdHelper.jcmd("JFR.dump", "filename=" + testBeginEnd.toFile().getAbsolutePath(), "begin=" + middle, "end=" + middle);
+        Asserts.assertEquals(middleSize, Files.size(testBeginEnd), "Expected dump with begin=" + middle + "end=" + middle + " contain data from middle recording");
+        Files.delete(testBeginEnd);
+    }
+
+    private static void testDumpBeginEndInstant() throws IOException {
+        Path testBeginEnd = Paths.get("testBeginEndInstant.jfr");
+        JcmdHelper.jcmd("JFR.dump", "filename=" + testBeginEnd.toFile().getAbsolutePath(), "begin=" + centerLeft, "end=" + centerRight);
+        Asserts.assertEquals(centerSize, Files.size(testBeginEnd), "Expected dump with begin=" + centerLeft + " end=" + centerRight + " contain data from the 'center'-recordings");
+        Files.delete(testBeginEnd);
+    }
+
+    private static void testDumpBeginEndLocalDateTime() throws IOException {
+        LocalDateTime centerLeftLocal = LocalDateTime.ofInstant(centerLeft, ZoneOffset.systemDefault());
+        LocalDateTime centerRightLocal = LocalDateTime.ofInstant(centerRight, ZoneOffset.systemDefault());
+        Path testBeginEnd = Paths.get("testBeginEndLocalDateTime.jfr");
+        JcmdHelper.jcmd("JFR.dump", "filename=" + testBeginEnd.toFile().getAbsolutePath(), "begin=" + centerLeftLocal, "end=" + centerRightLocal);
+        Asserts.assertEquals(centerSize, Files.size(testBeginEnd), "Expected dump with begin=" + centerLeftLocal + " end=" + centerRightLocal + " contain data from the 'center'-recordings");
+        Files.delete(testBeginEnd);
+    }
+
+    private static void testDumpBeginEndLocalTime() throws IOException {
+        LocalTime centerLeftLocal = LocalTime.ofInstant(centerLeft, ZoneOffset.systemDefault());
+        LocalTime centerRightLocal = LocalTime.ofInstant(centerRight, ZoneOffset.systemDefault());
+        Path testBeginEnd = Paths.get("testBeginEndLocalTime.jfr");
+        JcmdHelper.jcmd("JFR.dump", "filename=" + testBeginEnd.toFile().getAbsolutePath(), "begin=" + centerLeftLocal, "end=" + centerRightLocal);
+        Asserts.assertEquals(centerSize, Files.size(testBeginEnd), "Expected dump with begin=" + centerLeftLocal + " end=" + centerRightLocal + " contain data from the 'center'-recordings");
+        Files.delete(testBeginEnd);
+    }
+
+    private static void testDumpEnd() throws IOException {
+        Path testEnd = Paths.get("testEnd.jfr");
+        JcmdHelper.jcmd("JFR.dump", "filename=" + testEnd.toFile().getAbsolutePath(), "end=" + middle);
+        Asserts.assertEquals(firstFiveSize, Files.size(testEnd), "Expected dump with end=" + middle + " to contain data from the five first recordings");
+        Files.delete(testEnd);
+    }
+
+    private static void testDumpBegin() throws IOException {
+        Path testBegin = Paths.get("testBegin.jfr");
+        JcmdHelper.jcmd("JFR.dump", "filename=" + testBegin.toFile().getAbsolutePath(), "begin=" + middle);
+        Asserts.assertEquals(lastFiveSize, Files.size(testBegin), "Expected dump with begin=" + middle + " to contain data from the last five recordings");
+        Files.delete(testBegin);
+    }
+
+    private static void testDumpMaxSize() throws IOException {
+        Path testMaxSize = Paths.get("testMaxSize.jfr");
+        JcmdHelper.jcmd("JFR.dump", "filename=" + testMaxSize.toFile().getAbsolutePath(), "maxsize=" + lastFiveSize);
+        Asserts.assertEquals(lastFiveSize, Files.size(testMaxSize), "Expected dump with maxsize=" + lastFiveSize + " to contain data from the last five recordings");
+        Files.delete(testMaxSize);
+    }
+
+    private static void testDumpMaxSizeSmall() throws IOException {
+        Path testMaxSizeSmall = Paths.get("testMaxSizeSmall.jfr");
+        JcmdHelper.jcmd("JFR.dump", "filename=" + testMaxSizeSmall.toFile().getAbsolutePath(), "maxsize=1k");
+        Asserts.assertEquals(lastSize, Files.size(testMaxSizeSmall), "Expected dump with maxsize=1k to contain data from the last recording");
+        Files.delete(testMaxSizeSmall);
+    }
+
+    private static void testDump() throws IOException {
+        Path all = Paths.get("all.jfr");
+        OutputAnalyzer output = JcmdHelper.jcmd("JFR.dump", "filename=" + all.toFile().getAbsolutePath());
+        JcmdAsserts.assertRecordingDumpedToFile(output, all.toFile());
+        Asserts.assertEquals(totalSize, Files.size(all), "Expected dump to be sum of all recordings");
+        Files.delete(all);
+    }
+
+    private static void testDumpInvalidTime() throws Exception {
+        Path invalidTime = Paths.get("invalidTime.jfr");
+        OutputAnalyzer output = JcmdHelper.jcmd("JFR.dump", "filename=" + invalidTime.toFile().getAbsolutePath(), "begin=4711");
+        output.shouldContain("Dump failed, not a valid begin time.");
+        assertMissingFile(invalidTime);
+    }
+
+    private static void assertMissingFile(Path missing) throws Exception {
+        if (Files.exists(missing)) {
+            throw new Exception("Unexpected dumpfile found");
+        }
+    }
+
+}
--- a/test/jdk/jdk/jfr/jcmd/TestJcmdLegacy.java	Sun Jun 24 16:25:47 2018 +0100
+++ b/test/jdk/jdk/jfr/jcmd/TestJcmdLegacy.java	Mon Jun 25 02:07:42 2018 +0200
@@ -28,6 +28,7 @@
 import java.io.File;
 import java.io.IOException;
 import java.nio.file.Path;
+import java.nio.file.Paths;
 
 import jdk.jfr.Recording;
 import jdk.jfr.consumer.RecordedEvent;
@@ -58,13 +59,13 @@
 
     private static void testJcmd() throws Exception {
         String name = "testLegacy";
-        File p = new File(name + ".jfr");
+        Path p = Paths.get(name + ".jfr").toAbsolutePath().normalize();
         OutputAnalyzer output = JcmdHelper.jcmd("JFR.start", "name=" + name, "settings=" + SETTINGS.getCanonicalPath());
         JcmdAsserts.assertRecordingHasStarted(output);
         JcmdHelper.waitUntilRunning(name);
-        JcmdHelper.stopWriteToFileAndCheck(name, p);
-        FileHelper.verifyRecording(p);
-        verify(p.toPath());
+        JcmdHelper.stopWriteToFileAndCheck(name, p.toFile());
+        FileHelper.verifyRecording(p.toFile());
+        verify(p);
     }
 
     private static void testAPI() throws IOException, Exception {
--- a/test/jdk/jdk/jfr/jcmd/TestJcmdStartStopDefault.java	Sun Jun 24 16:25:47 2018 +0100
+++ b/test/jdk/jdk/jfr/jcmd/TestJcmdStartStopDefault.java	Mon Jun 25 02:07:42 2018 +0200
@@ -25,7 +25,8 @@
 
 package jdk.jfr.jcmd;
 
-import java.io.File;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -43,7 +44,7 @@
 public class TestJcmdStartStopDefault {
 
     public static void main(String[] args) throws Exception {
-        File recording = new File("TestJcmdStartStopDefault.jfr");
+        Path recording = Paths.get(".","TestJcmdStartStopDefault.jfr").toAbsolutePath().normalize();
 
         OutputAnalyzer output = JcmdHelper.jcmd("JFR.start");
         JcmdAsserts.assertRecordingHasStarted(output);
@@ -53,10 +54,10 @@
 
         output = JcmdHelper.jcmd("JFR.dump",
                 "name=" + name,
-                "filename=" + recording.getAbsolutePath());
-        JcmdAsserts.assertRecordingDumpedToFile(output, name, recording);
+                "filename=" + recording);
+        JcmdAsserts.assertRecordingDumpedToFile(output, recording.toFile());
         JcmdHelper.stopAndCheck(name);
-        FileHelper.verifyRecording(recording);
+        FileHelper.verifyRecording(recording.toFile());
     }
 
     private static String parseRecordingName(OutputAnalyzer output) {