Preview-addendum JEP-349-branch
authormgronlun
Fri, 17 May 2019 18:03:14 +0200
branchJEP-349-branch
changeset 57361 53dccc90a5be
parent 57360 5d043a159d5c
child 57364 29635339ef62
Preview-addendum
src/hotspot/share/jfr/recorder/repository/jfrChunk.cpp
src/hotspot/share/jfr/recorder/repository/jfrChunk.hpp
src/hotspot/share/jfr/utilities/jfrThreadIterator.cpp
src/hotspot/share/jfr/utilities/jfrThreadIterator.hpp
src/jdk.jfr/share/classes/jdk/jfr/consumer/ConstantLookup.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/EventDirectoryStream.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/EventFileStream.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/EventFilter.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/EventRunner.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/EventSet.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/EventSetLocation.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/EventStream.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/InternalEventFilter.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/Parser.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordingStream.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/StringParser.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/UseCasesStream.java
src/jdk.jfr/share/classes/jdk/jfr/internal/FilePurger.java
src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/Parser.java
src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/StringEncoding.java
src/jdk.jfr/share/classes/jdk/jfr/internal/util/PerfectHashMap.java
src/jdk.jfr/share/classes/jdk/jfr/internal/util/PrimitiveHashMap.java
src/jdk.jfr/share/classes/jdk/jfr/internal/util/UniversalHashFamily.java
test/jdk/jdk/jfr/api/consumer/recordingstream/TestAwaitTermination.java
test/jdk/jdk/jfr/api/consumer/recordingstream/TestClose.java
test/jdk/jdk/jfr/api/consumer/recordingstream/TestConstructor.java
test/jdk/jdk/jfr/api/consumer/recordingstream/TestDisable.java
test/jdk/jdk/jfr/api/consumer/recordingstream/TestEnable.java
test/jdk/jdk/jfr/api/consumer/recordingstream/TestMaxAge.java
test/jdk/jdk/jfr/api/consumer/recordingstream/TestOnClose.java
test/jdk/jdk/jfr/api/consumer/recordingstream/TestOnEvent.java
test/jdk/jdk/jfr/api/consumer/recordingstream/TestOnFlush.java
test/jdk/jdk/jfr/api/consumer/recordingstream/TestRemove.java
test/jdk/jdk/jfr/api/consumer/recordingstream/TestSetFlushInterval.java
test/jdk/jdk/jfr/api/consumer/recordingstream/TestSetMaxAge.java
test/jdk/jdk/jfr/api/consumer/recordingstream/TestSetMaxSize.java
test/jdk/jdk/jfr/api/consumer/recordingstream/TestStart.java
test/jdk/jdk/jfr/api/consumer/recordingstream/TestStartAsync.java
test/jdk/jdk/jfr/api/consumer/streaming/TestEmptyChunks.java
test/jdk/jdk/jfr/api/consumer/streaming/TestEnableEvents.java
test/jdk/jdk/jfr/api/consumer/streaming/TestEventRegistration.java
test/jdk/jdk/jfr/api/consumer/streaming/TestFilledChunks.java
test/jdk/jdk/jfr/api/consumer/streaming/TestFiltering.java
test/jdk/jdk/jfr/api/consumer/streaming/TestFromFile.java
test/jdk/jdk/jfr/api/consumer/streaming/TestRecordingBefore.java
test/jdk/jdk/jfr/api/consumer/streaming/TestRemovedChunks.java
test/jdk/jdk/jfr/api/consumer/streaming/TestRepositoryProperty.java
test/jdk/jdk/jfr/api/consumer/streaming/TestStartMultiChunk.java
test/jdk/jdk/jfr/api/consumer/streaming/TestStartSingleChunk.java
test/jdk/jdk/jfr/api/consumer/streaming/UseCasesStream.java
test/jdk/jdk/jfr/event/runtime/TestFlush.java
test/jdk/jdk/jfr/jvm/TestThreadExclusion.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hotspot/share/jfr/recorder/repository/jfrChunk.cpp	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2012, 2019, 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.
+ *
+ * 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.
+ *
+ */
+
+#include "precompiled.hpp"
+#include "jfr/dcmd/jfrDcmds.hpp"
+#include "jfr/recorder/jfrRecorder.hpp"
+#include "jfr/recorder/repository/jfrChunk.hpp"
+#include "jfr/recorder/repository/jfrChunkWriter.hpp"
+#include "jfr/utilities/jfrTimeConverter.hpp"
+#include "logging/log.hpp"
+#include "runtime/os.inline.hpp"
+#include "runtime/thread.inline.hpp"
+
+static const u1 GUARD = 0xff;
+
+static jlong nanos_now() {
+  return os::javaTimeMillis() * JfrTimeConverter::NANOS_PER_MILLISEC;
+}
+
+static jlong ticks_now() {
+  return JfrTicks::now();
+}
+
+JfrChunk::JfrChunk() :
+  _path(NULL),
+  _start_ticks(0),
+  _previous_start_ticks(invalid_time),
+  _start_nanos(0),
+  _previous_start_nanos(invalid_time),
+  _last_update_nanos(0),
+  _last_checkpoint_offset(0),
+  _last_metadata_offset(0),
+  _generation(1) {}
+
+JfrChunk::~JfrChunk() {
+  reset();
+}
+
+void JfrChunk::reset() {
+  if (_path != NULL) {
+    JfrCHeapObj::free(_path, strlen(_path) + 1);
+    _path = NULL;
+  }
+  _last_checkpoint_offset = _last_metadata_offset = 0;
+  _generation = 1;
+}
+
+void JfrChunk::set_last_checkpoint_offset(int64_t offset) {
+  _last_checkpoint_offset = offset;
+}
+
+int64_t JfrChunk::last_checkpoint_offset() const {
+  return _last_checkpoint_offset;
+}
+
+int64_t JfrChunk::start_ticks() const {
+  assert(_start_ticks != 0, "invariant");
+  return _start_ticks;
+}
+
+int64_t JfrChunk::start_nanos() const {
+  assert(_start_nanos != 0, "invariant");
+  return _start_nanos;
+}
+
+int64_t JfrChunk::previous_start_ticks() const {
+  assert(_previous_start_ticks != invalid_time, "invariant");
+  return _previous_start_ticks;
+}
+
+int64_t JfrChunk::previous_start_nanos() const {
+  assert(_previous_start_nanos != invalid_time, "invariant");
+  return _previous_start_nanos;
+}
+
+void JfrChunk::update_start_ticks() {
+  _start_ticks = ticks_now();
+}
+
+void JfrChunk::update_start_nanos() {
+  _start_nanos = _last_update_nanos = nanos_now();
+}
+
+void JfrChunk::update() {
+  _last_update_nanos = nanos_now();
+}
+
+void JfrChunk::save_current_and_update_start_ticks() {
+  _previous_start_ticks = _start_ticks;
+  update_start_ticks();
+}
+
+void JfrChunk::save_current_and_update_start_nanos() {
+  _previous_start_nanos = _start_nanos;
+  update_start_nanos();
+}
+
+void JfrChunk::update_time_to_now() {
+  save_current_and_update_start_nanos();
+  save_current_and_update_start_ticks();
+}
+
+int64_t JfrChunk::last_chunk_duration() const {
+  assert(_previous_start_nanos != invalid_time, "invariant");
+  return _start_nanos - _previous_start_nanos;
+}
+
+static char* copy_path(const char* path) {
+  assert(path != NULL, "invariant");
+  const size_t path_len = strlen(path);
+  char* new_path = JfrCHeapObj::new_array<char>(path_len + 1);
+  strncpy(new_path, path, path_len + 1);
+  return new_path;
+}
+
+void JfrChunk::set_path(const char* path) {
+  if (_path != NULL) {
+    JfrCHeapObj::free(_path, strlen(_path) + 1);
+    _path = NULL;
+  }
+  if (path != NULL) {
+    _path = copy_path(path);
+  }
+}
+
+const char* JfrChunk::path() const {
+  return _path;
+}
+
+bool JfrChunk::is_started() const {
+  return _start_nanos != 0;
+}
+
+bool JfrChunk::is_finished() const {
+  return 0 == _generation;
+}
+
+bool JfrChunk::is_initial_flush() const {
+  return 0 == _last_metadata_offset;
+}
+
+int64_t JfrChunk::duration() const {
+  assert(_last_update_nanos >= _start_nanos, "invariant");
+  return _last_update_nanos - _start_nanos;
+}
+
+int64_t JfrChunk::last_metadata_offset() const {
+  return _last_metadata_offset;
+}
+
+void JfrChunk::set_last_metadata_offset(int64_t offset) {
+  if (0 == offset) {
+    return;
+  }
+  assert(offset > _last_metadata_offset, "invariant");
+  _last_metadata_offset = offset;
+}
+
+bool JfrChunk::has_metadata() const {
+  return 0 != _last_metadata_offset;
+}
+
+u1 JfrChunk::generation() const {
+  assert(_generation > 0, "invariant");
+  const u1 this_generation = _generation++;
+  if (GUARD == _generation) {
+    _generation = 1;
+  }
+  return this_generation;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hotspot/share/jfr/recorder/repository/jfrChunk.hpp	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2012, 2019, 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.
+ *
+ * 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.
+ *
+ */
+
+#ifndef SHARE_VM_JFR_RECORDER_REPOSITORY_JFRRCHUNK_HPP
+#define SHARE_VM_JFR_RECORDER_REPOSITORY_JFRRCHUNK_HPP
+
+#include "jni.h"
+#include "jfr/utilities/jfrAllocation.hpp"
+#include "jfr/utilities/jfrTypes.hpp"
+
+class JfrChunk : public JfrCHeapObj {
+  friend class JfrChunkWriter;
+  friend class JfrChunkHeadWriter;
+ private:
+  char* _path;
+  int64_t _start_ticks;
+  int64_t _previous_start_ticks;
+  int64_t _start_nanos;
+  int64_t _previous_start_nanos;
+  int64_t _last_update_nanos;
+  int64_t _last_checkpoint_offset;
+  int64_t _last_metadata_offset;
+  mutable u1 _generation;
+
+  JfrChunk();
+  ~JfrChunk();
+
+  void update_start_ticks();
+  void update_start_nanos();
+  void save_current_and_update_start_ticks();
+  void save_current_and_update_start_nanos();
+
+  void reset();
+  int64_t last_checkpoint_offset() const;
+  void set_last_checkpoint_offset(int64_t offset);
+
+  int64_t last_metadata_offset() const;
+  void set_last_metadata_offset(int64_t offset);
+  bool has_metadata() const;
+
+  int64_t start_ticks() const;
+  int64_t start_nanos() const;
+
+  int64_t previous_start_ticks() const;
+  int64_t previous_start_nanos() const;
+  int64_t last_chunk_duration() const;
+
+  void update_time_to_now();
+  void set_path(const char* path);
+  const char* path() const;
+
+  void update();
+
+  bool is_started() const;
+  bool is_finished() const;
+  bool is_initial_flush() const;
+
+  int64_t duration() const;
+  u1 generation() const;
+};
+
+#endif // SHARE_VM_JFR_RECORDER_REPOSITORY_JFRRCHUNK_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hotspot/share/jfr/utilities/jfrThreadIterator.cpp	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2019, 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.
+ *
+ * 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.
+ *
+ */
+
+#include "precompiled.hpp"
+#include "jfr/support/jfrThreadLocal.hpp"
+#include "jfr/utilities/jfrThreadIterator.hpp"
+#include "runtime/thread.inline.hpp"
+
+static bool thread_inclusion_predicate(Thread* t) {
+  assert(t != NULL, "invariant");
+  return !(t->jfr_thread_local()->is_excluded() || t->jfr_thread_local()->is_dead());
+}
+
+static bool java_thread_inclusion_predicate(JavaThread* jt) {
+  assert(jt != NULL, "invariant");
+  return thread_inclusion_predicate(jt) && jt->thread_state() != _thread_new;
+}
+
+static JavaThread* next_java_thread(JavaThreadIteratorWithHandle& iter) {
+  JavaThread* next = iter.next();
+  while (next != NULL && !java_thread_inclusion_predicate(next)) {
+    next = iter.next();
+  }
+  return next;
+}
+
+static NonJavaThread* next_non_java_thread(NonJavaThread::Iterator& iter) {
+  NonJavaThread* next = NULL;
+  while (!iter.end()) {
+    next = iter.current();
+    iter.step();
+    assert(next != NULL, "invariant");
+    if (!thread_inclusion_predicate(next)) {
+      continue;
+    }
+  }
+  return next;
+}
+
+JfrJavaThreadIteratorAdapter::JfrJavaThreadIteratorAdapter() : _iter(), _next(next_java_thread(_iter)) {}
+
+JavaThread* JfrJavaThreadIteratorAdapter::next() {
+  assert(has_next(), "invariant");
+  Type* const temp = _next;
+  assert(java_thread_inclusion_predicate(temp), "invariant");
+  _next = next_java_thread(_iter);
+  assert(temp != _next, "invariant");
+  return temp;
+}
+
+JfrNonJavaThreadIteratorAdapter::JfrNonJavaThreadIteratorAdapter() : _iter(), _next(next_non_java_thread(_iter)) {}
+
+bool JfrNonJavaThreadIteratorAdapter::has_next() const {
+  return _next != NULL;
+}
+
+NonJavaThread* JfrNonJavaThreadIteratorAdapter::next() {
+  assert(has_next(), "invariant");
+  Type* const temp = _next;
+  assert(thread_inclusion_predicate(temp), "invariant");
+  _next = next_non_java_thread(_iter);
+  assert(temp != _next, "invariant");
+  return temp;
+}
+
+// explicit instantiations
+template class JfrThreadIterator<JfrJavaThreadIteratorAdapter, StackObj>;
+template class JfrThreadIterator<JfrNonJavaThreadIteratorAdapter, StackObj>;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hotspot/share/jfr/utilities/jfrThreadIterator.hpp	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2019, 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.
+ *
+ * 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.
+ *
+ */
+
+#ifndef SHARE_VM_JFR_UTILITIES_JFRTHREADITERATOR_HPP
+#define SHARE_VM_JFR_UTILITIES_JFRTHREADITERATOR_HPP
+
+#include "memory/allocation.hpp"
+#include "runtime/thread.hpp"
+#include "runtime/threadSMR.hpp"
+
+template <typename Adapter, typename AP = StackObj>
+class JfrThreadIterator : public AP {
+ private:
+  Adapter _adapter;
+ public:
+  JfrThreadIterator() : _adapter() {}
+  typename Adapter::Type* next() {
+    assert(has_next(), "invariant");
+    return _adapter.next();
+  }
+  bool has_next() const {
+    return _adapter.has_next();
+  }
+};
+
+class JfrJavaThreadIteratorAdapter {
+ private:
+  JavaThreadIteratorWithHandle _iter;
+  JavaThread* _next;
+ public:
+  typedef JavaThread Type;
+  JfrJavaThreadIteratorAdapter();
+  bool has_next() const {
+    return _next != NULL;
+  }
+  Type* next();
+};
+
+class JfrNonJavaThreadIteratorAdapter {
+ private:
+  NonJavaThread::Iterator _iter;
+  NonJavaThread* _next;
+ public:
+  typedef NonJavaThread Type;
+  JfrNonJavaThreadIteratorAdapter();
+  bool has_next() const;
+  Type* next();
+};
+
+typedef JfrThreadIterator<JfrJavaThreadIteratorAdapter, StackObj> JfrJavaThreadIterator;
+typedef JfrThreadIterator<JfrNonJavaThreadIteratorAdapter, StackObj> JfrNonJavaThreadIterator;
+
+#endif // SHARE_VM_JFR_UTILITIES_JFRTHREADITERATOR_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/ConstantLookup.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,42 @@
+package jdk.jfr.consumer;
+
+import jdk.jfr.internal.Type;
+
+final class ConstantLookup {
+
+    private final Type type;
+    private ConstantMap current;
+    private ConstantMap previous = ConstantMap.EMPTY;
+
+    ConstantLookup(ConstantMap current, Type type) {
+        this.current = current;
+        this.type = type;
+    }
+
+    public Type getType() {
+        return type;
+    }
+
+    public ConstantMap getLatestPool() {
+        return current;
+    }
+
+    public void newPool() {
+     //   previous = current;
+        current = new ConstantMap(current.factory, current.name);
+     //   previous =  new ConstantMap(); // disable cache
+    }
+
+    public Object getPreviousResolved(long key) {
+        return previous.getResolved(key);
+    }
+
+    public Object getCurrentResolved(long key) {
+        return current.getResolved(key);
+    }
+
+    public Object getCurrent(long key) {
+        return current.get(key);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/EventDirectoryStream.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,172 @@
+/*
+ * Copyright (c) 2019, 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.consumer;
+
+import java.io.IOException;
+import java.security.AccessControlContext;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Objects;
+import java.util.function.Consumer;
+
+import jdk.jfr.EventType;
+
+final class EventDirectoryStream implements EventStream {
+
+    public final static class EventConsumer {
+        final private String eventName;
+        final Consumer<RecordedEvent> action;
+
+        EventConsumer(String eventName, Consumer<RecordedEvent> eventConsumer) {
+            this.eventName = eventName;
+            this.action = eventConsumer;
+        }
+
+        public void offer(RecordedEvent event) {
+            action.accept(event);
+        }
+
+        public boolean accepts(EventType eventType) {
+            return (eventName == null || eventType.getName().equals(eventName));
+        }
+    }
+
+    private final EventRunner eventRunner;
+    private Thread thread;
+    private boolean started;
+
+    public EventDirectoryStream(AccessControlContext acc) throws IOException {
+        eventRunner = new EventRunner(acc);
+    }
+
+    public void close() {
+        synchronized (eventRunner) {
+            eventRunner.close();
+        }
+    }
+
+    public synchronized void onFlush(Runnable action) {
+        Objects.requireNonNull(action);
+        synchronized (eventRunner) {
+            this.eventRunner.addFlush(action);
+        }
+    }
+
+    void start(long startNanos) {
+        synchronized (eventRunner) {
+            if (started) {
+                throw new IllegalStateException("Event stream can only be started once");
+            }
+            started = true;
+            eventRunner.setStartNanos(startNanos);
+        }
+        eventRunner.run();
+    }
+
+    @Override
+    public void start() {
+        start(Instant.now().toEpochMilli() * 1000*1000L);
+    }
+
+    @Override
+    public void startAsync() {
+        startAsync(Instant.now().toEpochMilli() * 1000*1000L);
+    }
+
+    void startAsync(long startNanos) {
+        synchronized (eventRunner) {
+            eventRunner.setStartNanos(startNanos);
+            thread = new Thread(eventRunner);
+            thread.setDaemon(true);
+            thread.start();
+        }
+    }
+
+    public void addEventConsumer(EventConsumer action) {
+        Objects.requireNonNull(action);
+        synchronized (eventRunner) {
+            eventRunner.add(action);
+        }
+    }
+
+
+
+    @Override
+    public void onEvent(Consumer<RecordedEvent> action) {
+        Objects.requireNonNull(action);
+        synchronized (eventRunner) {
+            eventRunner.add(new EventConsumer(null, action));
+        }
+    }
+
+    @Override
+    public void onEvent(String eventName, Consumer<RecordedEvent> action) {
+        Objects.requireNonNull(eventName);
+        Objects.requireNonNull(action);
+        synchronized (eventRunner) {
+            eventRunner.add(new EventConsumer(eventName, action));
+        }
+    }
+
+    @Override
+    public void onClose(Runnable action) {
+        Objects.requireNonNull(action);
+        synchronized (eventRunner) {
+            eventRunner.addCloseAction(action);
+        }
+    }
+
+    @Override
+    public boolean remove(Object action) {
+        Objects.requireNonNull(action);
+        synchronized (eventRunner) {
+            return eventRunner.remove(action);
+        }
+    }
+
+    @Override
+    public void awaitTermination(Duration timeout) {
+        Objects.requireNonNull(timeout);
+        Thread t = null;
+        synchronized (eventRunner) {
+            t = thread;
+        }
+        if (t != null && t != Thread.currentThread()) {
+            try {
+                t.join(timeout.toMillis());
+            } catch (InterruptedException e) {
+                // ignore
+            }
+        }
+    }
+
+    @Override
+    public void awaitTermination() {
+        awaitTermination(Duration.ofMillis(0));
+    }
+
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/EventFileStream.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2019, 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.consumer;
+
+import java.nio.file.Path;
+import java.time.Duration;
+import java.util.Objects;
+import java.util.function.Consumer;
+
+/**
+ * Implementation of an event stream that operates against a recording file.
+ *
+ */
+final class EventFileStream implements EventStream {
+
+    public EventFileStream(Path path) {
+        Objects.requireNonNull(path);
+    }
+
+    @Override
+    public void onEvent(Consumer<RecordedEvent> action) {
+        Objects.requireNonNull(action);
+        notImplemented();
+    }
+
+    public void onEvent(EventFilter filter, Consumer<RecordedEvent> action) {
+        Objects.requireNonNull(filter);
+        Objects.requireNonNull(action);
+        notImplemented();
+    }
+
+    @Override
+    public void onEvent(String eventName, Consumer<RecordedEvent> action) {
+        Objects.requireNonNull(eventName);
+        Objects.requireNonNull(action);
+        notImplemented();
+    }
+
+    @Override
+    public void onFlush(Runnable action) {
+        Objects.requireNonNull(action);
+        notImplemented();
+    }
+
+    @Override
+    public void onClose(Runnable action) {
+        Objects.requireNonNull(action);
+        notImplemented();
+    }
+
+    @Override
+    public void close() {
+        notImplemented();
+    }
+
+    @Override
+    public boolean remove(Object action) {
+        Objects.requireNonNull(action);
+        notImplemented();
+        return false;
+    }
+
+    @Override
+    public void start() {
+        notImplemented();
+    }
+
+    @Override
+    public void startAsync() {
+        notImplemented();
+    }
+
+    @Override
+    public void awaitTermination(Duration timeout) {
+        Objects.requireNonNull(timeout);
+    }
+
+    @Override
+    public void awaitTermination() {
+        notImplemented();
+    }
+
+    private static void notImplemented() {
+        throw new UnsupportedOperationException("Streaming for files not yet implemenetd");
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/EventFilter.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2019, 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.consumer;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.List;
+
+final class EventFilter {
+    private final String[] eventNames;
+    private final Duration threshold;
+    private final String[] fields;
+
+    private EventFilter(String[] eventNames, Duration threshold, String[] fields) {
+        this.eventNames = eventNames;
+        this.threshold = threshold;
+        this.fields = fields;
+    }
+
+    public static EventFilter eventTypes(String... eventNames) {
+        return new EventFilter(eventNames.clone(), null, new String[0]);
+    }
+
+    public EventFilter threshold(Duration threshold) {
+        return new EventFilter(eventNames, threshold, fields);
+    }
+
+    public EventFilter fields(String... fieldNames) {
+        return new EventFilter(eventNames, threshold, fieldNames);
+    }
+
+    public EventFilter start(Instant instant) {
+        return this;
+    }
+
+    public EventFilter end(Instant instant) {
+        return this;
+    }
+
+    public EventFilter threads(Thread... t) {
+        return this;
+    }
+
+    public EventFilter threadIds(long... threadId) {
+        return this;
+    }
+
+    public EventFilter threadNames(String... threadName) {
+        return this;
+    }
+
+    public EventFilter threadFilters(String... filter) {
+        return this;
+    }
+
+    List<String> getFields() {
+        return Arrays.asList(fields);
+    }
+
+    List<String> getEventNames() {
+        return Arrays.asList(fields);
+    }
+
+    Duration getThreshold() {
+        return threshold;
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/EventRunner.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,266 @@
+/*
+ * Copyright (c) 2019, 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.consumer;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import jdk.jfr.consumer.EventDirectoryStream.EventConsumer;
+import jdk.jfr.internal.JVM;
+import jdk.jfr.internal.LogLevel;
+import jdk.jfr.internal.LogTag;
+import jdk.jfr.internal.Logger;
+
+class EventRunner implements Runnable {
+    private final static VarHandle closedHandle;
+    private final static VarHandle consumersHandle;
+    private final static VarHandle dispatcherHandle;
+    private final static VarHandle flushActionsHandle;
+    private final static VarHandle closeActionsHandle;
+    static {
+        try {
+            MethodHandles.Lookup l = MethodHandles.lookup();
+            closedHandle = l.findVarHandle(EventRunner.class, "closed", boolean.class);
+            consumersHandle = l.findVarHandle(EventRunner.class, "consumers", EventConsumer[].class);
+            dispatcherHandle = l.findVarHandle(EventRunner.class, "dispatcher", LongMap.class);
+            flushActionsHandle = l.findVarHandle(EventRunner.class, "flushActions", Runnable[].class);
+            closeActionsHandle = l.findVarHandle(EventRunner.class, "closeActions", Runnable[].class);
+        } catch (ReflectiveOperationException e) {
+            throw new InternalError(e);
+        }
+    }
+    // set by VarHandle
+    private boolean closed;
+    // set by VarHandle
+    private EventConsumer[] consumers = new EventConsumer[0];
+    // set by VarHandle
+    private LongMap<EventConsumer[]> dispatcher = new LongMap<>();
+    // set by VarHandle
+    private Runnable[] flushActions = new Runnable[0];
+    // set by VarHandle
+    private Runnable[] closeActions = new Runnable[0];
+
+    private final static JVM jvm = JVM.getJVM();
+    private final static EventConsumer[] NO_CONSUMERS = new EventConsumer[0];
+    private final AccessControlContext accessControlContext;
+    private EventSetLocation location;
+    private EventSet eventSet;
+    private InternalEventFilter eventFilter = InternalEventFilter.ACCEPT_ALL;
+    private int eventSetIndex;
+    private int eventArrayIndex;
+    private RecordedEvent[] currentEventArray = new RecordedEvent[0];
+    private volatile long startNanos;
+
+    public EventRunner(AccessControlContext acc) throws IOException {
+        this.accessControlContext = acc;
+    }
+
+    public void run() {
+        doPriviliged(() -> execute());
+    }
+
+    void doPriviliged(Runnable r) {
+        AccessController.doPrivileged(new PrivilegedAction<Void>() {
+            @Override
+            public Void run() {
+                r.run();
+                return null;
+            }
+        }, accessControlContext);
+    }
+
+    private void execute() {
+        jvm.exclude(Thread.currentThread());
+        try {
+            process();
+        } catch (Throwable e) {
+            e.printStackTrace();
+            Logger.log(LogTag.JFR_SYSTEM_STREAMING, LogLevel.DEBUG, "Unexpectedexception iterating consumer.");
+        } finally {
+            Logger.log(LogTag.JFR_SYSTEM_STREAMING, LogLevel.DEBUG, "Execution of stream ended.");
+        }
+    }
+
+    private void process() throws Exception, IOException {
+        this.location = EventSetLocation.current();
+        this.eventSet = location.acquire(startNanos, null); // use timestamp from
+        if (eventSet == null) {
+            return;
+        }
+        while (!closed) {
+            processSegment();
+            Runnable[] fas = this.flushActions;
+            for (int i = 0; i < fas.length; i++) {
+                fas[i].run();
+            }
+            do {
+                if (closed) {
+                    return;
+                }
+                currentEventArray = eventSet.readEvents(eventSetIndex);
+                if (currentEventArray == EventSet.END_OF_SET) {
+                    eventSet = eventSet.next(eventFilter);
+                    if (eventSet == null || closed) {
+                        return;
+                    }
+                    eventSetIndex = 0;
+                    continue;
+                }
+                if (currentEventArray == null) {
+                    return; // no more events
+                }
+                eventSetIndex++;
+            } while (currentEventArray.length == 0);
+            eventArrayIndex = 0;
+        }
+    }
+
+    private void processSegment() {
+        while (eventArrayIndex < currentEventArray.length) {
+            RecordedEvent e = currentEventArray[eventArrayIndex++];
+            if (e == null) {
+               return;
+            }
+            EventConsumer[] consumerDispatch = dispatcher.get(e.getEventType().getId());
+            if (consumerDispatch == null) {
+                consumerDispatch = NO_CONSUMERS;
+                for (EventConsumer ec : consumers.clone()) {
+                    if (ec.accepts(e.getEventType())) {
+                        consumerDispatch = merge(consumerDispatch, ec);
+                    }
+                }
+                dispatcher.put(e.getEventType().getId(), consumerDispatch);
+            }
+            for (int i = 0; i < consumerDispatch.length; i++) {
+                consumerDispatch[i].offer(e);
+            }
+        }
+    }
+
+    static EventConsumer[] merge(EventConsumer[] current, EventConsumer add) {
+        EventConsumer[] array = new EventConsumer[current.length + 1];
+        System.arraycopy(current, 0, array, 0, current.length);
+        array[current.length] = add;
+        return array;
+    }
+
+    public void add(EventConsumer e) {
+        consumersHandle.setVolatile(this, merge(consumers, e));
+        dispatcherHandle.setVolatile(this, new LongMap<>()); // will reset
+    }
+
+    private static Runnable[] removeAction(Runnable[] array, Object action)  {
+        if (array.length == 0) {
+            return null;
+        }
+        boolean remove = false;
+        List<Runnable> list = new ArrayList<>();
+        for (int i = 0; i < array.length; i++)  {
+            if (array[i] != action) {
+                list.add(array[i]);
+            } else {
+                remove = true;
+            }
+        }
+        if (remove) {
+            return list.toArray(new Runnable[list.size()]);
+        }
+        return null;
+    }
+
+    private static Runnable[] addAction(Runnable[] array, Runnable action)   {
+        ArrayList<Runnable> a = new ArrayList<>();
+        a.addAll(Arrays.asList(array));
+        a.add(action);
+        return a.toArray(new Runnable[0]);
+    }
+
+    public boolean remove(Object action) {
+        boolean remove = false;
+        Runnable[] updatedFlushActions = removeAction(flushActions, action);
+        if (updatedFlushActions != null) {
+            flushActionsHandle.setVolatile(this, updatedFlushActions);
+            remove = true;
+        }
+        Runnable[] updatedCloseActions = removeAction(closeActions, action);
+        if (updatedCloseActions != null) {
+            closeActionsHandle.setVolatile(this, updatedCloseActions);
+            remove = true;
+        }
+
+        boolean removeConsumer = false;
+        List<EventConsumer> list = new ArrayList<>();
+        for (int i = 0; i < consumers.length; i++) {
+            if (consumers[i].action != action) {
+                list.add(consumers[i]);
+            } else {
+                removeConsumer = true;
+                remove = true;
+            }
+        }
+        if (removeConsumer) {
+            EventConsumer[] array = list.toArray(new EventConsumer[list.size()]);
+            consumersHandle.setVolatile(this, array);
+            dispatcherHandle.setVolatile(this, new LongMap<>()); // will reset dispatch
+        }
+        return remove;
+    }
+
+    public void addFlush(Runnable action) {
+        flushActionsHandle.setVolatile(this, addAction(flushActions, action));
+    }
+
+    public void close() {
+        closedHandle.setVolatile(this, true);
+        // TODO: Data races here, must fix
+        if (eventSet != null) {
+            eventSet.release(null);
+        }
+        if (location != null) {
+            location.release();
+        }
+
+        Runnable[] cas = this.closeActions;
+        for (int i = 0; i < cas.length; i++) {
+            cas[i].run();
+        }
+    }
+
+    public void addCloseAction(Runnable action) {
+        closeActionsHandle.setVolatile(this, addAction(closeActions, action));
+    }
+
+    public void setStartNanos(long startNanos) {
+        this.startNanos = startNanos;
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/EventSet.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,232 @@
+/*
+ * Copyright (c) 2019, 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.consumer;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.locks.ReentrantLock;
+
+import jdk.jfr.internal.consumer.ChunkHeader;
+import jdk.jfr.internal.consumer.RecordingInput;
+
+/**
+ * Cache that represents all discovered events in a chunk.
+ *
+ */
+final class EventSet {
+
+    public static final RecordedEvent[] END_OF_SET = new RecordedEvent[0];
+    private static final AtomicInteger idCounter = new AtomicInteger(-1);
+
+    private volatile Object[][] segments = new Object[1000][];
+    private volatile boolean closed;
+    private final long startTimeNanos;
+    private final EventSetLocation location;
+    private final Path path;
+    private final int id;
+
+    // Guarded by lock
+    private boolean awaitNewEvents;
+    private RecordingInput input;
+    private ChunkParser chunkParser;
+    private int referenceCount;
+    private final ReentrantLock lock = new ReentrantLock();
+    private final Set<InternalEventFilter> filters = new HashSet<>();
+    private InternalEventFilter globalFilter = InternalEventFilter.ACCEPT_ALL;
+    private boolean dirtyFilter = true;
+
+    public void release(InternalEventFilter eventFilter) {
+        try {
+            lock.lock();
+            filters.remove(eventFilter);
+            updateGlobalFilter();
+            referenceCount--;
+            if (referenceCount == 0) {
+                closed = true;
+                if (input != null) {
+                    try {
+                        input.close();
+                    } catch (IOException e) {
+                        // TODO: Flie locked by other process?
+                    }
+                    chunkParser = null;
+                    input = null;
+                }
+            }
+        } finally {
+           lock.unlock();
+        }
+    }
+
+    public EventSet(EventSetLocation location, EventSet previousEventSet, Path p) throws IOException {
+        this.location = location;
+        this.path = p;
+        this.startTimeNanos = readStartTime(p);
+        this.id = idCounter.incrementAndGet();
+    }
+
+    private long readStartTime(Path p) throws IOException {
+        try (RecordingInput in = new RecordingInput(p.toFile(), 100)) {
+            ChunkHeader c = new ChunkHeader(in);
+            return c.getStartNanos();
+        }
+    }
+
+    Path getPath() {
+        return path;
+    }
+
+    // TODO: Use binary search, must use lock
+    public int findIndex(Instant timestamp) {
+        int index = 0;
+        for (int i = 0; i < segments.length; i++) {
+            RecordedEvent[] events = (RecordedEvent[]) segments[i];
+            if (events == null || events.length == 0) {
+                return Math.max(index - 1, 0);
+            }
+            RecordedEvent e = events[0]; // May not be sorted.
+            if (timestamp.isAfter(e.getEndTime())) {
+                return Math.max(index - 1, 0);
+            }
+        }
+        return segments.length;
+    }
+
+    public void addFilter(InternalEventFilter filter) {
+        try {
+            lock.lock();
+            filters.add(filter);
+            updateGlobalFilter();
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    // held with lock
+    private void updateGlobalFilter() {
+        globalFilter = InternalEventFilter.merge(filters);
+        dirtyFilter = true;
+    }
+
+    public RecordedEvent[] readEvents(int index) throws Exception {
+        while (!closed) {
+
+            RecordedEvent[] events = (RecordedEvent[]) segments[index];
+            if (events != null) {
+                return events;
+            }
+            if (lock.tryLock(250, TimeUnit.MILLISECONDS)) {
+                try {
+                    addSegment(index);
+                } finally {
+                    lock.unlock();
+                }
+            }
+        }
+        return null;
+    }
+
+    // held with lock
+    private void addSegment(int index) throws IOException {
+        if (chunkParser == null) {
+            chunkParser = new ChunkParser(new RecordingInput(path.toFile()));
+        }
+        if (dirtyFilter) {
+            chunkParser.setParserFilter(globalFilter);
+        }
+        if (segments[index] != null) {
+            return;
+        }
+        if (index == segments.length - 2) {
+            segments = Arrays.copyOf(segments, segments.length * 2);
+        }
+        RecordedEvent[] segment = new RecordedEvent[10];
+        int i = 0;
+        while (true) {
+            RecordedEvent e = chunkParser.readStreamingEvent(awaitNewEvents);
+            if (e == null) {
+                // wait for new event with next call to readStreamingEvent()
+                awaitNewEvents = true;
+                break;
+            }
+            awaitNewEvents = false;
+            if (i == segment.length) {
+                segment = Arrays.copyOf(segment, segment.length * 2);
+            }
+            segment[i++] = e;
+        }
+
+        // no events found
+        if (i == 0) {
+            if (chunkParser.isChunkFinished()) {
+                segments[index] = END_OF_SET;
+                return;
+            }
+        }
+        // at least 2 events, sort them
+        if (i > 1) {
+            Arrays.sort(segment, 0, i, (e1, e2) -> Long.compare(e1.endTime, e2.endTime));
+        }
+        segments[index] = segment;
+        if (chunkParser.isChunkFinished()) {
+            segments[index + 1] = END_OF_SET;
+        }
+    }
+
+    public long getStartTimeNanos() {
+        return startTimeNanos;
+    }
+
+    public EventSet next(InternalEventFilter filter) throws IOException {
+        EventSet next = location.acquire(startTimeNanos + 1, this);
+        if (next == null) {
+            // closed
+            return null;
+        }
+        next.addFilter(filter);
+        release(filter);
+        return next;
+    }
+
+    public void acquire() {
+        try {
+            lock.lock();
+            referenceCount++;
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    public String toString() {
+        return "Chunk:" + id + " (" + path + ")";
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/EventSetLocation.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2019, 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.consumer;
+
+import java.io.IOException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import jdk.jfr.internal.LogLevel;
+import jdk.jfr.internal.LogTag;
+import jdk.jfr.internal.Logger;
+import jdk.jfr.internal.Repository;
+import jdk.jfr.internal.consumer.ChunkHeader;
+
+/**
+ * This class corresponds to a disk repository.
+ * <p>
+ * Main purpose is to act as a cache if multiple {@code EventStream} want to
+ * access the same repository. An {@code EventSetLocation} should be released
+ * when it is no longer being used.
+ *
+ */
+final class EventSetLocation {
+    private static Map<Path, EventSetLocation> locations = new HashMap<>();
+
+    private final SortedMap<Long, EventSet> eventSets = new TreeMap<>();
+    private final Map<Path, Long> lastPaths = new HashMap<>();
+
+    final Path path;
+    private int count = 0;
+    private volatile boolean closed;
+
+    private EventSetLocation(Path path) {
+        this.path = path;
+    }
+
+    public static EventSetLocation get(Path absolutPath) {
+        synchronized (locations) {
+            EventSetLocation esl = locations.get(absolutPath);
+            if (esl == null) {
+                esl = new EventSetLocation(absolutPath);
+                locations.put(absolutPath, esl);
+            }
+            esl.count++;
+            return esl;
+        }
+    }
+
+    public static EventSetLocation current() throws IOException {
+        Repository.getRepository().ensureRepository();
+        return get(Repository.getRepository().getRepositoryPath().toPath());
+    }
+
+    public void release() {
+        synchronized (locations) {
+            count--;
+            if (count == 0) {
+                locations.remove(path);
+                closed = true;
+            }
+        }
+    }
+
+    public synchronized EventSet acquire(long startTimeNanos, EventSet previousEventSet) {
+        synchronized (eventSets) {
+            while (!closed) {
+                SortedMap<Long, EventSet> after = eventSets.tailMap(startTimeNanos);
+                if (!after.isEmpty()) {
+                    EventSet es =  after.get(after.firstKey());
+                    Logger.log(LogTag.JFR_SYSTEM_STREAMING, LogLevel.TRACE, "Acquired " + startTimeNanos + ", got " + es);
+                    es.acquire();
+                    return es;
+                }
+                try {
+                    updateEventSets(previousEventSet);
+                } catch (IOException e) {
+                    e.printStackTrace();
+                    // This can happen if a chunk is being removed
+                    // between the file was discovered and an instance
+                    // of an EventSet was constructed. Just ignore,
+                    // and retry later.
+                }
+                try {
+                    eventSets.wait(1000);
+                } catch (InterruptedException e) {
+                    // ignore
+                }
+            }
+        }
+        return null;
+    }
+
+    private void updateEventSets(EventSet previousEventSet) throws IOException {
+        List<Path> added = new ArrayList<>();
+        Set<Path> current = new HashSet<>();
+        try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(path, "*.jfr")) {
+            for (Path p : dirStream) {
+                if (!lastPaths.containsKey(p)) {
+                    added.add(p);
+                    Logger.log(LogTag.JFR_SYSTEM_STREAMING, LogLevel.DEBUG, "New file found: " + p.toAbsolutePath());
+                }
+                current.add(p);
+            }
+        }
+        List<Path> removed = new ArrayList<>();
+        for (Path p : lastPaths.keySet()) {
+            if (!current.contains(p)) {
+                removed.add(p);
+            }
+        }
+
+        for (Path remove : removed) {
+            Long time = lastPaths.get(remove);
+            eventSets.remove(time);
+            lastPaths.remove(remove);
+        }
+        Collections.sort(added, (p1,p2) -> p1.compareTo(p2));
+        for (Path p : added) {
+            // Only add files that have a complete header
+            // as the JVM may be in progress writing the file
+            long size = Files.size(p);
+            if (size >= ChunkHeader.HEADER_SIZE) {
+                EventSet es = new EventSet(this, previousEventSet, p);
+                long startTime = es.getStartTimeNanos();
+                if (startTime == 0) {
+                    String errorMsg = "Chunk header should always contain a valid start time";
+                    System.err.println(errorMsg);
+                    throw new InternalError(errorMsg);
+                }
+                EventSet previous = eventSets.get(startTime);
+                if (previous != null) {
+                    String errorMsg = "Found chunk " + p + " with the same start time " + startTime + " as previous chunk " + previous.getPath();
+                    System.err.println(errorMsg);
+                    throw new InternalError(errorMsg);
+                }
+                eventSets.put(startTime, es);
+                lastPaths.put(p, startTime);
+                previousEventSet = es;
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/EventStream.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2019, 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.consumer;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.time.Duration;
+import java.util.function.Consumer;
+
+/**
+ * Represents a stream of event that actions can be performed up on.
+ */
+public interface EventStream extends AutoCloseable {
+
+    /**
+     * Creates a stream starting from the next written event in a disk
+     * repository.
+     *
+     * @param directory location of the disk repository, not {@code null}
+     * @return an event stream, not {@code null}
+     */
+    public static EventStream openRepository(Path directory) throws IOException {
+        throw new UnsupportedOperationException("Not yet implemented");
+//       AccessControlContext acc = AccessController.getContext();
+//       return new EventDirectoryStream(acc);
+    }
+
+    /**
+     * Creates an event stream starting from the first event in a file.
+     *
+     * @param file location of the file, not {@code null}
+     * @return an event stream, not {@code null}
+     */
+    public static EventStream openFile(Path file) throws IOException {
+        throw new UnsupportedOperationException("Not yet implemented");
+//      return new EventFileStream(file);
+    }
+
+    /**
+     * Performs an action on all events in the stream.
+     *
+     * @param action an action to be performed on each {@code RecordedEvent},
+     *        not {@code null}
+     */
+    void onEvent(Consumer<RecordedEvent> action);
+
+    /**
+     * Performs an action on all events in the stream with a specified name.
+     *
+     * @param eventName the name of the event, not {@code null}
+     * @param action an action to be performed on each {@code RecordedEvent}
+     *        that matches the event name, not {@code null}
+     */
+    void onEvent(String eventName, Consumer<RecordedEvent> action);
+
+    /**
+     * Performs an action when the event stream has been flushed.
+     *
+     * @param action an action to be performed after stream has been flushed,
+     *        not {@code null}
+     */
+    void onFlush(Runnable action);
+
+    /**
+     * Performs an action when the event stream is closed.
+     *
+     * @param action an action to be performed after the stream has been closed,
+     *        not {@code null}
+     */
+    void onClose(Runnable action);
+
+    /**
+     * Releases all resources associated with this event stream.
+     */
+    void close();
+
+    /**
+     * Removes an action from the stream.
+     * <p>
+     * If the action has been added multiple times, all instance of it will be
+     * removed.
+     *
+     * @param action the action to remove, not {@code null}
+     * @return {@code true} if the action was removed, {@code false} otherwise
+     *
+     * @see #onClose(Runnable)
+     * @see #onFlush(Runnable)
+     * @see #onEvent(Consumer)
+     * @see #onEvent(String, Consumer)
+     */
+    boolean remove(Object action);
+
+    /**
+     * Starts processing events in the stream.
+     * <p>
+     * All actions will performed on this stream will happen in the current
+     * thread.
+     *
+     * @throws IllegalStateException if the stream is already started or if it
+     *         has been closed
+     */
+    void start();
+
+    /**
+     * Starts processing events in the stream asynchronously.
+     * <p>
+     * All actions on this stream will be performed in a separate thread.
+     *
+     * @throws IllegalStateException if the stream is already started or if it
+     *         has been closed
+     */
+    void startAsync();
+
+    /**
+     * Blocks the current thread until the stream is finished, closed, or it
+     * times out.
+     *
+     * @param timeout the maximum time to wait, not {@code null}
+     *
+     * @see #start()
+     * @see #startAsync()
+     */
+    void awaitTermination(Duration timeout);
+
+    /**
+     * Blocks the current thread until the stream is finished or closed.
+     *
+     * @see #start()
+     * @see #startAsync()
+     */
+    void awaitTermination();
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/InternalEventFilter.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2019, 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.consumer;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public final class InternalEventFilter {
+    static final InternalEventFilter ACCEPT_ALL = new InternalEventFilter();
+    private final Map<String, Long> thresholds = new HashMap<>();
+    private boolean acceptAll;
+
+    public static InternalEventFilter merge(Collection<InternalEventFilter> filters) {
+        for (InternalEventFilter ef : filters) {
+            if (ef.getAcceptAll()) {
+                return ACCEPT_ALL;
+            }
+        }
+        if (filters.size() == 1) {
+            return filters.iterator().next();
+        }
+
+        Set<String> eventNames = new HashSet<>();
+        for (InternalEventFilter ef : filters) {
+            eventNames.addAll(ef.thresholds.keySet());
+        }
+        InternalEventFilter result = new InternalEventFilter();
+        for (String eventName : eventNames) {
+            for (InternalEventFilter ef : filters) {
+                Long l = ef.thresholds.get(eventName);
+                if (l != null) {
+                    result.setThreshold(eventName, l.longValue());
+                }
+            }
+        }
+        return result;
+    }
+
+    private boolean getAcceptAll() {
+        return acceptAll;
+    }
+
+    public void setAcceptAll() {
+        acceptAll = true;
+    }
+
+    public void setThreshold(String eventName, long nanos) {
+        Long l = thresholds.get(eventName);
+        if (l != null) {
+            l = Math.min(l, nanos);
+        } else {
+            l = nanos;
+        }
+        thresholds.put(eventName, l);
+    }
+
+    public long getThreshold(String eventName) {
+        if (acceptAll) {
+            return 0;
+        }
+        Long l = thresholds.get(eventName);
+        if (l != null) {
+            return l;
+        }
+        return -1;
+    }
+    public String toString() {
+        if (acceptAll) {
+            return "ACCEPT ALL";
+        }
+        StringBuilder sb = new StringBuilder();
+        for (String key : thresholds.keySet().toArray(new String[0])) {
+            Long value = thresholds.get(key);
+            sb.append(key);
+            sb.append(" = ");
+            sb.append(value.longValue() / 1_000_000);
+            sb.append(" ms");
+        }
+        return sb.toString();
+    }
+}
\ No newline at end of file
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/Parser.java	Fri May 17 16:02:27 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,45 +0,0 @@
-/*
- * Copyright (c) 2016, 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.consumer;
-
-import java.io.IOException;
-
-import jdk.jfr.internal.consumer.RecordingInput;
-
-/**
- * Base class for parsing data from a {@link RecordingInput}.
- */
-abstract class Parser {
-    /**
-     * Parses data from a {@link RecordingInput} and return an object.
-     *
-     * @param input input to read from
-     * @return an object
-     * @throws IOException if operation couldn't be completed due to I/O
-     *         problems
-     */
-    abstract Object parse(RecordingInput input) throws IOException;
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordingStream.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,299 @@
+/*
+ * Copyright (c) 2019, 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.consumer;
+
+import java.io.IOException;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.time.Duration;
+import java.util.function.Consumer;
+
+import jdk.jfr.Configuration;
+import jdk.jfr.Event;
+import jdk.jfr.EventSettings;
+import jdk.jfr.EventType;
+import jdk.jfr.Recording;
+import jdk.jfr.internal.PlatformRecording;
+import jdk.jfr.internal.PrivateAccess;
+import jdk.jfr.internal.Utils;
+
+/**
+ * An event stream produces events from a file, directory or a running JVM (Java
+ * Virtual Machine).
+ */
+public class RecordingStream implements AutoCloseable, EventStream {
+
+    private final Recording recording;
+    private final EventDirectoryStream stream;
+
+    /**
+     * Creates an event stream for this JVM (Java Virtual Machine).
+     * <p>
+     * The following example shows how to create a recording stream that prints
+     * CPU usage and information about garbage collections.
+     *
+     * <pre>
+     * <code>
+     * try (RecordingStream  r = new RecordingStream()) {
+     *   r.enable("jdk.GarbageCollection");
+     *   r.enable("jdk.CPULoad").withPeriod(Duration.ofSeconds(1));
+     *   r.onEvent(System.out::println);
+     *   r.start();
+     * }
+     * </code>
+     * </pre>
+     *
+     * @throws IllegalStateException if Flight Recorder can't be created (for
+     *         example, if the Java Virtual Machine (JVM) lacks Flight Recorder
+     *         support, or if the file repository can't be created or accessed)
+     *
+     * @throws SecurityException if a security manager exists and the caller
+     *         does not have
+     *         {@code FlightRecorderPermission("accessFlightRecorder")}
+     */
+    public RecordingStream() {
+        Utils.checkAccessFlightRecorder();
+        AccessControlContext acc = AccessController.getContext();
+        this.recording = new Recording();
+        this.recording.setFlushInterval(Duration.ofMillis(1000));
+        try {
+            this.stream = new EventDirectoryStream(acc);
+        } catch (IOException ioe) {
+            throw new IllegalStateException(ioe.getMessage());
+        }
+    }
+
+    /**
+     * Creates a recording stream using settings from a configuration.
+     * <p>
+     * The following example shows how to create a recording stream that uses a
+     * predefined configuration.
+     *
+     * <pre>
+     * <code>
+     * Configuration c = Configuration.getConfiguration("default");
+     * try (RecordingStream  r = new RecordingStream(c)) {
+     *   r.onEvent(System.out::println);
+     *   r.start();
+     * }
+     * </code>
+     * </pre>
+     *
+     * @param configuration configuration that contains the settings to be use,
+     *        not {@code null}
+     *
+     * @throws IllegalStateException if Flight Recorder can't be created (for
+     *         example, if the Java Virtual Machine (JVM) lacks Flight Recorder
+     *         support, or if the file repository can't be created or accessed)
+     *
+     * @throws SecurityException if a security manager is used and
+     *         FlightRecorderPermission "accessFlightRecorder" is not set.
+     *
+     * @see Configuration
+     */
+    public RecordingStream(Configuration configuration) {
+        this();
+        recording.setSettings(configuration.getSettings());
+    }
+
+    /**
+     * Enables the event with the specified name.
+     * <p>
+     * If multiple events have the same name (for example, the same class is
+     * loaded in different class loaders), then all events that match the name
+     * are enabled. To enable a specific class, use the {@link #enable(Class)}
+     * method or a {@code String} representation of the event type ID.
+     *
+     * @param name the settings for the event, not {@code null}
+     *
+     * @return an event setting for further configuration, not {@code null}
+     *
+     * @see EventType
+     */
+    public EventSettings enable(String name) {
+        return recording.enable(name);
+    }
+
+    /**
+     * Enables event.
+     *
+     * @param eventClass the event to enable, not {@code null}
+     *
+     * @throws IllegalArgumentException if {@code eventClass} is an abstract
+     *         class or not a subclass of {@link Event}
+     *
+     * @return an event setting for further configuration, not {@code null}
+     */
+    public EventSettings enable(Class<? extends Event> eventClass) {
+        return recording.enable(eventClass);
+    }
+
+    /**
+     * Disables event with the specified name.
+     * <p>
+     * If multiple events with same name (for example, the same class is loaded
+     * in different class loaders), then all events that match the name is
+     * disabled. To disable a specific class, use the {@link #disable(Class)}
+     * method or a {@code String} representation of the event type ID.
+     *
+     * @param name the settings for the event, not {@code null}
+     *
+     * @return an event setting for further configuration, not {@code null}
+     *
+     */
+    public EventSettings disable(String name) {
+        return recording.disable(name);
+    }
+
+    /**
+     * Disables event.
+     *
+     * @param eventClass the event to enable, not {@code null}
+     *
+     * @throws IllegalArgumentException if {@code eventClass} is an abstract
+     *         class or not a subclass of {@link Event}
+     *
+     * @return an event setting for further configuration, not {@code null}
+     *
+     */
+    public EventSettings disable(Class<? extends Event> eventClass) {
+        return recording.disable(eventClass);
+    }
+    /**
+     * Determines how far back data is kept for the stream if the stream can't
+     * keep up.
+     * <p>
+     * To control the amount of recording data stored on disk, the maximum
+     * length of time to retain the data can be specified. Data stored on disk
+     * that is older than the specified length of time is removed by the Java
+     * Virtual Machine (JVM).
+     * <p>
+     * If neither maximum limit or the maximum age is set, the size of the
+     * recording may grow indefinitely if events are on
+     *
+     * @param maxAge the length of time that data is kept, or {@code null} if
+     *        infinite
+     *
+     * @throws IllegalArgumentException if <code>maxAge</code> is negative
+     *
+     * @throws IllegalStateException if the recording is in the {@code CLOSED}
+     *         state
+     */
+    public void setMaxAge(Duration maxAge) {
+        recording.setMaxAge(maxAge);
+    }
+
+    /**
+     * Determines how much data is kept in the disk repository if the stream
+     * can't keep up.
+     * <p>
+     * To control the amount of recording data that is stored on disk, the
+     * maximum amount of data to retain can be specified. When the maximum limit
+     * is exceeded, the Java Virtual Machine (JVM) removes the oldest chunk to
+     * make room for a more recent chunk.
+     * <p>
+     * If neither maximum limit or the maximum age is set, the size of the
+     * recording may grow indefinitely.
+     *
+     * @param maxSize the amount of data to retain, {@code 0} if infinite
+     *
+     * @throws IllegalArgumentException if <code>maxSize</code> is negative
+     *
+     * @throws IllegalStateException if the recording is in {@code CLOSED} state
+     */
+    public void setMaxSize(long maxSize) {
+        recording.setMaxSize(maxSize);
+    }
+
+    @Override
+    public void onEvent(String eventName, Consumer<RecordedEvent> action) {
+        stream.onEvent(eventName, action);
+    }
+
+    @Override
+    public void onEvent(Consumer<RecordedEvent> action) {
+        stream.onEvent(action);
+    }
+
+    @Override
+    public void onFlush(Runnable action) {
+        stream.onFlush(action);
+    }
+
+    @Override
+    public void onClose(Runnable action) {
+        stream.onClose(action);
+    }
+
+    @Override
+    public void close() {
+        recording.close();
+        stream.close();
+    }
+
+    @Override
+    public boolean remove(Object action) {
+        return stream.remove(action);
+    }
+
+    @Override
+    public void start() {
+        PlatformRecording pr = PrivateAccess.getInstance().getPlatformRecording(recording);
+        long startNanos = pr.start();
+        stream.start(startNanos);
+    }
+
+    @Override
+    public void startAsync() {
+        PlatformRecording pr = PrivateAccess.getInstance().getPlatformRecording(recording);
+        long startNanos = pr.start();
+        stream.startAsync(startNanos);
+    }
+
+    @Override
+    public void awaitTermination(Duration timeout) {
+        stream.awaitTermination(timeout);
+    }
+
+    /**
+     * Determines how often events are made available for streaming.
+     *
+     * @param interval the interval at which events are made available to the
+     *        stream
+     *
+     * @throws IllegalArgumentException if <code>interval</code> is negative
+     *
+     * @throws IllegalStateException if the stream is closed
+     */
+    public void setInterval(Duration duration) {
+        recording.setFlushInterval(duration);
+    }
+
+    @Override
+    public void awaitTermination() {
+        stream.awaitTermination();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/StringParser.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 2019, 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.consumer;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+
+import jdk.jfr.internal.consumer.Parser;
+import jdk.jfr.internal.consumer.RecordingInput;
+import jdk.jfr.internal.consumer.StringEncoding;
+
+final class StringParser extends Parser {
+    private final static Charset UTF8 = Charset.forName("UTF-8");
+    private final static Charset LATIN1 = Charset.forName("ISO-8859-1");
+
+
+    final static class CharsetParser extends Parser {
+        private final Charset charset;
+        private int lastSize;
+        private byte[] buffer = new byte[16];
+        private String lastString;
+
+        CharsetParser(Charset charset) {
+            this.charset = charset;
+        }
+
+        @Override
+        public Object parse(RecordingInput input) throws IOException {
+            int size = input.readInt();
+            ensureSize(size);
+            if (lastSize == size) {
+                boolean equalsLastString = true;
+                for (int i = 0; i < size; i++) {
+                    // TODO: No need to read byte per byte
+                    byte b = input.readByte();
+                    if (buffer[i] != b) {
+                        equalsLastString = false;
+                        buffer[i] = b;
+                    }
+                }
+                if (equalsLastString) {
+                    return lastString;
+                }
+            } else {
+                for (int i = 0; i < size; i++) {
+                    buffer[i] = input.readByte();
+                }
+            }
+            lastString = new String(buffer, 0, size, charset);
+            lastSize = size;
+            return lastString;
+        }
+
+        @Override
+        public void skip(RecordingInput input) throws IOException {
+            int size = input.readInt();
+            input.skipBytes(size);
+        }
+
+        private void ensureSize(int size) {
+            if (buffer.length < size) {
+                buffer = new byte[size];
+            }
+        }
+    }
+
+    final static class CharArrayParser extends Parser {
+        private char[] buffer = new char[16];
+        private int lastSize = -1;
+        private String lastString = null;
+
+        @Override
+        public Object parse(RecordingInput input) throws IOException {
+            int size = input.readInt();
+            ensureSize(size);
+            if (lastSize == size) {
+                boolean equalsLastString = true;
+                for (int i = 0; i < size; i++) {
+                    char c = input.readChar();
+                    if (buffer[i] != c) {
+                        equalsLastString = false;
+                        buffer[i] = c;
+                    }
+                }
+                if (equalsLastString) {
+                    return lastString;
+                }
+            } else {
+                for (int i = 0; i < size; i++) {
+                    buffer[i] = input.readChar();
+                }
+            }
+            lastString = new String(buffer, 0, size);
+            lastSize = size;
+            return lastString;
+        }
+
+        @Override
+        public void skip(RecordingInput input) throws IOException {
+            int size = input.readInt();
+            for (int i = 0; i < size; i++) {
+                input.readChar();
+            }
+        }
+
+        private void ensureSize(int size) {
+            if (buffer.length < size) {
+                buffer = new char[size];
+            }
+        }
+    }
+
+    private final ConstantLookup stringLookup;
+    private final CharArrayParser charArrayParser = new CharArrayParser();
+    private final CharsetParser utf8parser = new CharsetParser(UTF8);
+    private final CharsetParser latin1parser = new CharsetParser(LATIN1);
+    private final boolean event;
+
+    public StringParser(ConstantLookup stringLookup, boolean event) {
+        this.stringLookup = stringLookup;
+        this.event = event;
+    }
+
+    @Override
+    public Object parse(RecordingInput input) throws IOException {
+        byte encoding = input.readByte();
+        if (encoding == StringEncoding.STRING_ENCODING_CONSTANT_POOL) {
+            long key = input.readLong();
+            if (event) {
+                return stringLookup.getCurrentResolved(key);
+            } else {
+                return stringLookup.getCurrent(key);
+            }
+        }
+        if (encoding == StringEncoding.STRING_ENCODING_NULL) {
+            return null;
+        }
+        if (encoding == StringEncoding.STRING_ENCODING_EMPTY_STRING) {
+            return "";
+        }
+        if (encoding == StringEncoding.STRING_ENCODING_CHAR_ARRAY) {
+            return charArrayParser.parse(input);
+        }
+        if (encoding == StringEncoding.STRING_ENCODING_UTF8_BYTE_ARRAY) {
+            return utf8parser.parse(input);
+        }
+        if (encoding == StringEncoding.STRING_ENCODING_LATIN1_BYTE_ARRAY) {
+            return latin1parser.parse(input);
+        }
+        throw new IOException("Unknown string encoding " + encoding);
+    }
+
+    @Override
+    public void skip(RecordingInput input) throws IOException {
+        byte encoding = input.readByte();
+        if (encoding == StringEncoding.STRING_ENCODING_CONSTANT_POOL) {
+            input.readLong();
+            return;
+        }
+        if (encoding == StringEncoding.STRING_ENCODING_EMPTY_STRING) {
+            return;
+        }
+        if (encoding == StringEncoding.STRING_ENCODING_NULL) {
+            return;
+        }
+        if (encoding == StringEncoding.STRING_ENCODING_CHAR_ARRAY) {
+            charArrayParser.skip(input);
+            return;
+        }
+        if (encoding == StringEncoding.STRING_ENCODING_UTF8_BYTE_ARRAY) {
+            utf8parser.skip(input);
+            return;
+        }
+        if (encoding == StringEncoding.STRING_ENCODING_LATIN1_BYTE_ARRAY) {
+            latin1parser.skip(input);
+            return;
+        }
+        throw new IOException("Unknown string encoding " + encoding);
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/UseCasesStream.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,193 @@
+/*
+ * Copyright (c) 2019, 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.consumer;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.text.ParseException;
+import java.time.Duration;
+import java.util.ArrayDeque;
+
+import jdk.jfr.Configuration;
+import jdk.jfr.EventType;
+import jdk.jfr.ValueDescriptor;
+
+class UseCasesStream {
+
+    //
+    // Use case: Out-of-the-Box Experience
+    //
+    // - Simple things should be simple
+    // - Pique interest, i.e. a one-liner on Stack Overflow
+    // - Few lines of code as possible
+    // - Should be easier than alternative technologies, like JMX and JVM TI
+    //
+    // - Non-goals: Corner-cases, advanced configuration, releasing resources
+    //
+    public static void outOfTheBox() throws Exception {
+        try (RecordingStream rs = new RecordingStream()) {
+            rs.enable("jdk.ExceptionThrown");
+            rs.onEvent(e -> System.out.println(e.getString("message")));
+            rs.start();
+        }
+
+        // EventStream.start("jdk.JavaMonitorEnter", "threshold", "20 ms",
+        // "stackTrace", "false")
+        // .addConsumer(System.out::println);
+        //
+        // EventStream.start("jdk.CPULoad", "period", "1 s")
+        // .addConsumer(e -> System.out.println(100 *
+        // e.getDouble("totalMachine") + " %"));
+        //
+        // EventStream.start("jdk.GarbageCollection")
+        // .addConsumer(e -> System.out.println("GC: " + e.getStartTime() + "
+        // maxPauseTime=" + e.getDuration("maxPauseTime").toMillis() + " ms"));
+
+        Thread.sleep(100_000);
+    }
+
+    // Use case: Event Forwarding
+    //
+    // - Forward arbitrary event to frameworks such as RxJava, JSON/XML and
+    // Kafka
+    // - Handle flooding
+    // - Performant
+    // - Graceful shutdown
+    // - Non-goals: Filter events
+    //
+    public static void eventForwarding() throws InterruptedException, IOException, ParseException {
+        // KafkaProducer producer = new KafkaProducer<String, String>();
+        try (RecordingStream rs = new RecordingStream(Configuration.getConfiguration("default"))) {
+            rs.setMaxAge(Duration.ofMinutes(5));
+            rs.setMaxSize(1000_000_000L);
+            // es.setParallel(true);
+            // es.setReuse(true);
+            // es.consume(e -> producer.send(new ProducerRecord<String,
+            // String>("topic",
+            // e.getString("key"), e.getString("value"))));
+            rs.start();
+        }
+        // Write primitive values to XML
+        try (RecordingStream rs = new RecordingStream(Configuration.getConfiguration("deafult"))) {
+            try (PrintWriter p = new PrintWriter(new FileWriter("recording.xml"))) {
+                // es.setParallel(false);
+                // es.setReuse(true);
+                p.println("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
+                p.println("<events>");
+                rs.onEvent(e -> {
+                    EventType type = e.getEventType();
+                    p.println("  <event type=\"" + type.getName() + "\" start=\"" + e.getStartTime() + "\" end=\"" + e.getEndTime() + "\">");
+                    for (ValueDescriptor field : e.getEventType().getFields()) {
+                        Object value = e.getValue(field.getName());
+                        if (value instanceof Number || field.getTypeName().equals("java.lang.String")) {
+                            p.println("    <value field=\"" + field.getName() + "\">" + value + "</value>");
+                        }
+                    }
+                });
+                rs.start();
+                p.println("</events>");
+            }
+        }
+    }
+
+    // Use case: Repository Access
+    //
+    // - Read the disk repository from another process, for example a side car
+    // in
+    // Docker container
+    // - Be able to configure flush interval from command line or jcmd.
+    // - Graceful shutdown
+    //
+    public static void repositoryAccess() throws IOException, InterruptedException {
+        Path repository = Paths.get("c:\\repository").toAbsolutePath();
+        String command = new String();
+        command += "java -XX:StartFlightRecording:flush=2s";
+        command += "-XX:FlightRecorderOption:repository=" + repository + " Application";
+        Process myProcess = Runtime.getRuntime().exec(command);
+        try (RecordingStream rs = new RecordingStream()) {
+            rs.onEvent(System.out::println);
+            rs.startAsync();
+            Thread.sleep(10_000);
+            myProcess.destroy();
+            Thread.sleep(10_000);
+        }
+    }
+
+    // Use: Tooling
+    //
+    // - Monitor a stream of data for a very long time
+    // - Predictable interval, i.e. once every second
+    // - Notification with minimal delay
+    // - Events with the same period should arrive together
+    // - Consume events in chronological order
+    // - Low overhead
+    //
+    public static void tooling() throws IOException, ParseException {
+        ArrayDeque<Double> measurements = new ArrayDeque<>();
+        try (RecordingStream rs = new RecordingStream(Configuration.getConfiguration("profile"))) {
+            rs.setInterval(Duration.ofSeconds(1));
+            rs.setMaxAge(Duration.ofMinutes(1));
+            // rs.setOrdered(true);
+            // rs.setReuse(false);
+            // rs.setParallel(true);
+            rs.onEvent("jdk.CPULoad", e -> {
+                double d = e.getDouble("totalMachine");
+                measurements.addFirst(d);
+                if (measurements.size() > 60) {
+                    measurements.removeLast();
+                }
+                // repaint();
+            });
+            rs.start();
+        }
+    }
+
+    // Use case: Low Impact
+    //
+    // - Support event subscriptions in a low latency environment (minimal GC
+    // pauses)
+    // - Filter out relevant events to minimize disk overhead and allocation
+    // pressure
+    // - Avoid impact from other recordings
+    // - Avoid Heisenberg effects, in particular self-recursion
+    //
+    // Non-goals: one-liner
+    //
+    public static void lowImpact() throws InterruptedException, IOException, ParseException {
+        try (RecordingStream rs = new RecordingStream()) {
+            rs.enable("jdk.JavaMonitorEnter#threshold").withThreshold(Duration.ofMillis(10));
+            rs.enable("jdk.ExceptionThrow#enabled");
+            // ep.setReuse(true);
+            rs.onEvent("jdk.JavaMonitorEnter", System.out::println);
+            rs.onEvent("jdk.ExceptionThrow", System.out::println);
+            rs.start();
+            ;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/FilePurger.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,47 @@
+package jdk.jfr.internal;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import jdk.jfr.internal.SecuritySupport.SafePath;
+
+// This class keeps track of files that can't be deleted
+// so they can a later staged be removed.
+final class FilePurger {
+
+	private final static Set<SafePath> paths = new LinkedHashSet<>();
+
+	public synchronized static void add(SafePath p) {
+		paths.add(p);
+		if (paths.size() > 1000) {
+			removeOldest();
+		}
+	}
+
+	public synchronized static void purge() {
+		if (paths.isEmpty()) {
+			return;
+		}
+
+		for (SafePath p : new ArrayList<>(paths)) {
+			if (delete(p)) {
+				paths.remove(p);
+			}
+		}
+	}
+
+    private static void removeOldest() {
+        SafePath oldest = paths.iterator().next();
+        paths.remove(oldest);
+    }
+
+	private static boolean delete(SafePath p) {
+		try {
+			SecuritySupport.delete(p);
+			return true;
+		} catch (IOException e) {
+			return false;
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/Parser.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2016, 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.internal.consumer;
+
+import java.io.IOException;
+
+/**
+ * Base class for parsing data from a {@link RecordingInput}.
+ */
+public abstract class Parser {
+    /**
+     * Parses data from a {@link RecordingInput} and return an object.
+     *
+     * @param input input to read from
+     * @return an object
+     * @throws IOException if operation couldn't be completed due to I/O
+     *         problems
+     */
+    public abstract Object parse(RecordingInput input) throws IOException;
+
+    /**
+     * Skips data that would usually be by parsed the {@code #parse(RecordingInput)} method.
+     *
+     * @param input input to read from
+     * @throws IOException if operation couldn't be completed due to I/O
+     *         problems
+     */
+    public abstract void skip(RecordingInput input) throws IOException;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/StringEncoding.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,11 @@
+package jdk.jfr.internal.consumer;
+
+public final class StringEncoding {
+    public final static byte STRING_ENCODING_NULL = (byte) 0;
+    public final static byte STRING_ENCODING_EMPTY_STRING = (byte) 1;
+    public final static byte STRING_ENCODING_CONSTANT_POOL = (byte) 2;
+    public final static byte STRING_ENCODING_UTF8_BYTE_ARRAY = (byte) 3;
+    public final static byte STRING_ENCODING_CHAR_ARRAY = (byte) 4;
+    public final static byte STRING_ENCODING_LATIN1_BYTE_ARRAY = (byte) 5;
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/PerfectHashMap.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,340 @@
+package jdk.jfr.internal.util;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+public class PerfectHashMap<V> {
+    private static final long COLLISION_SHIFT = 63;
+    private static final long COLLISION_BIT = 1L << COLLISION_SHIFT;
+    private static final long COLLISION_MASK = COLLISION_BIT - 1;
+    private static final int MAX_REMAP_ATTEMPTS = 100000;
+    private static final int  MAX_ATTEMPS_BEFORE_RESIZE = 100;
+
+    static final long W = 64L;
+    static class LinkedValue<V> {
+        final V value;
+        long next;
+
+        LinkedValue(V value) {
+            this.value = value;
+            this.next = 0;
+        }
+    }
+
+    private UniversalHashFamily hashFamily = new UniversalHashFamily();
+    private PrimitiveHashMap<LinkedValue<V>> loadMap;
+    private Object[] valueTable;
+    private long[] routeTable;
+    private long shift;
+    private long shiftMask;
+    private int tableLengthMask;
+    private long primaryHashFunction = 0;
+    private int collisions = 0;
+    private int retries = 0;
+    private int sizeFactor = 1;
+    private boolean minimal;
+
+    public V get(long key) {
+        LinkedValue<V> v = loadMap.get(key);
+        return v != null ? v.value : null;
+    }
+
+    public V put(long key, V value) {
+        LinkedValue<V> existing = loadMap.put(key, new LinkedValue<V>(value));
+        return existing != null ? existing.value : null;
+    }
+
+    public void forEach(BiConsumer<? super Long, ? super V> action) {
+        //loadMap.forEach(PerfectHashMap<V>::callback);
+    }
+
+    public final void forEach(Consumer<? super V> action) {
+        //loadMap.forEach(action);
+    }
+
+    public final long[] keys() {
+        return loadMap.keys();
+    }
+
+    static class Log2 {
+        private static final int MAX_SIZE_EXPONENT = 32;
+
+        static long log2base10(long exponent) {
+            return 1L << exponent;
+        }
+
+        static int log2(int value) {
+            int i = 0;
+            int lastMultiple = 0;
+            while (i < MAX_SIZE_EXPONENT) {
+                int multiple = (int)log2base10(i);
+                if ((value & multiple) != 0) {
+                    lastMultiple = i;
+                }
+                ++i;
+            }
+            return ((int)log2base10(lastMultiple) ^ value) != 0 ? lastMultiple + 1 : lastMultiple;
+        }
+            }
+
+    static final int tableExponent(int cap) {
+        return Log2.log2(cap);
+    }
+
+    PerfectHashMap() {
+        this(false, 101);
+    }
+
+    PerfectHashMap(int size) {
+        this(false, size);
+    }
+
+    PerfectHashMap(boolean minimal, int size) {
+        this.minimal = minimal;
+        this.loadMap = new PrimitiveHashMap<>(size);
+        this.primaryHashFunction = hashFamily.getRandomHashFunction();
+    }
+
+    @SuppressWarnings("unchecked")
+    public V getPerfect(long key) {
+        int routeIdx = getIndex(key, primaryHashFunction);
+        assert(routeIdx >= 0);
+        assert(routeIdx < routeTable.length);
+        long element = routeTable[routeIdx];
+        int valueIdx = element < 0 ? getIndex(key, -element - 1) : (int)element;
+        assert(valueIdx >= 0);
+        assert(valueIdx < valueTable.length);
+        return (V)valueTable[valueIdx];
+    }
+
+    private long getRandomHashFunction() {
+        return hashFamily.getRandomHashFunction();
+    }
+    private int getIndex(long key, long hashFunction) {
+       final int idx = UniversalHashFamily.getIndex(key, hashFunction, shift, shiftMask);
+       assert(idx >= 0);
+       assert(idx < routeTable.length);
+       return idx;
+    }
+    private static boolean isColliding(long entry) {
+        return entry < 0;
+    }
+    private boolean isNonColliding(long entry) {
+        return entry > 0;
+    }
+    private static long setColliding(long entry) {
+        return entry | COLLISION_BIT;
+    }
+    private static long read(long entry) {
+        return entry & COLLISION_MASK;
+    }
+
+    private int nextValueTableSlot(int lastIdx) {
+        assert(lastIdx < valueTable.length);
+        int i = lastIdx;
+        for (; i < valueTable.length; ++i) {
+            if (valueTable[i] == null) {
+                break;
+            }
+        }
+        return i;
+    }
+
+    private int valueTableStore(V value, int lastIdx) {
+        if (lastIdx > valueTable.length) {
+            lastIdx = 0;
+        }
+        assert(lastIdx < valueTable.length);
+        final int idx = nextValueTableSlot(lastIdx);
+        assert(idx < valueTable.length);
+        assert(valueTable[idx] == null);
+        valueTable[idx] = value;
+        return idx;
+    }
+
+
+    private void routeNonCollisions() {
+        int lastIdx = 0;
+        for (int i = 0; i < routeTable.length; ++i) {
+            if (isNonColliding(routeTable[i])) {
+                lastIdx = valueTableStore(loadMap.get(routeTable[i]).value, lastIdx);
+                routeTable[i] = lastIdx++;
+           }
+        }
+    }
+
+    private void rollback(int idx, int length, long hashFunction) {
+        assert(isColliding(routeTable[idx]));
+        long key = read(routeTable[idx]);
+        LinkedValue<V> v = loadMap.get(key); // boxing
+        for (int i = 0; i < length; ++i) {
+            final int valueIdx = getIndex(key, hashFunction);
+            assert(valueIdx >= 0);
+            assert(valueIdx < valueTable.length);
+            assert(valueTable[valueIdx] != null);
+            valueTable[valueIdx] = null;
+            key = v.next;
+            v = loadMap.get(v.next); // no boxing
+        }
+    }
+
+    private boolean remap(int idx, long hashFunction) {
+        assert(isColliding(routeTable[idx]));
+        int completed = 0;
+        long key = read(routeTable[idx]);
+        LinkedValue<V> v = loadMap.get(key);
+        while (key != 0) {
+            final int valueIdx = getIndex(key, hashFunction);
+            assert(valueIdx >= 0);
+            assert(valueIdx < valueTable.length);
+            if (valueTable[valueIdx] == null) {
+                valueTable[valueIdx] = v.value;
+                ++completed;
+                key = v.next;
+                v = loadMap.get(v.next);
+                continue;
+            }
+            rollback(idx, completed, hashFunction);
+            return false;
+        }
+        return true;
+    }
+
+    private boolean routeCollisions(int idx) {
+        assert(isColliding(routeTable[idx]));
+        boolean success = false;
+        int attempts = 0;
+        long randomHashFunction = 0;
+        do {
+            randomHashFunction = getRandomHashFunction();
+            success = remap(idx, randomHashFunction);
+            if (++attempts == MAX_REMAP_ATTEMPTS) {
+                System.out.println("Failed number of attempts - restart: " + attempts);
+                return false;
+            }
+        } while (!success);
+        System.out.println("Number of remap attempts: " + attempts);
+        routeTable[idx] = -1 - randomHashFunction;
+        assert(-routeTable[idx] - 1 == randomHashFunction);
+        return true;
+    }
+
+
+    private boolean routeCollisions() {
+        for (int i = 0; i < routeTable.length; ++i) {
+            if (isColliding(routeTable[i])) {
+                if (!routeCollisions(i)) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    private static void clearLongTable(long[] table) {
+        Arrays.fill(table, 0);
+        for (int i = 0; i < table.length; ++i) {
+            assert(table[i] == 0);
+        }
+    }
+
+    private static <T extends Object> void clearReferenceTable(T[] table) {
+        Arrays.fill(table, null);
+        for (int i = 0; i < table.length; ++i) {
+            assert(table[i] == null);
+        }
+    }
+
+    private void unlinkChains() {
+        for (long key : loadMap.keys()) {
+            loadMap.get(key).next = 0;
+        }
+    }
+
+    private void routeTableStore(long key, LinkedValue<V> value, int idx) {
+        assert(idx >= 0);
+        assert(idx < routeTable.length);
+        long existing = read(routeTable[idx]);
+        if (existing == 0) {
+            routeTable[idx] = key;
+            return;
+        }
+        ++collisions;
+        routeTable[idx] = setColliding(existing);
+        LinkedValue<V> existingValue = loadMap.get(existing);
+        value.next = existingValue.next;
+        existingValue.next = key;
+    }
+
+    private void mapKeys() {
+        for (long key : loadMap.keys()) {
+            routeTableStore(key, loadMap.get(key), getIndex(key, primaryHashFunction));
+        }
+    }
+
+    private void validate() {
+        for (long key : loadMap.keys()) {
+            long element = routeTable[getIndex(key, primaryHashFunction)];
+            int valueIdx = element < 0 ? getIndex(key, -element - 1) : (int)element;
+            assert(valueIdx >= 0);
+            assert(loadMap.get(key) == valueTable[valueIdx]);
+        }
+    }
+
+    private void reset() {
+        collisions = 0;
+        clearLongTable(routeTable);
+        clearReferenceTable(valueTable);
+    }
+
+    private int dimensionTableSize() {
+        int size = loadMap.size() * sizeFactor;
+        return (int)Log2.log2base10(Log2.log2(size));
+    }
+
+    @SuppressWarnings({"rawtypes","unchecked"})
+    private void allocateTables() {
+        int size = dimensionTableSize();
+        this.tableLengthMask = size - 1;
+        this.shift = W - tableExponent(size);
+        this.shiftMask = Log2.log2base10(shift) - 1;
+        routeTable = new long[size];
+        valueTable = (V[])new Object[size];
+        collisions = 0;
+        retries = 0;
+    }
+
+    public void build() {
+        start:
+        while (true) {
+            allocateTables();
+            System.out.println("Table size " + routeTable.length);
+            mapKeys();
+            if (collisions > 0) {
+                if (!routeCollisions()) {
+                    unlinkChains();
+                    if (++retries <= MAX_ATTEMPS_BEFORE_RESIZE) {
+                      reset();
+                    } else {
+                      sizeFactor *= 2;
+                    }
+                    continue start;
+                }
+            }
+            routeNonCollisions();
+            return;
+        }
+    }
+
+    public void rebuild() {
+        sizeFactor = 1;
+        build();
+    }
+    public int size() {
+        return loadMap.size();
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/PrimitiveHashMap.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,341 @@
+package jdk.jfr.internal.util;
+
+import java.util.AbstractCollection;
+import java.util.Collection;
+import java.util.Objects;
+import java.util.Set;
+import java.util.Map;
+import java.util.Iterator;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.ConcurrentModificationException;
+import java.util.Random;
+
+public class PrimitiveHashMap<V> {
+
+    static final long W = 64L;
+    static final long A = 4633630788178346939L;
+    final Random rand = new Random();
+
+    private static int getIndex(long key, long hashFunction, long shift, long mask) {
+        return (int)(((A * key) + (hashFunction & mask)) >>> shift);
+    }
+    private long getRandomHashFunction() {
+        return rand.nextLong();
+    }
+    /**
+     * The maximum capacity, used if a higher value is implicitly specified
+     * by either of the constructors with arguments.
+     */
+    static final int MAX_SIZE_EXPONENT = 30;
+    static final int MAXIMUM_CAPACITY = 1 << MAX_SIZE_EXPONENT;
+
+    static final int DEFAULT_SIZE_EXPONENT = 4;
+    static final int DEFAULT_INITIAL_CAPACITY = 1 << DEFAULT_SIZE_EXPONENT; // aka 16
+    /**
+     * The load factor used when none specified in constructor.
+     */
+    static final float DEFAULT_LOAD_FACTOR = 0.75f;
+    static class Log2 {
+
+        static long log2base10(long exponent) {
+            return 1L << exponent;
+        }
+        static int log2(int value) {
+            int i = 0;
+            int lastMultiple = 0;
+            while (i < MAX_SIZE_EXPONENT) {
+                int multiple = (int)log2base10(i);
+                if ((value & multiple) != 0) {
+                    lastMultiple = i;
+                }
+                ++i;
+            }
+            return ((int)log2base10(lastMultiple) ^ value) != 0 ? lastMultiple + 1 : lastMultiple;
+        }
+    }
+        /**
+     * Returns a power of two size for the given target capacity.
+     */
+    static final int tableSizeFor(int cap) {
+        int n = -1 >>> Integer.numberOfLeadingZeros(cap - 1);
+        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
+    }
+    static final int tableExponent(int cap) {
+        return Log2.log2(cap);
+    }
+
+    static class Node<V> {
+        final long key;
+        V value;
+
+        Node(long key, V value) {
+            this.key = key;
+            this.value = value;
+        }
+
+        public final long getKey()     { return key; }
+        public final V getValue()      { return value; }
+        public final String toString() { return key + "=" + value; }
+    }
+
+    private Node<V>[] table;
+    private int size;
+    private int threshold;
+    private long shift;
+    private long shiftMask;
+    private int tableLengthMask;
+    int modCount;
+    private final float loadFactor;
+    long h1 = 0;
+    long h2 = 0;
+
+    public PrimitiveHashMap(int initialCapacity, float loadFactor) {
+        if (initialCapacity < 0) {
+            throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
+        }
+        if (initialCapacity > MAXIMUM_CAPACITY) {
+            initialCapacity = MAXIMUM_CAPACITY;
+        }
+        if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
+            throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
+        }
+        this.loadFactor = loadFactor;
+        this.threshold = tableSizeFor(initialCapacity);
+        h1 = getRandomHashFunction();
+        h2 = getRandomHashFunction();
+        resize();
+    }
+
+    public PrimitiveHashMap(int initialCapacity) {
+        this(initialCapacity, DEFAULT_LOAD_FACTOR);
+    }
+
+    public PrimitiveHashMap() {
+        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
+    }
+
+    public final void forEach(BiConsumer<? super Long, ? super V> action) {
+        Node<V>[] tab;
+        if (action == null)
+            throw new NullPointerException();
+        if (size > 0 && (tab = table) != null) {
+            int mc = modCount;
+            for (int i = 0; i < table.length; ++i) {
+                if (table[i] == null) continue;
+                action.accept(table[i].getKey(), table[i].getValue());
+            }
+            if (modCount != mc)
+                throw new ConcurrentModificationException();
+        }
+    }
+
+    public final void forEach(Consumer<? super V> action) {
+        Node<V>[] tab;
+        if (action == null)
+            throw new NullPointerException();
+        if (size > 0 && (tab = table) != null) {
+            int mc = modCount;
+            for (int i = 0; i < table.length; ++i) {
+                if (table[i] == null) continue;
+                action.accept(table[i].getValue());
+            }
+            if (modCount != mc)
+                throw new ConcurrentModificationException();
+        }
+    }
+
+    public final long[] keys() {
+        long[] keys = new long[size];
+        int j = 0;
+        for (int i = 0; i < table.length; ++i) {
+            if (table[i] == null) continue;
+            keys[j++] = table[i].getKey();
+        }
+        assert(j == size);
+        assert(keys.length == size);
+        return keys;
+    }
+
+    public Collection<V> values () {
+        final PrimitiveHashMap<V> thisMap = this;
+        return new AbstractCollection<V>() {
+            private PrimitiveHashMap<V> map = thisMap;
+            public Iterator<V> iterator() {
+                return new Iterator<V>() {
+                    private int i = 0;
+                    private long [] k = keys();
+                    public boolean hasNext() {
+                        return i < k.length;
+                    }
+                    public V next() {
+                        assert(i < k.length);
+                        return map.get(k[i++]);
+                    }
+                };
+            }
+            public int size() {
+                return map.size();
+            }
+            public boolean isEmpty() {
+                return size() != 0;
+            }
+            public void clear() {
+                throw new UnsupportedOperationException();
+            }
+            public boolean contains(Object v) {
+                for (V value : map.values()) {
+                    if (v == value) {
+                        return true;
+                    }
+                }
+                return false;
+            }
+        };
+    }
+    private int doubleHash(long key, int i) {
+        int h1_idx = getIndex(key, h1, shift, shiftMask);
+        assert(h1_idx < table.length);
+        int h2_idx = 0;
+        if (i != 0) {
+            h2_idx = getIndex(key, h2, shift, shiftMask);
+            h2_idx |= 1;
+            assert((h2_idx & 1) == 1);
+        }
+        assert(h2_idx < table.length);
+        final int idx = (h1_idx + (i * h2_idx)) & tableLengthMask;
+        assert(idx >= 0);
+        assert(idx < table.length);
+        return idx;
+    }
+
+     /**
+     * Initializes or doubles table size.  If null, allocates in
+     * accord with initial capacity target held in field threshold.
+     * Otherwise, because we are using power-of-two expansion, the
+     * elements from each bin must either stay at same index, or move
+     * with a power of two offset in the new table.
+     *
+     * @return the table
+     */
+    final Node<V>[] resize() {
+        Node<V>[] oldTab = table;
+        int oldCap = (oldTab == null) ? 0 : oldTab.length;
+        int oldThr = threshold;
+        int newCap, newThr = 0;
+        if (oldCap > 0) {
+            if (oldCap >= MAXIMUM_CAPACITY) {
+                threshold = Integer.MAX_VALUE;
+                return oldTab;
+            }
+            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
+                     oldCap >= DEFAULT_INITIAL_CAPACITY)
+                newThr = oldThr << 1; // double threshold
+        }
+        else if (oldThr > 0) // initial capacity was placed in threshold
+            newCap = oldThr;
+        else {               // zero initial threshold signifies using defaults
+            newCap = DEFAULT_INITIAL_CAPACITY;
+            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
+        }
+        if (newThr == 0) {
+            float ft = (float)newCap * loadFactor;
+            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
+                      (int)ft : Integer.MAX_VALUE);
+        }
+        threshold = newThr;
+        @SuppressWarnings({"rawtypes","unchecked"})
+        Node<V>[] newTab = (Node<V>[])new Node[newCap];
+        table = newTab;
+        tableLengthMask = newCap - 1;
+        this.shift = W - tableExponent(newCap);
+        this.shiftMask = Log2.log2base10(shift) - 1;
+        if (oldTab != null) {
+            for (int j = 0; j < oldCap; ++j) {
+                Node<V> e;
+                if ((e = oldTab[j]) != null) {
+                    oldTab[j] = null;
+                    reinsert(e);
+                }
+            }
+        }
+        return newTab;
+    }
+
+    // used by table resize
+    private void reinsert(Node<V> e) {
+        assert(size < table.length);
+        for (int i = 0; i < table.length; ++i) {
+            int idx = doubleHash(e.getKey(), i);
+            assert(idx >= 0);
+            assert(idx < table.length);
+            if (table[idx] == null) {
+                table[idx] = e;
+                return;
+            }
+            assert(table[idx].key != e.getKey());
+        }
+    }
+
+    public V put(long key, V value) {
+        Node<V> existing = insert(key, value);
+        return existing != null ? existing.value : null;
+    }
+
+    private Node<V> insert(long key, V value) {
+        return insert(new Node<V>(key, value), key);
+    }
+
+    private Node<V> insert(Node<V> e, final long key) {
+        assert(size < table.length);
+        assert(e.getKey() == key);
+        Node<V> existing = null;
+        for (int i = 0; i < table.length; ++i) {
+            int idx = doubleHash(key, i);
+            assert(idx >= 0);
+            assert(idx < table.length);
+            if (table[idx] == null) {
+                table[idx] = e;
+                ++size;
+                break;
+            } else {
+               if (table[idx].key == key) {
+                   existing = table[idx];
+                   table[idx] = e;
+                   break;
+               }
+            }
+        }
+        if (size > threshold) {
+            resize();
+        }
+        return existing;
+    }
+
+    private Node<V> find(long key) {
+        Node<V> result = null;
+        for (int i = 0; i < table.length; ++i) {
+            int idx = doubleHash(key, i);
+            assert(idx >= 0);
+            assert(idx < table.length);
+            result = table[idx];
+            if (result == null || result.key == key) {
+                break;
+            }
+        }
+        return result;
+    }
+
+
+    public V get(long key) {
+        Node<V> existing = find(key);
+        return existing != null ? existing.value : null;
+    }
+
+    public boolean containsKey(long key) {
+        return find(key) != null;
+    }
+    public int size() {
+        return this.size;
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/util/UniversalHashFamily.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,27 @@
+package jdk.jfr.internal.util;
+
+import java.util.Random;
+
+public class UniversalHashFamily {
+    final Random rand = new Random();
+
+    private static long getA(long hashFunction) {
+        return hashFunction | 1;
+    }
+
+    private static long getB(long hashFunction, long mask) {
+        return hashFunction & mask;
+    }
+
+    private static long getHash(long key, long hashFunction, long mask) {
+        return (getA(hashFunction) * key) + (hashFunction & mask);
+    }
+
+    public static int getIndex(long key, long hashFunction, long shift, long mask) {
+        return (int)(getHash(key, hashFunction, mask) >>> shift);
+    }
+
+    public long getRandomHashFunction() {
+        return rand.nextLong() & Long.MAX_VALUE;
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestAwaitTermination.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2019, 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.api.consumer.recordingstream;
+
+import java.time.Duration;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+
+import jdk.jfr.consumer.RecordingStream;
+
+/**
+ * @test
+ * @summary Test RecordingStream::awaitTermination(...)
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestAwaitTermination
+ */
+public class TestAwaitTermination {
+
+    public static void main(String... args) throws Exception {
+        testAwaitClose();
+        testAwaitTimeOut();
+    }
+
+    private static void testAwaitClose() throws InterruptedException, ExecutionException {
+        try (RecordingStream r = new RecordingStream()) {
+            r.startAsync();
+            var c = CompletableFuture.runAsync(() -> {
+                r.awaitTermination();
+            });
+            r.close();
+            c.get();
+        }
+    }
+
+    private static void testAwaitTimeOut() throws InterruptedException, ExecutionException {
+        try (RecordingStream r = new RecordingStream()) {
+            r.startAsync();
+            var c = CompletableFuture.runAsync(() -> {
+                r.awaitTermination(Duration.ofMillis(10));
+            });
+            c.get();
+            r.close();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestClose.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2019, 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.api.consumer.recordingstream;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicLong;
+
+import jdk.jfr.Event;
+import jdk.jfr.consumer.RecordingStream;
+
+/**
+ * @test
+ * @summary Tests RecordingStream::close()
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestClose
+ */
+public class TestClose {
+
+    private static class CloseEvent extends Event {
+    }
+
+    public static void main(String... args) throws Exception {
+        testCloseUnstarted();
+        testCloseStarted();
+        testCloseTwice();
+        testCloseStreaming();
+        testCloseMySelf();
+    }
+
+    private static void testCloseMySelf() throws Exception {
+        CountDownLatch l1 = new CountDownLatch(1);
+        CountDownLatch l2 = new CountDownLatch(1);
+        RecordingStream r = new RecordingStream();
+        r.onEvent(e -> {
+            try {
+                l1.await();
+                r.close();
+                l2.countDown();
+            } catch (InterruptedException ie) {
+                throw new Error(ie);
+            }
+        });
+        r.startAsync();
+        CloseEvent c = new CloseEvent();
+        c.commit();
+        l1.countDown();
+        l2.await();
+    }
+
+    private static void testCloseStreaming() throws Exception {
+        CountDownLatch streaming = new CountDownLatch(1);
+        RecordingStream r = new RecordingStream();
+        AtomicLong count = new AtomicLong();
+        r.onEvent(e -> {
+            if (count.incrementAndGet() == 100) {
+                streaming.countDown();
+            }
+        });
+        r.startAsync();
+        var streamingLoop = CompletableFuture.runAsync(() -> {
+            while (true) {
+                CloseEvent c = new CloseEvent();
+                c.commit();
+            }
+        });
+        streaming.await();
+        r.close();
+        streamingLoop.cancel(true);
+    }
+
+    private static void testCloseStarted() {
+        RecordingStream r = new RecordingStream();
+        r.startAsync();
+        r.close();
+    }
+
+    private static void testCloseUnstarted() {
+        RecordingStream r = new RecordingStream();
+        r.close();
+    }
+
+    private static void testCloseTwice() {
+        RecordingStream r = new RecordingStream();
+        r.startAsync();
+        r.close();
+        r.close();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestConstructor.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2019, 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.api.consumer.recordingstream;
+
+import java.util.concurrent.CountDownLatch;
+
+import jdk.jfr.Configuration;
+import jdk.jfr.Enabled;
+import jdk.jfr.consumer.RecordingStream;
+import jdk.test.lib.jfr.EventNames;
+
+/**
+ * @test
+ * @summary Tests RecordingStream::RecordingStream()
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestConstructor
+ */
+public class TestConstructor {
+
+    public static void main(String... args) throws Exception {
+        testEmpty();
+        testConfiguration();
+    }
+
+    private static void testConfiguration() throws Exception {
+        CountDownLatch jvmInformation = new CountDownLatch(1);
+        Configuration c = Configuration.getConfiguration("default");
+        if (!c.getSettings().containsKey(EventNames.JVMInformation + "#" + Enabled.NAME)) {
+            throw new Exception("Expected default configuration to contain enabled " + EventNames.JVMInformation);
+        }
+        RecordingStream r = new RecordingStream(c);
+        r.onEvent("jdk.JVMInformation", e -> {
+            jvmInformation.countDown();
+        });
+        r.startAsync();
+        jvmInformation.await();
+        r.close();
+    }
+
+    private static void testEmpty() {
+        RecordingStream r = new RecordingStream();
+        r.close();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestDisable.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2019, 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.api.consumer.recordingstream;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import jdk.jfr.Event;
+import jdk.jfr.consumer.RecordingStream;
+
+/**
+ * @test
+ * @summary Tests RecordingStream::disable(...)
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestConstructor
+ */
+public class TestDisable {
+
+    private static class DisabledEvent extends Event {
+    }
+
+    private static class EnabledEvent extends Event {
+    }
+
+    public static void main(String... args) throws Exception {
+        CountDownLatch twoEvent = new CountDownLatch(2);
+        AtomicBoolean fail = new AtomicBoolean(false);
+        try(RecordingStream r = new RecordingStream()) {
+            r.onEvent(e -> {
+                if (e.getEventType().getName().equals(DisabledEvent.class.getName())) {
+                    fail.set(true);
+                }
+                twoEvent.countDown();
+            });
+            r.disable(DisabledEvent.class.getName());
+            r.startAsync();
+            EnabledEvent e1 = new EnabledEvent();
+            e1.commit();
+            DisabledEvent d1 = new DisabledEvent();
+            d1.commit();
+            EnabledEvent e2 = new EnabledEvent();
+            e2.commit();
+            twoEvent.await();
+            if (fail.get()) {
+                throw new Exception("Should not receive a disabled event");
+            }
+        }
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestEnable.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2019, 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.api.consumer.recordingstream;
+
+import java.util.concurrent.CountDownLatch;
+
+import jdk.jfr.Enabled;
+import jdk.jfr.Event;
+import jdk.jfr.consumer.RecordingStream;
+
+/**
+ * @test
+ * @summary Tests RecordingStream::enable(...)
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestEnable
+ */
+public class TestEnable {
+
+    @Enabled(false)
+    private static class EnabledEvent extends Event {
+    }
+
+    public static void main(String... args) throws Exception {
+        CountDownLatch event = new CountDownLatch(1);
+        try (RecordingStream r = new RecordingStream()) {
+            r.onEvent(e -> {
+                event.countDown();
+            });
+            r.enable(EnabledEvent.class.getName());
+            r.startAsync();
+            EnabledEvent e = new EnabledEvent();
+            e.commit();
+            event.await();
+        }
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestMaxAge.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2019, 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.api.consumer.recordingstream;
+
+import java.time.Duration;
+
+import jdk.jfr.consumer.RecordingStream;
+import jdk.test.lib.jfr.EventNames;
+
+/**
+ * @test
+ * @summary Tests RecordingStream::setMaxAge(...)
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestMaxAge
+ */
+public class TestMaxAge {
+
+    public static void main(String... args) throws Exception {
+        Duration testDuration = Duration.ofMillis(1234567);
+        try (RecordingStream r = new RecordingStream()) {
+            r.setMaxAge(testDuration);
+            r.enable(EventNames.ActiveRecording);
+            r.onEvent(e -> {
+                System.out.println(e);
+                Duration d = e.getDuration("maxAge");
+                System.out.println(d.toMillis());
+                if (testDuration.equals(d)) {
+                    r.close();
+                    return;
+                }
+                System.out.println("Max age not set, was " + d.toMillis() + " ms , but expected " + testDuration.toMillis() + " ms");
+            });
+            r.start();
+        }
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestOnClose.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2019, 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.api.consumer.recordingstream;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import jdk.jfr.Event;
+import jdk.jfr.consumer.RecordingStream;
+
+/**
+ * @test
+ * @summary Tests RecordingStream::onClose(...)
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestMaxAge
+ */
+public class TestOnClose {
+
+    private static class CloseEvent extends Event {
+    }
+    public static void main(String... args) throws Exception {
+        testOnCloseNull();
+        testOnClosedUnstarted();
+        testOnClosedStarted();
+    }
+
+    private static void testOnCloseNull() {
+       try (RecordingStream rs = new RecordingStream()) {
+          try {
+              rs.onClose(null);
+              throw new AssertionError("Expected NullPointerException from onClose(null");
+          } catch (NullPointerException npe) {
+              // OK; as expected
+          }
+       }
+    }
+
+    private static void testOnClosedStarted() throws InterruptedException {
+        AtomicBoolean onClose = new AtomicBoolean(false);
+        CountDownLatch event = new CountDownLatch(1);
+        try (RecordingStream r = new RecordingStream()) {
+            r.onEvent(e -> {
+                event.countDown();
+            });
+            r.onClose(() -> {
+                onClose.set(true);
+            });
+            r.startAsync();
+            CloseEvent c = new CloseEvent();
+            c.commit();
+            event.await();
+        }
+        if (!onClose.get()) {
+            throw new AssertionError("OnClose was not called");
+        }
+    }
+
+    private static void testOnClosedUnstarted() {
+        AtomicBoolean onClose = new AtomicBoolean(false);
+        try (RecordingStream r = new RecordingStream()) {
+            r.onClose(() -> {
+                onClose.set(true);
+            });
+        }
+        if (!onClose.get()) {
+            throw new AssertionError("OnClose was not called");
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestOnEvent.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2019, 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.api.consumer.recordingstream;
+
+import java.util.concurrent.CountDownLatch;
+
+import jdk.jfr.Event;
+import jdk.jfr.Name;
+import jdk.jfr.consumer.RecordingStream;
+
+/**
+ * @test
+ * @summary Tests RecordingStream::onEvent(...)
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestOnEvent
+ */
+public class TestOnEvent {
+
+    @Name("A")
+    static class EventA extends Event {
+    }
+
+    @Name("A")
+    static class EventAlsoA extends Event {
+    }
+
+    @Name("C")
+    static class EventC extends Event {
+    }
+
+    public static void main(String... args) throws Exception {
+        testOnEventNull();
+        testOnEvent();
+        testNamedEvent();
+        testTwoEventWithSameName();
+    }
+
+    private static void testOnEventNull() {
+        try (RecordingStream rs = new RecordingStream()) {
+           try {
+               rs.onEvent(null);
+               throw new AssertionError("Expected NullPointerException from onEvent(null)");
+           } catch (NullPointerException npe) {
+               // OK; as expected
+           }
+           try {
+               rs.onEvent("A", null);
+               throw new AssertionError("Expected NullPointerException from onEvent(\"A\", null)");
+
+           } catch (NullPointerException npe) {
+               // OK; as expected
+           }
+           try {
+               String s = null;
+               rs.onEvent(s, null);
+               throw new AssertionError("Expected NullPointerException from onEvent(null, null)");
+           } catch (NullPointerException npe) {
+               // OK; as expected
+           }
+        }
+     }
+
+    private static void testTwoEventWithSameName() throws Exception {
+        CountDownLatch eventA = new CountDownLatch(2);
+        try (RecordingStream r = new RecordingStream()) {
+            r.onEvent("A", e -> {
+                System.out.println("testTwoEventWithSameName" +  e);
+                eventA.countDown();
+            });
+            r.startAsync();
+            EventA a1 = new EventA();
+            a1.commit();
+            EventAlsoA a2 = new EventAlsoA();
+            a2.commit();
+            eventA.await();
+        }
+    }
+
+    private static void testNamedEvent() throws Exception {
+        try (RecordingStream r = new RecordingStream()) {
+            CountDownLatch eventA = new CountDownLatch(1);
+            CountDownLatch eventC = new CountDownLatch(1);
+            r.onEvent("A", e -> {
+                System.out.println("TestNamedEvent:" + e);
+                if (e.getEventType().getName().equals("A")) {
+                    eventA.countDown();
+                }
+            });
+            r.onEvent("C", e -> {
+                System.out.println("TestNamedEvent:" + e);
+                if (e.getEventType().getName().equals("C")) {
+                    eventC.countDown();
+                }
+            });
+
+            r.startAsync();
+            EventA a = new EventA();
+            a.commit();
+            EventC c = new EventC();
+            c.commit();
+            eventA.await();
+            eventC.await();
+        }
+    }
+
+    private static void testOnEvent() throws Exception {
+        try (RecordingStream r = new RecordingStream()) {
+            CountDownLatch event = new CountDownLatch(1);
+            r.onEvent(e -> {
+                event.countDown();
+            });
+            r.startAsync();
+            EventA a = new EventA();
+            a.commit();
+            event.await();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestOnFlush.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2019, 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.api.consumer.recordingstream;
+
+import java.util.concurrent.CountDownLatch;
+
+import jdk.jfr.Event;
+import jdk.jfr.consumer.RecordingStream;
+
+/**
+ * @test
+ * @summary Tests RecordingStream::onFlush(...)
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestOnFlush
+ */
+public class TestOnFlush {
+
+    static class OneEvent extends Event {
+    }
+
+    public static void main(String... args) throws Exception {
+        testOnFLushNull();
+        testOneEvent();
+        testNoEvent();
+    }
+
+    private static void testOnFLushNull() {
+        try (RecordingStream rs = new RecordingStream()) {
+           try {
+               rs.onFlush(null);
+               throw new AssertionError("Expected NullPointerException from onFlush(null");
+           } catch (NullPointerException npe) {
+               // OK; as expected
+           }
+        }
+     }
+
+    private static void testNoEvent() throws Exception {
+        CountDownLatch flush = new CountDownLatch(1);
+        try (RecordingStream r = new RecordingStream()) {
+            r.onFlush(() -> {
+                flush.countDown();
+            });
+            r.startAsync();
+            flush.await();
+        }
+    }
+
+    private static void testOneEvent() throws InterruptedException {
+        CountDownLatch flush = new CountDownLatch(1);
+        try (RecordingStream r = new RecordingStream()) {
+            r.onEvent(e -> {
+                // ignore event
+            });
+            r.onFlush(() -> {
+                flush.countDown();
+            });
+            r.startAsync();
+            OneEvent e = new OneEvent();
+            e.commit();
+            flush.await();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestRemove.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2019, 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.api.consumer.recordingstream;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+
+import jdk.jfr.Event;
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.jfr.consumer.RecordingStream;
+
+/**
+ * @test
+ * @summary Tests RecordingStrream::remove(...)
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestRemove
+ */
+public class TestRemove {
+
+    static class RemoveEvent extends Event {
+
+    }
+
+    public static void main(String... args) throws Exception {
+        testRemoveNull();
+        testRemoveOnFlush();
+        testRemoveOnClose();
+        testRemoveOnEvent();
+    }
+
+    private static void testRemoveNull() {
+        try (RecordingStream rs = new RecordingStream()) {
+           try {
+               rs.remove(null);
+               throw new AssertionError("Expected NullPointerException from remove(null");
+           } catch (NullPointerException npe) {
+               // OK; as expected
+           }
+        }
+     }
+
+    private static void testRemoveOnEvent() throws Exception {
+        try (RecordingStream rs = new RecordingStream()) {
+            AtomicInteger counter = new AtomicInteger(0);
+            CountDownLatch events = new CountDownLatch(2);
+            Consumer<RecordedEvent> c1 = e -> {
+                counter.incrementAndGet();
+            };
+
+            Consumer<RecordedEvent> c2 = e -> {
+                events.countDown();
+            };
+            rs.onEvent(c1);
+            rs.onEvent(c2);
+
+            rs.remove(c1);
+            rs.startAsync();
+            RemoveEvent r1 = new RemoveEvent();
+            r1.commit();
+            RemoveEvent r2 = new RemoveEvent();
+            r2.commit();
+            events.await();
+            if (counter.get() > 0) {
+                throw new AssertionError("OnEvent handler not removed!");
+            }
+        }
+    }
+
+    private static void testRemoveOnClose() {
+        try (RecordingStream rs = new RecordingStream()) {
+            AtomicBoolean onClose = new AtomicBoolean(false);
+            Runnable r = () -> {
+                onClose.set(true);
+            };
+            rs.onClose(r);
+            rs.remove(r);
+            rs.close();
+            if (onClose.get()) {
+                throw new AssertionError("onClose handler not removed!");
+            }
+        }
+    }
+
+    private static void testRemoveOnFlush() throws Exception {
+        try (RecordingStream rs = new RecordingStream()) {
+            AtomicInteger flushCount = new AtomicInteger(2);
+            AtomicBoolean removeExecuted = new AtomicBoolean(false);
+            Runnable onFlush1 = () -> {
+                removeExecuted.set(true);
+            };
+            Runnable onFlush2 = () -> {
+                flushCount.incrementAndGet();
+            };
+
+            rs.onFlush(onFlush1);
+            rs.onFlush(onFlush2);
+            rs.remove(onFlush1);
+            rs.startAsync();
+            while (flushCount.get() < 2) {
+                RemoveEvent r = new RemoveEvent();
+                r.commit();
+                Thread.sleep(100);
+            }
+
+            if (removeExecuted.get()) {
+                throw new AssertionError("onFlush handler not removed!");
+            }
+        }
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestSetFlushInterval.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2019, 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.api.consumer.recordingstream;
+
+import java.time.Duration;
+
+import jdk.jfr.consumer.RecordingStream;
+import jdk.test.lib.jfr.EventNames;
+
+/**
+ * @test
+ * @summary Tests RecordingStrream::setFlushInterval
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestSetFlushInterval
+ */
+public class TestSetFlushInterval {
+
+    public static void main(String... args) throws Exception {
+        Duration expectedDuration = Duration.ofMillis(1001);
+        try (RecordingStream r = new RecordingStream()) {
+            r.setInterval(expectedDuration);
+            r.enable(EventNames.ActiveRecording);
+            r.onEvent(e -> {
+                System.out.println(e);
+                Duration duration = e.getDuration("flushInterval");
+                if (expectedDuration.equals(duration)) {
+                    System.out.println("Closing recording");
+                    r.close();
+                    return;
+                }
+                System.out.println("Flush interval not set, was " + duration+ ", but expected " + expectedDuration);
+            });
+            r.start();
+        }
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestSetMaxAge.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2019, 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.api.consumer.recordingstream;
+
+import java.time.Duration;
+
+import jdk.jfr.consumer.RecordingStream;
+import jdk.test.lib.jfr.EventNames;
+
+/**
+ * @test
+ * @summary Tests RecordingStrream::setMaxAge
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestSetMaxAge
+ */
+public class TestSetMaxAge {
+
+    public static void main(String... args) throws Exception {
+        Duration expecteddAge = Duration.ofMillis(123456789);
+        try (RecordingStream r = new RecordingStream()) {
+            r.setMaxAge(expecteddAge);
+            r.enable(EventNames.ActiveRecording);
+            r.onEvent(e -> {
+                System.out.println(e);
+                Duration age = e.getDuration("maxAge");
+                if (expecteddAge.equals(age)) {
+                    r.close();
+                    return;
+                }
+                System.out.println("Max age not set, was " + age + ", but expected " + expecteddAge);
+            });
+            r.start();
+        }
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestSetMaxSize.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2019, 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.api.consumer.recordingstream;
+
+import jdk.jfr.consumer.RecordingStream;
+import jdk.test.lib.jfr.EventNames;
+
+/**
+* @test
+* @summary Tests RecordingStrream::setMaxSize
+* @key jfr
+* @requires vm.hasJFR
+* @library /test/lib
+* @run main/othervm jdk.jfr.api.consumer.recordingstream.TestSetMaxSize
+*/
+public class TestSetMaxSize {
+
+   public static void main(String... args) throws Exception {
+       long testSize = 123456789;
+       try (RecordingStream r = new RecordingStream()) {
+           r.setMaxSize(123456789);
+           r.enable(EventNames.ActiveRecording);
+           r.onEvent(e -> {
+               System.out.println(e);
+               long size= e.getLong("maxSize");
+               if (size == testSize) {
+                   r.close();
+                   return;
+               }
+               System.out.println("Max size not set, was " + size + ", but expected " + testSize);
+           });
+           r.start();
+       }
+   }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestStart.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2019, 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.api.consumer.recordingstream;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import jdk.jfr.Event;
+import jdk.jfr.consumer.RecordingStream;
+
+/**
+ * @test
+ * @summary Tests RecordingStream::start()
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestStart
+ */
+public class TestStart {
+    static class StartEvent extends Event {
+    }
+    static class EventProducer extends Thread {
+        private boolean killed = false;
+        public void run() {
+            while (true) {
+                StartEvent s = new StartEvent();
+                s.commit();
+                synchronized (this) {
+                    try {
+                        wait(10);
+                        if (killed) {
+                            return; // end thread
+                        }
+                    } catch (InterruptedException e) {
+                        // ignore
+                    }
+                }
+            }
+        }
+        public void kill() {
+            synchronized (this) {
+                this.killed = true;
+                this.notifyAll();
+            }
+        }
+    }
+
+    public static void main(String... args) throws Exception {
+        testStart();
+        testStartOnEvent();
+        testStartTwice();
+        testStartClosed();
+    }
+
+    private static void testStartTwice() throws Exception {
+        CountDownLatch started = new CountDownLatch(1);
+        try (RecordingStream rs = new RecordingStream()) {
+            EventProducer t = new EventProducer();
+            t.start();
+            CompletableFuture.runAsync(() -> {
+                rs.start();
+            });
+            rs.onEvent(e -> {
+                if (started.getCount() > 0) {
+                    started.countDown();
+                }
+            });
+            started.await();
+            t.kill();
+            try {
+                rs.start();
+                throw new AssertionError("Expected IllegalStateException if started twice");
+            } catch (IllegalStateException ise) {
+                // OK, as expected
+            }
+        }
+    }
+
+    static void testStart() throws Exception {
+        CountDownLatch started = new CountDownLatch(1);
+        try (RecordingStream rs = new RecordingStream()) {
+            rs.onEvent(e -> {
+                started.countDown();
+            });
+            EventProducer t = new EventProducer();
+            t.start();
+            CompletableFuture.runAsync(() -> {
+                rs.start();
+            });
+            started.await();
+            t.kill();
+        }
+    }
+
+    static void testStartOnEvent() throws Exception {
+        AtomicBoolean ISE = new AtomicBoolean(false);
+        CountDownLatch startedTwice = new CountDownLatch(1);
+        try (RecordingStream rs = new RecordingStream()) {
+            rs.onEvent(e -> {
+                try {
+                    rs.start(); // must not deadlock
+                } catch (IllegalStateException ise) {
+                    if (!ISE.get())  {
+                        startedTwice.countDown();
+                        ISE.set(true);
+                    }
+                }
+            });
+            EventProducer t = new EventProducer();
+            t.start();
+            CompletableFuture.runAsync(() -> {
+                rs.start();
+            });
+            startedTwice.await();
+            t.kill();
+            if (!ISE.get()) {
+                throw new AssertionError("Expected IllegalStateException");
+            }
+        }
+    }
+
+    static void testStartClosed() {
+        RecordingStream rs = new RecordingStream();
+        rs.close();
+        try {
+            rs.start();
+            throw new AssertionError("Expected IllegalStateException");
+        } catch (IllegalStateException ise) {
+            // OK, as expected.
+        }
+    }
+
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestStartAsync.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2019, 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.api.consumer.recordingstream;
+
+import java.util.concurrent.CountDownLatch;
+
+import jdk.jfr.Event;
+import jdk.jfr.consumer.RecordingStream;
+
+/**
+ * @test
+ * @summary Tests RecordingStream::startAsync()
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestStartAsync
+ */
+public class TestStartAsync {
+    static class StartEvent extends Event {
+    }
+
+    public static void main(String... args) throws Exception {
+        testStart();
+        testStartTwice();
+        testStartClosed();
+    }
+
+    private static void testStartTwice() throws Exception {
+        try (RecordingStream rs = new RecordingStream()) {
+            rs.startAsync();
+            try {
+                rs.startAsync();
+                throw new AssertionError("Expected IllegalStateException if started twice");
+            } catch (IllegalStateException ise) {
+                // OK, as expected
+            }
+        }
+    }
+
+    static void testStart() throws Exception {
+        CountDownLatch started = new CountDownLatch(1);
+        try (RecordingStream rs = new RecordingStream()) {
+            rs.onEvent(e -> {
+                started.countDown();
+            });
+            rs.startAsync();
+            StartEvent e = new StartEvent();
+            e.commit();
+            started.await();
+        }
+    }
+
+    static void testStartClosed() {
+        RecordingStream rs = new RecordingStream();
+        rs.close();
+        try {
+            rs.startAsync();
+            throw new AssertionError("Expected IllegalStateException");
+        } catch (IllegalStateException ise) {
+            // OK, as expected.
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestEmptyChunks.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2019, 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.api.consumer.streaming;
+
+import java.util.concurrent.CountDownLatch;
+
+import jdk.jfr.Event;
+import jdk.jfr.Recording;
+import jdk.jfr.consumer.RecordingStream;
+
+/**
+ * @test
+ * @summary Test that it is possible to iterate over chunk without normal events
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm jdk.jfr.api.consumer.streaming.TestEmptyChunks
+ */
+public class TestEmptyChunks {
+    static class EndEvent extends Event {
+    }
+
+    public static void main(String... args) throws Exception {
+        CountDownLatch end = new CountDownLatch(1);
+        try (RecordingStream es = new RecordingStream()) {
+            es.onEvent(EndEvent.class.getName(), e -> {
+                end.countDown();
+            });
+            es.startAsync();
+            Recording r1 = new Recording();
+            r1.start();
+            System.out.println("Chunk 1 started");
+            Recording r2 = new Recording();
+            r2.start();
+            System.out.println("Chunk 2 started");
+            Recording r3 = new Recording();
+            r3.start();
+            System.out.println("Chunk 3 started");
+            r2.stop();
+            System.out.println("Chunk 4 started");
+            r3.stop();
+            System.out.println("Chunk 5 started");
+            EndEvent e = new EndEvent();
+            e.commit();
+            end.await();
+            r1.stop();
+            System.out.println("Chunk 5 ended");
+            r1.close();
+            r2.close();
+            r3.close();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestEnableEvents.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2019, 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.api.consumer.streaming;
+
+import java.time.Duration;
+import java.util.concurrent.CountDownLatch;
+
+import jdk.jfr.Enabled;
+import jdk.jfr.Event;
+import jdk.jfr.FlightRecorder;
+import jdk.jfr.consumer.RecordingStream;
+
+/**
+ * @test
+ * @summary Verifies that it is possible to stream contents from specified event
+ *          settings
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ *
+ * @run main/othervm jdk.jfr.api.consumer.streaming.TestEnableEvents
+ */
+public class TestEnableEvents {
+
+    @Enabled(false)
+    static class HorseEvent extends Event {
+    }
+
+    @Enabled(false)
+    static class ElephantEvent extends Event {
+    }
+
+    @Enabled(false)
+    static class TigerEvent extends Event {
+    }
+
+    public static void main(String... args) throws Exception {
+        CountDownLatch elephantLatch = new CountDownLatch(1);
+        CountDownLatch tigerLatch = new CountDownLatch(1);
+        CountDownLatch horseLatch = new CountDownLatch(1);
+
+        FlightRecorder.addPeriodicEvent(ElephantEvent.class, () -> {
+            HorseEvent ze = new HorseEvent();
+            ze.commit();
+        });
+
+        try (RecordingStream s = new RecordingStream()) {
+            s.enable(HorseEvent.class.getName()).withPeriod(Duration.ofMillis(50));
+            s.enable(TigerEvent.class.getName());
+            s.enable(ElephantEvent.class.getName());
+            s.onEvent(TigerEvent.class.getName(), e -> {
+                System.out.println("Event: " + e.getEventType().getName());
+                System.out.println("Found tiger!");
+                tigerLatch.countDown();
+            });
+            s.onEvent(HorseEvent.class.getName(), e -> {
+                System.out.println("Found horse!");
+                horseLatch.countDown();
+            });
+            s.onEvent(ElephantEvent.class.getName(), e -> {
+                System.out.println("Found elelphant!");
+                elephantLatch.countDown();
+            });
+            s.startAsync();
+            TigerEvent te = new TigerEvent();
+            te.commit();
+            ElephantEvent ee = new ElephantEvent();
+            ee.commit();
+            elephantLatch.await();
+            horseLatch.await();
+            tigerLatch.await();
+        }
+
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestEventRegistration.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2019, 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.api.consumer.streaming;
+
+import java.util.concurrent.CountDownLatch;
+
+import jdk.jfr.Event;
+import jdk.jfr.FlightRecorder;
+import jdk.jfr.Registered;
+import jdk.jfr.consumer.RecordingStream;
+
+/**
+ * @test
+ * @summary Test that it is possible to register new metadata in a chunk
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm jdk.jfr.api.consumer.streaming.TestEventRegistration
+ */
+public class TestEventRegistration {
+    @Registered(false)
+    static class StreamEvent1 extends Event {
+    }
+
+    @Registered(false)
+    static class StreamEvent2 extends Event {
+    }
+
+    public static void main(String... args) throws Exception {
+
+        CountDownLatch s1Latch = new CountDownLatch(1);
+        CountDownLatch s2Latch = new CountDownLatch(1);
+        try (RecordingStream es = new RecordingStream()) {
+            es.onEvent(StreamEvent1.class.getName(), e -> {
+                s1Latch.countDown();
+            });
+            es.onEvent(StreamEvent2.class.getName(), e -> {
+                s2Latch.countDown();
+            });
+            es.startAsync();
+            System.out.println("Registering " + StreamEvent1.class.getName());
+            FlightRecorder.register(StreamEvent1.class);
+            StreamEvent1 s1 = new StreamEvent1();
+            s1.commit();
+            System.out.println(StreamEvent1.class.getName() + " commited");
+            System.out.println("Awaiting latch for " + StreamEvent1.class.getName());
+            s1Latch.await();
+            System.out.println();
+            System.out.println("Registering " + StreamEvent2.class.getName());
+            FlightRecorder.register(StreamEvent2.class);
+            StreamEvent2 s2 = new StreamEvent2();
+            s2.commit();
+            System.out.println(StreamEvent2.class.getName() + " commited");
+            System.out.println("Awaiting latch for " + StreamEvent2.class.getName());
+            s2Latch.await();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestFilledChunks.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2019, 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.api.consumer.streaming;
+
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import jdk.jfr.Event;
+import jdk.jfr.consumer.RecordingStream;
+
+/**
+ * @test
+ * @summary Test that it is possible to iterate over chunk with normal events
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm jdk.jfr.api.consumer.streaming.TestFilledChunks
+ */
+public class TestFilledChunks {
+
+    static class FillEvent extends Event {
+        String message;
+        int value;
+        int id;
+    }
+
+    static class EndEvent extends Event {
+    }
+
+    // Will generate about 100 MB of data, or 8-9 chunks
+    private static final int EVENT_COUNT = 5_000_000;
+
+    public static void main(String... args) throws Exception {
+        CountDownLatch end = new CountDownLatch(1);
+        AtomicInteger idCounter = new AtomicInteger();
+        try (RecordingStream es = new RecordingStream()) {
+            es.onEvent(EndEvent.class.getName(), e -> end.countDown());
+            es.onEvent(FillEvent.class.getName(), e -> {
+                idCounter.incrementAndGet();
+//                if (id != expected) {
+//                    throw new Error("Expected id " + expected + ", but got " + id);
+//                }
+            });
+            es.startAsync();
+            long seed = System.currentTimeMillis();
+            System.out.println("Random seed: " + seed);
+            Random r = new Random(seed);
+            for (int i = 1; i < EVENT_COUNT; i++) {
+                FillEvent f = new FillEvent();
+                f.message = i %2 == 0 ? "ko" : "kak";
+                f.value = r.nextInt(10000);
+                f.id = i;
+                f.commit();
+                if (i % 1_000_000 == 0) {
+                    System.out.println("Emitted " + i + " events");
+                }
+            }
+            System.out.println("Awaiting end event");
+            Thread.sleep(1_000);
+            for (int i = 1; i < EVENT_COUNT; i++) {
+                EndEvent e = new EndEvent();
+                e.commit();
+            }
+            end.await();
+
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestFiltering.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2019, 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.api.consumer.streaming;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import jdk.jfr.Event;
+import jdk.jfr.consumer.RecordingStream;
+
+/**
+ * @test
+ * @summary Verifies that it is possible to filter a stream for an event
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm jdk.jfr.api.consumer.streaming.TestFiltering
+ */
+public class TestFiltering {
+
+    static class SnakeEvent extends Event {
+        int id;
+    }
+
+    static class EelEvent extends Event {
+        int id;
+    }
+
+    public static void main(String... args) throws Exception {
+        CountDownLatch l = new CountDownLatch(1);
+        String eventName = SnakeEvent.class.getName();
+        AtomicInteger idCounter = new AtomicInteger(-1);
+        try (RecordingStream e = new RecordingStream()) {
+            e.onEvent(eventName, event -> {
+                if (!event.getEventType().getName().equals(eventName)) {
+                    throw new InternalError("Unexpected event " + e);
+                }
+                if (event.getInt("id") != idCounter.incrementAndGet()) {
+                    throw new InternalError("Incorrect id");
+                }
+                if (idCounter.get() == 99) {
+                    l.countDown();
+                }
+            });
+            e.startAsync();
+            for (int i = 0; i < 100; i++) {
+                SnakeEvent se = new SnakeEvent();
+                se.id = i;
+                se.commit();
+
+                EelEvent ee = new EelEvent();
+                ee.id = i;
+                ee.commit();
+            }
+            l.await();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestFromFile.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2019, 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.api.consumer.streaming;
+
+import java.nio.file.Path;
+import java.util.concurrent.atomic.AtomicLong;
+
+import jdk.jfr.Event;
+import jdk.jfr.consumer.EventStream;
+
+/**
+ * @test
+ * @summary Verifies that it is possible to stream contents from a file
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm jdk.jfr.api.consumer.streaming.TestFromFile
+ */
+public class TestFromFile {
+
+    static class SnakeEvent extends Event {
+        int id;
+    }
+
+    public static void main(String... args) throws Exception {
+//        Path path = Paths.get("./using-file.jfr");
+//        try (Recording r1 = new Recording()) {
+//            r1.start();
+//            emitSnakeEvent(1);
+//            emitSnakeEvent(2);
+//            emitSnakeEvent(3);
+//            // Force a chunk rotation
+//            try (Recording r2 = new Recording()) {
+//                r2.start();
+//                emitSnakeEvent(4);
+//                emitSnakeEvent(5);
+//                emitSnakeEvent(6);
+//                r2.stop();
+//            }
+//            r1.stop();
+//            r1.dump(path);
+//
+//            testIterator(path);
+//            testConsumer(path);
+//        }
+    }
+
+    static void testConsumer(Path path) throws Exception {
+        AtomicLong counter = new AtomicLong();
+        try (EventStream es = EventStream.openFile(path)) {
+            es.onEvent(e -> {
+                counter.incrementAndGet();
+            });
+            es.startAsync();
+            if (counter.get() != 6) {
+                throw new Exception("Expected 6 event, but got " + counter.get());
+            }
+            es.awaitTermination();
+        }
+    }
+
+    static void emitSnakeEvent(int id) {
+        SnakeEvent e = new SnakeEvent();
+        e.id = id;
+        e.commit();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestRecordingBefore.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,98 @@
+/*
+ * Copyright (c) 2019, 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.api.consumer.streaming;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import jdk.jfr.Event;
+import jdk.jfr.Recording;
+import jdk.jfr.consumer.RecordingStream;
+
+/**
+ * @test
+ * @summary Verifies that it is possible to start a stream when there are
+ *          already chunk in the repository
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm jdk.jfr.api.consumer.streaming.TestRecordingBefore
+ */
+public class TestRecordingBefore {
+
+    static class SnakeEvent extends Event {
+        int id;
+    }
+
+    public static void main(String... args) throws Exception {
+
+        try (Recording r1 = new Recording()) {
+            r1.start();
+            emitSnakeEvent(1);
+            emitSnakeEvent(2);
+            emitSnakeEvent(3);
+            // Force a chunk rotation
+            try (Recording r2 = new Recording()) {
+                r2.start();
+                emitSnakeEvent(4);
+                emitSnakeEvent(5);
+                emitSnakeEvent(6);
+                r2.stop();
+            }
+            r1.stop();
+            // Two chunks should now exist in the repository
+            AtomicBoolean fail = new AtomicBoolean(false);
+            CountDownLatch lastEvent = new CountDownLatch(1);
+            try (RecordingStream rs = new RecordingStream()) {
+                rs.onEvent(e -> {
+                    long id = e.getLong("id");
+                    if (id < 7) {
+                        System.out.println("Found unexpected id " + id);
+                        fail.set(true);
+                    }
+                    if (id == 9) {
+                        lastEvent.countDown();
+                    }
+                });
+                rs.startAsync();
+                emitSnakeEvent(7);
+                emitSnakeEvent(8);
+                emitSnakeEvent(9);
+                lastEvent.await();
+                if (fail.get()) {
+                    throw new Exception("Found events from a previous recording");
+                }
+            }
+        }
+    }
+
+    static void emitSnakeEvent(int id) {
+        SnakeEvent e = new SnakeEvent();
+        e.id = id;
+        e.commit();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestRemovedChunks.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 2019, 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.api.consumer.streaming;
+
+import java.util.concurrent.CountDownLatch;
+
+import jdk.jfr.Event;
+import jdk.jfr.consumer.RecordingStream;
+
+/**
+ * @test
+ * @summary Tests that a stream can gracefully handle chunk being removed
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm jdk.jfr.api.consumer.streaming.TestFilledChunks
+ */
+public class TestRemovedChunks {
+    private final static CountDownLatch parkLatch = new CountDownLatch(1);
+    private final static CountDownLatch removalLatch = new CountDownLatch(1);
+    private final static CountDownLatch IfeelFineLatch = new CountDownLatch(1);
+
+    static class DataEvent extends Event {
+        double double1;
+        double double2;
+        double double3;
+        double double4;
+        double double5;
+    }
+
+    static class ParkStream extends Event {
+    }
+
+    static class IFeelFine extends Event {
+    }
+
+    public static void main(String... args) throws Exception {
+
+        try (RecordingStream s = new RecordingStream()) {
+            s.setMaxSize(20_000_000);
+            s.onEvent(ParkStream.class.getName(), e -> {
+                parkLatch.countDown();
+                await(removalLatch);
+
+            });
+            s.onEvent(IFeelFine.class.getName(), e -> {
+                IfeelFineLatch.countDown();
+            });
+            s.startAsync();
+            emitData(15_000_000);
+            ParkStream ps = new ParkStream();
+            ps.commit();
+            await(parkLatch);
+            // Try to force removal of chunk that is being streamed
+            emitData(50_000_000);
+            removalLatch.countDown();
+            IFeelFine i = new IFeelFine();
+            i.commit();
+            await(IfeelFineLatch);
+        }
+
+    }
+
+    private static void await(CountDownLatch latch) throws Error {
+        try {
+            latch.await();
+        } catch (InterruptedException e1) {
+            throw new Error("Latch interupted");
+        }
+    }
+
+    private static void emitData(int amount) throws InterruptedException {
+        int count = 0;
+        while (amount > 0) {
+            DataEvent de = new DataEvent();
+            // 5 doubles are 40 bytes bytes
+            // and event size, event type, thread,
+            // start time, duration and stack trace about 15 bytes
+            de.double1 = 0.0;
+            de.double2 = 1.0;
+            de.double3 = 2.0;
+            de.double4 = 3.0;
+            de.double5 = 4.0;
+            de.commit();
+            amount -= 55;
+            count++;
+            //
+            if (count % 100_000 == 0) {
+                Thread.sleep(10);
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestRepositoryProperty.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 2019, 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.api.consumer.streaming;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Properties;
+
+import com.sun.tools.attach.AttachNotSupportedException;
+import com.sun.tools.attach.VirtualMachine;
+
+import jdk.jfr.Recording;
+import jdk.test.lib.dcmd.CommandExecutor;
+import jdk.test.lib.dcmd.PidJcmdExecutor;
+
+/**
+ * @test
+ * @summary Verifies that it is possible to access JFR repository from a system
+ *          property
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @modules jdk.attach
+ *          jdk.jfr
+ * @run main/othervm -Djdk.attach.allowAttachSelf=true jdk.jfr.api.consumer.streaming.TestRepositoryProperty
+ */
+public class TestRepositoryProperty {
+
+    private final static String JFR_REPOSITORY_LOCATION_PROPERTY = "jdk.jfr.repository";
+
+    public static void main(String... args) throws Exception {
+        testBeforeInitialization();
+        testAfterInitialization();
+        testFromAgent();
+        testAfterChange();
+    }
+
+    private static void testFromAgent() throws AttachNotSupportedException, IOException {
+        String pidText = String.valueOf(ProcessHandle.current().pid());
+        VirtualMachine vm = VirtualMachine.attach(pidText);
+        Properties p = vm.getSystemProperties();
+        String location = (String) p.get(JFR_REPOSITORY_LOCATION_PROPERTY);
+        if (location == null) {
+            throw new AssertionError("Could not find repository path in agent properties");
+        }
+        Path path = Path.of(location);
+        if (!Files.isDirectory(path)) {
+            throw new AssertionError("Repository path doesn't point to directory");
+        }
+    }
+
+    private static void testAfterChange() {
+        Path newRepository = Path.of(".").toAbsolutePath();
+
+        String cmd = "JFR.configure repository=" +  newRepository.toString();
+        CommandExecutor executor = new PidJcmdExecutor();
+        executor.execute(cmd);
+        String location = System.getProperty(JFR_REPOSITORY_LOCATION_PROPERTY);
+        if (newRepository.toString().equals(location)) {
+            throw new AssertionError("Repository path not updated after it has been changed");
+        }
+    }
+
+    private static void testAfterInitialization() {
+        try (Recording r = new Recording()) {
+            r.start();
+            String location = System.getProperty(JFR_REPOSITORY_LOCATION_PROPERTY);
+            if (location == null) {
+                throw new AssertionError("Repository path should exit before JFR is initialized");
+            }
+            System.out.println("repository=" + location);
+            Path p = Path.of(location);
+            if (!Files.isDirectory(p)) {
+                throw new AssertionError("Repository path doesn't point to directory");
+            }
+        }
+
+    }
+
+    private static void testBeforeInitialization() {
+        String location = System.getProperty(JFR_REPOSITORY_LOCATION_PROPERTY);
+        if (location != null) {
+            throw new AssertionError("Repository path should exit before JFR is initialized");
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestStartMultiChunk.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2019, 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.api.consumer.streaming;
+
+import java.util.concurrent.CountDownLatch;
+
+import jdk.jfr.Event;
+import jdk.jfr.FlightRecorder;
+import jdk.jfr.Name;
+import jdk.jfr.Period;
+import jdk.jfr.Recording;
+import jdk.jfr.consumer.RecordingStream;
+
+/**
+ * @test
+ * @summary Verifies that it is possible to stream contents of ongoing
+ *          recordings
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm -Xlog:jfr+system+streaming=trace
+ *      jdk.jfr.api.consumer.streaming.TestStartMultiChunk
+ */
+public class TestStartMultiChunk {
+
+    @Period("10 s")
+    @Name("Zebra")
+    static class ZebraEvent extends Event {
+    }
+
+    @Name("Cat")
+    static class CatEvent extends Event {
+    }
+
+    @Name("Dog")
+    static class DogEvent extends Event {
+    }
+
+    @Name("Mouse")
+    static class MouseEvent extends Event {
+    }
+
+    public static void main(String... args) throws Exception {
+        CountDownLatch dogLatch = new CountDownLatch(1);
+        CountDownLatch catLatch = new CountDownLatch(1);
+        CountDownLatch mouseLatch = new CountDownLatch(1);
+        CountDownLatch zebraLatch = new CountDownLatch(3);
+
+        FlightRecorder.addPeriodicEvent(ZebraEvent.class, () -> {
+            ZebraEvent ze = new ZebraEvent();
+            ze.commit();
+            System.out.println("Zebra emitted");
+        });
+
+        try (RecordingStream s = new RecordingStream()) {
+            s.onEvent("Cat", e -> {
+                System.out.println("Found cat!");
+                catLatch.countDown();
+            });
+            s.onEvent("Dog", e -> {
+                System.out.println("Found dog!");
+                dogLatch.countDown();
+            });
+            s.onEvent("Zebra", e -> {
+                System.out.println("Found zebra!");
+                zebraLatch.countDown();
+            });
+            s.onEvent("Mouse", e -> {
+                System.out.println("Found mouse!");
+                mouseLatch.countDown();
+            });
+            s.startAsync();
+            System.out.println("Stream recoding started");
+
+            try (Recording r1 = new Recording()) {
+                r1.start();
+                System.out.println("r1.start()");
+                MouseEvent me = new MouseEvent();
+                me.commit();
+                System.out.println("Mouse emitted");
+                mouseLatch.await();
+                try (Recording r2 = new Recording()) { // force chunk rotation
+                                                       // in stream
+                    r2.start();
+                    System.out.println("r2.start()");
+                    DogEvent de = new DogEvent();
+                    de.commit();
+                    System.out.println("Dog emitted");
+                    dogLatch.await();
+                    CatEvent ce = new CatEvent();
+                    ce.commit();
+                    System.out.println("Cat emitted");
+                    catLatch.await();
+                    zebraLatch.await();
+                }
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestStartSingleChunk.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2019, 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.api.consumer.streaming;
+
+import java.util.concurrent.CountDownLatch;
+
+import jdk.jfr.Event;
+import jdk.jfr.FlightRecorder;
+import jdk.jfr.Period;
+import jdk.jfr.consumer.RecordingStream;
+
+/**
+ * @test
+ * @summary Verifies that it is possible to stream contents of ongoing
+ *          recordings
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm -Xlog:jfr+system+streaming=trace
+ *      jdk.jfr.api.consumer.streaming.TestStartSingleChunk
+ */
+public class TestStartSingleChunk {
+
+    @Period("500 ms")
+    static class ElkEvent extends Event {
+    }
+
+    static class FrogEvent extends Event {
+    }
+
+    static class LionEvent extends Event {
+    }
+
+    public static void main(String... args) throws Exception {
+        CountDownLatch frogLatch = new CountDownLatch(1);
+        CountDownLatch lionLatch = new CountDownLatch(1);
+        CountDownLatch elkLatch = new CountDownLatch(3);
+
+        FlightRecorder.addPeriodicEvent(ElkEvent.class, () -> {
+            ElkEvent ee = new ElkEvent();
+            ee.commit();
+        });
+        try (RecordingStream s = new RecordingStream()) {
+            s.onEvent(ElkEvent.class.getName(), e -> {
+                System.out.println("Found elk!");
+                elkLatch.countDown();
+            });
+            s.onEvent(LionEvent.class.getName(), e -> {
+                System.out.println("Found lion!");
+                lionLatch.countDown();
+            });
+            s.onEvent(FrogEvent.class.getName(), e -> {
+                System.out.println("Found frog!");
+                frogLatch.countDown();
+            });
+            s.startAsync();
+            FrogEvent fe = new FrogEvent();
+            fe.commit();
+
+            LionEvent le = new LionEvent();
+            le.commit();
+
+            frogLatch.await();
+            lionLatch.await();
+            elkLatch.await();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/streaming/UseCasesStream.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,194 @@
+/*
+ * Copyright (c) 2019, 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.api.consumer.streaming;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.text.ParseException;
+import java.time.Duration;
+import java.util.ArrayDeque;
+
+import jdk.jfr.Configuration;
+import jdk.jfr.EventType;
+import jdk.jfr.ValueDescriptor;
+import jdk.jfr.consumer.RecordingStream;
+
+class UseCasesStream {
+
+    //
+    // Use case: Out-of-the-Box Experience
+    //
+    // - Simple things should be simple
+    // - Pique interest, i.e. a one-liner on Stack Overflow
+    // - Few lines of code as possible
+    // - Should be easier than alternative technologies, like JMX and JVM TI
+    //
+    // - Non-goals: Corner-cases, advanced configuration, releasing resources
+    //
+    public static void outOfTheBox() throws InterruptedException {
+        try (RecordingStream rs = new RecordingStream()) {
+            rs.enable("jdk.ExceptionThrown");
+            rs.onEvent(e -> System.out.println(e.getString("message")));
+            rs.start();
+        }
+
+        // EventStream.start("jdk.JavaMonitorEnter", "threshold", "20 ms",
+        // "stackTrace", "false")
+        // .addConsumer(System.out::println);
+        //
+        // EventStream.start("jdk.CPULoad", "period", "1 s")
+        // .addConsumer(e -> System.out.println(100 *
+        // e.getDouble("totalMachine") + " %"));
+        //
+        // EventStream.start("jdk.GarbageCollection")
+        // .addConsumer(e -> System.out.println("GC: " + e.getStartTime() + "
+        // maxPauseTime=" + e.getDuration("maxPauseTime").toMillis() + " ms"));
+
+        Thread.sleep(100_000);
+    }
+
+    // Use case: Event Forwarding
+    //
+    // - Forward arbitrary event to frameworks such as RxJava, JSON/XML and
+    // Kafka
+    // - Handle flooding
+    // - Performant
+    // - Graceful shutdown
+    // - Non-goals: Filter events
+    //
+    public static void eventForwarding() throws InterruptedException, IOException, ParseException {
+        // KafkaProducer producer = new KafkaProducer<String, String>();
+        try (RecordingStream rs = new RecordingStream(Configuration.getConfiguration("default"))) {
+            rs.setMaxAge(Duration.ofMinutes(5));
+            rs.setMaxSize(1000_000_000L);
+            // es.setParallel(true);
+            // es.setReuse(true);
+            // es.consume(e -> producer.send(new ProducerRecord<String,
+            // String>("topic",
+            // e.getString("key"), e.getString("value"))));
+            rs.start();
+        }
+        // Write primitive values to XML
+        try (RecordingStream rs = new RecordingStream(Configuration.getConfiguration("deafult"))) {
+            try (PrintWriter p = new PrintWriter(new FileWriter("recording.xml"))) {
+                // es.setParallel(false);
+                // es.setReuse(true);
+                p.println("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
+                p.println("<events>");
+                rs.onEvent(e -> {
+                    EventType type = e.getEventType();
+                    p.println("  <event type=\"" + type.getName() + "\" start=\"" + e.getStartTime() + "\" end=\"" + e.getEndTime() + "\">");
+                    for (ValueDescriptor field : e.getEventType().getFields()) {
+                        Object value = e.getValue(field.getName());
+                        if (value instanceof Number || field.getTypeName().equals("java.lang.String")) {
+                            p.println("    <value field=\"" + field.getName() + "\">" + value + "</value>");
+                        }
+                    }
+                });
+                rs.start();
+                p.println("</events>");
+            }
+        }
+    }
+
+    // Use case: Repository Access
+    //
+    // - Read the disk repository from another process, for example a side car
+    // in
+    // Docker container
+    // - Be able to configure flush interval from command line or jcmd.
+    // - Graceful shutdown
+    //
+    public static void repositoryAccess() throws IOException, InterruptedException {
+        Path repository = Paths.get("c:\\repository").toAbsolutePath();
+        String command = new String();
+        command += "java -XX:StartFlightRecording:flush=2s";
+        command += "-XX:FlightRecorderOption:repository=" + repository + " Application";
+        Process myProcess = Runtime.getRuntime().exec(command);
+        try (RecordingStream rs = new RecordingStream()) {
+            rs.onEvent(System.out::println);
+            rs.startAsync();
+            Thread.sleep(10_000);
+            myProcess.destroy();
+            Thread.sleep(10_000);
+        }
+    }
+
+    // Use: Tooling
+    //
+    // - Monitor a stream of data for a very long time
+    // - Predictable interval, i.e. once every second
+    // - Notification with minimal delay
+    // - Events with the same period should arrive together
+    // - Consume events in chronological order
+    // - Low overhead
+    //
+    public static void tooling() throws IOException, ParseException {
+        ArrayDeque<Double> measurements = new ArrayDeque<>();
+        try (RecordingStream rs = new RecordingStream(Configuration.getConfiguration("profile"))) {
+            rs.setInterval(Duration.ofSeconds(1));
+            rs.setMaxAge(Duration.ofMinutes(1));
+            // rs.setOrdered(true);
+            // rs.setReuse(false);
+            // rs.setParallel(true);
+            rs.onEvent("jdk.CPULoad", e -> {
+                double d = e.getDouble("totalMachine");
+                measurements.addFirst(d);
+                if (measurements.size() > 60) {
+                    measurements.removeLast();
+                }
+                // repaint();
+            });
+            rs.start();
+        }
+    }
+
+    // Use case: Low Impact
+    //
+    // - Support event subscriptions in a low latency environment (minimal GC
+    // pauses)
+    // - Filter out relevant events to minimize disk overhead and allocation
+    // pressure
+    // - Avoid impact from other recordings
+    // - Avoid Heisenberg effects, in particular self-recursion
+    //
+    // Non-goals: one-liner
+    //
+    public static void lowImpact() throws InterruptedException, IOException, ParseException {
+        try (RecordingStream rs = new RecordingStream()) {
+            rs.enable("jdk.JavaMonitorEnter#threshold").withThreshold(Duration.ofMillis(10));
+            rs.enable("jdk.ExceptionThrow#enabled");
+            // ep.setReuse(true);
+            rs.onEvent("jdk.JavaMonitorEnter", System.out::println);
+            rs.onEvent("jdk.ExceptionThrow", System.out::println);
+            rs.start();
+            ;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/event/runtime/TestFlush.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2019, 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.event.runtime;
+
+import java.util.concurrent.CountDownLatch;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import jdk.jfr.Event;
+import jdk.jfr.FlightRecorder;
+import jdk.jfr.Period;
+import jdk.jfr.Recording;
+import jdk.jfr.consumer.RecordingStream;
+import jdk.jfr.consumer.RecordedEvent;
+
+import jdk.test.lib.Asserts;
+import jdk.test.lib.jfr.EventNames;
+
+/**
+ * @test
+ * @summary Verifies at the metalevel that stream contents are written to ongoing recordings
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm -Xlog:jfr+system+streaming=trace jdk.jfr.event.runtime.TestFlush
+ */
+public class TestFlush {
+    private static boolean flushEventAck = false;
+
+    @Period("2 s")
+    static class ZebraEvent extends Event {
+    }
+    static class CatEvent extends Event {
+    }
+    static class DogEvent extends Event {
+    }
+    static class MouseEvent extends Event {
+    }
+
+    public static void main(String... args) throws InterruptedException {
+        CountDownLatch dogLatch = new CountDownLatch(1);
+        CountDownLatch catLatch = new CountDownLatch(1);
+        CountDownLatch mouseLatch = new CountDownLatch(1);
+        CountDownLatch zebraLatch = new CountDownLatch(3);
+
+        FlightRecorder.addPeriodicEvent(ZebraEvent.class, () -> {
+            ZebraEvent ze = new ZebraEvent();
+            ze.commit();
+        });
+
+        try (RecordingStream rs = new RecordingStream()) {
+            rs.enable(EventNames.Flush);
+            rs.enable(EventNames.FlushStorage);
+            rs.enable(EventNames.FlushStacktrace);
+            rs.enable(EventNames.FlushStringPool);
+            rs.enable(EventNames.FlushMetadata);
+            rs.enable(EventNames.FlushTypeSet);
+            rs.onEvent(e -> {
+                switch (e.getEventType().getName()) {
+                    case EventNames.Flush:
+                        flushEventAck = true;
+                    case EventNames.FlushStorage:
+                    case EventNames.FlushStacktrace:
+                    case EventNames.FlushStringPool:
+                    case EventNames.FlushMetadata:
+                    case EventNames.FlushTypeSet:
+                        validateFlushEvent(e);
+                        return;
+                }
+                if (e.getEventType().getName().equals(CatEvent.class.getName())) {
+                    System.out.println("Found cat!");
+                    catLatch.countDown();
+                    return;
+                }
+                if (e.getEventType().getName().equals(DogEvent.class.getName())) {
+                    System.out.println("Found dog!");
+                    dogLatch.countDown();
+                    return;
+                }
+                if (e.getEventType().getName().equals(ZebraEvent.class.getName())) {
+                    System.out.println("Found zebra!");
+                    zebraLatch.countDown();
+                    return;
+                }
+                if (e.getEventType().getName().equals(MouseEvent.class.getName())) {
+                    System.out.println("Found mouse!");
+                    mouseLatch.countDown();
+                    return;
+                }
+                System.out.println("Unexpected event: " + e.getEventType().getName());
+            });
+
+            rs.startAsync();
+
+            try (Recording r1 = new Recording()) {
+                r1.start();
+                MouseEvent me = new MouseEvent();
+                me.commit();
+                System.out.println("Mouse emitted");
+                mouseLatch.await();
+                try (Recording r2 = new Recording()) { // force chunk rotation in stream
+                    r2.start();
+                    DogEvent de = new DogEvent();
+                    de.commit();
+                    System.out.println("Dog emitted");
+                    dogLatch.await();
+                    CatEvent ce = new CatEvent();
+                    ce.commit();
+                    System.out.println("Cat emitted");
+                    catLatch.await();
+                    zebraLatch.await();
+                    acknowledgeFlushEvent();
+                }
+            }
+        }
+    }
+
+    private static void printEvent(RecordedEvent re) {
+        System.out.println(re.getEventType().getName());
+        System.out.println(re.getStartTime().toEpochMilli());
+        System.out.println(re.getEndTime().toEpochMilli());
+    }
+
+    private static void printFlushEvent(RecordedEvent re) {
+        printEvent(re);
+        System.out.println("flushID: " + (long) re.getValue("flushId"));
+        System.out.println("elements: " + (long) re.getValue("elements"));
+        System.out.println("size: " + (long) re.getValue("size"));
+    }
+
+    private static void validateFlushEvent(RecordedEvent re) {
+        printFlushEvent(re);
+        Asserts.assertTrue(re.getEventType().getName().contains("Flush"), "invalid Event type");
+        Asserts.assertGT((long) re.getValue("flushId"), 0L, "Invalid flush ID");
+        Asserts.assertGT((long) re.getValue("elements"), 0L, "No elements");
+        Asserts.assertGT((long) re.getValue("size"), 0L, "Empty size");
+    }
+
+    private static void acknowledgeFlushEvent() {
+        Asserts.assertTrue(flushEventAck, "No Flush event");
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/jvm/TestThreadExclusion.java	Fri May 17 18:03:14 2019 +0200
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2019, 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.jvm;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.jfr.internal.JVM;
+import jdk.jfr.Recording;
+
+import jdk.test.lib.jfr.EventNames;
+import jdk.test.lib.jfr.Events;
+
+import static jdk.test.lib.Asserts.assertTrue;
+
+/**
+ * @test
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @modules jdk.jfr/jdk.jfr.internal
+ * @run main/othervm jdk.jfr.jvm.TestThreadExclusion
+ */
+
+/**
+ * Starts and stops a number of threads in order.
+ * Verifies that events are in the same order.
+ */
+public class TestThreadExclusion {
+    private final static String EVENT_NAME_THREAD_START = EventNames.ThreadStart;
+    private final static String EVENT_NAME_THREAD_END = EventNames.ThreadEnd;
+    private static final String THREAD_NAME_PREFIX = "TestThread-";
+    private static JVM jvm;
+
+    public static void main(String[] args) throws Throwable {
+        // Test Java Thread Start event
+        Recording recording = new Recording();
+        recording.enable(EVENT_NAME_THREAD_START).withThreshold(Duration.ofMillis(0));
+        recording.enable(EVENT_NAME_THREAD_END).withThreshold(Duration.ofMillis(0));
+        recording.start();
+        LatchedThread[] threads = startThreads();
+        long[] javaThreadIds = getJavaThreadIds(threads);
+        stopThreads(threads);
+        recording.stop();
+        List<RecordedEvent> events = Events.fromRecording(recording);
+        verifyThreadExclusion(events, javaThreadIds);
+    }
+
+    private static void verifyThreadExclusion(List<RecordedEvent> events, long[] javaThreadIds) throws Exception {
+        for (RecordedEvent event : events) {
+            System.out.println("Event:" + event);
+            final long eventJavaThreadId = event.getThread().getJavaThreadId();
+            for (int i = 0; i < javaThreadIds.length; ++i) {
+                if (eventJavaThreadId == javaThreadIds[i]) {
+                    throw new Exception("Event " + event.getEventType().getName() + " has a thread id " + eventJavaThreadId + " that should have been excluded");
+                }
+            }
+        }
+    }
+
+    private static LatchedThread[] startThreads() {
+        LatchedThread threads[] = new LatchedThread[10];
+        ThreadGroup threadGroup = new ThreadGroup("TestThreadGroup");
+        jvm = JVM.getJVM();
+        for (int i = 0; i < threads.length; i++) {
+            threads[i] = new LatchedThread(threadGroup, THREAD_NAME_PREFIX + i);
+            jvm.exclude(threads[i]);
+            threads[i].startThread();
+            System.out.println("Started thread id=" + threads[i].getId());
+        }
+        return threads;
+    }
+    
+    private static long[] getJavaThreadIds(LatchedThread[] threads) {
+        long[] javaThreadIds = new long[threads.length];
+        for (int i = 0; i < threads.length; ++i) {
+            javaThreadIds[i] = threads[i].getId();
+        }
+        return javaThreadIds;
+    }
+
+    private static void stopThreads(LatchedThread[] threads) {
+        for (LatchedThread thread : threads) {
+            assertTrue(jvm.isExcluded(thread), "Thread " + thread + "should be excluded");
+            thread.stopThread();
+            while (thread.isAlive()) {
+                try {
+                    Thread.sleep(5);
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    private static class LatchedThread extends Thread {
+        private final CountDownLatch start = new CountDownLatch(1);
+        private final CountDownLatch stop = new CountDownLatch(1);
+
+        public LatchedThread(ThreadGroup threadGroup, String name) {
+            super(threadGroup, name);
+        }
+
+        public void run() {
+            start.countDown();
+            try {
+                stop.await();
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+
+        public void startThread() {
+            this.start();
+            try {
+                start.await();
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+
+        public void stopThread() {
+            stop.countDown();
+        }
+    }
+}