8226511: Implement JFR Event Streaming jdk-14+21
authormgronlun
Wed, 30 Oct 2019 19:43:52 +0100
changeset 58863 c16ac7a2eba4
parent 58861 2c3cc4b01880
child 58864 fba8635290df
8226511: Implement JFR Event Streaming Reviewed-by: egahlin, mseledtsov, mgronlun Contributed-by: erik.gahlin@oracle.com, mikhailo.seledtsov@oracle.com, markus.gronlund@oracle.com
src/hotspot/share/gc/g1/g1Trace.cpp
src/hotspot/share/gc/shenandoah/shenandoahJfrSupport.cpp
src/hotspot/share/gc/z/zTracer.cpp
src/hotspot/share/jfr/dcmd/jfrDcmds.cpp
src/hotspot/share/jfr/dcmd/jfrDcmds.hpp
src/hotspot/share/jfr/jfr.cpp
src/hotspot/share/jfr/jfr.hpp
src/hotspot/share/jfr/jni/jfrJavaSupport.cpp
src/hotspot/share/jfr/jni/jfrJavaSupport.hpp
src/hotspot/share/jfr/jni/jfrJniMethod.cpp
src/hotspot/share/jfr/jni/jfrJniMethod.hpp
src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp
src/hotspot/share/jfr/leakprofiler/chains/edgeStore.cpp
src/hotspot/share/jfr/leakprofiler/checkpoint/eventEmitter.cpp
src/hotspot/share/jfr/leakprofiler/checkpoint/objectSampleCheckpoint.cpp
src/hotspot/share/jfr/leakprofiler/checkpoint/objectSampleWriter.cpp
src/hotspot/share/jfr/leakprofiler/checkpoint/rootResolver.cpp
src/hotspot/share/jfr/leakprofiler/leakProfiler.cpp
src/hotspot/share/jfr/leakprofiler/sampling/objectSampler.cpp
src/hotspot/share/jfr/metadata/jfrSerializer.hpp
src/hotspot/share/jfr/metadata/metadata.xml
src/hotspot/share/jfr/periodic/jfrNetworkUtilization.cpp
src/hotspot/share/jfr/periodic/jfrPeriodic.cpp
src/hotspot/share/jfr/periodic/jfrThreadCPULoadEvent.cpp
src/hotspot/share/jfr/periodic/sampling/jfrThreadSampler.cpp
src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointManager.cpp
src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointManager.hpp
src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointWriter.cpp
src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointWriter.hpp
src/hotspot/share/jfr/recorder/checkpoint/jfrMetadataEvent.cpp
src/hotspot/share/jfr/recorder/checkpoint/jfrMetadataEvent.hpp
src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadGroup.cpp
src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadState.cpp
src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadState.hpp
src/hotspot/share/jfr/recorder/checkpoint/types/jfrType.cpp
src/hotspot/share/jfr/recorder/checkpoint/types/jfrType.hpp
src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeManager.cpp
src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeManager.hpp
src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSet.cpp
src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSet.hpp
src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceId.cpp
src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceId.inline.hpp
src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.cpp
src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.hpp
src/hotspot/share/jfr/recorder/repository/jfrChunk.cpp
src/hotspot/share/jfr/recorder/repository/jfrChunk.hpp
src/hotspot/share/jfr/recorder/repository/jfrChunkState.cpp
src/hotspot/share/jfr/recorder/repository/jfrChunkState.hpp
src/hotspot/share/jfr/recorder/repository/jfrChunkWriter.cpp
src/hotspot/share/jfr/recorder/repository/jfrChunkWriter.hpp
src/hotspot/share/jfr/recorder/repository/jfrEmergencyDump.cpp
src/hotspot/share/jfr/recorder/repository/jfrRepository.cpp
src/hotspot/share/jfr/recorder/repository/jfrRepository.hpp
src/hotspot/share/jfr/recorder/service/jfrPostBox.cpp
src/hotspot/share/jfr/recorder/service/jfrPostBox.hpp
src/hotspot/share/jfr/recorder/service/jfrRecorderService.cpp
src/hotspot/share/jfr/recorder/service/jfrRecorderService.hpp
src/hotspot/share/jfr/recorder/service/jfrRecorderThread.cpp
src/hotspot/share/jfr/recorder/service/jfrRecorderThreadLoop.cpp
src/hotspot/share/jfr/recorder/stacktrace/jfrStackTraceRepository.cpp
src/hotspot/share/jfr/recorder/stacktrace/jfrStackTraceRepository.hpp
src/hotspot/share/jfr/recorder/storage/jfrBuffer.cpp
src/hotspot/share/jfr/recorder/storage/jfrBuffer.hpp
src/hotspot/share/jfr/recorder/storage/jfrMemorySpace.hpp
src/hotspot/share/jfr/recorder/storage/jfrMemorySpace.inline.hpp
src/hotspot/share/jfr/recorder/storage/jfrStorage.cpp
src/hotspot/share/jfr/recorder/storage/jfrStorage.hpp
src/hotspot/share/jfr/recorder/storage/jfrStorageControl.cpp
src/hotspot/share/jfr/recorder/storage/jfrStorageUtils.hpp
src/hotspot/share/jfr/recorder/storage/jfrStorageUtils.inline.hpp
src/hotspot/share/jfr/recorder/storage/jfrVirtualMemory.cpp
src/hotspot/share/jfr/recorder/stringpool/jfrStringPool.cpp
src/hotspot/share/jfr/recorder/stringpool/jfrStringPool.hpp
src/hotspot/share/jfr/support/jfrThreadLocal.cpp
src/hotspot/share/jfr/support/jfrThreadLocal.hpp
src/hotspot/share/jfr/support/jfrTraceIdExtension.hpp
src/hotspot/share/jfr/utilities/jfrAllocation.cpp
src/hotspot/share/jfr/utilities/jfrDoublyLinkedList.hpp
src/hotspot/share/jfr/utilities/jfrLogTagSets.hpp
src/hotspot/share/jfr/utilities/jfrThreadIterator.cpp
src/hotspot/share/jfr/utilities/jfrThreadIterator.hpp
src/hotspot/share/jfr/utilities/jfrTypes.hpp
src/hotspot/share/jfr/writers/jfrJavaEventWriter.hpp
src/hotspot/share/jfr/writers/jfrStorageAdapter.hpp
src/hotspot/share/jfr/writers/jfrWriterHost.inline.hpp
src/hotspot/share/logging/logTag.hpp
src/hotspot/share/runtime/mutexLocker.cpp
src/jdk.jfr/share/classes/jdk/jfr/Recording.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/ChunkParser.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/ConstantMap.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/EventParser.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/EventStream.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/LongMap.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/ObjectFactory.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/Parser.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/ParserFactory.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedClass.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedClassLoader.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedEvent.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedFrame.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedMethod.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedObject.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedStackTrace.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedThread.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedThreadGroup.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordingFile.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordingStream.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/TimeConverter.java
src/jdk.jfr/share/classes/jdk/jfr/events/ActiveRecordingEvent.java
src/jdk.jfr/share/classes/jdk/jfr/events/ActiveSettingEvent.java
src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java
src/jdk.jfr/share/classes/jdk/jfr/internal/EventInstrumentation.java
src/jdk.jfr/share/classes/jdk/jfr/internal/EventWriter.java
src/jdk.jfr/share/classes/jdk/jfr/internal/FilePurger.java
src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java
src/jdk.jfr/share/classes/jdk/jfr/internal/LogTag.java
src/jdk.jfr/share/classes/jdk/jfr/internal/LongMap.java
src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataDescriptor.java
src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataReader.java
src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataRepository.java
src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataWriter.java
src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java
src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecorder.java
src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecording.java
src/jdk.jfr/share/classes/jdk/jfr/internal/Repository.java
src/jdk.jfr/share/classes/jdk/jfr/internal/RepositoryChunk.java
src/jdk.jfr/share/classes/jdk/jfr/internal/RequestEngine.java
src/jdk.jfr/share/classes/jdk/jfr/internal/SecuritySupport.java
src/jdk.jfr/share/classes/jdk/jfr/internal/SettingsManager.java
src/jdk.jfr/share/classes/jdk/jfr/internal/TypeLibrary.java
src/jdk.jfr/share/classes/jdk/jfr/internal/Utils.java
src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/AbstractEventStream.java
src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ChunkHeader.java
src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ChunkParser.java
src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ConstantLookup.java
src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ConstantMap.java
src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/Dispatcher.java
src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventDirectoryStream.java
src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventFileStream.java
src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventParser.java
src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/FileAccess.java
src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/JdkJfrConsumer.java
src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ObjectContext.java
src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ObjectFactory.java
src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/Parser.java
src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ParserFactory.java
src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ParserFilter.java
src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/RecordingInput.java
src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/RecordingInternals.java
src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/RepositoryFiles.java
src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/StreamConfiguration.java
src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/StringParser.java
src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/TimeConverter.java
src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdConfigure.java
src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdStart.java
src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Disassemble.java
src/jdk.jfr/share/classes/jdk/jfr/internal/tool/EventPrintWriter.java
src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Metadata.java
src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Summary.java
src/jdk.jfr/share/conf/jfr/default.jfc
src/jdk.jfr/share/conf/jfr/profile.jfc
test/hotspot/gtest/jfr/test_networkUtilization.cpp
test/hotspot/gtest/jfr/test_threadCpuLoad.cpp
test/jdk/ProblemList.txt
test/jdk/jdk/jfr/api/consumer/TestReadTwice.java
test/jdk/jdk/jfr/api/consumer/TestRecordingFile.java
test/jdk/jdk/jfr/api/consumer/TestRecordingInternals.java
test/jdk/jdk/jfr/api/consumer/filestream/TestMultipleChunk.java
test/jdk/jdk/jfr/api/consumer/filestream/TestOrdered.java
test/jdk/jdk/jfr/api/consumer/filestream/TestReuse.java
test/jdk/jdk/jfr/api/consumer/recordingstream/EventProducer.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/TestOnErrorAsync.java
test/jdk/jdk/jfr/api/consumer/recordingstream/TestOnErrorSync.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/TestRecursive.java
test/jdk/jdk/jfr/api/consumer/recordingstream/TestRemove.java
test/jdk/jdk/jfr/api/consumer/recordingstream/TestSetEndTime.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/TestSetSettings.java
test/jdk/jdk/jfr/api/consumer/recordingstream/TestSetStartTime.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/recordingstream/TestUtils.java
test/jdk/jdk/jfr/api/consumer/security/DriverRecordingDumper.java
test/jdk/jdk/jfr/api/consumer/security/TestMissingPermission.java
test/jdk/jdk/jfr/api/consumer/security/TestRecordingFile.java
test/jdk/jdk/jfr/api/consumer/security/TestRecordingStream.java
test/jdk/jdk/jfr/api/consumer/security/TestStreamingFile.java
test/jdk/jdk/jfr/api/consumer/security/TestStreamingLocal.java
test/jdk/jdk/jfr/api/consumer/security/TestStreamingRemote.java
test/jdk/jdk/jfr/api/consumer/security/local-streaming.policy
test/jdk/jdk/jfr/api/consumer/security/no-permission.policy
test/jdk/jdk/jfr/api/consumer/streaming/TestChunkGap.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/TestLatestEvent.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/TestRepositoryMigration.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/TestUnstarted.java
test/jdk/jdk/jfr/api/event/TestEventDuration.java
test/jdk/jdk/jfr/api/recording/time/TestSetFlushInterval.java
test/jdk/jdk/jfr/event/metadata/TestLookForUntestedEvents.java
test/jdk/jdk/jfr/event/oldobject/TestLargeRootSet.java
test/jdk/jdk/jfr/event/runtime/TestFlush.java
test/jdk/jdk/jfr/jcmd/TestJcmdStartFlushInterval.java
test/jdk/jdk/jfr/jvm/TestThreadExclusion.java
test/jdk/jdk/jfr/jvm/TestUnsupportedVM.java
test/jdk/jdk/jfr/startupargs/TestFlushInterval.java
test/lib/jdk/test/lib/jfr/EventNames.java
--- a/src/hotspot/share/gc/g1/g1Trace.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/gc/g1/g1Trace.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -59,10 +59,10 @@
 };
 
 static void register_jfr_type_constants() {
-  JfrSerializer::register_serializer(TYPE_G1HEAPREGIONTYPE, false, true,
+  JfrSerializer::register_serializer(TYPE_G1HEAPREGIONTYPE, true,
                                      new G1HeapRegionTypeConstant());
 
-  JfrSerializer::register_serializer(TYPE_G1YCTYPE, false, true,
+  JfrSerializer::register_serializer(TYPE_G1YCTYPE, true,
                                      new G1YCTypeConstant());
 }
 
--- a/src/hotspot/share/gc/shenandoah/shenandoahJfrSupport.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/gc/shenandoah/shenandoahJfrSupport.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -47,7 +47,6 @@
 
 void ShenandoahJFRSupport::register_jfr_type_serializers() {
   JfrSerializer::register_serializer(TYPE_SHENANDOAHHEAPREGIONSTATE,
-                                     false,
                                      true,
                                      new ShenandoahHeapRegionStateConstant());
 }
--- a/src/hotspot/share/gc/z/zTracer.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/gc/z/zTracer.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -59,11 +59,9 @@
 
 static void register_jfr_type_serializers() {
   JfrSerializer::register_serializer(TYPE_ZSTATISTICSCOUNTERTYPE,
-                                     false /* require_safepoint */,
                                      true /* permit_cache */,
                                      new ZStatisticsCounterTypeConstant());
   JfrSerializer::register_serializer(TYPE_ZSTATISTICSSAMPLERTYPE,
-                                     false /* require_safepoint */,
                                      true /* permit_cache */,
                                      new ZStatisticsSamplerTypeConstant());
 }
--- a/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -349,6 +349,7 @@
   _filename("filename", "Resulting recording filename, e.g. \\\"" JFR_FILENAME_EXAMPLE "\\\"", "STRING", false),
   _maxage("maxage", "Maximum time to keep recorded data (on disk) in (s)econds, (m)inutes, (h)ours, or (d)ays, e.g. 60m, or 0 for no limit", "NANOTIME", false, "0"),
   _maxsize("maxsize", "Maximum amount of bytes to keep (on disk) in (k)B, (M)B or (G)B, e.g. 500M, or 0 for no limit", "MEMORY SIZE", false, "0"),
+  _flush_interval("flush-interval", "Minimum time before flushing buffers, measured in (s)econds, e.g. 4 s, or 0 for flushing when a recording ends", "NANOTIME", false, "1s"),
   _dump_on_exit("dumponexit", "Dump running recording when JVM shuts down", "BOOLEAN", false),
   _path_to_gc_roots("path-to-gc-roots", "Collect path to GC roots", "BOOLEAN", false, "false") {
   _dcmdparser.add_dcmd_option(&_name);
@@ -359,6 +360,7 @@
   _dcmdparser.add_dcmd_option(&_filename);
   _dcmdparser.add_dcmd_option(&_maxage);
   _dcmdparser.add_dcmd_option(&_maxsize);
+  _dcmdparser.add_dcmd_option(&_flush_interval);
   _dcmdparser.add_dcmd_option(&_dump_on_exit);
   _dcmdparser.add_dcmd_option(&_path_to_gc_roots);
 };
@@ -411,6 +413,10 @@
     maxsize = JfrJavaSupport::new_java_lang_Long(_maxsize.value()._size, CHECK);
   }
 
+  jobject flush_interval = NULL;
+  if (_flush_interval.is_set()) {
+    flush_interval = JfrJavaSupport::new_java_lang_Long(_flush_interval.value()._nanotime, CHECK);
+  }
   jobject duration = NULL;
   if (_duration.is_set()) {
     duration = JfrJavaSupport::new_java_lang_Long(_duration.value()._nanotime, CHECK);
@@ -464,7 +470,7 @@
   static const char method[] = "execute";
   static const char signature[] = "(Ljava/lang/String;[Ljava/lang/String;Ljava/lang/Long;"
     "Ljava/lang/Long;Ljava/lang/Boolean;Ljava/lang/String;"
-    "Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Boolean;Ljava/lang/Boolean;)Ljava/lang/String;";
+    "Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Boolean;Ljava/lang/Boolean;)Ljava/lang/String;";
 
   JfrJavaArguments execute_args(&result, klass, method, signature, CHECK);
   execute_args.set_receiver(h_dcmd_instance);
@@ -478,6 +484,7 @@
   execute_args.push_jobject(filename);
   execute_args.push_jobject(maxage);
   execute_args.push_jobject(maxsize);
+  execute_args.push_jobject(flush_interval);
   execute_args.push_jobject(dump_on_exit);
   execute_args.push_jobject(path_to_gc_roots);
 
--- a/src/hotspot/share/jfr/dcmd/jfrDcmds.hpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/dcmd/jfrDcmds.hpp	Wed Oct 30 19:43:52 2019 +0100
@@ -90,6 +90,7 @@
   DCmdArgument<char*> _filename;
   DCmdArgument<NanoTimeArgument> _maxage;
   DCmdArgument<MemorySizeArgument> _maxsize;
+  DCmdArgument<NanoTimeArgument> _flush_interval;
   DCmdArgument<bool> _dump_on_exit;
   DCmdArgument<bool> _path_to_gc_roots;
 
--- a/src/hotspot/share/jfr/jfr.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/jfr.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -71,6 +71,18 @@
   JfrThreadLocal::on_exit(t);
 }
 
+void Jfr::exclude_thread(Thread* t) {
+  JfrThreadLocal::exclude(t);
+}
+
+void Jfr::include_thread(Thread* t) {
+  JfrThreadLocal::include(t);
+}
+
+bool Jfr::is_excluded(Thread* t) {
+  return t != NULL && t->jfr_thread_local()->is_excluded();
+}
+
 void Jfr::on_java_thread_dismantle(JavaThread* jt) {
   if (JfrRecorder::is_recording()) {
     JfrCheckpointManager::write_thread_checkpoint(jt);
--- a/src/hotspot/share/jfr/jfr.hpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/jfr.hpp	Wed Oct 30 19:43:52 2019 +0100
@@ -53,6 +53,9 @@
   static bool on_flight_recorder_option(const JavaVMOption** option, char* delimiter);
   static bool on_start_flight_recording_option(const JavaVMOption** option, char* delimiter);
   static void weak_oops_do(BoolObjectClosure* is_alive, OopClosure* f);
+  static void exclude_thread(Thread* thread);
+  static bool is_excluded(Thread* thread);
+  static void include_thread(Thread* thread);
 };
 
 #endif // SHARE_JFR_JFR_HPP
--- a/src/hotspot/share/jfr/jni/jfrJavaSupport.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/jni/jfrJavaSupport.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -23,7 +23,6 @@
  */
 
 #include "precompiled.hpp"
-#include "jni.h"
 #include "classfile/javaClasses.inline.hpp"
 #include "classfile/modules.hpp"
 #include "classfile/symbolTable.hpp"
@@ -42,9 +41,11 @@
 #include "runtime/fieldDescriptor.inline.hpp"
 #include "runtime/java.hpp"
 #include "runtime/jniHandles.inline.hpp"
+#include "runtime/semaphore.inline.hpp"
 #include "runtime/synchronizer.hpp"
 #include "runtime/thread.inline.hpp"
 #include "runtime/threadSMR.hpp"
+#include "utilities/growableArray.hpp"
 
 #ifdef ASSERT
 void JfrJavaSupport::check_java_thread_in_vm(Thread* t) {
@@ -58,6 +59,12 @@
   assert(t->is_Java_thread(), "invariant");
   assert(((JavaThread*)t)->thread_state() == _thread_in_native, "invariant");
 }
+
+static void check_new_unstarted_java_thread(Thread* t) {
+  assert(t != NULL, "invariant");
+  assert(t->is_Java_thread(), "invariant");
+  assert(((JavaThread*)t)->thread_state() == _thread_new, "invariant");
+}
 #endif
 
 /*
@@ -93,6 +100,21 @@
   JNIHandles::destroy_global(handle);
 }
 
+jweak JfrJavaSupport::global_weak_jni_handle(const oop obj, Thread* t) {
+  DEBUG_ONLY(check_java_thread_in_vm(t));
+  HandleMark hm(t);
+  return JNIHandles::make_weak_global(Handle(t, obj));
+}
+
+jweak JfrJavaSupport::global_weak_jni_handle(const jobject handle, Thread* t) {
+  const oop obj = JNIHandles::resolve(handle);
+  return obj == NULL ? NULL : global_weak_jni_handle(obj, t);
+}
+
+void JfrJavaSupport::destroy_global_weak_jni_handle(jweak handle) {
+  JNIHandles::destroy_weak_global(handle);
+}
+
 oop JfrJavaSupport::resolve_non_null(jobject obj) {
   return JNIHandles::resolve_non_null(obj);
 }
@@ -603,9 +625,149 @@
   return true;
 }
 
-jlong JfrJavaSupport::jfr_thread_id(jobject target_thread) {
+class ThreadExclusionListAccess : public StackObj {
+ private:
+  static Semaphore _mutex_semaphore;
+ public:
+  ThreadExclusionListAccess() { _mutex_semaphore.wait(); }
+  ~ThreadExclusionListAccess() { _mutex_semaphore.signal(); }
+};
+
+Semaphore ThreadExclusionListAccess::_mutex_semaphore(1);
+static GrowableArray<jweak>* exclusion_list = NULL;
+
+static bool equals(const jweak excluded_thread, Handle target_thread) {
+  return JfrJavaSupport::resolve_non_null(excluded_thread) == target_thread();
+}
+
+static int find_exclusion_thread_idx(Handle thread) {
+  if (exclusion_list != NULL) {
+    for (int i = 0; i < exclusion_list->length(); ++i) {
+      if (equals(exclusion_list->at(i), thread)) {
+        return i;
+      }
+    }
+  }
+  return -1;
+}
+
+static Handle as_handle(jobject thread) {
+  return Handle(Thread::current(), JfrJavaSupport::resolve_non_null(thread));
+}
+
+static bool thread_is_not_excluded(Handle thread) {
+  return -1 == find_exclusion_thread_idx(thread);
+}
+
+static bool thread_is_not_excluded(jobject thread) {
+  return thread_is_not_excluded(as_handle(thread));
+}
+
+static bool is_thread_excluded(jobject thread) {
+  return !thread_is_not_excluded(thread);
+}
+
+#ifdef ASSERT
+static bool is_thread_excluded(Handle thread) {
+  return !thread_is_not_excluded(thread);
+}
+#endif // ASSERT
+
+static int add_thread_to_exclusion_list(jobject thread) {
+  ThreadExclusionListAccess lock;
+  if (exclusion_list == NULL) {
+    exclusion_list = new (ResourceObj::C_HEAP, mtTracing) GrowableArray<jweak>(10, true, mtTracing);
+  }
+  assert(exclusion_list != NULL, "invariant");
+  assert(thread_is_not_excluded(thread), "invariant");
+  jweak ref = JfrJavaSupport::global_weak_jni_handle(thread, Thread::current());
+  const int idx = exclusion_list->append(ref);
+  assert(is_thread_excluded(thread), "invariant");
+  return idx;
+}
+
+static void remove_thread_from_exclusion_list(Handle thread) {
+  assert(exclusion_list != NULL, "invariant");
+  assert(is_thread_excluded(thread), "invariant");
+  assert(exclusion_list != NULL, "invariant");
+  const int idx = find_exclusion_thread_idx(thread);
+  assert(idx >= 0, "invariant");
+  assert(idx < exclusion_list->length(), "invariant");
+  JfrJavaSupport::destroy_global_weak_jni_handle(exclusion_list->at(idx));
+  exclusion_list->delete_at(idx);
+  assert(thread_is_not_excluded(thread), "invariant");
+  if (0 == exclusion_list->length()) {
+    delete exclusion_list;
+    exclusion_list = NULL;
+  }
+}
+
+static void remove_thread_from_exclusion_list(jobject thread) {
+  ThreadExclusionListAccess lock;
+  remove_thread_from_exclusion_list(as_handle(thread));
+}
+
+// includes removal
+static bool check_exclusion_state_on_thread_start(JavaThread* jt) {
+  Handle h_obj(jt, jt->threadObj());
+  ThreadExclusionListAccess lock;
+  if (thread_is_not_excluded(h_obj)) {
+    return false;
+  }
+  remove_thread_from_exclusion_list(h_obj);
+  return true;
+}
+
+jlong JfrJavaSupport::jfr_thread_id(jobject thread) {
   ThreadsListHandle tlh;
   JavaThread* native_thread = NULL;
-  (void)tlh.cv_internal_thread_to_JavaThread(target_thread, &native_thread, NULL);
+  (void)tlh.cv_internal_thread_to_JavaThread(thread, &native_thread, NULL);
   return native_thread != NULL ? JFR_THREAD_ID(native_thread) : 0;
 }
+
+void JfrJavaSupport::exclude(jobject thread) {
+  HandleMark hm;
+  ThreadsListHandle tlh;
+  JavaThread* native_thread = NULL;
+  (void)tlh.cv_internal_thread_to_JavaThread(thread, &native_thread, NULL);
+  if (native_thread != NULL) {
+    JfrThreadLocal::exclude(native_thread);
+  } else {
+    // not started yet, track the thread oop
+    add_thread_to_exclusion_list(thread);
+  }
+}
+
+void JfrJavaSupport::include(jobject thread) {
+  HandleMark hm;
+  ThreadsListHandle tlh;
+  JavaThread* native_thread = NULL;
+  (void)tlh.cv_internal_thread_to_JavaThread(thread, &native_thread, NULL);
+  if (native_thread != NULL) {
+    JfrThreadLocal::include(native_thread);
+  } else {
+    // not started yet, untrack the thread oop
+    remove_thread_from_exclusion_list(thread);
+  }
+}
+
+bool JfrJavaSupport::is_excluded(jobject thread) {
+  HandleMark hm;
+  ThreadsListHandle tlh;
+  JavaThread* native_thread = NULL;
+  (void)tlh.cv_internal_thread_to_JavaThread(thread, &native_thread, NULL);
+  return native_thread != NULL ? native_thread->jfr_thread_local()->is_excluded() : is_thread_excluded(thread);
+}
+
+void JfrJavaSupport::on_thread_start(Thread* t) {
+  assert(t != NULL, "invariant");
+  assert(Thread::current() == t, "invariant");
+  if (!t->is_Java_thread()) {
+    return;
+  }
+  DEBUG_ONLY(check_new_unstarted_java_thread(t);)
+  HandleMark hm;
+  if (check_exclusion_state_on_thread_start((JavaThread*)t)) {
+    JfrThreadLocal::exclude(t);
+  }
+}
--- a/src/hotspot/share/jfr/jni/jfrJavaSupport.hpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/jni/jfrJavaSupport.hpp	Wed Oct 30 19:43:52 2019 +0100
@@ -29,18 +29,21 @@
 #include "utilities/exceptions.hpp"
 
 class Klass;
-class JavaThread;
 class outputStream;
 
 class JfrJavaSupport : public AllStatic {
  public:
   static jobject local_jni_handle(const oop obj, Thread* t);
   static jobject local_jni_handle(const jobject handle, Thread* t);
-  static void destroy_local_jni_handle(const jobject handle);
+  static void destroy_local_jni_handle(jobject handle);
 
   static jobject global_jni_handle(const oop obj, Thread* t);
   static jobject global_jni_handle(const jobject handle, Thread* t);
-  static void destroy_global_jni_handle(const jobject handle);
+  static void destroy_global_jni_handle(jobject handle);
+
+  static jweak global_weak_jni_handle(const oop obj, Thread* t);
+  static jweak global_weak_jni_handle(const jobject handle, Thread* t);
+  static void destroy_global_weak_jni_handle(jweak handle);
 
   static oop resolve_non_null(jobject obj);
   static void notify_all(jobject obj, TRAPS);
@@ -85,7 +88,11 @@
   static bool is_jdk_jfr_module_available();
   static bool is_jdk_jfr_module_available(outputStream* stream, TRAPS);
 
-  static jlong jfr_thread_id(jobject target_thread);
+  static jlong jfr_thread_id(jobject thread);
+  static void exclude(jobject thread);
+  static void include(jobject thread);
+  static bool is_excluded(jobject thread);
+  static void on_thread_start(Thread* t);
 
   // critical
   static void abort(jstring errorMsg, TRAPS);
--- a/src/hotspot/share/jfr/jni/jfrJniMethod.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 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
@@ -284,6 +284,10 @@
   return JfrJavaEventWriter::flush(writer, used_size, requested_size, thread);
 JVM_END
 
+JVM_ENTRY_NO_ENV(void, jfr_flush(JNIEnv* env, jobject jvm))
+  JfrRepository::flush(thread);
+JVM_END
+
 JVM_ENTRY_NO_ENV(void, jfr_set_repository_location(JNIEnv* env, jobject repo, jstring location))
   return JfrRepository::set_path(location, thread);
 JVM_END
@@ -311,3 +315,20 @@
 JVM_ENTRY_NO_ENV(void, jfr_emit_old_object_samples(JNIEnv* env, jobject jvm, jlong cutoff_ticks, jboolean emit_all))
   LeakProfiler::emit_events(cutoff_ticks, emit_all == JNI_TRUE);
 JVM_END
+
+JVM_ENTRY_NO_ENV(void, jfr_exclude_thread(JNIEnv* env, jobject jvm, jobject t))
+  JfrJavaSupport::exclude(t);
+JVM_END
+
+JVM_ENTRY_NO_ENV(void, jfr_include_thread(JNIEnv* env, jobject jvm, jobject t))
+  JfrJavaSupport::include(t);
+JVM_END
+
+JVM_ENTRY_NO_ENV(jboolean, jfr_is_thread_excluded(JNIEnv* env, jobject jvm, jobject t))
+  return JfrJavaSupport::is_excluded(t);
+JVM_END
+
+JVM_ENTRY_NO_ENV(jlong, jfr_chunk_start_nanos(JNIEnv* env, jobject jvm))
+  return JfrRepository::current_chunk_start_nanos();
+JVM_END
+
--- a/src/hotspot/share/jfr/jni/jfrJniMethod.hpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/jni/jfrJniMethod.hpp	Wed Oct 30 19:43:52 2019 +0100
@@ -113,6 +113,7 @@
 
 jboolean JNICALL jfr_event_writer_flush(JNIEnv* env, jclass cls, jobject writer, jint used_size, jint requested_size);
 
+void JNICALL jfr_flush(JNIEnv* env, jobject jvm);
 void JNICALL jfr_abort(JNIEnv* env, jobject jvm, jstring errorMsg);
 
 jlong JNICALL jfr_get_epoch_address(JNIEnv* env, jobject jvm);
@@ -131,6 +132,13 @@
 
 jboolean JNICALL jfr_should_rotate_disk(JNIEnv* env, jobject jvm);
 
+void JNICALL jfr_exclude_thread(JNIEnv* env, jobject jvm, jobject t);
+
+void JNICALL jfr_include_thread(JNIEnv* env, jobject jvm, jobject t);
+
+jboolean JNICALL jfr_is_thread_excluded(JNIEnv* env, jobject jvm, jobject t);
+
+jlong JNICALL jfr_chunk_start_nanos(JNIEnv* env, jobject jvm);
 
 #ifdef __cplusplus
 }
--- a/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -70,6 +70,7 @@
       (char*)"getEventWriter", (char*)"()Ljava/lang/Object;", (void*)jfr_get_event_writer,
       (char*)"newEventWriter", (char*)"()Ljdk/jfr/internal/EventWriter;", (void*)jfr_new_event_writer,
       (char*)"flush", (char*)"(Ljdk/jfr/internal/EventWriter;II)Z", (void*)jfr_event_writer_flush,
+      (char*)"flush", (char*)"()V", (void*)jfr_flush,
       (char*)"setRepositoryLocation", (char*)"(Ljava/lang/String;)V", (void*)jfr_set_repository_location,
       (char*)"abort", (char*)"(Ljava/lang/String;)V", (void*)jfr_abort,
       (char*)"getEpochAddress", (char*)"()J",(void*)jfr_get_epoch_address,
@@ -79,7 +80,11 @@
       (char*)"getUnloadedEventClassCount", (char*)"()J", (void*)jfr_get_unloaded_event_classes_count,
       (char*)"setCutoff", (char*)"(JJ)Z", (void*)jfr_set_cutoff,
       (char*)"emitOldObjectSamples", (char*)"(JZ)V", (void*)jfr_emit_old_object_samples,
-      (char*)"shouldRotateDisk", (char*)"()Z", (void*)jfr_should_rotate_disk
+      (char*)"shouldRotateDisk", (char*)"()Z", (void*)jfr_should_rotate_disk,
+      (char*)"exclude", (char*)"(Ljava/lang/Thread;)V", (void*)jfr_exclude_thread,
+      (char*)"include", (char*)"(Ljava/lang/Thread;)V", (void*)jfr_include_thread,
+      (char*)"isExcluded", (char*)"(Ljava/lang/Thread;)Z", (void*)jfr_is_thread_excluded,
+      (char*)"getChunkStartNanos", (char*)"()J", (void*)jfr_chunk_start_nanos
     };
 
     const size_t method_array_length = sizeof(method) / sizeof(JNINativeMethod);
--- a/src/hotspot/share/jfr/leakprofiler/chains/edgeStore.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/leakprofiler/chains/edgeStore.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -259,6 +259,7 @@
   assert(leak_context_edge->parent() == NULL, "invariant");
 
   if (1 == length) {
+    store_gc_root_id_in_leak_context_edge(leak_context_edge, leak_context_edge);
     return;
   }
 
--- a/src/hotspot/share/jfr/leakprofiler/checkpoint/eventEmitter.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/leakprofiler/checkpoint/eventEmitter.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -34,6 +34,7 @@
 #include "memory/resourceArea.hpp"
 #include "oops/markWord.hpp"
 #include "oops/oop.inline.hpp"
+#include "runtime/mutexLocker.hpp"
 #include "runtime/thread.inline.hpp"
 #include "runtime/vmThread.hpp"
 
@@ -51,8 +52,8 @@
 }
 
 void EventEmitter::emit(ObjectSampler* sampler, int64_t cutoff_ticks, bool emit_all) {
+  assert(JfrStream_lock->owned_by_self(), "invariant");
   assert(sampler != NULL, "invariant");
-
   ResourceMark rm;
   EdgeStore edge_store;
   if (cutoff_ticks <= 0) {
@@ -68,6 +69,7 @@
 }
 
 size_t EventEmitter::write_events(ObjectSampler* object_sampler, EdgeStore* edge_store, bool emit_all) {
+  assert_locked_or_safepoint(JfrStream_lock);
   assert(_thread == Thread::current(), "invariant");
   assert(_thread->jfr_thread_local() == _jfr_thread_local, "invariant");
   assert(object_sampler != NULL, "invariant");
--- a/src/hotspot/share/jfr/leakprofiler/checkpoint/objectSampleCheckpoint.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/leakprofiler/checkpoint/objectSampleCheckpoint.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -37,6 +37,7 @@
 #include "jfr/recorder/stacktrace/jfrStackTraceRepository.hpp"
 #include "jfr/utilities/jfrHashtable.hpp"
 #include "jfr/utilities/jfrTypes.hpp"
+#include "runtime/mutexLocker.hpp"
 #include "runtime/safepoint.hpp"
 #include "runtime/thread.hpp"
 #include "utilities/growableArray.hpp"
@@ -271,7 +272,7 @@
   }
   const JfrStackTrace* const stack_trace = resolve(sample);
   DEBUG_ONLY(validate_stack_trace(sample, stack_trace));
-  JfrCheckpointWriter writer(false, true, Thread::current());
+  JfrCheckpointWriter writer;
   writer.write_type(TYPE_STACKTRACE);
   writer.write_count(1);
   ObjectSampleCheckpoint::write_stacktrace(stack_trace, writer);
@@ -291,6 +292,7 @@
 
 // caller needs ResourceMark
 void ObjectSampleCheckpoint::on_rotation(const ObjectSampler* sampler, JfrStackTraceRepository& stack_trace_repo) {
+  assert(JfrStream_lock->owned_by_self(), "invariant");
   assert(sampler != NULL, "invariant");
   assert(LeakProfiler::is_running(), "invariant");
   install_stack_traces(sampler, stack_trace_repo);
@@ -388,7 +390,7 @@
 static void write_sample_blobs(const ObjectSampler* sampler, bool emit_all, Thread* thread) {
   // sample set is predicated on time of last sweep
   const jlong last_sweep = emit_all ? max_jlong : sampler->last_sweep().value();
-  JfrCheckpointWriter writer(false, false, thread);
+  JfrCheckpointWriter writer(thread, false);
   BlobWriter cbw(sampler, writer, last_sweep);
   iterate_samples(cbw, true);
   // reset blob write states
@@ -397,13 +399,14 @@
 }
 
 void ObjectSampleCheckpoint::write(const ObjectSampler* sampler, EdgeStore* edge_store, bool emit_all, Thread* thread) {
+  assert_locked_or_safepoint(JfrStream_lock);
   assert(sampler != NULL, "invariant");
   assert(edge_store != NULL, "invariant");
   assert(thread != NULL, "invariant");
   write_sample_blobs(sampler, emit_all, thread);
   // write reference chains
   if (!edge_store->is_empty()) {
-    JfrCheckpointWriter writer(false, true, thread);
+    JfrCheckpointWriter writer(thread);
     ObjectSampleWriter osw(writer, edge_store);
     edge_store->iterate(osw);
   }
--- a/src/hotspot/share/jfr/leakprofiler/checkpoint/objectSampleWriter.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/leakprofiler/checkpoint/objectSampleWriter.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -355,10 +355,6 @@
 
 static traceid get_gc_root_description_info_id(const Edge& edge, traceid id) {
   assert(edge.is_root(), "invariant");
-  if (EdgeUtils::is_leak_edge(edge)) {
-    return 0;
-  }
-
   if (root_infos == NULL) {
     root_infos = new RootDescriptionInfo();
   }
@@ -606,8 +602,8 @@
 static void register_serializers() {
   static bool is_registered = false;
   if (!is_registered) {
-    JfrSerializer::register_serializer(TYPE_OLDOBJECTROOTSYSTEM, false, true, new RootSystemType());
-    JfrSerializer::register_serializer(TYPE_OLDOBJECTROOTTYPE, false, true, new RootType());
+    JfrSerializer::register_serializer(TYPE_OLDOBJECTROOTSYSTEM, true, new RootSystemType());
+    JfrSerializer::register_serializer(TYPE_OLDOBJECTROOTTYPE, true, new RootType());
     is_registered = true;
   }
 }
--- a/src/hotspot/share/jfr/leakprofiler/checkpoint/rootResolver.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/leakprofiler/checkpoint/rootResolver.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -29,6 +29,7 @@
 #include "gc/shared/strongRootsScope.hpp"
 #include "jfr/leakprofiler/utilities/unifiedOop.hpp"
 #include "jfr/leakprofiler/checkpoint/rootResolver.hpp"
+#include "jfr/utilities/jfrThreadIterator.hpp"
 #include "memory/iterator.hpp"
 #include "memory/universe.hpp"
 #include "oops/klass.hpp"
@@ -36,7 +37,6 @@
 #include "prims/jvmtiThreadState.hpp"
 #include "runtime/frame.inline.hpp"
 #include "runtime/mutexLocker.hpp"
-#include "runtime/threadSMR.inline.hpp"
 #include "runtime/vframe_hp.hpp"
 #include "services/management.hpp"
 #include "utilities/growableArray.hpp"
@@ -256,8 +256,9 @@
  public:
   ReferenceToThreadRootClosure(RootCallback& callback) :_callback(callback), _complete(false) {
     assert_locked_or_safepoint(Threads_lock);
-    for (JavaThreadIteratorWithHandle jtiwh; JavaThread *jt = jtiwh.next(); ) {
-      if (do_thread_roots(jt)) {
+    JfrJavaThreadIterator iter;
+    while (iter.has_next()) {
+      if (do_thread_roots(iter.next())) {
         return;
       }
     }
--- a/src/hotspot/share/jfr/leakprofiler/leakProfiler.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/leakprofiler/leakProfiler.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -31,6 +31,7 @@
 #include "jfr/recorder/service/jfrOptionSet.hpp"
 #include "logging/log.hpp"
 #include "memory/iterator.hpp"
+#include "runtime/mutexLocker.hpp"
 #include "runtime/thread.inline.hpp"
 #include "runtime/vmThread.hpp"
 
@@ -92,6 +93,7 @@
   if (!is_running()) {
     return;
   }
+  MutexLocker lock(JfrStream_lock);
   // exclusive access to object sampler instance
   ObjectSampler* const sampler = ObjectSampler::acquire();
   assert(sampler != NULL, "invariant");
--- a/src/hotspot/share/jfr/leakprofiler/sampling/objectSampler.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/leakprofiler/sampling/objectSampler.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -110,6 +110,9 @@
   }
   const JfrThreadLocal* const tl = thread->jfr_thread_local();
   assert(tl != NULL, "invariant");
+  if (tl->is_excluded()) {
+    return 0;
+  }
   if (!tl->has_thread_blob()) {
     JfrCheckpointManager::create_thread_blob(thread);
   }
--- a/src/hotspot/share/jfr/metadata/jfrSerializer.hpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/metadata/jfrSerializer.hpp	Wed Oct 30 19:43:52 2019 +0100
@@ -70,7 +70,8 @@
 class JfrSerializer : public CHeapObj<mtTracing> {
  public:
   virtual ~JfrSerializer() {}
-  static bool register_serializer(JfrTypeId id, bool require_safepoint, bool permit_cache, JfrSerializer* serializer);
+  virtual void on_rotation() {}
+  static bool register_serializer(JfrTypeId id, bool permit_cache, JfrSerializer* serializer);
   virtual void serialize(JfrCheckpointWriter& writer) = 0;
 };
 
--- a/src/hotspot/share/jfr/metadata/metadata.xml	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/metadata/metadata.xml	Wed Oct 30 19:43:52 2019 +0100
@@ -154,7 +154,7 @@
     <Field type="string" name="newValue" label="New Value" />
     <Field type="FlagValueOrigin" name="origin" label="Origin" />
   </Event>
- 
+
   <Type name="VirtualSpace">
     <Field type="ulong" contentType="address" name="start" label="Start Address" description="Start address of the virtual space" />
     <Field type="ulong" contentType="address" name="committedEnd" label="Committed End Address" description="End address of the committed memory for the virtual space" />
@@ -162,27 +162,27 @@
     <Field type="ulong" contentType="address" name="reservedEnd" label="Reserved End Address" description="End address of the reserved memory for the virtual space" />
     <Field type="ulong" contentType="bytes" name="reservedSize" label="Reserved Size" description="Size of the reserved memory for the virtual space" />
   </Type>
-  
+
   <Type name="ObjectSpace">
     <Field type="ulong" contentType="address" name="start" label="Start Address" description="Start address of the space" />
     <Field type="ulong" contentType="address" name="end" label="End Address" description="End address of the space" />
     <Field type="ulong" contentType="bytes" name="used" label="Used" description="Bytes allocated by objects in the space" />
     <Field type="ulong" contentType="bytes" name="size" label="Size" description="Size of the space" />
   </Type>
-  
+
   <Event name="GCHeapSummary" category="Java Virtual Machine, GC, Heap" label="Heap Summary" startTime="false">
     <Field type="uint" name="gcId" label="GC Identifier" relation="GcId" />
     <Field type="GCWhen" name="when" label="When" />
     <Field type="VirtualSpace" struct="true" name="heapSpace" label="Heap Space" />
     <Field type="ulong" contentType="bytes" name="heapUsed" label="Heap Used" description="Bytes allocated by objects in the heap" />
   </Event>
- 
+
   <Type name="MetaspaceSizes">
     <Field type="ulong" contentType="bytes" name="committed" label="Committed" description="Committed memory for this space" />
     <Field type="ulong" contentType="bytes" name="used" label="Used" description="Bytes allocated by objects in the space" />
     <Field type="ulong" contentType="bytes" name="reserved" label="Reserved" description="Reserved memory for this space" />
   </Type>
- 
+
   <Event name="MetaspaceSummary" category="Java Virtual Machine, GC, Heap" label="Metaspace Summary" startTime="false">
     <Field type="uint" name="gcId" label="GC Identifier" relation="GcId" />
     <Field type="GCWhen" name="when" label="When" />
@@ -442,7 +442,7 @@
     <Field type="uint" name="gcWorkerId" label="GC Worker Identifier" />
     <Field type="string" name="name" label="Name" />
   </Event>
-  
+
   <Event name="AllocationRequiringGC" category="Java Virtual Machine, GC, Detailed" label="Allocation Requiring GC" thread="true" stackTrace="true"
     startTime="false">
     <Field type="uint" name="gcId" label="Pending GC Identifier" relation="GcId" />
@@ -484,7 +484,7 @@
     <Field type="string" name="failureMessage" label="Failure Message" />
     <Field type="uint" name="compileId" label="Compilation Identifier" relation="CompileId" />
   </Event>
-  
+
   <Type name="CalleeMethod">
     <Field type="string" name="type" label="Class" />
     <Field type="string" name="name" label="Method Name" />
@@ -585,21 +585,21 @@
     <Field type="OldObjectGcRoot" name="root" label="GC Root" />
   </Event>
 
-  <Event name="DumpReason" category="Flight Recorder" label="Recording Reason" 
-         description="Who requested the recording and why" 
+  <Event name="DumpReason" category="Flight Recorder" label="Recording Reason"
+         description="Who requested the recording and why"
          startTime="false">
     <Field type="string" name="reason" label="Reason" description="Reason for writing recording data to disk" />
     <Field type="int" name="recordingId" label="Recording Id" description="Id of the recording that triggered the dump, or -1 if it was not related to a recording" />
   </Event>
 
-  <Event name="DataLoss" category="Flight Recorder" label="Data Loss" 
+  <Event name="DataLoss" category="Flight Recorder" label="Data Loss"
          description="Data could not be copied out from a buffer, typically because of contention"
          startTime="false">
     <Field type="ulong" contentType="bytes" name="amount" label="Amount" description="Amount lost data" />
     <Field type="ulong" contentType="bytes" name="total" label="Total" description="Total lost amount for thread" />
   </Event>
 
-  <Event name="JVMInformation" category="Java Virtual Machine" label="JVM Information" 
+  <Event name="JVMInformation" category="Java Virtual Machine" label="JVM Information"
          description="Description of JVM and the Java application"
          period="endChunk">
     <Field type="string" name="jvmName" label="JVM Name" />
@@ -1004,6 +1004,42 @@
     <Field type="string" name="state" label="State" />
   </Type>
 
+  <Event name="Flush" category="Flight Recorder" label="Flush" thread="false" experimental="true">
+    <Field type="ulong" name="flushId" label="Flush Identifier" relation="FlushId" />
+    <Field type="ulong" name="elements" label="Elements Written" />
+    <Field type="ulong" contentType="bytes" name="size" label="Size Written" />
+  </Event>
+
+  <Event name="FlushStorage" category="Flight Recorder" label="Flush Storage" thread="false" experimental="true">
+    <Field type="ulong" name="flushId" label="Flush Identifier" relation="FlushId" />
+    <Field type="ulong" name="elements" label="Elements Written" />
+    <Field type="ulong" contentType="bytes" name="size" label="Size Written" />
+  </Event>
+
+  <Event name="FlushStacktrace" category="Flight Recorder" label="Flush Stacktrace" thread="false" experimental="true">
+    <Field type="ulong" name="flushId" label="Flush Identifier" relation="FlushId" />
+    <Field type="ulong" name="elements" label="Elements Written" />
+    <Field type="ulong" contentType="bytes" name="size" label="Size Written" />
+  </Event>
+
+  <Event name="FlushStringPool" category="Flight Recorder" label="Flush String Pool" thread="false" experimental="true">
+    <Field type="ulong" name="flushId" label="Flush Identifier" relation="FlushId" />
+    <Field type="ulong" name="elements" label="Elements Written" />
+    <Field type="ulong" contentType="bytes" name="size" label="Size Written" />
+  </Event>
+
+  <Event name="FlushMetadata" category="Flight Recorder" label="Flush Metadata" thread="false" experimental="true">
+    <Field type="ulong" name="flushId" label="Flush Identifier" relation="FlushId" />
+    <Field type="ulong" name="elements" label="Elements Written" />
+    <Field type="ulong" contentType="bytes" name="size" label="Size Written" />
+  </Event>
+
+  <Event name="FlushTypeSet" category="Flight Recorder" label="Flush Type Set" thread="false" experimental="true">
+    <Field type="ulong" name="flushId" label="Flush Identifier" relation="FlushId" />
+    <Field type="ulong" name="elements" label="Elements Written" />
+    <Field type="ulong" contentType="bytes" name="size" label="Size Written" />
+  </Event>
+
   <Type name="ZStatisticsCounterType" label="Z Statistics Counter">
     <Field type="string" name="counter" label="Counter" />
   </Type>
@@ -1183,35 +1219,40 @@
     <Field type="int" name="bytecodeIndex" label="Bytecode Index" />
     <Field type="FrameType" name="type" label="Frame Type" />
   </Type>
- 
+
+  <Type name="ChunkHeader" label="Chunk Header">
+    <Field type="byte" array="true" name="payload" label="Payload" />
+  </Type>
+
   <Relation name="JavaMonitorAddress"/>
   <Relation name="SafepointId"/>
   <Relation name="GcId"/>
   <Relation name="CompileId" />
   <Relation name="SweepId"/>
- 
-  <XmlType name="Package" parameterType="const PackageEntry*" fieldType="const PackageEntry*"/> 
-  <XmlType name="Class" javaType="java.lang.Class" parameterType="const Klass*" fieldType="const Klass*"/> 
-  <XmlType name="Module"  parameterType="const ModuleEntry*" fieldType="const ModuleEntry*"/> 
-  <XmlType name="ClassLoader" parameterType="const ClassLoaderData*" fieldType="const ClassLoaderData*"/> 
-  <XmlType name="Method" parameterType="const Method*" fieldType="const Method*"/> 
-  <XmlType name="Thread" javaType="java.lang.Thread" parameterType="u8" fieldType="u8"/> 
-  <XmlType name="Tickspan" contentType="tickspan" javaType="long" parameterType="const Tickspan&amp;" fieldType="Tickspan"/> 
-  <XmlType name="Ticks" contentType="tickstamp" javaType="long" parameterType="const Ticks&amp;" fieldType="Ticks"/> 
-  <XmlType name="ulong" javaType="long" unsigned="true" parameterType="u8" fieldType="u8"/> 
-  <XmlType name="uint" javaType="int" unsigned="true" parameterType="unsigned" fieldType="unsigned"/> 
-  <XmlType name="ushort" javaType="short" unsigned="true" parameterType="u2" fieldType="u2"/> 
-  <XmlType name="ubyte" javaType="byte" unsigned="true" parameterType="u1" fieldType="u1"/> 
-  <XmlType name="long" javaType="long" parameterType="s8" fieldType="s8"/> 
-  <XmlType name="int" javaType="int" parameterType="s4" fieldType="s4"/> 
-  <XmlType name="short" javaType="short" parameterType="s2" fieldType="s2"/> 
-  <XmlType name="byte" javaType="byte"  parameterType="s1" fieldType="s1"/> 
-  <XmlType name="double" javaType="double" parameterType="double" fieldType="double"/> 
-  <XmlType name="float" javaType="float"  parameterType="float" fieldType="float"/> 
-  <XmlType name="boolean" javaType="boolean" parameterType="bool" fieldType="bool"/> 
-  <XmlType name="char" javaType="char" parameterType="char" fieldType="char"/> 
-  <XmlType name="string" javaType="java.lang.String" parameterType="const char*" fieldType="const char*"/> 
- 
+  <Relation name="FlushId"/>
+
+  <XmlType name="Package" parameterType="const PackageEntry*" fieldType="const PackageEntry*"/>
+  <XmlType name="Class" javaType="java.lang.Class" parameterType="const Klass*" fieldType="const Klass*"/>
+  <XmlType name="Module"  parameterType="const ModuleEntry*" fieldType="const ModuleEntry*"/>
+  <XmlType name="ClassLoader" parameterType="const ClassLoaderData*" fieldType="const ClassLoaderData*"/>
+  <XmlType name="Method" parameterType="const Method*" fieldType="const Method*"/>
+  <XmlType name="Thread" javaType="java.lang.Thread" parameterType="u8" fieldType="u8"/>
+  <XmlType name="Tickspan" contentType="tickspan" javaType="long" parameterType="const Tickspan&amp;" fieldType="Tickspan"/>
+  <XmlType name="Ticks" contentType="tickstamp" javaType="long" parameterType="const Ticks&amp;" fieldType="Ticks"/>
+  <XmlType name="ulong" javaType="long" unsigned="true" parameterType="u8" fieldType="u8"/>
+  <XmlType name="uint" javaType="int" unsigned="true" parameterType="unsigned" fieldType="unsigned"/>
+  <XmlType name="ushort" javaType="short" unsigned="true" parameterType="u2" fieldType="u2"/>
+  <XmlType name="ubyte" javaType="byte" unsigned="true" parameterType="u1" fieldType="u1"/>
+  <XmlType name="long" javaType="long" parameterType="s8" fieldType="s8"/>
+  <XmlType name="int" javaType="int" parameterType="s4" fieldType="s4"/>
+  <XmlType name="short" javaType="short" parameterType="s2" fieldType="s2"/>
+  <XmlType name="byte" javaType="byte"  parameterType="s1" fieldType="s1"/>
+  <XmlType name="double" javaType="double" parameterType="double" fieldType="double"/>
+  <XmlType name="float" javaType="float"  parameterType="float" fieldType="float"/>
+  <XmlType name="boolean" javaType="boolean" parameterType="bool" fieldType="bool"/>
+  <XmlType name="char" javaType="char" parameterType="char" fieldType="char"/>
+  <XmlType name="string" javaType="java.lang.String" parameterType="const char*" fieldType="const char*"/>
+
   <XmlContentType name="bytes" annotation="jdk.jfr.DataAmount(BYTES)" />
   <XmlContentType name="tickstamp" annotation="jdk.jfr.Timestamp(TICKS)" />
   <XmlContentType name="epochmillis" annotation="jdk.jfr.Timestamp(MILLISECONDS_SINCE_EPOCH)" />
@@ -1223,5 +1264,5 @@
   <XmlContentType name="hertz" annotation="jdk.jfr.Frequency" />
   <XmlContentType name="bytes-per-second" annotation="jdk.jfr.DataAmount(BYTES), jdk.jfr.Frequency" />
   <XmlContentType name="bits-per-second" annotation="jdk.jfr.DataAmount(BITS), jdk.jfr.Frequency" />
- 
+
 </Metadata>
--- a/src/hotspot/share/jfr/periodic/jfrNetworkUtilization.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/periodic/jfrNetworkUtilization.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * 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
@@ -39,7 +39,7 @@
   traceid id;
   uint64_t bytes_in;
   uint64_t bytes_out;
-  bool in_use;
+  mutable bool written;
 };
 
 static GrowableArray<InterfaceEntry>* _interfaces = NULL;
@@ -71,7 +71,7 @@
   entry.id = ++interface_id;
   entry.bytes_in = iface->get_bytes_in();
   entry.bytes_out = iface->get_bytes_out();
-  entry.in_use = false;
+  entry.written = false;
   return _interfaces->at(_interfaces->append(entry));
 }
 
@@ -108,6 +108,39 @@
   return ((current - old) * NANOSECS_PER_SEC) / interval.nanoseconds();
 }
 
+class JfrNetworkInterfaceName : public JfrSerializer {
+ public:
+   void serialize(JfrCheckpointWriter& writer) {} // we write each constant lazily
+
+   void on_rotation() {
+     for (int i = 0; i < _interfaces->length(); ++i) {
+       const InterfaceEntry& entry = _interfaces->at(i);
+       if (entry.written) {
+         entry.written = false;
+       }
+     }
+   }
+};
+
+static bool register_network_interface_name_serializer() {
+  assert(_interfaces != NULL, "invariant");
+  return JfrSerializer::register_serializer(TYPE_NETWORKINTERFACENAME,
+    false, // disallow caching; we want a callback every rotation
+    new JfrNetworkInterfaceName());
+}
+
+static void write_interface_constant(const InterfaceEntry& entry) {
+  if (entry.written) {
+    return;
+  }
+  JfrCheckpointWriter writer;
+  writer.write_type(TYPE_NETWORKINTERFACENAME);
+  writer.write_count(1);
+  writer.write_key(entry.id);
+  writer.write(entry.name);
+  entry.written = true;
+}
+
 static bool get_interfaces(NetworkInterface** network_interfaces) {
   const int ret_val = JfrOSInterface::network_utilization(network_interfaces);
   if (ret_val == OS_ERR) {
@@ -117,39 +150,6 @@
   return ret_val != FUNCTIONALITY_NOT_IMPLEMENTED;
 }
 
-class JfrNetworkInterfaceName : public JfrSerializer {
- public:
-  void serialize(JfrCheckpointWriter& writer) {
-    assert(_interfaces != NULL, "invariant");
-    const JfrCheckpointContext ctx = writer.context();
-    const intptr_t count_offset = writer.reserve(sizeof(u4)); // Don't know how many yet
-    int active_interfaces = 0;
-    for (int i = 0; i < _interfaces->length(); ++i) {
-      InterfaceEntry& entry = _interfaces->at(i);
-      if (entry.in_use) {
-        entry.in_use = false;
-        writer.write_key(entry.id);
-        writer.write(entry.name);
-        ++active_interfaces;
-      }
-    }
-    if (active_interfaces == 0) {
-      // nothing to write, restore context
-      writer.set_context(ctx);
-      return;
-    }
-    writer.write_count(active_interfaces, count_offset);
-  }
-};
-
-static bool register_network_interface_name_serializer() {
-  assert(_interfaces != NULL, "invariant");
-  return JfrSerializer::register_serializer(TYPE_NETWORKINTERFACENAME,
-                                            false, // require safepoint
-                                            false, // disallow caching; we want a callback every rotation
-                                            new JfrNetworkInterfaceName());
-}
-
 void JfrNetworkUtilization::send_events() {
   ResourceMark rm;
   NetworkInterface* network_interfaces;
@@ -169,7 +169,7 @@
       const uint64_t read_rate = rate_per_second(current_bytes_in, entry.bytes_in, interval);
       const uint64_t write_rate = rate_per_second(current_bytes_out, entry.bytes_out, interval);
       if (read_rate > 0 || write_rate > 0) {
-        entry.in_use = true;
+        write_interface_constant(entry);
         EventNetworkUtilization event(UNTIMED);
         event.set_starttime(cur_time);
         event.set_endtime(cur_time);
--- a/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -44,6 +44,7 @@
 #include "jfr/periodic/jfrNetworkUtilization.hpp"
 #include "jfr/recorder/jfrRecorder.hpp"
 #include "jfr/support/jfrThreadId.hpp"
+#include "jfr/utilities/jfrThreadIterator.hpp"
 #include "jfr/utilities/jfrTime.hpp"
 #include "jfrfiles/jfrPeriodic.hpp"
 #include "logging/log.hpp"
@@ -56,7 +57,6 @@
 #include "runtime/os.hpp"
 #include "runtime/os_perf.hpp"
 #include "runtime/thread.inline.hpp"
-#include "runtime/threadSMR.hpp"
 #include "runtime/sweeper.hpp"
 #include "runtime/vmThread.hpp"
 #include "services/classLoadingService.hpp"
@@ -410,13 +410,12 @@
   GrowableArray<jlong> allocated(initial_size);
   GrowableArray<traceid> thread_ids(initial_size);
   JfrTicks time_stamp = JfrTicks::now();
-  {
-    // Collect allocation statistics while holding threads lock
-    MutexLocker ml(Threads_lock);
-    for (JavaThreadIteratorWithHandle jtiwh; JavaThread *jt = jtiwh.next(); ) {
-      allocated.append(jt->cooked_allocated_bytes());
-      thread_ids.append(JFR_THREAD_ID(jt));
-    }
+  JfrJavaThreadIterator iter;
+  while (iter.has_next()) {
+    JavaThread* const jt = iter.next();
+    assert(jt != NULL, "invariant");
+    allocated.append(jt->cooked_allocated_bytes());
+    thread_ids.append(JFR_THREAD_ID(jt));
   }
 
   // Write allocation statistics to buffer.
--- a/src/hotspot/share/jfr/periodic/jfrThreadCPULoadEvent.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/periodic/jfrThreadCPULoadEvent.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 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
@@ -28,11 +28,10 @@
 #include "jfr/periodic/jfrThreadCPULoadEvent.hpp"
 #include "jfr/support/jfrThreadId.hpp"
 #include "jfr/support/jfrThreadLocal.hpp"
+#include "jfr/utilities/jfrThreadIterator.hpp"
 #include "jfr/utilities/jfrTime.hpp"
 #include "utilities/globalDefinitions.hpp"
 #include "runtime/os.hpp"
-#include "runtime/thread.inline.hpp"
-#include "runtime/threadSMR.inline.hpp"
 
 jlong JfrThreadCPULoadEvent::get_wallclock_time() {
   return os::javaTimeNanos();
@@ -115,8 +114,12 @@
   JfrTicks event_time = JfrTicks::now();
   jlong cur_wallclock_time = JfrThreadCPULoadEvent::get_wallclock_time();
 
-  JavaThreadIteratorWithHandle jtiwh;
-  while (JavaThread* jt = jtiwh.next()) {
+  JfrJavaThreadIterator iter;
+  int number_of_threads = 0;
+  while (iter.has_next()) {
+    JavaThread* const jt = iter.next();
+    assert(jt != NULL, "invariant");
+    ++number_of_threads;
     EventThreadCPULoad event(UNTIMED);
     if (JfrThreadCPULoadEvent::update_event(event, jt, cur_wallclock_time, processor_count)) {
       event.set_starttime(event_time);
@@ -129,7 +132,7 @@
       event.commit();
     }
   }
-  log_trace(jfr)("Measured CPU usage for %d threads in %.3f milliseconds", jtiwh.length(),
+  log_trace(jfr)("Measured CPU usage for %d threads in %.3f milliseconds", number_of_threads,
     (double)(JfrTicks::now() - event_time).milliseconds());
   // Restore this thread's thread id
   periodic_thread_tl->set_thread_id(periodic_thread_id);
--- a/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampler.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampler.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -30,6 +30,7 @@
 #include "jfr/recorder/service/jfrOptionSet.hpp"
 #include "jfr/recorder/stacktrace/jfrStackTraceRepository.hpp"
 #include "jfr/support/jfrThreadId.hpp"
+#include "jfr/support/jfrThreadLocal.hpp"
 #include "jfr/utilities/jfrTime.hpp"
 #include "logging/log.hpp"
 #include "runtime/frame.inline.hpp"
@@ -352,9 +353,14 @@
   }
 }
 
+static bool is_excluded(JavaThread* thread) {
+  assert(thread != NULL, "invariant");
+  return thread->is_hidden_from_external_view() || thread->in_deopt_handler() || thread->jfr_thread_local()->is_excluded();
+}
+
 bool JfrThreadSampleClosure::do_sample_thread(JavaThread* thread, JfrStackFrame* frames, u4 max_frames, JfrSampleType type) {
   assert(Threads_lock->owned_by_self(), "Holding the thread table lock.");
-  if (thread->is_hidden_from_external_view() || thread->in_deopt_handler()) {
+  if (is_excluded(thread)) {
     return false;
   }
 
--- a/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointManager.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointManager.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -37,11 +37,14 @@
 #include "jfr/recorder/storage/jfrMemorySpace.inline.hpp"
 #include "jfr/recorder/storage/jfrStorageUtils.inline.hpp"
 #include "jfr/utilities/jfrBigEndian.hpp"
+#include "jfr/utilities/jfrIterator.hpp"
+#include "jfr/utilities/jfrThreadIterator.hpp"
 #include "jfr/utilities/jfrTypes.hpp"
+#include "jfr/writers/jfrJavaEventWriter.hpp"
 #include "logging/log.hpp"
 #include "memory/resourceArea.hpp"
 #include "runtime/handles.inline.hpp"
-#include "runtime/mutexLocker.hpp"
+#include "runtime/mutex.hpp"
 #include "runtime/orderAccess.hpp"
 #include "runtime/os.inline.hpp"
 #include "runtime/safepoint.hpp"
@@ -168,7 +171,7 @@
 }
 
 bool JfrCheckpointManager::use_epoch_transition_mspace(const Thread* thread) const {
-  return _service_thread != thread && OrderAccess::load_acquire(&_checkpoint_epoch_state) != JfrTraceIdEpoch::epoch();
+  return _service_thread != thread && _checkpoint_epoch_state != JfrTraceIdEpoch::epoch();
 }
 
 static const size_t lease_retry = 10;
@@ -181,12 +184,24 @@
   return lease_free(size, manager._free_list_mspace, lease_retry, thread);
 }
 
+JfrCheckpointMspace* JfrCheckpointManager::lookup(BufferPtr old) const {
+  assert(old != NULL, "invariant");
+  return _free_list_mspace->in_free_list(old) ? _free_list_mspace : _epoch_transition_mspace;
+}
+
+BufferPtr JfrCheckpointManager::lease_buffer(BufferPtr old, Thread* thread, size_t size /* 0 */) {
+  assert(old != NULL, "invariant");
+  JfrCheckpointMspace* mspace = instance().lookup(old);
+  assert(mspace != NULL, "invariant");
+  return lease_free(size, mspace, lease_retry, thread);
+}
+
 /*
-* If the buffer was a "lease" from the free list, release back.
-*
-* The buffer is effectively invalidated for the thread post-return,
-* and the caller should take means to ensure that it is not referenced.
-*/
+ * If the buffer was a lease, release back.
+ *
+ * The buffer is effectively invalidated for the thread post-return,
+ * and the caller should take means to ensure that it is not referenced.
+ */
 static void release(BufferPtr const buffer, Thread* thread) {
   DEBUG_ONLY(assert_release(buffer);)
   buffer->clear_lease();
@@ -202,7 +217,7 @@
     return NULL;
   }
   // migration of in-flight information
-  BufferPtr const new_buffer = lease_buffer(thread, used + requested);
+  BufferPtr const new_buffer = lease_buffer(old, thread, used + requested);
   if (new_buffer != NULL) {
     migrate_outstanding_writes(old, new_buffer, used, requested);
   }
@@ -213,8 +228,8 @@
 // offsets into the JfrCheckpointEntry
 static const juint starttime_offset = sizeof(jlong);
 static const juint duration_offset = starttime_offset + sizeof(jlong);
-static const juint flushpoint_offset = duration_offset + sizeof(jlong);
-static const juint types_offset = flushpoint_offset + sizeof(juint);
+static const juint checkpoint_type_offset = duration_offset + sizeof(jlong);
+static const juint types_offset = checkpoint_type_offset + sizeof(juint);
 static const juint payload_offset = types_offset + sizeof(juint);
 
 template <typename Return>
@@ -234,21 +249,21 @@
   return read_data<jlong>(data + duration_offset);
 }
 
-static bool is_flushpoint(const u1* data) {
-  return read_data<juint>(data + flushpoint_offset) == (juint)1;
+static u1 checkpoint_type(const u1* data) {
+  return read_data<u1>(data + checkpoint_type_offset);
 }
 
 static juint number_of_types(const u1* data) {
   return read_data<juint>(data + types_offset);
 }
 
-static void write_checkpoint_header(JfrChunkWriter& cw, int64_t offset_prev_cp_event, const u1* data) {
+static void write_checkpoint_header(JfrChunkWriter& cw, int64_t delta_to_last_checkpoint, const u1* data) {
   cw.reserve(sizeof(u4));
   cw.write<u8>(EVENT_CHECKPOINT);
   cw.write(starttime(data));
   cw.write(duration(data));
-  cw.write(offset_prev_cp_event);
-  cw.write(is_flushpoint(data));
+  cw.write(delta_to_last_checkpoint);
+  cw.write(checkpoint_type(data));
   cw.write(number_of_types(data));
 }
 
@@ -261,9 +276,9 @@
   assert(data != NULL, "invariant");
   const int64_t event_begin = cw.current_offset();
   const int64_t last_checkpoint_event = cw.last_checkpoint_offset();
-  const int64_t delta = last_checkpoint_event == 0 ? 0 : last_checkpoint_event - event_begin;
+  const int64_t delta_to_last_checkpoint = last_checkpoint_event == 0 ? 0 : last_checkpoint_event - event_begin;
   const int64_t checkpoint_size = total_size(data);
-  write_checkpoint_header(cw, delta, data);
+  write_checkpoint_header(cw, delta_to_last_checkpoint, data);
   write_checkpoint_content(cw, data, checkpoint_size);
   const int64_t event_size = cw.current_offset() - event_begin;
   cw.write_padded_at_offset<u4>(event_size, event_begin);
@@ -305,13 +320,13 @@
 typedef CheckpointWriteOp<JfrCheckpointMspace::Type> WriteOperation;
 typedef ReleaseOp<JfrCheckpointMspace> CheckpointReleaseOperation;
 
-template <template <typename> class WriterHost, template <typename, typename> class CompositeOperation>
+template <template <typename> class WriterHost, template <typename, typename, typename> class CompositeOperation>
 static size_t write_mspace(JfrCheckpointMspace* mspace, JfrChunkWriter& chunkwriter) {
   assert(mspace != NULL, "invariant");
   WriteOperation wo(chunkwriter);
   WriterHost<WriteOperation> wh(wo);
   CheckpointReleaseOperation cro(mspace, Thread::current(), false);
-  CompositeOperation<WriterHost<WriteOperation>, CheckpointReleaseOperation> co(&wh, &cro);
+  CompositeOperation<WriterHost<WriteOperation>, CheckpointReleaseOperation, CompositeOperationAnd> co(&wh, &cro);
   assert(mspace->is_full_empty(), "invariant");
   process_free_list(co, mspace);
   return wo.processed();
@@ -333,6 +348,16 @@
   return write_mspace<ExclusiveOp, CompositeOperation>(_epoch_transition_mspace, _chunkwriter);
 }
 
+typedef MutexedWriteOp<WriteOperation> FlushOperation;
+
+size_t JfrCheckpointManager::flush() {
+  WriteOperation wo(_chunkwriter);
+  FlushOperation fo(wo);
+  assert(_free_list_mspace->is_full_empty(), "invariant");
+  process_free_list(fo, _free_list_mspace);
+  return wo.processed();
+}
+
 typedef DiscardOp<DefaultDiscarder<JfrBuffer> > DiscardOperation;
 size_t JfrCheckpointManager::clear() {
   JfrTypeSet::clear();
@@ -340,44 +365,88 @@
   process_free_list(discarder, _free_list_mspace);
   process_free_list(discarder, _epoch_transition_mspace);
   synchronize_epoch();
-  return discarder.processed();
+  return discarder.elements();
 }
 
-size_t JfrCheckpointManager::write_types() {
-  JfrCheckpointWriter writer(false, true, Thread::current());
-  JfrTypeManager::write_types(writer);
+// Optimization for write_static_type_set() and write_threads() is to write
+// directly into the epoch transition mspace because we will immediately
+// serialize and reset this mspace post-write.
+static JfrBuffer* get_epoch_transition_buffer(JfrCheckpointMspace* mspace, Thread* t) {
+  assert(mspace != NULL, "invariant");
+  JfrBuffer* const buffer = mspace->free_head();
+  assert(buffer != NULL, "invariant");
+  buffer->acquire(t);
+  buffer->set_lease();
+  DEBUG_ONLY(assert_free_lease(buffer);)
+  return buffer;
+}
+
+bool JfrCheckpointManager::is_static_type_set_required() {
+  return JfrTypeManager::has_new_static_type();
+}
+
+size_t JfrCheckpointManager::write_static_type_set() {
+  Thread* const t = Thread::current();
+  ResourceMark rm(t);
+  HandleMark hm(t);
+  JfrCheckpointWriter writer(t, get_epoch_transition_buffer(_epoch_transition_mspace, t), STATICS);
+  JfrTypeManager::write_static_types(writer);
   return writer.used_size();
 }
 
-size_t JfrCheckpointManager::write_safepoint_types() {
-  // this is also a "flushpoint"
-  JfrCheckpointWriter writer(true, true, Thread::current());
-  JfrTypeManager::write_safepoint_types(writer);
+size_t JfrCheckpointManager::write_threads() {
+  Thread* const t = Thread::current();
+  ResourceMark rm(t);
+  HandleMark hm(t);
+  JfrCheckpointWriter writer(t, get_epoch_transition_buffer(_epoch_transition_mspace, t), THREADS);
+  JfrTypeManager::write_threads(writer);
   return writer.used_size();
 }
 
+size_t JfrCheckpointManager::write_static_type_set_and_threads() {
+  write_static_type_set();
+  write_threads();
+  return write_epoch_transition_mspace();
+}
+
+void JfrCheckpointManager::shift_epoch() {
+  debug_only(const u1 current_epoch = JfrTraceIdEpoch::current();)
+  JfrTraceIdEpoch::shift_epoch();
+  assert(current_epoch != JfrTraceIdEpoch::current(), "invariant");
+}
+
+void JfrCheckpointManager::on_rotation() {
+  assert(SafepointSynchronize::is_at_safepoint(), "invariant");
+  JfrTypeManager::on_rotation();
+  notify_threads();
+}
+
 void JfrCheckpointManager::write_type_set() {
   assert(!SafepointSynchronize::is_at_safepoint(), "invariant");
-  // can safepoint here
-  MutexLocker cld_lock(ClassLoaderDataGraph_lock);
-  MutexLocker module_lock(Module_lock);
-  if (!LeakProfiler::is_running()) {
-    JfrCheckpointWriter writer(true, true, Thread::current());
-    JfrTypeSet::serialize(&writer, NULL, false);
+  if (LeakProfiler::is_running()) {
+    Thread* const t = Thread::current();
+    // can safepoint here
+    MutexLocker cld_lock(ClassLoaderDataGraph_lock);
+    MutexLocker module_lock(Module_lock);
+    JfrCheckpointWriter leakp_writer(t);
+    JfrCheckpointWriter writer(t);
+    JfrTypeSet::serialize(&writer, &leakp_writer, false, false);
+    ObjectSampleCheckpoint::on_type_set(leakp_writer);
   } else {
-    Thread* const t = Thread::current();
-    JfrCheckpointWriter leakp_writer(false, true, t);
-    JfrCheckpointWriter writer(false, true, t);
-    JfrTypeSet::serialize(&writer, &leakp_writer, false);
-    ObjectSampleCheckpoint::on_type_set(leakp_writer);
+    // can safepoint here
+    MutexLocker cld_lock(ClassLoaderDataGraph_lock);
+    MutexLocker module_lock(Module_lock);
+    JfrCheckpointWriter writer(Thread::current());
+    JfrTypeSet::serialize(&writer, NULL, false, false);
   }
+  write();
 }
 
 void JfrCheckpointManager::write_type_set_for_unloaded_classes() {
   assert_locked_or_safepoint(ClassLoaderDataGraph_lock);
-  JfrCheckpointWriter writer(false, true, Thread::current());
+  JfrCheckpointWriter writer(Thread::current());
   const JfrCheckpointContext ctx = writer.context();
-  JfrTypeSet::serialize(&writer, NULL, true);
+  JfrTypeSet::serialize(&writer, NULL, true, false);
   if (LeakProfiler::is_running()) {
     ObjectSampleCheckpoint::on_type_set_unload(writer);
   }
@@ -387,16 +456,51 @@
   }
 }
 
-void JfrCheckpointManager::create_thread_blob(JavaThread* jt) {
-  JfrTypeManager::create_thread_blob(jt);
+bool JfrCheckpointManager::is_type_set_required() {
+  return JfrTraceIdEpoch::has_changed_tag_state();
+}
+
+size_t JfrCheckpointManager::flush_type_set() {
+  assert(!SafepointSynchronize::is_at_safepoint(), "invariant");
+  size_t elements = 0;
+  {
+    JfrCheckpointWriter writer(Thread::current());
+    // can safepoint here
+    MutexLocker cld_lock(ClassLoaderDataGraph_lock);
+    MutexLocker module_lock(Module_lock);
+    elements = JfrTypeSet::serialize(&writer, NULL, false, true);
+  }
+  flush();
+  return elements;
+}
+
+void JfrCheckpointManager::flush_static_type_set() {
+  flush();
 }
 
-void JfrCheckpointManager::write_thread_checkpoint(JavaThread* jt) {
-  JfrTypeManager::write_thread_checkpoint(jt);
+void JfrCheckpointManager::create_thread_blob(Thread* t) {
+  JfrTypeManager::create_thread_blob(t);
+}
+
+void JfrCheckpointManager::write_thread_checkpoint(Thread* t) {
+  JfrTypeManager::write_thread_checkpoint(t);
 }
 
-void JfrCheckpointManager::shift_epoch() {
-  debug_only(const u1 current_epoch = JfrTraceIdEpoch::current();)
-  JfrTraceIdEpoch::shift_epoch();
-  assert(current_epoch != JfrTraceIdEpoch::current(), "invariant");
+class JfrNotifyClosure : public ThreadClosure {
+ public:
+  void do_thread(Thread* t) {
+    assert(t != NULL, "invariant");
+    assert(t->is_Java_thread(), "invariant");
+    assert_locked_or_safepoint(Threads_lock);
+    JfrJavaEventWriter::notify((JavaThread*)t);
+  }
+};
+
+void JfrCheckpointManager::notify_threads() {
+  assert(SafepointSynchronize::is_at_safepoint(), "invariant");
+  JfrNotifyClosure tc;
+  JfrJavaThreadIterator iter;
+  while (iter.has_next()) {
+    tc.do_thread(iter.next());
+  }
 }
--- a/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointManager.hpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointManager.hpp	Wed Oct 30 19:43:52 2019 +0100
@@ -68,18 +68,29 @@
   void unlock();
   DEBUG_ONLY(bool is_locked() const;)
 
+  JfrCheckpointMspace* lookup(Buffer* old) const;
+  bool use_epoch_transition_mspace(const Thread* t) const;
+  size_t write_epoch_transition_mspace();
+
   static Buffer* lease_buffer(Thread* t, size_t size = 0);
+  static Buffer* lease_buffer(Buffer* old, Thread* t, size_t size = 0);
   static Buffer* flush(Buffer* old, size_t used, size_t requested, Thread* t);
 
   size_t clear();
   size_t write();
-  size_t write_epoch_transition_mspace();
-  size_t write_types();
-  size_t write_safepoint_types();
+  size_t flush();
+
+  bool is_static_type_set_required();
+  size_t write_static_type_set();
+  size_t write_threads();
+  size_t write_static_type_set_and_threads();
+  bool is_type_set_required();
   void write_type_set();
+  static void write_type_set_for_unloaded_classes();
+
   void shift_epoch();
   void synchronize_epoch();
-  bool use_epoch_transition_mspace(const Thread* t) const;
+  void notify_threads();
 
   JfrCheckpointManager(JfrChunkWriter& cw);
   ~JfrCheckpointManager();
@@ -87,14 +98,17 @@
   static JfrCheckpointManager& instance();
   static JfrCheckpointManager* create(JfrChunkWriter& cw);
   bool initialize();
+  void on_rotation();
   static void destroy();
 
  public:
+  size_t flush_type_set();
+  void flush_static_type_set();
+  static void create_thread_blob(Thread* t);
+  static void write_thread_checkpoint(Thread* t);
   void register_service_thread(const Thread* t);
-  static void write_type_set_for_unloaded_classes();
-  static void create_thread_blob(JavaThread* jt);
-  static void write_thread_checkpoint(JavaThread* jt);
 
+  friend class Jfr;
   friend class JfrRecorder;
   friend class JfrRecorderService;
   friend class JfrCheckpointFlush;
--- a/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointWriter.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointWriter.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -31,12 +31,26 @@
 JfrCheckpointFlush::JfrCheckpointFlush(Type* old, size_t used, size_t requested, Thread* t) :
   _result(JfrCheckpointManager::flush(old, used, requested, t)) {}
 
-JfrCheckpointWriter::JfrCheckpointWriter(bool flushpoint, bool header, Thread* thread) :
-  JfrCheckpointWriterBase(JfrCheckpointManager::lease_buffer(thread), thread),
+JfrCheckpointWriter::JfrCheckpointWriter(JfrCheckpointType type /* GENERIC */) :
+  JfrCheckpointWriterBase(JfrCheckpointManager::lease_buffer(Thread::current()), Thread::current()),
   _time(JfrTicks::now()),
   _offset(0),
   _count(0),
-  _flushpoint(flushpoint),
+  _type(type),
+  _header(true) {
+  assert(this->is_acquired(), "invariant");
+  assert(0 == this->current_offset(), "invariant");
+  if (_header) {
+    reserve(sizeof(JfrCheckpointEntry));
+  }
+}
+
+JfrCheckpointWriter::JfrCheckpointWriter(Thread* t, bool header /* true */, JfrCheckpointType type /* GENERIC */) :
+  JfrCheckpointWriterBase(JfrCheckpointManager::lease_buffer(t), t),
+  _time(JfrTicks::now()),
+  _offset(0),
+  _count(0),
+  _type(type),
   _header(header) {
   assert(this->is_acquired(), "invariant");
   assert(0 == this->current_offset(), "invariant");
@@ -45,13 +59,27 @@
   }
 }
 
-static void write_checkpoint_header(u1* pos, int64_t size, jlong time, bool flushpoint, u4 type_count) {
+JfrCheckpointWriter::JfrCheckpointWriter(Thread* t, JfrBuffer* buffer, JfrCheckpointType type /* GENERIC */) :
+  JfrCheckpointWriterBase(buffer, t),
+  _time(JfrTicks::now()),
+  _offset(0),
+  _count(0),
+  _type(type),
+  _header(true) {
+  assert(this->is_acquired(), "invariant");
+  assert(0 == this->current_offset(), "invariant");
+  if (_header) {
+    reserve(sizeof(JfrCheckpointEntry));
+  }
+}
+
+static void write_checkpoint_header(u1* pos, int64_t size, jlong time, u4 checkpoint_type, u4 type_count) {
   assert(pos != NULL, "invariant");
   JfrBigEndianWriter be_writer(pos, sizeof(JfrCheckpointEntry));
   be_writer.write(size);
   be_writer.write(time);
   be_writer.write(JfrTicks::now().value() - time);
-  be_writer.write(flushpoint ? (u4)1 : (u4)0);
+  be_writer.write(checkpoint_type);
   be_writer.write(type_count);
   assert(be_writer.is_valid(), "invariant");
 }
@@ -74,18 +102,10 @@
   assert(this->used_size() > sizeof(JfrCheckpointEntry), "invariant");
   const int64_t size = this->current_offset();
   assert(size + this->start_pos() == this->current_pos(), "invariant");
-  write_checkpoint_header(const_cast<u1*>(this->start_pos()), size, _time, is_flushpoint(), count());
+  write_checkpoint_header(const_cast<u1*>(this->start_pos()), size, _time, (u4)_type, count());
   release();
 }
 
-void JfrCheckpointWriter::set_flushpoint(bool flushpoint) {
-  _flushpoint = flushpoint;
-}
-
-bool JfrCheckpointWriter::is_flushpoint() const {
-  return _flushpoint;
-}
-
 u4 JfrCheckpointWriter::count() const {
   return _count;
 }
@@ -140,7 +160,7 @@
   }
   *size = this->used_size();
   assert(this->start_pos() + *size == this->current_pos(), "invariant");
-  write_checkpoint_header(const_cast<u1*>(this->start_pos()), this->used_offset(), _time, is_flushpoint(), count());
+  write_checkpoint_header(const_cast<u1*>(this->start_pos()), this->used_offset(), _time, (u4)_type, count());
   _header = false; // the header was just written
   if (move) {
     this->seek(_offset);
--- a/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointWriter.hpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/checkpoint/jfrCheckpointWriter.hpp	Wed Oct 30 19:43:52 2019 +0100
@@ -54,23 +54,24 @@
 };
 
 class JfrCheckpointWriter : public JfrCheckpointWriterBase {
+  friend class JfrCheckpointManager;
   friend class JfrSerializerRegistration;
  private:
   JfrTicks _time;
   int64_t _offset;
   u4 _count;
-  bool _flushpoint;
+  JfrCheckpointType _type;
   bool _header;
 
   u4 count() const;
   void set_count(u4 count);
   void increment();
-  void set_flushpoint(bool flushpoint);
-  bool is_flushpoint() const;
   const u1* session_data(size_t* size, bool move = false, const JfrCheckpointContext* ctx = NULL);
   void release();
+  JfrCheckpointWriter(Thread* t, JfrBuffer* buffer, JfrCheckpointType type = GENERIC);
  public:
-  JfrCheckpointWriter(bool flushpoint, bool header, Thread* thread);
+  JfrCheckpointWriter(JfrCheckpointType type = GENERIC);
+  JfrCheckpointWriter(Thread* t, bool header = true, JfrCheckpointType mode = GENERIC);
   ~JfrCheckpointWriter();
   void write_type(JfrTypeId type_id);
   void write_count(u4 nof_entries);
--- a/src/hotspot/share/jfr/recorder/checkpoint/jfrMetadataEvent.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/checkpoint/jfrMetadataEvent.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -29,61 +29,53 @@
 #include "oops/klass.inline.hpp"
 #include "oops/oop.inline.hpp"
 #include "oops/typeArrayOop.inline.hpp"
-#include "runtime/semaphore.hpp"
 #include "runtime/thread.inline.hpp"
 
-static jbyteArray _metadata_blob = NULL;
-static Semaphore metadata_mutex_semaphore(1);
+static jbyteArray metadata_blob = NULL;
+static u8 metadata_id = 0;
+static u8 last_metadata_id = 0;
 
-void JfrMetadataEvent::lock() {
-  metadata_mutex_semaphore.wait();
-}
-
-void JfrMetadataEvent::unlock() {
-  metadata_mutex_semaphore.signal();
+static void write_metadata_blob(JfrChunkWriter& chunkwriter) {
+  assert(metadata_blob != NULL, "invariant");
+  const typeArrayOop arr = (typeArrayOop)JfrJavaSupport::resolve_non_null(metadata_blob);
+  assert(arr != NULL, "invariant");
+  const int length = arr->length();
+  const Klass* const k = arr->klass();
+  assert(k != NULL && k->is_array_klass(), "invariant");
+  const TypeArrayKlass* const byte_arr_klass = TypeArrayKlass::cast(k);
+  const jbyte* const data_address = arr->byte_at_addr(0);
+  chunkwriter.write_unbuffered(data_address, length);
 }
 
-static void write_metadata_blob(JfrChunkWriter& chunkwriter, jbyteArray metadata_blob) {
-  if (metadata_blob != NULL) {
-    const typeArrayOop arr = (typeArrayOop)JfrJavaSupport::resolve_non_null(metadata_blob);
-    assert(arr != NULL, "invariant");
-    const int length = arr->length();
-    const Klass* const k = arr->klass();
-    assert(k != NULL && k->is_array_klass(), "invariant");
-    const TypeArrayKlass* const byte_arr_klass = TypeArrayKlass::cast(k);
-    const jbyte* const data_address = arr->byte_at_addr(0);
-    chunkwriter.write_unbuffered(data_address, length);
+void JfrMetadataEvent::write(JfrChunkWriter& chunkwriter) {
+  assert(chunkwriter.is_valid(), "invariant");
+  if (last_metadata_id == metadata_id && chunkwriter.has_metadata()) {
+    return;
   }
-}
-
-// the semaphore is assumed to be locked  (was locked previous safepoint)
-size_t JfrMetadataEvent::write(JfrChunkWriter& chunkwriter, jlong metadata_offset) {
-  assert(chunkwriter.is_valid(), "invariant");
-  assert(chunkwriter.current_offset() == metadata_offset, "invariant");
   // header
-  chunkwriter.reserve(sizeof(u4));
+  const int64_t metadata_offset = chunkwriter.reserve(sizeof(u4));
   chunkwriter.write<u8>(EVENT_METADATA); // ID 0
   // time data
   chunkwriter.write(JfrTicks::now());
   chunkwriter.write((u8)0); // duration
-  chunkwriter.write((u8)0); // metadata id
-  write_metadata_blob(chunkwriter, _metadata_blob); // payload
-  unlock(); // open up for java to provide updated metadata
+  chunkwriter.write(metadata_id); // metadata id
+  write_metadata_blob(chunkwriter); // payload
   // fill in size of metadata descriptor event
-  const jlong size_written = chunkwriter.current_offset() - metadata_offset;
+  const int64_t size_written = chunkwriter.current_offset() - metadata_offset;
   chunkwriter.write_padded_at_offset((u4)size_written, metadata_offset);
-  return size_written;
+  chunkwriter.set_last_metadata_offset(metadata_offset);
+  last_metadata_id = metadata_id;
 }
 
 void JfrMetadataEvent::update(jbyteArray metadata) {
   JavaThread* thread = (JavaThread*)Thread::current();
   assert(thread->is_Java_thread(), "invariant");
   DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(thread));
-  lock();
-  if (_metadata_blob != NULL) {
-    JfrJavaSupport::destroy_global_jni_handle(_metadata_blob);
+  if (metadata_blob != NULL) {
+    JfrJavaSupport::destroy_global_jni_handle(metadata_blob);
   }
   const oop new_desc_oop = JfrJavaSupport::resolve_non_null(metadata);
-  _metadata_blob = new_desc_oop != NULL ? (jbyteArray)JfrJavaSupport::global_jni_handle(new_desc_oop, thread) : NULL;
-  unlock();
+  assert(new_desc_oop != NULL, "invariant");
+  metadata_blob = (jbyteArray)JfrJavaSupport::global_jni_handle(new_desc_oop, thread);
+  ++metadata_id;
 }
--- a/src/hotspot/share/jfr/recorder/checkpoint/jfrMetadataEvent.hpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/checkpoint/jfrMetadataEvent.hpp	Wed Oct 30 19:43:52 2019 +0100
@@ -33,13 +33,10 @@
 //
 // Metadata is continuously updated in Java as event classes are loaded / unloaded.
 // Using update(), Java stores a binary representation back to native.
-// This is for easy access on chunk finalization as well as having it readily available in the case of fatal error.
 //
 class JfrMetadataEvent : AllStatic {
  public:
-  static void lock();
-  static void unlock();
-  static size_t write(JfrChunkWriter& writer, jlong metadata_offset);
+  static void write(JfrChunkWriter& writer);
   static void update(jbyteArray metadata);
 };
 
--- a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadGroup.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadGroup.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -25,7 +25,6 @@
 #include "precompiled.hpp"
 #include "jfr/recorder/checkpoint/jfrCheckpointWriter.hpp"
 #include "jfr/recorder/checkpoint/types/jfrThreadGroup.hpp"
-#include "jfr/utilities/jfrResourceManager.hpp"
 #include "jfr/utilities/jfrTypes.hpp"
 #include "runtime/handles.inline.hpp"
 #include "runtime/jniHandles.inline.hpp"
@@ -33,6 +32,8 @@
 #include "runtime/semaphore.hpp"
 #include "utilities/growableArray.hpp"
 
+static const int initial_array_size = 30;
+
 class ThreadGroupExclusiveAccess : public StackObj {
  private:
   static Semaphore _mutex_semaphore;
@@ -257,12 +258,10 @@
   }
 }
 
-JfrThreadGroup::JfrThreadGroup() : _list(NULL) {
-  _list = new (ResourceObj::C_HEAP, mtTracing) GrowableArray<JfrThreadGroupEntry*>(30, true);
-}
+JfrThreadGroup::JfrThreadGroup() :
+  _list(new (ResourceObj::C_HEAP, mtTracing) GrowableArray<JfrThreadGroupEntry*>(initial_array_size, true, mtTracing)) {}
 
 JfrThreadGroup::~JfrThreadGroup() {
-  assert(SafepointSynchronize::is_at_safepoint(), "invariant");
   if (_list != NULL) {
     for (int i = 0; i < _list->length(); i++) {
       JfrThreadGroupEntry* e = _list->at(i);
@@ -281,14 +280,11 @@
 }
 
 traceid JfrThreadGroup::thread_group_id(const JavaThread* jt, Thread* current) {
-  ResourceMark rm(current);
-  HandleMark hm(current);
   JfrThreadGroupsHelper helper(jt, current);
   return helper.is_valid() ? thread_group_id_internal(helper) : 0;
 }
 
 traceid JfrThreadGroup::thread_group_id(JavaThread* const jt) {
-  assert(!JfrStream_lock->owned_by_self(), "holding stream lock but should not hold it here");
   return thread_group_id(jt, jt);
 }
 
@@ -396,9 +392,7 @@
   ThreadGroupExclusiveAccess lock;
   JfrThreadGroup* tg_instance = instance();
   assert(tg_instance != NULL, "invariant");
-  ResourceManager<JfrThreadGroup> tg_handle(tg_instance);
-  set_instance(NULL);
-  tg_handle->write_thread_group_entries(writer);
+  tg_instance->write_thread_group_entries(writer);
 }
 
 // for writing a particular thread group
--- a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadState.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadState.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
-* Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+* Copyright (c) 2016, 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
@@ -23,9 +23,13 @@
 */
 
 #include "precompiled.hpp"
+#include "classfile/javaClasses.inline.hpp"
 #include "jfr/recorder/checkpoint/types/jfrThreadState.hpp"
 #include "jfr/recorder/checkpoint/jfrCheckpointWriter.hpp"
+#include "jfr/support/jfrThreadLocal.hpp"
 #include "jvmtifiles/jvmti.h"
+#include "runtime/osThread.hpp"
+#include "runtime/thread.hpp"
 
 struct jvmti_thread_state {
   u8 id;
@@ -80,3 +84,47 @@
   }
 }
 
+traceid JfrThreadId::id(const Thread* t) {
+  assert(t != NULL, "invariant");
+  if (!t->is_Java_thread()) {
+    return os_id(t);
+  }
+  const JavaThread* const jt = (JavaThread*)t;
+  const oop thread_obj = jt->threadObj();
+  return thread_obj != NULL ? java_lang_Thread::thread_id(thread_obj) : 0;
+}
+
+traceid JfrThreadId::os_id(const Thread* t) {
+  assert(t != NULL, "invariant");
+  const OSThread* const os_thread = t->osthread();
+  return os_thread != NULL ? os_thread->thread_id() : 0;
+}
+
+traceid JfrThreadId::jfr_id(const Thread* t) {
+  assert(t != NULL, "invariant");
+  return t->jfr_thread_local()->thread_id();
+}
+
+// caller needs ResourceMark
+const char* get_java_thread_name(const Thread* t) {
+  assert(t != NULL, "invariant");
+  assert(t->is_Java_thread(), "invariant");
+  const JavaThread* const jt = ((JavaThread*)t);
+  const char* name_str = "<no-name - thread name unresolved>";
+  const oop thread_obj = jt->threadObj();
+  if (thread_obj != NULL) {
+    const oop name = java_lang_Thread::name(thread_obj);
+    if (name != NULL) {
+      name_str = java_lang_String::as_utf8_string(name);
+    }
+  } else if (jt->is_attaching_via_jni()) {
+    name_str = "<no-name - thread is attaching>";
+  }
+  assert(name_str != NULL, "unexpected NULL thread name");
+  return name_str;
+}
+
+const char* JfrThreadName::name(const Thread* t) {
+  assert(t != NULL, "invariant");
+  return t->is_Java_thread() ? get_java_thread_name(t) : t->name();
+}
--- a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadState.hpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrThreadState.hpp	Wed Oct 30 19:43:52 2019 +0100
@@ -28,10 +28,24 @@
 #include "memory/allocation.hpp"
 
 class JfrCheckpointWriter;
+class Thread;
 
 class JfrThreadState : public AllStatic {
  public:
   static void serialize(JfrCheckpointWriter& writer);
 };
 
+class JfrThreadId : public AllStatic {
+public:
+  static traceid id(const Thread* t);
+  static traceid os_id(const Thread* t);
+  static traceid jfr_id(const Thread* t);
+};
+
+class JfrThreadName : public AllStatic {
+ public:
+  // Requires a ResourceMark for get_thread_name/as_utf8
+  static const char* name(const Thread* t);
+};
+
 #endif // SHARE_JFR_RECORDER_CHECKPOINT_TYPES_JFRTHREADSTATE_HPP
--- a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrType.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrType.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -31,13 +31,14 @@
 #include "gc/shared/gcTrace.hpp"
 #include "gc/shared/gcWhen.hpp"
 #include "jfr/leakprofiler/leakProfiler.hpp"
-#include "jfr/recorder/checkpoint/jfrCheckpointManager.hpp"
+#include "jfr/recorder/checkpoint/jfrCheckpointWriter.hpp"
 #include "jfr/recorder/checkpoint/types/jfrType.hpp"
 #include "jfr/recorder/jfrRecorder.hpp"
 #include "jfr/recorder/checkpoint/types/jfrThreadGroup.hpp"
 #include "jfr/recorder/checkpoint/types/jfrThreadState.hpp"
 #include "jfr/support/jfrThreadLocal.hpp"
 #include "jfr/writers/jfrJavaEventWriter.hpp"
+#include "jfr/utilities/jfrThreadIterator.hpp"
 #include "memory/metaspaceGCThresholdUpdater.hpp"
 #include "memory/referenceType.hpp"
 #include "memory/universe.hpp"
@@ -84,27 +85,18 @@
   void do_thread(Thread* t);
 };
 
-// Requires a ResourceMark for get_thread_name/as_utf8
 void JfrCheckpointThreadClosure::do_thread(Thread* t) {
   assert(t != NULL, "invariant");
-  assert_locked_or_safepoint(Threads_lock);
-  const JfrThreadLocal* const tl = t->jfr_thread_local();
-  assert(tl != NULL, "invariant");
-  if (tl->is_dead()) {
-    return;
-  }
   ++_count;
-  _writer.write_key(tl->thread_id());
-  _writer.write(t->name());
-  const OSThread* const os_thread = t->osthread();
-  _writer.write<traceid>(os_thread != NULL ? os_thread->thread_id() : 0);
+  _writer.write_key(JfrThreadId::jfr_id(t));
+  const char* const name = JfrThreadName::name(t);
+  assert(name != NULL, "invariant");
+  _writer.write(name);
+  _writer.write<traceid>(JfrThreadId::os_id(t));
   if (t->is_Java_thread()) {
-    JavaThread* const jt = (JavaThread*)t;
-    _writer.write(jt->name());
-    _writer.write(java_lang_Thread::thread_id(jt->threadObj()));
-    _writer.write(JfrThreadGroup::thread_group_id(jt, _curthread));
-    // since we are iterating threads during a safepoint, also issue notification
-    JfrJavaEventWriter::notify(jt);
+    _writer.write(name);
+    _writer.write(JfrThreadId::id(t));
+    _writer.write(JfrThreadGroup::thread_group_id((JavaThread*)t, _curthread));
     return;
   }
   _writer.write((const char*)NULL); // java name
@@ -113,13 +105,18 @@
 }
 
 void JfrThreadConstantSet::serialize(JfrCheckpointWriter& writer) {
-  assert(SafepointSynchronize::is_at_safepoint(), "invariant");
   JfrCheckpointThreadClosure tc(writer);
-  Threads::threads_do(&tc);
+  JfrJavaThreadIterator javathreads;
+  while (javathreads.has_next()) {
+    tc.do_thread(javathreads.next());
+  }
+  JfrNonJavaThreadIterator nonjavathreads;
+  while (nonjavathreads.has_next()) {
+    tc.do_thread(nonjavathreads.next());
+  }
 }
 
 void JfrThreadGroupConstant::serialize(JfrCheckpointWriter& writer) {
-  assert(SafepointSynchronize::is_at_safepoint(), "invariant");
   JfrThreadGroup::serialize(writer);
 }
 
@@ -278,19 +275,21 @@
 void JfrThreadConstant::serialize(JfrCheckpointWriter& writer) {
   assert(_thread != NULL, "invariant");
   assert(_thread == Thread::current(), "invariant");
-  assert(_thread->is_Java_thread(), "invariant");
-  ResourceMark rm(_thread);
-  const oop threadObj = _thread->threadObj();
-  assert(threadObj != NULL, "invariant");
-  const u8 java_lang_thread_id = java_lang_Thread::thread_id(threadObj);
-  const char* const thread_name = _thread->name();
-  const traceid thread_group_id = JfrThreadGroup::thread_group_id(_thread);
   writer.write_count(1);
-  writer.write_key(_thread->jfr_thread_local()->thread_id());
-  writer.write(thread_name);
-  writer.write((traceid)_thread->osthread()->thread_id());
-  writer.write(thread_name);
-  writer.write(java_lang_thread_id);
-  writer.write(thread_group_id);
-  JfrThreadGroup::serialize(&writer, thread_group_id);
+  writer.write_key(JfrThreadId::jfr_id(_thread));
+  const char* const name = JfrThreadName::name(_thread);
+  writer.write(name);
+  writer.write(JfrThreadId::os_id(_thread));
+  if (_thread->is_Java_thread()) {
+    writer.write(name);
+    writer.write(JfrThreadId::id(_thread));
+    JavaThread* const jt = (JavaThread*)_thread;
+    const traceid thread_group_id = JfrThreadGroup::thread_group_id(jt, jt);
+    writer.write(thread_group_id);
+    JfrThreadGroup::serialize(&writer, thread_group_id);
+    return;
+  }
+  writer.write((const char*)NULL); // java name
+  writer.write((traceid)0); // java thread id
+  writer.write((traceid)0); // java thread group
 }
--- a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrType.hpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrType.hpp	Wed Oct 30 19:43:52 2019 +0100
@@ -27,16 +27,6 @@
 
 #include "jfr/metadata/jfrSerializer.hpp"
 
-class JfrThreadConstantSet : public JfrSerializer {
- public:
-  void serialize(JfrCheckpointWriter& writer);
-};
-
-class JfrThreadGroupConstant : public JfrSerializer {
- public:
-  void serialize(JfrCheckpointWriter& writer);
-};
-
 class FlagValueOriginConstant : public JfrSerializer {
  public:
   void serialize(JfrCheckpointWriter& writer);
@@ -102,6 +92,16 @@
   void serialize(JfrCheckpointWriter& writer);
 };
 
+class JfrThreadConstantSet : public JfrSerializer {
+ public:
+  void serialize(JfrCheckpointWriter& writer);
+};
+
+class JfrThreadGroupConstant : public JfrSerializer {
+ public:
+  void serialize(JfrCheckpointWriter& writer);
+};
+
 class ThreadStateConstant : public JfrSerializer {
  public:
   void serialize(JfrCheckpointWriter& writer);
@@ -109,9 +109,9 @@
 
 class JfrThreadConstant : public JfrSerializer {
  private:
-  JavaThread* _thread;
+  Thread* _thread;
  public:
-  JfrThreadConstant(JavaThread* jt) : _thread(jt) {}
+  JfrThreadConstant(Thread* t) : _thread(t) {}
   void serialize(JfrCheckpointWriter& writer);
 };
 
--- a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeManager.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeManager.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -27,6 +27,7 @@
 #include "jfr/recorder/checkpoint/jfrCheckpointWriter.hpp"
 #include "jfr/recorder/checkpoint/types/jfrType.hpp"
 #include "jfr/recorder/checkpoint/types/jfrTypeManager.hpp"
+#include "jfr/recorder/jfrRecorder.hpp"
 #include "jfr/utilities/jfrDoublyLinkedList.hpp"
 #include "jfr/utilities/jfrIterator.hpp"
 #include "memory/resourceArea.hpp"
@@ -73,29 +74,71 @@
     return _id;
   }
 
-  void invoke(JfrCheckpointWriter& writer) const;
+  void on_rotation() const {
+    _serializer->on_rotation();
+  }
+
+  void invoke(JfrCheckpointWriter& writer) const {
+    if (_cache.valid()) {
+      writer.increment();
+      _cache->write(writer);
+      return;
+    }
+    const JfrCheckpointContext ctx = writer.context();
+    // serialize the type id before invoking callback
+    writer.write_type(_id);
+    const intptr_t start = writer.current_offset();
+    // invoke the serializer routine
+    _serializer->serialize(writer);
+    if (start == writer.current_offset()) {
+      // the serializer implementation did nothing, rewind to restore
+      writer.set_context(ctx);
+      return;
+    }
+    if (_permit_cache) {
+      _cache = writer.copy(&ctx);
+    }
+  }
 };
 
-void JfrSerializerRegistration::invoke(JfrCheckpointWriter& writer) const {
-  if (_cache.valid()) {
-    writer.increment();
-    _cache->write(writer);
-    return;
-  }
-  const JfrCheckpointContext ctx = writer.context();
-  // serialize the type id before invoking callback
-  writer.write_type(_id);
-  const intptr_t start = writer.current_offset();
-  // invoke the serializer routine
-  _serializer->serialize(writer);
-  if (start == writer.current_offset() ) {
-    // the serializer implementation did nothing, rewind to restore
-    writer.set_context(ctx);
-    return;
-  }
-  if (_permit_cache) {
-    _cache = writer.copy(&ctx);
-  }
+static void serialize_threads(JfrCheckpointWriter& writer) {
+  JfrThreadConstantSet thread_set;
+  writer.write_type(TYPE_THREAD);
+  thread_set.serialize(writer);
+}
+
+static void serialize_thread_groups(JfrCheckpointWriter& writer) {
+  JfrThreadGroupConstant thread_group_set;
+  writer.write_type(TYPE_THREADGROUP);
+  thread_group_set.serialize(writer);
+}
+
+void JfrTypeManager::write_threads(JfrCheckpointWriter& writer) {
+  serialize_threads(writer);
+  serialize_thread_groups(writer);
+}
+
+void JfrTypeManager::create_thread_blob(Thread* t) {
+  assert(t != NULL, "invariant");
+  ResourceMark rm(t);
+  HandleMark hm(t);
+  JfrThreadConstant type_thread(t);
+  JfrCheckpointWriter writer(t, true, THREADS);
+  writer.write_type(TYPE_THREAD);
+  type_thread.serialize(writer);
+  // create and install a checkpoint blob
+  t->jfr_thread_local()->set_thread_blob(writer.move());
+  assert(t->jfr_thread_local()->has_thread_blob(), "invariant");
+}
+
+void JfrTypeManager::write_thread_checkpoint(Thread* t) {
+  assert(t != NULL, "invariant");
+  ResourceMark rm(t);
+  HandleMark hm(t);
+  JfrThreadConstant type_thread(t);
+  JfrCheckpointWriter writer(t, true, THREADS);
+  writer.write_type(TYPE_THREAD);
+  type_thread.serialize(writer);
 }
 
 class SerializerRegistrationGuard : public StackObj {
@@ -115,7 +158,6 @@
 typedef JfrDoublyLinkedList<JfrSerializerRegistration> List;
 typedef StopOnNullIterator<const List> Iterator;
 static List types;
-static List safepoint_types;
 
 void JfrTypeManager::destroy() {
   SerializerRegistrationGuard guard;
@@ -126,52 +168,15 @@
     assert(registration != NULL, "invariant");
     delete registration;
   }
-  Iterator sp_type_iter(safepoint_types);
-  while (sp_type_iter.has_next()) {
-    registration = safepoint_types.remove(sp_type_iter.next());
-    assert(registration != NULL, "invariant");
-    delete registration;
-  }
-}
-
-void JfrTypeManager::write_types(JfrCheckpointWriter& writer) {
-  const Iterator iter(types);
-  while (iter.has_next()) {
-    iter.next()->invoke(writer);
-  }
 }
 
-void JfrTypeManager::write_safepoint_types(JfrCheckpointWriter& writer) {
-  assert(SafepointSynchronize::is_at_safepoint(), "invariant");
-  const Iterator iter(safepoint_types);
+void JfrTypeManager::on_rotation() {
+  const Iterator iter(types);
   while (iter.has_next()) {
-    iter.next()->invoke(writer);
+    iter.next()->on_rotation();
   }
 }
 
-void JfrTypeManager::create_thread_blob(JavaThread* jt) {
-  assert(jt != NULL, "invariant");
-  ResourceMark rm(jt);
-  HandleMark hm(jt);
-  JfrThreadConstant type_thread(jt);
-  JfrCheckpointWriter writer(false, true, jt);
-  writer.write_type(TYPE_THREAD);
-  type_thread.serialize(writer);
-  // create and install a checkpoint blob
-  jt->jfr_thread_local()->set_thread_blob(writer.move());
-  assert(jt->jfr_thread_local()->has_thread_blob(), "invariant");
-}
-
-void JfrTypeManager::write_thread_checkpoint(JavaThread* jt) {
-  assert(jt != NULL, "JavaThread is NULL!");
-  ResourceMark rm(jt);
-  HandleMark hm(jt);
-  JfrThreadConstant type_thread(jt);
-  JfrCheckpointWriter writer(false, true, jt);
-  writer.write_type(TYPE_THREAD);
-  type_thread.serialize(writer);
-}
-
 #ifdef ASSERT
 static void assert_not_registered_twice(JfrTypeId id, List& list) {
   const Iterator iter(list);
@@ -181,52 +186,65 @@
 }
 #endif
 
-static bool register_type(JfrTypeId id, bool require_safepoint, bool permit_cache, JfrSerializer* serializer) {
+static bool new_registration = false;
+
+static bool register_static_type(JfrTypeId id, bool permit_cache, JfrSerializer* serializer) {
   assert(serializer != NULL, "invariant");
   JfrSerializerRegistration* const registration = new JfrSerializerRegistration(id, permit_cache, serializer);
   if (registration == NULL) {
     delete serializer;
     return false;
   }
-  if (require_safepoint) {
-    assert(!safepoint_types.in_list(registration), "invariant");
-    DEBUG_ONLY(assert_not_registered_twice(id, safepoint_types);)
-    safepoint_types.prepend(registration);
-  } else {
-    assert(!types.in_list(registration), "invariant");
-    DEBUG_ONLY(assert_not_registered_twice(id, types);)
-    types.prepend(registration);
+  assert(!types.in_list(registration), "invariant");
+  DEBUG_ONLY(assert_not_registered_twice(id, types);)
+  if (JfrRecorder::is_recording()) {
+    JfrCheckpointWriter writer(STATICS);
+    registration->invoke(writer);
+    new_registration = true;
   }
+  types.prepend(registration);
   return true;
 }
 
 bool JfrTypeManager::initialize() {
   SerializerRegistrationGuard guard;
-
-  // register non-safepointing type serialization
-  register_type(TYPE_FLAGVALUEORIGIN, false, true, new FlagValueOriginConstant());
-  register_type(TYPE_INFLATECAUSE, false, true, new MonitorInflateCauseConstant());
-  register_type(TYPE_GCCAUSE, false, true, new GCCauseConstant());
-  register_type(TYPE_GCNAME, false, true, new GCNameConstant());
-  register_type(TYPE_GCWHEN, false, true, new GCWhenConstant());
-  register_type(TYPE_GCTHRESHOLDUPDATER, false, true, new GCThresholdUpdaterConstant());
-  register_type(TYPE_METADATATYPE, false, true, new MetadataTypeConstant());
-  register_type(TYPE_METASPACEOBJECTTYPE, false, true, new MetaspaceObjectTypeConstant());
-  register_type(TYPE_REFERENCETYPE, false, true, new ReferenceTypeConstant());
-  register_type(TYPE_NARROWOOPMODE, false, true, new NarrowOopModeConstant());
-  register_type(TYPE_COMPILERPHASETYPE, false, true, new CompilerPhaseTypeConstant());
-  register_type(TYPE_CODEBLOBTYPE, false, true, new CodeBlobTypeConstant());
-  register_type(TYPE_VMOPERATIONTYPE, false, true, new VMOperationTypeConstant());
-  register_type(TYPE_THREADSTATE, false, true, new ThreadStateConstant());
-
-  // register safepointing type serialization
-  register_type(TYPE_THREADGROUP, true, false, new JfrThreadGroupConstant());
-  register_type(TYPE_THREAD, true, false, new JfrThreadConstantSet());
+  register_static_type(TYPE_FLAGVALUEORIGIN, true, new FlagValueOriginConstant());
+  register_static_type(TYPE_INFLATECAUSE, true, new MonitorInflateCauseConstant());
+  register_static_type(TYPE_GCCAUSE, true, new GCCauseConstant());
+  register_static_type(TYPE_GCNAME, true, new GCNameConstant());
+  register_static_type(TYPE_GCWHEN, true, new GCWhenConstant());
+  register_static_type(TYPE_GCTHRESHOLDUPDATER, true, new GCThresholdUpdaterConstant());
+  register_static_type(TYPE_METADATATYPE, true, new MetadataTypeConstant());
+  register_static_type(TYPE_METASPACEOBJECTTYPE, true, new MetaspaceObjectTypeConstant());
+  register_static_type(TYPE_REFERENCETYPE, true, new ReferenceTypeConstant());
+  register_static_type(TYPE_NARROWOOPMODE, true, new NarrowOopModeConstant());
+  register_static_type(TYPE_COMPILERPHASETYPE, true, new CompilerPhaseTypeConstant());
+  register_static_type(TYPE_CODEBLOBTYPE, true, new CodeBlobTypeConstant());
+  register_static_type(TYPE_VMOPERATIONTYPE, true, new VMOperationTypeConstant());
+  register_static_type(TYPE_THREADSTATE, true, new ThreadStateConstant());
   return true;
 }
 
 // implementation for the static registration function exposed in the JfrSerializer api
-bool JfrSerializer::register_serializer(JfrTypeId id, bool require_safepoint, bool permit_cache, JfrSerializer* serializer) {
+bool JfrSerializer::register_serializer(JfrTypeId id, bool permit_cache, JfrSerializer* serializer) {
   SerializerRegistrationGuard guard;
-  return register_type(id, require_safepoint, permit_cache, serializer);
+  return register_static_type(id, permit_cache, serializer);
 }
+
+bool JfrTypeManager::has_new_static_type() {
+  if (new_registration) {
+    SerializerRegistrationGuard guard;
+    new_registration = false;
+    return true;
+  }
+  return false;
+}
+
+void JfrTypeManager::write_static_types(JfrCheckpointWriter& writer) {
+  SerializerRegistrationGuard guard;
+  const Iterator iter(types);
+  while (iter.has_next()) {
+    iter.next()->invoke(writer);
+  }
+  new_registration = false;
+}
--- a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeManager.hpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeManager.hpp	Wed Oct 30 19:43:52 2019 +0100
@@ -33,10 +33,12 @@
  public:
   static bool initialize();
   static void destroy();
-  static void write_types(JfrCheckpointWriter& writer);
-  static void write_safepoint_types(JfrCheckpointWriter& writer);
-  static void create_thread_blob(JavaThread* jt);
-  static void write_thread_checkpoint(JavaThread* jt);
+  static void on_rotation();
+  static void write_threads(JfrCheckpointWriter& writer);
+  static void create_thread_blob(Thread* t);
+  static void write_thread_checkpoint(Thread* t);
+  static bool has_new_static_type();
+  static void write_static_types(JfrCheckpointWriter& writer);
 };
 
 #endif // SHARE_JFR_RECORDER_CHECKPOINT_TYPES_JFRTYPEMANAGER_HPP
--- a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSet.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSet.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -73,7 +73,7 @@
 }
 
 static bool current_epoch() {
-  return _class_unload;
+  return _class_unload || _flushpoint;
 }
 
 static bool previous_epoch() {
@@ -246,7 +246,7 @@
 static void do_klass(Klass* klass) {
   assert(klass != NULL, "invariant");
   assert(_subsystem_callback != NULL, "invariant");
-  if (current_epoch()) {
+  if (_flushpoint) {
     if (USED_THIS_EPOCH(klass)) {
       _subsystem_callback->do_artifact(klass);
       return;
@@ -911,10 +911,11 @@
   return total_count;
 }
 
-static void setup(JfrCheckpointWriter* writer, JfrCheckpointWriter* leakp_writer, bool class_unload) {
+static void setup(JfrCheckpointWriter* writer, JfrCheckpointWriter* leakp_writer, bool class_unload, bool flushpoint) {
   _writer = writer;
   _leakp_writer = leakp_writer;
   _class_unload = class_unload;
+  _flushpoint = flushpoint;
   if (_artifacts == NULL) {
     _artifacts = new JfrArtifactSet(class_unload);
   } else {
@@ -928,10 +929,10 @@
 /**
  * Write all "tagged" (in-use) constant artifacts and their dependencies.
  */
-size_t JfrTypeSet::serialize(JfrCheckpointWriter* writer, JfrCheckpointWriter* leakp_writer, bool class_unload) {
+size_t JfrTypeSet::serialize(JfrCheckpointWriter* writer, JfrCheckpointWriter* leakp_writer, bool class_unload, bool flushpoint) {
   assert(writer != NULL, "invariant");
   ResourceMark rm;
-  setup(writer, leakp_writer, class_unload);
+  setup(writer, leakp_writer, class_unload, flushpoint);
   // write order is important because an individual write step
   // might tag an artifact to be written in a subsequent step
   if (!write_klasses()) {
--- a/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSet.hpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/checkpoint/types/jfrTypeSet.hpp	Wed Oct 30 19:43:52 2019 +0100
@@ -32,7 +32,7 @@
 class JfrTypeSet : AllStatic {
  public:
   static void clear();
-  static size_t serialize(JfrCheckpointWriter* writer, JfrCheckpointWriter* leakp_writer, bool class_unload);
+  static size_t serialize(JfrCheckpointWriter* writer, JfrCheckpointWriter* leakp_writer, bool class_unload, bool flushpoint);
 };
 
 #endif // SHARE_JFR_RECORDER_CHECKPOINT_TYPES_JFRTYPESET_HPP
--- a/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceId.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceId.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -33,7 +33,6 @@
 #include "oops/method.hpp"
 #include "oops/oop.inline.hpp"
 #include "runtime/atomic.hpp"
-#include "runtime/orderAccess.hpp"
 #include "runtime/vm_version.hpp"
 #include "runtime/jniHandles.inline.hpp"
 #include "runtime/thread.inline.hpp"
@@ -45,7 +44,7 @@
   traceid compare_value;
   traceid exchange_value;
   do {
-    compare_value = OrderAccess::load_acquire(dest);
+    compare_value = *dest;
     exchange_value = compare_value + 1;
   } while (Atomic::cmpxchg(exchange_value, dest, compare_value) != compare_value);
   return exchange_value;
--- a/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceId.inline.hpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceId.inline.hpp	Wed Oct 30 19:43:52 2019 +0100
@@ -31,7 +31,6 @@
 #include "jfr/recorder/checkpoint/types/traceid/jfrTraceId.hpp"
 #include "jfr/recorder/checkpoint/types/traceid/jfrTraceIdBits.inline.hpp"
 #include "jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.hpp"
-#include "jfr/recorder/checkpoint/types/traceid/jfrTraceIdMacros.hpp"
 #include "jfr/support/jfrKlassExtension.hpp"
 #include "oops/arrayKlass.hpp"
 #include "oops/klass.hpp"
@@ -60,7 +59,14 @@
 
 inline traceid JfrTraceId::use(const Klass* klass) {
   assert(klass != NULL, "invariant");
-  return set_used_and_get(klass);
+  if (SHOULD_TAG(klass)) {
+    SET_USED_THIS_EPOCH(klass);
+    assert(USED_THIS_EPOCH(klass), "invariant");
+    JfrTraceIdEpoch::set_changed_tag_state();
+    return get(klass);
+  }
+  assert(USED_THIS_EPOCH(klass), "invariant");
+  return TRACE_ID(klass);
 }
 
 inline traceid JfrTraceId::use(const Method* method) {
@@ -71,10 +77,16 @@
 inline traceid JfrTraceId::use(const Klass* klass, const Method* method) {
   assert(klass != NULL, "invariant");
   assert(method != NULL, "invariant");
-  SET_METHOD_FLAG_USED_THIS_EPOCH(method);
-
-  SET_METHOD_AND_CLASS_USED_THIS_EPOCH(klass);
+  if (SHOULD_TAG_KLASS_METHOD(klass)) {
+    SET_METHOD_AND_CLASS_USED_THIS_EPOCH(klass);
+  }
   assert(METHOD_AND_CLASS_USED_THIS_EPOCH(klass), "invariant");
+  if (METHOD_FLAG_NOT_USED_THIS_EPOCH(method)) {
+    assert(USED_THIS_EPOCH(klass), "invariant");
+    SET_METHOD_FLAG_USED_THIS_EPOCH(method);
+    JfrTraceIdEpoch::set_changed_tag_state();
+  }
+  assert(METHOD_FLAG_USED_THIS_EPOCH(method), "invariant");
   return (METHOD_ID(klass, method));
 }
 
--- a/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -25,15 +25,13 @@
 #include "precompiled.hpp"
 #include "jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.hpp"
 #include "runtime/safepoint.hpp"
-#include "runtime/orderAccess.hpp"
 
 // Alternating epochs on each rotation allow for concurrent tagging.
-// The regular epoch shift happens only during a safepoint.
-// The fence is there only for the emergency dump case which happens outside of safepoint.
+// The epoch shift happens only during a safepoint.
 bool JfrTraceIdEpoch::_epoch_state = false;
+bool volatile JfrTraceIdEpoch::_tag_state = false;
+
 void JfrTraceIdEpoch::shift_epoch() {
+  assert(SafepointSynchronize::is_at_safepoint(), "invariant");
   _epoch_state = !_epoch_state;
-  if (!SafepointSynchronize::is_at_safepoint()) {
-    OrderAccess::fence();
-  }
 }
--- a/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.hpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/checkpoint/types/traceid/jfrTraceIdEpoch.hpp	Wed Oct 30 19:43:52 2019 +0100
@@ -25,18 +25,19 @@
 #ifndef SHARE_JFR_RECORDER_CHECKPOINT_TYPES_TRACEID_JFRTRACEIDEPOCH_HPP
 #define SHARE_JFR_RECORDER_CHECKPOINT_TYPES_TRACEID_JFRTRACEIDEPOCH_HPP
 
+#include "jfr/utilities/jfrTypes.hpp"
 #include "memory/allocation.hpp"
-#include "jfr/utilities/jfrTypes.hpp"
+#include "runtime/orderAccess.hpp"
 
-#define USED_BIT 1
-#define METHOD_USED_BIT (USED_BIT << 2)
-#define EPOCH_1_SHIFT 0
-#define EPOCH_2_SHIFT 1
-#define USED_EPOCH_1_BIT (USED_BIT << EPOCH_1_SHIFT)
-#define USED_EPOCH_2_BIT (USED_BIT << EPOCH_2_SHIFT)
-#define METHOD_USED_EPOCH_1_BIT (METHOD_USED_BIT << EPOCH_1_SHIFT)
-#define METHOD_USED_EPOCH_2_BIT (METHOD_USED_BIT << EPOCH_2_SHIFT)
-#define METHOD_AND_CLASS_IN_USE_BITS (METHOD_USED_BIT | USED_BIT)
+#define USED_BIT                             1
+#define METHOD_USED_BIT                      (USED_BIT << 2)
+#define EPOCH_1_SHIFT                        0
+#define EPOCH_2_SHIFT                        1
+#define USED_EPOCH_1_BIT                     (USED_BIT << EPOCH_1_SHIFT)
+#define USED_EPOCH_2_BIT                     (USED_BIT << EPOCH_2_SHIFT)
+#define METHOD_USED_EPOCH_1_BIT              (METHOD_USED_BIT << EPOCH_1_SHIFT)
+#define METHOD_USED_EPOCH_2_BIT              (METHOD_USED_BIT << EPOCH_2_SHIFT)
+#define METHOD_AND_CLASS_IN_USE_BITS         (METHOD_USED_BIT | USED_BIT)
 #define METHOD_AND_CLASS_IN_USE_EPOCH_1_BITS (METHOD_AND_CLASS_IN_USE_BITS << EPOCH_1_SHIFT)
 #define METHOD_AND_CLASS_IN_USE_EPOCH_2_BITS (METHOD_AND_CLASS_IN_USE_BITS << EPOCH_2_SHIFT)
 
@@ -44,6 +45,8 @@
   friend class JfrCheckpointManager;
  private:
   static bool _epoch_state;
+  static bool volatile _tag_state;
+
   static void shift_epoch();
 
  public:
@@ -86,6 +89,20 @@
   static traceid method_and_class_in_use_prev_epoch_bits() {
     return _epoch_state ? METHOD_AND_CLASS_IN_USE_EPOCH_1_BITS :  METHOD_AND_CLASS_IN_USE_EPOCH_2_BITS;
   }
+
+  static bool has_changed_tag_state() {
+    if (OrderAccess::load_acquire(&_tag_state)) {
+      OrderAccess::release_store(&_tag_state, false);
+      return true;
+    }
+    return false;
+  }
+
+  static void set_changed_tag_state() {
+    if (!OrderAccess::load_acquire(&_tag_state)) {
+      OrderAccess::release_store(&_tag_state, true);
+    }
+  }
 };
 
 #endif // SHARE_JFR_RECORDER_CHECKPOINT_TYPES_TRACEID_JFRTRACEIDEPOCH_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hotspot/share/jfr/recorder/repository/jfrChunk.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,226 @@
+/*
+ * 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/recorder/repository/jfrChunk.hpp"
+#include "jfr/recorder/service/jfrOptionSet.hpp"
+#include "jfr/utilities/jfrTime.hpp"
+#include "jfr/utilities/jfrTimeConverter.hpp"
+#include "jfr/utilities/jfrTypes.hpp"
+#include "runtime/os.inline.hpp"
+
+static const char* const MAGIC = "FLR";
+static const u2 JFR_VERSION_MAJOR = 2;
+static const u2 JFR_VERSION_MINOR = 1;
+
+// strictly monotone
+static jlong nanos_now() {
+  static jlong last = 0;
+  const jlong now = os::javaTimeMillis() * JfrTimeConverter::NANOS_PER_MILLISEC;
+  if (now > last) {
+    last = now;
+  } else {
+    ++last;
+  }
+  return last;
+}
+
+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;
+}
+
+const char* JfrChunk::magic() const {
+  return MAGIC;
+}
+
+u2 JfrChunk::major_version() const {
+  return JFR_VERSION_MAJOR;
+}
+
+u2 JfrChunk::minor_version() const {
+  return JFR_VERSION_MINOR;
+}
+
+u2 JfrChunk::capabilities() const {
+  // chunk capabilities, CompressedIntegers etc
+  static bool compressed_integers = JfrOptionSet::compressed_integers();
+  return compressed_integers;
+}
+
+int64_t JfrChunk::cpu_frequency() const {
+  static const jlong frequency = JfrTime::frequency();
+  return frequency;
+}
+
+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() {
+  const jlong now = nanos_now();
+  assert(now > _start_nanos, "invariant");
+  assert(now > _last_update_nanos, "invariant");
+  _start_nanos = _last_update_nanos = now;
+}
+
+void JfrChunk::update_current_nanos() {
+  const jlong now = nanos_now();
+  assert(now > _last_update_nanos, "invariant");
+  _last_update_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::set_time_stamp() {
+  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;
+}
+
+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) {
+  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;
+}
+
+u1 JfrChunk::next_generation() const {
+  assert(_generation > 0, "invariant");
+  const u1 next_gen = _generation;
+  return GUARD == next_gen ? 1 : next_gen;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hotspot/share/jfr/recorder/repository/jfrChunk.hpp	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,91 @@
+/*
+ * 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 "jfr/utilities/jfrAllocation.hpp"
+
+const u1 COMPLETE = 0;
+const u1 GUARD = 0xff;
+const u1 PAD = 0;
+
+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 reset();
+
+  const char* magic() const;
+  u2 major_version() const;
+  u2 minor_version() const;
+  int64_t cpu_frequency() const;
+  u2 capabilities() const;
+
+  void update_start_ticks();
+  void update_start_nanos();
+  void save_current_and_update_start_ticks();
+  void save_current_and_update_start_nanos();
+
+  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 set_time_stamp();
+  void update_current_nanos();
+
+  void set_path(const char* path);
+  const char* path() const;
+
+  bool is_started() const;
+  bool is_finished() const;
+
+  int64_t duration() const;
+  u1 generation() const;
+  u1 next_generation() const;
+};
+
+#endif // SHARE_VM_JFR_RECORDER_REPOSITORY_JFRRCHUNK_HPP
--- a/src/hotspot/share/jfr/recorder/repository/jfrChunkState.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,120 +0,0 @@
-/*
- * 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/jfrChunkState.hpp"
-#include "jfr/recorder/repository/jfrChunkWriter.hpp"
-#include "jfr/utilities/jfrTime.hpp"
-#include "jfr/utilities/jfrTimeConverter.hpp"
-#include "logging/log.hpp"
-#include "runtime/os.inline.hpp"
-#include "runtime/thread.inline.hpp"
-
-JfrChunkState::JfrChunkState() :
-  _path(NULL),
-  _start_ticks(0),
-  _start_nanos(0),
-  _previous_start_ticks(0),
-  _previous_start_nanos(0),
-  _last_checkpoint_offset(0) {}
-
-JfrChunkState::~JfrChunkState() {
-  reset();
-}
-
-void JfrChunkState::reset() {
-  if (_path != NULL) {
-    JfrCHeapObj::free(_path, strlen(_path) + 1);
-    _path = NULL;
-  }
-  set_last_checkpoint_offset(0);
-}
-
-void JfrChunkState::set_last_checkpoint_offset(int64_t offset) {
-  _last_checkpoint_offset = offset;
-}
-
-int64_t JfrChunkState::last_checkpoint_offset() const {
-  return _last_checkpoint_offset;
-}
-
-int64_t JfrChunkState::previous_start_ticks() const {
-  return _previous_start_ticks;
-}
-
-int64_t JfrChunkState::previous_start_nanos() const {
-  return _previous_start_nanos;
-}
-
-void JfrChunkState::update_start_ticks() {
-  _start_ticks = JfrTicks::now();
-}
-
-void JfrChunkState::update_start_nanos() {
-  _start_nanos = os::javaTimeMillis() * JfrTimeConverter::NANOS_PER_MILLISEC;
-}
-
-void JfrChunkState::save_current_and_update_start_ticks() {
-  _previous_start_ticks = _start_ticks;
-  update_start_ticks();
-}
-
-void JfrChunkState::save_current_and_update_start_nanos() {
-  _previous_start_nanos = _start_nanos;
-  update_start_nanos();
-}
-
-void JfrChunkState::update_time_to_now() {
-  save_current_and_update_start_nanos();
-  save_current_and_update_start_ticks();
-}
-
-int64_t JfrChunkState::last_chunk_duration() const {
-  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 JfrChunkState::set_path(const char* path) {
-  assert(JfrStream_lock->owned_by_self(), "invariant");
-  if (_path != NULL) {
-    JfrCHeapObj::free(_path, strlen(_path) + 1);
-    _path = NULL;
-  }
-  if (path != NULL) {
-    _path = copy_path(path);
-  }
-}
-
-const char* JfrChunkState::path() const {
-  return _path;
-}
--- a/src/hotspot/share/jfr/recorder/repository/jfrChunkState.hpp	Wed Oct 30 16:14:56 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,59 +0,0 @@
-/*
- * 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_JFR_RECORDER_REPOSITORY_JFRCHUNKSTATE_HPP
-#define SHARE_JFR_RECORDER_REPOSITORY_JFRCHUNKSTATE_HPP
-
-#include "jfr/utilities/jfrAllocation.hpp"
-#include "jfr/utilities/jfrTypes.hpp"
-
-class JfrChunkState : public JfrCHeapObj {
-  friend class JfrChunkWriter;
- private:
-  char* _path;
-  int64_t _start_ticks;
-  int64_t _start_nanos;
-  int64_t _previous_start_ticks;
-  int64_t _previous_start_nanos;
-  int64_t _last_checkpoint_offset;
-
-  void update_start_ticks();
-  void update_start_nanos();
-  void save_current_and_update_start_ticks();
-  void save_current_and_update_start_nanos();
-
-  JfrChunkState();
-  ~JfrChunkState();
-  void reset();
-  int64_t last_checkpoint_offset() const;
-  void set_last_checkpoint_offset(int64_t offset);
-  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;
-};
-
-#endif // SHARE_JFR_RECORDER_REPOSITORY_JFRCHUNKSTATE_HPP
--- a/src/hotspot/share/jfr/recorder/repository/jfrChunkWriter.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/repository/jfrChunkWriter.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -23,82 +23,218 @@
  */
 
 #include "precompiled.hpp"
-#include "jfr/recorder/repository/jfrChunkState.hpp"
+#include "jfr/recorder/repository/jfrChunk.hpp"
 #include "jfr/recorder/repository/jfrChunkWriter.hpp"
-#include "jfr/recorder/service/jfrOptionSet.hpp"
 #include "jfr/utilities/jfrTime.hpp"
-#include "jfr/utilities/jfrTypes.hpp"
 #include "runtime/mutexLocker.hpp"
-#include "runtime/os.hpp"
 #include "runtime/os.inline.hpp"
 
-static const u2 JFR_VERSION_MAJOR = 2;
-static const u2 JFR_VERSION_MINOR = 0;
-static const size_t MAGIC_LEN = 4;
-static const size_t FILEHEADER_SLOT_SIZE = 8;
-static const size_t CHUNK_SIZE_OFFSET = 8;
-
-JfrChunkWriter::JfrChunkWriter() : JfrChunkWriterBase(NULL), _chunkstate(NULL) {}
-
-bool JfrChunkWriter::initialize() {
-  assert(_chunkstate == NULL, "invariant");
-  _chunkstate = new JfrChunkState();
-  return _chunkstate != NULL;
-}
+static const int64_t MAGIC_OFFSET = 0;
+static const int64_t MAGIC_LEN = 4;
+static const int64_t VERSION_OFFSET = MAGIC_LEN;
+static const int64_t SIZE_OFFSET = 8;
+static const int64_t SLOT_SIZE = 8;
+static const int64_t CHECKPOINT_OFFSET = SIZE_OFFSET + SLOT_SIZE;
+static const int64_t METADATA_OFFSET = CHECKPOINT_OFFSET + SLOT_SIZE;
+static const int64_t START_NANOS_OFFSET = METADATA_OFFSET + SLOT_SIZE;
+static const int64_t DURATION_NANOS_OFFSET = START_NANOS_OFFSET + SLOT_SIZE;
+static const int64_t START_TICKS_OFFSET = DURATION_NANOS_OFFSET + SLOT_SIZE;
+static const int64_t CPU_FREQUENCY_OFFSET = START_TICKS_OFFSET + SLOT_SIZE;
+static const int64_t GENERATION_OFFSET = CPU_FREQUENCY_OFFSET + SLOT_SIZE;
+static const int64_t CAPABILITY_OFFSET = GENERATION_OFFSET + 2;
+static const int64_t HEADER_SIZE = CAPABILITY_OFFSET + 2;
 
 static fio_fd open_chunk(const char* path) {
-  assert(JfrStream_lock->owned_by_self(), "invariant");
   return path != NULL ? os::open(path, O_CREAT | O_RDWR, S_IREAD | S_IWRITE) : invalid_fd;
 }
 
-bool JfrChunkWriter::open() {
-  assert(_chunkstate != NULL, "invariant");
-  JfrChunkWriterBase::reset(open_chunk(_chunkstate->path()));
-  const bool is_open = this->has_valid_fd();
-  if (is_open) {
-    this->bytes("FLR", MAGIC_LEN);
-    this->be_write((u2)JFR_VERSION_MAJOR);
-    this->be_write((u2)JFR_VERSION_MINOR);
-    this->reserve(6 * FILEHEADER_SLOT_SIZE);
-    // u8 chunk_size
-    // u8 initial checkpoint offset
-    // u8 metadata section offset
-    // u8 chunk start nanos
-    // u8 chunk duration nanos
-    // u8 chunk start ticks
-    this->be_write(JfrTime::frequency());
-    // chunk capabilities, CompressedIntegers etc
-    this->be_write((u4)JfrOptionSet::compressed_integers() ? 1 : 0);
-    _chunkstate->reset();
+#ifdef ASSERT
+static void assert_writer_position(JfrChunkWriter* writer, int64_t offset) {
+  assert(writer != NULL, "invariant");
+  assert(offset == writer->current_offset(), "invariant");
+}
+#endif
+
+class JfrChunkHeadWriter : public StackObj {
+ private:
+  JfrChunkWriter* _writer;
+  JfrChunk* _chunk;
+ public:
+  void write_magic() {
+    _writer->bytes(_chunk->magic(), MAGIC_LEN);
+  }
+
+  void write_version() {
+    _writer->be_write(_chunk->major_version());
+    _writer->be_write(_chunk->minor_version());
+  }
+
+  void write_size(int64_t size) {
+    _writer->be_write(size);
+  }
+
+  void write_checkpoint() {
+    _writer->be_write(_chunk->last_checkpoint_offset());
+  }
+
+  void write_metadata() {
+    _writer->be_write(_chunk->last_metadata_offset());
+  }
+
+  void write_time(bool finalize) {
+    if (finalize) {
+      _writer->be_write(_chunk->previous_start_nanos());
+      _writer->be_write(_chunk->last_chunk_duration());
+      _writer->be_write(_chunk->previous_start_ticks());
+      return;
+    }
+    _writer->be_write(_chunk->start_nanos());
+    _writer->be_write(_chunk->duration());
+    _writer->be_write(_chunk->start_ticks());
+  }
+
+  void write_cpu_frequency() {
+    _writer->be_write(_chunk->cpu_frequency());
+  }
+
+  void write_generation(bool finalize) {
+    _writer->be_write(finalize ? COMPLETE : _chunk->generation());
+    _writer->be_write(PAD);
+  }
+
+  void write_next_generation() {
+    _writer->be_write(_chunk->next_generation());
+    _writer->be_write(PAD);
+  }
+
+  void write_guard() {
+    _writer->be_write(GUARD);
+    _writer->be_write(PAD);
+  }
+
+  void write_guard_flush() {
+    write_guard();
+    _writer->flush();
   }
-  return is_open;
+
+  void write_capabilities() {
+    _writer->be_write(_chunk->capabilities());
+  }
+
+  void write_size_to_generation(int64_t size, bool finalize) {
+    write_size(size);
+    write_checkpoint();
+    write_metadata();
+    write_time(finalize);
+    write_cpu_frequency();
+    write_generation(finalize);
+  }
+
+  void flush(int64_t size, bool finalize) {
+    assert(_writer->is_valid(), "invariant");
+    assert(_chunk != NULL, "invariant");
+    DEBUG_ONLY(assert_writer_position(_writer, SIZE_OFFSET);)
+    write_size_to_generation(size, finalize);
+    // no need to write capabilities
+    _writer->seek(size); // implicit flush
+  }
+
+  void initialize() {
+    assert(_writer->is_valid(), "invariant");
+    assert(_chunk != NULL, "invariant");
+    DEBUG_ONLY(assert_writer_position(_writer, 0);)
+    write_magic();
+    write_version();
+    write_size_to_generation(HEADER_SIZE, false);
+    write_capabilities();
+    DEBUG_ONLY(assert_writer_position(_writer, HEADER_SIZE);)
+    _writer->flush();
+  }
+
+  JfrChunkHeadWriter(JfrChunkWriter* writer, int64_t offset, bool guard = true) : _writer(writer), _chunk(writer->_chunk) {
+    assert(_writer != NULL, "invariant");
+    assert(_writer->is_valid(), "invariant");
+    assert(_chunk != NULL, "invariant");
+    if (0 == _writer->current_offset()) {
+      assert(HEADER_SIZE == offset, "invariant");
+      initialize();
+    } else {
+      if (guard) {
+        _writer->seek(GENERATION_OFFSET);
+        write_guard();
+        _writer->seek(offset);
+      } else {
+        _chunk->update_current_nanos();
+      }
+    }
+    DEBUG_ONLY(assert_writer_position(_writer, offset);)
+  }
+};
+
+static int64_t prepare_chunk_header_constant_pool(JfrChunkWriter& cw, int64_t event_offset, bool flushpoint) {
+  const int64_t delta = cw.last_checkpoint_offset() == 0 ? 0 : cw.last_checkpoint_offset() - event_offset;
+  const u4 checkpoint_type = flushpoint ? (u4)(FLUSH | HEADER) : (u4)HEADER;
+  cw.reserve(sizeof(u4));
+  cw.write<u8>(EVENT_CHECKPOINT);
+  cw.write<u8>(JfrTicks::now().value());
+  cw.write<u8>(0); // duration
+  cw.write<u8>(delta); // to previous checkpoint
+  cw.write<u4>(checkpoint_type);
+  cw.write<u4>(1); // pool count
+  cw.write<u8>(TYPE_CHUNKHEADER);
+  cw.write<u4>(1); // count
+  cw.write<u8>(1); // key
+  cw.write<u4>(HEADER_SIZE); // length of byte array
+  return cw.current_offset();
 }
 
-size_t JfrChunkWriter::close(int64_t metadata_offset) {
-  write_header(metadata_offset);
-  this->flush();
-  this->close_fd();
-  return (size_t)size_written();
+int64_t JfrChunkWriter::write_chunk_header_checkpoint(bool flushpoint) {
+  assert(this->has_valid_fd(), "invariant");
+  const int64_t event_size_offset = current_offset();
+  const int64_t header_content_pos = prepare_chunk_header_constant_pool(*this, event_size_offset, flushpoint);
+  JfrChunkHeadWriter head(this, header_content_pos, false);
+  head.write_magic();
+  head.write_version();
+  const int64_t chunk_size_offset = reserve(sizeof(int64_t)); // size to be decided when we are done
+  be_write(event_size_offset); // last checkpoint offset will be this checkpoint
+  head.write_metadata();
+  head.write_time(false);
+  head.write_cpu_frequency();
+  head.write_next_generation();
+  head.write_capabilities();
+  assert(current_offset() - header_content_pos == HEADER_SIZE, "invariant");
+  const u4 checkpoint_size = current_offset() - event_size_offset;
+  write_padded_at_offset<u4>(checkpoint_size, event_size_offset);
+  set_last_checkpoint_offset(event_size_offset);
+  const size_t sz_written = size_written();
+  write_be_at_offset(sz_written, chunk_size_offset);
+  return sz_written;
 }
 
-void JfrChunkWriter::write_header(int64_t metadata_offset) {
-  assert(this->is_valid(), "invariant");
-  // Chunk size
-  this->write_be_at_offset(size_written(), CHUNK_SIZE_OFFSET);
-  // initial checkpoint event offset
-  this->write_be_at_offset(_chunkstate->last_checkpoint_offset(), CHUNK_SIZE_OFFSET + (1 * FILEHEADER_SLOT_SIZE));
-  // metadata event offset
-  this->write_be_at_offset(metadata_offset, CHUNK_SIZE_OFFSET + (2 * FILEHEADER_SLOT_SIZE));
-  // start of chunk in nanos since epoch
-  this->write_be_at_offset(_chunkstate->previous_start_nanos(), CHUNK_SIZE_OFFSET + (3 * FILEHEADER_SLOT_SIZE));
-  // duration of chunk in nanos
-  this->write_be_at_offset(_chunkstate->last_chunk_duration(), CHUNK_SIZE_OFFSET + (4 * FILEHEADER_SLOT_SIZE));
-  // start of chunk in ticks
-  this->write_be_at_offset(_chunkstate->previous_start_ticks(), CHUNK_SIZE_OFFSET + (5 * FILEHEADER_SLOT_SIZE));
+int64_t JfrChunkWriter::flush_chunk(bool flushpoint) {
+  assert(_chunk != NULL, "invariant");
+  const int64_t sz_written = write_chunk_header_checkpoint(flushpoint);
+  assert(size_written() == sz_written, "invariant");
+  JfrChunkHeadWriter head(this, SIZE_OFFSET);
+  head.flush(sz_written, !flushpoint);
+  return sz_written;
 }
 
-void JfrChunkWriter::set_chunk_path(const char* chunk_path) {
-  _chunkstate->set_path(chunk_path);
+JfrChunkWriter::JfrChunkWriter() : JfrChunkWriterBase(NULL), _chunk(new JfrChunk()) {}
+
+JfrChunkWriter::~JfrChunkWriter() {
+  assert(_chunk != NULL, "invariant");
+  delete _chunk;
+}
+
+void JfrChunkWriter::set_path(const char* path) {
+  assert(_chunk != NULL, "invariant");
+  _chunk->set_path(path);
+}
+
+void JfrChunkWriter::set_time_stamp() {
+  assert(_chunk != NULL, "invariant");
+  _chunk->set_time_stamp();
 }
 
 int64_t JfrChunkWriter::size_written() const {
@@ -106,13 +242,46 @@
 }
 
 int64_t JfrChunkWriter::last_checkpoint_offset() const {
-  return _chunkstate->last_checkpoint_offset();
+  assert(_chunk != NULL, "invariant");
+  return _chunk->last_checkpoint_offset();
+}
+
+int64_t JfrChunkWriter::current_chunk_start_nanos() const {
+  assert(_chunk != NULL, "invariant");
+  return this->is_valid() ? _chunk->start_nanos() : invalid_time;
 }
 
 void JfrChunkWriter::set_last_checkpoint_offset(int64_t offset) {
-  _chunkstate->set_last_checkpoint_offset(offset);
+  assert(_chunk != NULL, "invariant");
+  _chunk->set_last_checkpoint_offset(offset);
+}
+
+void JfrChunkWriter::set_last_metadata_offset(int64_t offset) {
+  assert(_chunk != NULL, "invariant");
+  _chunk->set_last_metadata_offset(offset);
+}
+
+bool JfrChunkWriter::has_metadata() const {
+  assert(_chunk != NULL, "invariant");
+  return _chunk->has_metadata();
 }
 
-void JfrChunkWriter::time_stamp_chunk_now() {
-  _chunkstate->update_time_to_now();
+bool JfrChunkWriter::open() {
+  assert(_chunk != NULL, "invariant");
+  JfrChunkWriterBase::reset(open_chunk(_chunk->path()));
+  const bool is_open = this->has_valid_fd();
+  if (is_open) {
+    assert(0 == this->current_offset(), "invariant");
+    _chunk->reset();
+    JfrChunkHeadWriter head(this, HEADER_SIZE);
+  }
+  return is_open;
 }
+
+int64_t JfrChunkWriter::close() {
+  assert(this->has_valid_fd(), "invariant");
+  const int64_t size_written = flush_chunk(false);
+  this->close_fd();
+  assert(!this->is_valid(), "invariant");
+  return size_written;
+}
--- a/src/hotspot/share/jfr/recorder/repository/jfrChunkWriter.hpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/repository/jfrChunkWriter.hpp	Wed Oct 30 19:43:52 2019 +0100
@@ -29,29 +29,36 @@
 #include "jfr/writers/jfrStreamWriterHost.inline.hpp"
 #include "jfr/writers/jfrWriterHost.inline.hpp"
 
-typedef MallocAdapter<M> JfrStreamBuffer; // 1 mb buffered writes
-typedef StreamWriterHost<JfrStreamBuffer, JfrCHeapObj> JfrBufferedStreamWriter;
-typedef WriterHost<BigEndianEncoder, CompressedIntegerEncoder, JfrBufferedStreamWriter> JfrChunkWriterBase;
+typedef MallocAdapter<M> JfrChunkBuffer; // 1 mb buffered writes
+typedef StreamWriterHost<JfrChunkBuffer, JfrCHeapObj> JfrBufferedChunkWriter;
+typedef WriterHost<BigEndianEncoder, CompressedIntegerEncoder, JfrBufferedChunkWriter> JfrChunkWriterBase;
 
-class JfrChunkState;
+class JfrChunk;
+class JfrChunkHeadWriter;
 
 class JfrChunkWriter : public JfrChunkWriterBase {
+  friend class JfrChunkHeadWriter;
   friend class JfrRepository;
  private:
-  JfrChunkState* _chunkstate;
-
+  JfrChunk* _chunk;
+  void set_path(const char* path);
+  int64_t flush_chunk(bool flushpoint);
   bool open();
-  size_t close(int64_t metadata_offset);
-  void write_header(int64_t metadata_offset);
-  void set_chunk_path(const char* chunk_path);
+  int64_t close();
+  int64_t current_chunk_start_nanos() const;
+  int64_t write_chunk_header_checkpoint(bool flushpoint);
 
  public:
   JfrChunkWriter();
-  bool initialize();
+  ~JfrChunkWriter();
+
   int64_t size_written() const;
   int64_t last_checkpoint_offset() const;
   void set_last_checkpoint_offset(int64_t offset);
-  void time_stamp_chunk_now();
+  void set_last_metadata_offset(int64_t offset);
+
+  bool has_metadata() const;
+  void set_time_stamp();
 };
 
 #endif // SHARE_JFR_RECORDER_REPOSITORY_JFRCHUNKWRITER_HPP
--- a/src/hotspot/share/jfr/recorder/repository/jfrEmergencyDump.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/repository/jfrEmergencyDump.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -248,7 +248,6 @@
 }
 
 static const char* create_emergency_dump_path() {
-  assert(JfrStream_lock->owned_by_self(), "invariant");
   char* buffer = NEW_RESOURCE_ARRAY_RETURN_NULL(char, JVM_MAXPATHLEN);
   if (NULL == buffer) {
     return NULL;
@@ -291,7 +290,6 @@
 // Caller needs ResourceMark
 static const char* create_emergency_chunk_path(const char* repository_path) {
   assert(repository_path != NULL, "invariant");
-  assert(JfrStream_lock->owned_by_self(), "invariant");
   const size_t repository_path_len = strlen(repository_path);
   // date time
   char date_time_buffer[32] = { 0 };
@@ -307,12 +305,11 @@
     return NULL;
   }
   // append the individual substrings
-  jio_snprintf(chunk_path, chunkname_max_len, "%s%s%s%s", repository_path_len, os::file_separator(), date_time_buffer, chunk_file_jfr_ext);
+  jio_snprintf(chunk_path, chunkname_max_len, "%s%s%s%s", repository_path, os::file_separator(), date_time_buffer, chunk_file_jfr_ext);
   return chunk_path;
 }
 
 static fio_fd emergency_dump_file_descriptor() {
-  assert(JfrStream_lock->owned_by_self(), "invariant");
   ResourceMark rm;
   const char* const emergency_dump_path = create_emergency_dump_path();
   return emergency_dump_path != NULL ? open_exclusivly(emergency_dump_path) : invalid_fd;
@@ -325,7 +322,6 @@
 void JfrEmergencyDump::on_vm_error(const char* repository_path) {
   assert(repository_path != NULL, "invariant");
   ResourceMark rm;
-  MutexLocker stream_lock(JfrStream_lock, Mutex::_no_safepoint_check_flag);
   const fio_fd emergency_fd = emergency_dump_file_descriptor();
   if (emergency_fd != invalid_fd) {
     RepositoryIterator iterator(repository_path, strlen(repository_path));
@@ -340,17 +336,25 @@
 *
 * 1. if the thread state is not "_thread_in_vm", we will quick transition
 *    it to "_thread_in_vm".
-* 2. the nesting state for both resource and handle areas are unknown,
-*    so we allocate new fresh arenas, discarding the old ones.
-* 3. if the thread is the owner of some critical lock(s), unlock them.
+* 2. if the thread is the owner of some critical lock(s), unlock them.
 *
 * If we end up deadlocking in the attempt of dumping out jfr data,
 * we rely on the WatcherThread task "is_error_reported()",
-* to exit the VM after a hard-coded timeout.
+* to exit the VM after a hard-coded timeout (disallow WatcherThread to emergency dump).
 * This "safety net" somewhat explains the aggressiveness in this attempt.
 *
 */
-static void prepare_for_emergency_dump(Thread* thread) {
+static bool prepare_for_emergency_dump() {
+  if (JfrStream_lock->owned_by_self()) {
+    // crashed during jfr rotation, disallow recursion
+    return false;
+  }
+  Thread* const thread = Thread::current();
+  if (thread->is_Watcher_thread()) {
+    // need WatcherThread as a safeguard against potential deadlocks
+    return false;
+  }
+
   if (thread->is_Java_thread()) {
     ((JavaThread*)thread)->set_thread_state(_thread_in_vm);
   }
@@ -388,7 +392,6 @@
     VMOperationRequest_lock->unlock();
   }
 
-
   if (Service_lock->owned_by_self()) {
     Service_lock->unlock();
   }
@@ -413,13 +416,10 @@
     JfrBuffer_lock->unlock();
   }
 
-  if (JfrStream_lock->owned_by_self()) {
-    JfrStream_lock->unlock();
-  }
-
   if (JfrStacktrace_lock->owned_by_self()) {
     JfrStacktrace_lock->unlock();
   }
+  return true;
 }
 
 static volatile int jfr_shutdown_lock = 0;
@@ -429,24 +429,9 @@
 }
 
 void JfrEmergencyDump::on_vm_shutdown(bool exception_handler) {
-  if (!guard_reentrancy()) {
+  if (!(guard_reentrancy() && prepare_for_emergency_dump())) {
     return;
   }
-  // function made non-reentrant
-  Thread* thread = Thread::current();
-  if (exception_handler) {
-    // we are crashing
-    if (thread->is_Watcher_thread()) {
-      // The Watcher thread runs the periodic thread sampling task.
-      // If it has crashed, it is likely that another thread is
-      // left in a suspended state. This would mean the system
-      // will not be able to ever move to a safepoint. We try
-      // to avoid issuing safepoint operations when attempting
-      // an emergency dump, but a safepoint might be already pending.
-      return;
-    }
-    prepare_for_emergency_dump(thread);
-  }
   EventDumpReason event;
   if (event.should_commit()) {
     event.set_reason(exception_handler ? "Crash" : "Out of Memory");
@@ -458,8 +443,6 @@
     LeakProfiler::emit_events(max_jlong, false);
   }
   const int messages = MSGBIT(MSG_VM_ERROR);
-  ResourceMark rm(thread);
-  HandleMark hm(thread);
   JfrRecorderService service;
   service.rotate(messages);
 }
--- a/src/hotspot/share/jfr/recorder/repository/jfrRepository.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/repository/jfrRepository.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -26,13 +26,14 @@
 #include "jfr/jfr.hpp"
 #include "jfr/jni/jfrJavaSupport.hpp"
 #include "jfr/recorder/jfrRecorder.hpp"
-#include "jfr/recorder/repository/jfrChunkState.hpp"
 #include "jfr/recorder/repository/jfrChunkWriter.hpp"
 #include "jfr/recorder/repository/jfrEmergencyDump.hpp"
 #include "jfr/recorder/repository/jfrRepository.hpp"
 #include "jfr/recorder/service/jfrPostBox.hpp"
+#include "logging/log.hpp"
 #include "memory/resourceArea.hpp"
 #include "runtime/mutex.hpp"
+#include "runtime/os.hpp"
 #include "runtime/thread.inline.hpp"
 
 static JfrRepository* _instance = NULL;
@@ -43,11 +44,6 @@
 
 static JfrChunkWriter* _chunkwriter = NULL;
 
-static bool initialize_chunkwriter() {
-  assert(_chunkwriter == NULL, "invariant");
-  _chunkwriter = new JfrChunkWriter();
-  return _chunkwriter != NULL && _chunkwriter->initialize();
-}
 
 JfrChunkWriter& JfrRepository::chunkwriter() {
   return *_chunkwriter;
@@ -56,7 +52,9 @@
 JfrRepository::JfrRepository(JfrPostBox& post_box) : _path(NULL), _post_box(post_box) {}
 
 bool JfrRepository::initialize() {
-  return initialize_chunkwriter();
+  assert(_chunkwriter == NULL, "invariant");
+  _chunkwriter = new JfrChunkWriter();
+  return _chunkwriter != NULL;
 }
 
 JfrRepository::~JfrRepository() {
@@ -84,7 +82,6 @@
 }
 
 void JfrRepository::on_vm_error() {
-  assert(!JfrStream_lock->owned_by_self(), "invariant");
   if (_path == NULL) {
     // completed already
     return;
@@ -107,15 +104,19 @@
   return true;
 }
 
-void JfrRepository::set_chunk_path(const char* path) {
-  assert(JfrStream_lock->owned_by_self(), "invariant");
-  chunkwriter().set_chunk_path(path);
+void JfrRepository::notify_on_new_chunk_path() {
+  if (Jfr::is_recording()) {
+    // rotations are synchronous, block until rotation completes
+    instance()._post_box.post(MSG_ROTATE);
+  }
 }
 
-void JfrRepository::notify_on_new_chunk_path() {
-  if (Jfr::is_recording()) {
-    instance()._post_box.post(MSG_ROTATE);
-  }
+void JfrRepository::set_chunk_path(const char* path) {
+  chunkwriter().set_path(path);
+}
+
+jlong JfrRepository::current_chunk_start_nanos() {
+  return chunkwriter().current_chunk_start_nanos();
 }
 
 /**
@@ -134,14 +135,11 @@
   DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(jt));
   ResourceMark rm(jt);
   const char* const canonical_chunk_path = JfrJavaSupport::c_str(path, jt);
-  {
-    MutexLocker stream_lock(JfrStream_lock, Mutex::_no_safepoint_check_flag);
-    if (NULL == canonical_chunk_path && !_chunkwriter->is_valid()) {
-      // new output is NULL and current output is NULL
-      return;
-    }
-    instance().set_chunk_path(canonical_chunk_path);
+  if (NULL == canonical_chunk_path && !_chunkwriter->is_valid()) {
+    // new output is NULL and current output is NULL
+    return;
   }
+  instance().set_chunk_path(canonical_chunk_path);
   notify_on_new_chunk_path();
 }
 
@@ -155,14 +153,28 @@
 }
 
 bool JfrRepository::open_chunk(bool vm_error /* false */) {
-  assert(JfrStream_lock->owned_by_self(), "invariant");
   if (vm_error) {
     ResourceMark rm;
-    _chunkwriter->set_chunk_path(JfrEmergencyDump::build_dump_path(_path));
+    _chunkwriter->set_path(JfrEmergencyDump::build_dump_path(_path));
   }
   return _chunkwriter->open();
 }
 
-size_t JfrRepository::close_chunk(int64_t metadata_offset) {
-  return _chunkwriter->close(metadata_offset);
+size_t JfrRepository::close_chunk() {
+  return _chunkwriter->close();
 }
+
+void JfrRepository::flush(JavaThread* jt) {
+  DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(jt));
+  if (!Jfr::is_recording()) {
+    return;
+  }
+  if (!_chunkwriter->is_valid()) {
+    return;
+  }
+  instance()._post_box.post(MSG_FLUSHPOINT);
+}
+
+size_t JfrRepository::flush_chunk() {
+  return _chunkwriter->flush_chunk(true);
+}
--- a/src/hotspot/share/jfr/recorder/repository/jfrRepository.hpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/repository/jfrRepository.hpp	Wed Oct 30 19:43:52 2019 +0100
@@ -55,8 +55,10 @@
   bool set_path(const char* path);
   void set_chunk_path(const char* path);
   bool open_chunk(bool vm_error = false);
-  size_t close_chunk(int64_t metadata_offset);
+  size_t close_chunk();
+  size_t flush_chunk();
   void on_vm_error();
+
   static void notify_on_new_chunk_path();
   static JfrChunkWriter& chunkwriter();
 
@@ -68,6 +70,8 @@
  public:
   static void set_path(jstring location, JavaThread* jt);
   static void set_chunk_path(jstring path, JavaThread* jt);
+  static void flush(JavaThread* jt);
+  static jlong current_chunk_start_nanos();
 };
 
 #endif // SHARE_JFR_RECORDER_REPOSITORY_JFRREPOSITORY_HPP
--- a/src/hotspot/share/jfr/recorder/service/jfrPostBox.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/service/jfrPostBox.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -26,14 +26,14 @@
 #include "jfr/recorder/service/jfrPostBox.hpp"
 #include "jfr/utilities/jfrTryLock.hpp"
 #include "runtime/atomic.hpp"
-#include "runtime/orderAccess.hpp"
 #include "runtime/thread.inline.hpp"
 
 #define MSG_IS_SYNCHRONOUS ( (MSGBIT(MSG_ROTATE)) |          \
                              (MSGBIT(MSG_STOP))   |          \
                              (MSGBIT(MSG_START))  |          \
                              (MSGBIT(MSG_CLONE_IN_MEMORY)) | \
-                             (MSGBIT(MSG_VM_ERROR))          \
+                             (MSGBIT(MSG_VM_ERROR))        | \
+                             (MSGBIT(MSG_FLUSHPOINT))        \
                            )
 
 static JfrPostBox* _instance = NULL;
@@ -84,7 +84,7 @@
 
 void JfrPostBox::deposit(int new_messages) {
   while (true) {
-    const int current_msgs = OrderAccess::load_acquire(&_messages);
+    const int current_msgs = Atomic::load(&_messages);
     // OR the new message
     const int exchange_value = current_msgs | new_messages;
     const int result = Atomic::cmpxchg(exchange_value, &_messages, current_msgs);
@@ -114,7 +114,7 @@
   deposit(msg);
   // serial_id is used to check when what we send in has been processed.
   // _msg_read_serial is read under JfrMsg_lock protection.
-  const uintptr_t serial_id = OrderAccess::load_acquire(&_msg_read_serial) + 1;
+  const uintptr_t serial_id = Atomic::load(&_msg_read_serial) + 1;
   msg_lock.notify_all();
   while (!is_message_processed(serial_id)) {
     msg_lock.wait();
@@ -129,12 +129,12 @@
  */
 bool JfrPostBox::is_message_processed(uintptr_t serial_id) const {
   assert(JfrMsg_lock->owned_by_self(), "_msg_handled_serial must be read under JfrMsg_lock protection");
-  return serial_id <= OrderAccess::load_acquire(&_msg_handled_serial);
+  return serial_id <= Atomic::load(&_msg_handled_serial);
 }
 
 bool JfrPostBox::is_empty() const {
   assert(JfrMsg_lock->owned_by_self(), "not holding JfrMsg_lock!");
-  return OrderAccess::load_acquire(&_messages) == 0;
+  return Atomic::load(&_messages) == 0;
 }
 
 int JfrPostBox::collect() {
--- a/src/hotspot/share/jfr/recorder/service/jfrPostBox.hpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/service/jfrPostBox.hpp	Wed Oct 30 19:43:52 2019 +0100
@@ -41,6 +41,7 @@
   MSG_SHUTDOWN,
   MSG_VM_ERROR,
   MSG_DEADBUFFER,
+  MSG_FLUSHPOINT,
   MSG_NO_OF_MSGS
 };
 
@@ -53,15 +54,16 @@
  *  MSG_START(1)            ; MSGBIT(MSG_START) == (1 << 0x1) == 0x2
  *  MSG_STOP (2)            ; MSGBIT(MSG_STOP) == (1 << 0x2) == 0x4
  *  MSG_ROTATE (3)          ; MSGBIT(MSG_ROTATE) == (1 << 0x3) == 0x8
- *  MSG_VM_ERROR (8)        ; MSGBIT(MSG_VM_ERROR) == (1 << 8) == 0x100
+ *  MSG_VM_ERROR (8)        ; MSGBIT(MSG_VM_ERROR) == (1 << 0x8) == 0x100
+ *  MSG_FLUSHPOINT (10)     ; MSGBIT(MSG_FLUSHPOINT) == (1 << 0xa) == 0x400
  *
  *  Asynchronous messages (posting thread returns immediately upon deposit):
  *
  *  MSG_FULLBUFFER (4)      ; MSGBIT(MSG_FULLBUFFER) == (1 << 0x4) == 0x10
- *  MSG_CHECKPOINT (5)      ; MSGBIT(CHECKPOINT) == (1 << 5) == 0x20
- *  MSG_WAKEUP (6)          ; MSGBIT(WAKEUP) == (1 << 6) == 0x40
- *  MSG_SHUTDOWN (7)        ; MSGBIT(MSG_SHUTDOWN) == (1 << 7) == 0x80
- *  MSG_DEADBUFFER (9)      ; MSGBIT(MSG_DEADBUFFER) == (1 << 9) == 0x200
+ *  MSG_CHECKPOINT (5)      ; MSGBIT(CHECKPOINT) == (1 << 0x5) == 0x20
+ *  MSG_WAKEUP (6)          ; MSGBIT(WAKEUP) == (1 << 0x6) == 0x40
+ *  MSG_SHUTDOWN (7)        ; MSGBIT(MSG_SHUTDOWN) == (1 << 0x7) == 0x80
+ *  MSG_DEADBUFFER (9)      ; MSGBIT(MSG_DEADBUFFER) == (1 << 0x9) == 0x200
  */
 
 class JfrPostBox : public JfrCHeapObj {
--- a/src/hotspot/share/jfr/recorder/service/jfrRecorderService.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/service/jfrRecorderService.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -23,6 +23,7 @@
  */
 
 #include "precompiled.hpp"
+#include "jfrfiles/jfrEventClasses.hpp"
 #include "jfr/jni/jfrJavaSupport.hpp"
 #include "jfr/leakprofiler/leakProfiler.hpp"
 #include "jfr/leakprofiler/checkpoint/objectSampleCheckpoint.hpp"
@@ -55,81 +56,102 @@
 #include "runtime/vmOperations.hpp"
 #include "runtime/vmThread.hpp"
 
-// set data iff *dest == NULL
-static bool try_set(void* const data, void** dest, bool clear) {
-  assert(data != NULL, "invariant");
-  const void* const current = OrderAccess::load_acquire(dest);
-  if (current != NULL) {
-    if (current != data) {
-      // already set
-      return false;
-    }
-    assert(current == data, "invariant");
-    if (!clear) {
-      // recursion disallowed
-      return false;
-    }
+// incremented on each flushpoint
+static u8 flushpoint_id = 0;
+
+template <typename E, typename Instance, size_t(Instance::*func)()>
+class Content {
+ private:
+  Instance& _instance;
+  u4 _elements;
+ public:
+  typedef E EventType;
+  Content(Instance& instance) : _instance(instance), _elements(0) {}
+  bool process() {
+    _elements = (u4)(_instance.*func)();
+    return true;
   }
-  return Atomic::cmpxchg(clear ? NULL : data, dest, current) == current;
-}
-
-static void* rotation_thread = NULL;
-static const int rotation_try_limit = 1000;
-static const int rotation_retry_sleep_millis = 10;
-
-class RotationLock : public StackObj {
- private:
-  Thread* const _thread;
-  bool _acquired;
+  u4 elements() const { return _elements; }
+};
 
-  void log(bool recursion) {
-    assert(!_acquired, "invariant");
-    const char* error_msg = NULL;
-    if (recursion) {
-      error_msg = "Unable to issue rotation due to recursive calls.";
-    }
-    else {
-      error_msg = "Unable to issue rotation due to wait timeout.";
-    }
-    log_info(jfr)( // For user, should not be "jfr, system"
-      "%s", error_msg);
-  }
+template <typename Content>
+class WriteContent : public StackObj {
+ protected:
+  const JfrTicks _start_time;
+  JfrTicks _end_time;
+  JfrChunkWriter& _cw;
+  Content& _content;
+  const int64_t _start_offset;
  public:
-  RotationLock(Thread* thread) : _thread(thread), _acquired(false) {
-    assert(_thread != NULL, "invariant");
-    if (_thread == rotation_thread) {
-      // recursion not supported
-      log(true);
-      return;
-    }
+  typedef typename Content::EventType EventType;
 
-    // limited to not spin indefinitely
-    for (int i = 0; i < rotation_try_limit; ++i) {
-      if (try_set(_thread, &rotation_thread, false)) {
-        _acquired = true;
-        assert(_thread == rotation_thread, "invariant");
-        return;
-      }
-      if (_thread->is_Java_thread()) {
-        // in order to allow the system to move to a safepoint
-        MutexLocker msg_lock(JfrMsg_lock);
-        JfrMsg_lock->wait(rotation_retry_sleep_millis);
-      }
-      else {
-        os::naked_short_sleep(rotation_retry_sleep_millis);
-      }
-    }
-    log(false);
+  WriteContent(JfrChunkWriter& cw, Content& content) :
+    _start_time(JfrTicks::now()),
+    _end_time(),
+    _cw(cw),
+    _content(content),
+    _start_offset(_cw.current_offset()) {
+    assert(_cw.is_valid(), "invariant");
+  }
+
+  bool process() {
+    // invocation
+    _content.process();
+    _end_time = JfrTicks::now();
+    return 0 != _content.elements();
   }
 
-  ~RotationLock() {
-    assert(_thread != NULL, "invariant");
-    if (_acquired) {
-      assert(_thread == rotation_thread, "invariant");
-      while (!try_set(_thread, &rotation_thread, true));
-    }
+  const JfrTicks& start_time() const {
+    return _start_time;
+  }
+
+  const JfrTicks& end_time() const {
+    return _end_time;
+  }
+
+  int64_t start_offset() const {
+    return _start_offset;
+  }
+
+  int64_t end_offset() const {
+    return current_offset();
+  }
+
+  int64_t current_offset() const {
+    return _cw.current_offset();
+  }
+
+  u4 elements() const {
+    return (u4) _content.elements();
   }
-  bool not_acquired() const { return !_acquired; }
+
+  u4 size() const {
+    return (u4)(end_offset() - start_offset());
+  }
+
+  static bool is_event_enabled() {
+    return EventType::is_enabled();
+  }
+
+  static u8 event_id() {
+    return EventType::eventId;
+  }
+
+  void write_elements(int64_t offset) {
+    _cw.write_padded_at_offset<u4>(elements(), offset);
+  }
+
+  void write_size() {
+    _cw.write_padded_at_offset<u4>(size(), start_offset());
+  }
+
+  void set_last_checkpoint() {
+    _cw.set_last_checkpoint_offset(start_offset());
+  }
+
+  void rewind() {
+    _cw.seek(start_offset());
+  }
 };
 
 static int64_t write_checkpoint_event_prologue(JfrChunkWriter& cw, u8 type_id) {
@@ -138,65 +160,176 @@
   cw.reserve(sizeof(u4));
   cw.write<u8>(EVENT_CHECKPOINT);
   cw.write(JfrTicks::now());
-  cw.write((int64_t)0); // duration
+  cw.write<u8>(0); // duration
   cw.write(delta_to_last_checkpoint);
-  cw.write<bool>(false); // flushpoint
-  cw.write((u4)1); // nof types in this checkpoint
+  cw.write<u4>(GENERIC); // checkpoint type
+  cw.write<u4>(1); // nof types in this checkpoint
   cw.write(type_id);
-  const int64_t number_of_elements_offset = cw.current_offset();
-  cw.reserve(sizeof(u4));
-  return number_of_elements_offset;
+  return cw.reserve(sizeof(u4));
 }
 
-template <typename ContentFunctor>
-class WriteCheckpointEvent : public StackObj {
+template <typename Content>
+class WriteCheckpointEvent : public WriteContent<Content> {
  private:
-  JfrChunkWriter& _cw;
-  u8 _type_id;
-  ContentFunctor& _content_functor;
+  const u8 _type_id;
  public:
-  WriteCheckpointEvent(JfrChunkWriter& cw, u8 type_id, ContentFunctor& functor) :
-    _cw(cw),
-    _type_id(type_id),
-    _content_functor(functor) {
-    assert(_cw.is_valid(), "invariant");
-  }
+  WriteCheckpointEvent(JfrChunkWriter& cw, Content& content, u8 type_id) :
+    WriteContent<Content>(cw, content), _type_id(type_id) {}
+
   bool process() {
-    // current_cp_offset is also offset for the event size header field
-    const int64_t current_cp_offset = _cw.current_offset();
-    const int64_t num_elements_offset = write_checkpoint_event_prologue(_cw, _type_id);
-    // invocation
-    _content_functor.process();
-    const u4 number_of_elements = (u4)_content_functor.processed();
-    if (number_of_elements == 0) {
+    const int64_t num_elements_offset = write_checkpoint_event_prologue(this->_cw, _type_id);
+    if (!WriteContent<Content>::process()) {
       // nothing to do, rewind writer to start
-      _cw.seek(current_cp_offset);
-      return true;
+      this->rewind();
+      assert(this->current_offset() == this->start_offset(), "invariant");
+      return false;
     }
-    assert(number_of_elements > 0, "invariant");
-    assert(_cw.current_offset() > num_elements_offset, "invariant");
-    _cw.write_padded_at_offset<u4>(number_of_elements, num_elements_offset);
-    _cw.write_padded_at_offset<u4>((u4)_cw.current_offset() - current_cp_offset, current_cp_offset);
-    // update writer with last checkpoint position
-    _cw.set_last_checkpoint_offset(current_cp_offset);
+    assert(this->elements() > 0, "invariant");
+    assert(this->current_offset() > num_elements_offset, "invariant");
+    this->write_elements(num_elements_offset);
+    this->write_size();
+    this->set_last_checkpoint();
     return true;
   }
 };
 
-template <typename Instance, size_t(Instance::*func)()>
-class ServiceFunctor {
+template <typename Functor>
+static u4 invoke(Functor& f) {
+  f.process();
+  return f.elements();
+}
+
+template <typename Functor>
+static void write_flush_event(Functor& f) {
+  if (Functor::is_event_enabled()) {
+    typename Functor::EventType e(UNTIMED);
+    e.set_starttime(f.start_time());
+    e.set_endtime(f.end_time());
+    e.set_flushId(flushpoint_id);
+    e.set_elements(f.elements());
+    e.set_size(f.size());
+    e.commit();
+  }
+}
+
+template <typename Functor>
+static u4 invoke_with_flush_event(Functor& f) {
+  const u4 elements = invoke(f);
+  write_flush_event(f);
+  return elements;
+}
+
+class StackTraceRepository : public StackObj {
  private:
-  Instance& _instance;
-  size_t _processed;
+  JfrStackTraceRepository& _repo;
+  JfrChunkWriter& _cw;
+  size_t _elements;
+  bool _clear;
+
  public:
-  ServiceFunctor(Instance& instance) : _instance(instance), _processed(0) {}
+  typedef EventFlushStacktrace EventType;
+  StackTraceRepository(JfrStackTraceRepository& repo, JfrChunkWriter& cw, bool clear) :
+    _repo(repo), _cw(cw), _elements(0), _clear(clear) {}
   bool process() {
-    _processed = (_instance.*func)();
+    _elements = _repo.write(_cw, _clear);
     return true;
   }
-  size_t processed() const { return _processed; }
+  size_t elements() const { return _elements; }
+  void reset() { _elements = 0; }
 };
 
+typedef WriteCheckpointEvent<StackTraceRepository> WriteStackTrace;
+
+static u4 flush_stacktrace(JfrStackTraceRepository& stack_trace_repo, JfrChunkWriter& chunkwriter) {
+  StackTraceRepository str(stack_trace_repo, chunkwriter, false);
+  WriteStackTrace wst(chunkwriter, str, TYPE_STACKTRACE);
+  return invoke_with_flush_event(wst);
+}
+
+static u4 write_stacktrace(JfrStackTraceRepository& stack_trace_repo, JfrChunkWriter& chunkwriter, bool clear) {
+  StackTraceRepository str(stack_trace_repo, chunkwriter, clear);
+  WriteStackTrace wst(chunkwriter, str, TYPE_STACKTRACE);
+  return invoke(wst);
+}
+
+typedef Content<EventFlushStorage, JfrStorage, &JfrStorage::write> Storage;
+typedef WriteContent<Storage> WriteStorage;
+
+static size_t flush_storage(JfrStorage& storage, JfrChunkWriter& chunkwriter) {
+  assert(chunkwriter.is_valid(), "invariant");
+  Storage fsf(storage);
+  WriteStorage fs(chunkwriter, fsf);
+  return invoke_with_flush_event(fs);
+}
+
+static size_t write_storage(JfrStorage& storage, JfrChunkWriter& chunkwriter) {
+  assert(chunkwriter.is_valid(), "invariant");
+  Storage fsf(storage);
+  WriteStorage fs(chunkwriter, fsf);
+  return invoke(fs);
+}
+
+typedef Content<EventFlushStringPool, JfrStringPool, &JfrStringPool::write> StringPool;
+typedef Content<EventFlushStringPool, JfrStringPool, &JfrStringPool::write_at_safepoint> StringPoolSafepoint;
+typedef WriteCheckpointEvent<StringPool> WriteStringPool;
+typedef WriteCheckpointEvent<StringPoolSafepoint> WriteStringPoolSafepoint;
+
+static u4 flush_stringpool(JfrStringPool& string_pool, JfrChunkWriter& chunkwriter) {
+  StringPool sp(string_pool);
+  WriteStringPool wsp(chunkwriter, sp, TYPE_STRING);
+  return invoke_with_flush_event(wsp);
+}
+
+static u4 write_stringpool(JfrStringPool& string_pool, JfrChunkWriter& chunkwriter) {
+  StringPool sp(string_pool);
+  WriteStringPool wsp(chunkwriter, sp, TYPE_STRING);
+  return invoke(wsp);
+}
+
+static u4 write_stringpool_safepoint(JfrStringPool& string_pool, JfrChunkWriter& chunkwriter) {
+  StringPoolSafepoint sps(string_pool);
+  WriteStringPoolSafepoint wsps(chunkwriter, sps, TYPE_STRING);
+  return invoke(wsps);
+}
+
+typedef Content<EventFlushTypeSet, JfrCheckpointManager, &JfrCheckpointManager::flush_type_set> FlushTypeSetFunctor;
+typedef WriteContent<FlushTypeSetFunctor> FlushTypeSet;
+
+static u4 flush_typeset(JfrCheckpointManager& checkpoint_manager, JfrChunkWriter& chunkwriter) {
+  FlushTypeSetFunctor flush_type_set(checkpoint_manager);
+  FlushTypeSet fts(chunkwriter, flush_type_set);
+  return invoke_with_flush_event(fts);
+}
+
+class MetadataEvent : public StackObj {
+ private:
+  JfrChunkWriter& _cw;
+ public:
+  typedef EventFlushMetadata EventType;
+  MetadataEvent(JfrChunkWriter& cw) : _cw(cw) {}
+  bool process() {
+    JfrMetadataEvent::write(_cw);
+    return true;
+  }
+  size_t elements() const { return 1; }
+};
+
+typedef WriteContent<MetadataEvent> WriteMetadata;
+
+static u4 flush_metadata(JfrChunkWriter& chunkwriter) {
+  assert(chunkwriter.is_valid(), "invariant");
+  MetadataEvent me(chunkwriter);
+  WriteMetadata wm(chunkwriter, me);
+  return invoke_with_flush_event(wm);
+}
+
+static u4 write_metadata(JfrChunkWriter& chunkwriter) {
+  assert(chunkwriter.is_valid(), "invariant");
+  MetadataEvent me(chunkwriter);
+  WriteMetadata wm(chunkwriter, me);
+  return invoke(wm);
+}
+
 template <typename Instance, void(Instance::*func)()>
 class JfrVMOperation : public VM_Operation {
  private:
@@ -208,23 +341,13 @@
   Mode evaluation_mode() const { return _safepoint; } // default
 };
 
-class WriteStackTraceRepository : public StackObj {
- private:
-  JfrStackTraceRepository& _repo;
-  JfrChunkWriter& _cw;
-  size_t _elements_processed;
-  bool _clear;
-
- public:
-  WriteStackTraceRepository(JfrStackTraceRepository& repo, JfrChunkWriter& cw, bool clear) :
-    _repo(repo), _cw(cw), _elements_processed(0), _clear(clear) {}
-  bool process() {
-    _elements_processed = _repo.write(_cw, _clear);
-    return true;
-  }
-  size_t processed() const { return _elements_processed; }
-  void reset() { _elements_processed = 0; }
-};
+JfrRecorderService::JfrRecorderService() :
+  _checkpoint_manager(JfrCheckpointManager::instance()),
+  _chunkwriter(JfrRepository::chunkwriter()),
+  _repository(JfrRepository::instance()),
+  _stack_trace_repository(JfrStackTraceRepository::instance()),
+  _storage(JfrStorage::instance()),
+  _string_pool(JfrStringPool::instance()) {}
 
 static bool recording = false;
 
@@ -237,19 +360,8 @@
   return recording;
 }
 
-JfrRecorderService::JfrRecorderService() :
-  _checkpoint_manager(JfrCheckpointManager::instance()),
-  _chunkwriter(JfrRepository::chunkwriter()),
-  _repository(JfrRepository::instance()),
-  _stack_trace_repository(JfrStackTraceRepository::instance()),
-  _storage(JfrStorage::instance()),
-  _string_pool(JfrStringPool::instance()) {}
-
 void JfrRecorderService::start() {
-  RotationLock rl(Thread::current());
-  if (rl.not_acquired()) {
-    return;
-  }
+  MutexLocker lock(JfrStream_lock);
   log_debug(jfr, system)("Request to START recording");
   assert(!is_recording(), "invariant");
   clear();
@@ -268,9 +380,9 @@
 }
 
 void JfrRecorderService::pre_safepoint_clear() {
-  _stack_trace_repository.clear();
   _string_pool.clear();
   _storage.clear();
+  _stack_trace_repository.clear();
 }
 
 void JfrRecorderService::invoke_safepoint_clear() {
@@ -278,28 +390,28 @@
   VMThread::execute(&safepoint_task);
 }
 
-//
-// safepoint clear sequence
-//
-//  clear stacktrace repository ->
-//    clear string pool ->
-//      clear storage ->
-//        shift epoch ->
-//          update time
-//
 void JfrRecorderService::safepoint_clear() {
   assert(SafepointSynchronize::is_at_safepoint(), "invariant");
-  _stack_trace_repository.clear();
   _string_pool.clear();
   _storage.clear();
   _checkpoint_manager.shift_epoch();
-  _chunkwriter.time_stamp_chunk_now();
+  _chunkwriter.set_time_stamp();
+  _stack_trace_repository.clear();
 }
 
 void JfrRecorderService::post_safepoint_clear() {
   _checkpoint_manager.clear();
 }
 
+void JfrRecorderService::open_new_chunk(bool vm_error) {
+  JfrChunkRotation::on_rotation();
+  const bool valid_chunk = _repository.open_chunk(vm_error);
+  _storage.control().set_to_disk(valid_chunk);
+  if (valid_chunk) {
+    _checkpoint_manager.write_static_type_set_and_threads();
+  }
+}
+
 static void stop() {
   assert(JfrRecorderService::is_recording(), "invariant");
   log_debug(jfr, system)("Recording STOPPED");
@@ -307,11 +419,30 @@
   assert(!JfrRecorderService::is_recording(), "invariant");
 }
 
+void JfrRecorderService::prepare_for_vm_error_rotation() {
+  assert(JfrStream_lock->owned_by_self(), "invariant");
+  if (!_chunkwriter.is_valid()) {
+    open_new_chunk(true);
+  }
+  _checkpoint_manager.register_service_thread(Thread::current());
+}
+
+void JfrRecorderService::vm_error_rotation() {
+  assert(JfrStream_lock->owned_by_self(), "invariant");
+  if (_chunkwriter.is_valid()) {
+    Thread* const t = Thread::current();
+    _storage.flush_regular_buffer(t->jfr_thread_local()->native_buffer(), t);
+    invoke_flush();
+    _chunkwriter.set_time_stamp();
+    _repository.close_chunk();
+    assert(!_chunkwriter.is_valid(), "invariant");
+    _repository.on_vm_error();
+  }
+}
+
 void JfrRecorderService::rotate(int msgs) {
-  RotationLock rl(Thread::current());
-  if (rl.not_acquired()) {
-    return;
-  }
+  assert(!JfrStream_lock->owned_by_self(), "invariant");
+  MutexLocker lock(JfrStream_lock);
   static bool vm_error = false;
   if (msgs & MSGBIT(MSG_VM_ERROR)) {
     vm_error = true;
@@ -329,45 +460,19 @@
   }
 }
 
-void JfrRecorderService::prepare_for_vm_error_rotation() {
-  if (!_chunkwriter.is_valid()) {
-    open_new_chunk(true);
-  }
-  _checkpoint_manager.register_service_thread(Thread::current());
-  JfrMetadataEvent::lock();
-}
-
-void JfrRecorderService::open_new_chunk(bool vm_error) {
-  assert(!_chunkwriter.is_valid(), "invariant");
-  assert(!JfrStream_lock->owned_by_self(), "invariant");
-  JfrChunkRotation::on_rotation();
-  MutexLocker stream_lock(JfrStream_lock, Mutex::_no_safepoint_check_flag);
-  if (!_repository.open_chunk(vm_error)) {
-    assert(!_chunkwriter.is_valid(), "invariant");
-    _storage.control().set_to_disk(false);
-    return;
-  }
-  assert(_chunkwriter.is_valid(), "invariant");
-  _storage.control().set_to_disk(true);
-}
-
 void JfrRecorderService::in_memory_rotation() {
-  assert(!_chunkwriter.is_valid(), "invariant");
+  assert(JfrStream_lock->owned_by_self(), "invariant");
   // currently running an in-memory recording
+  assert(!_storage.control().to_disk(), "invariant");
   open_new_chunk();
   if (_chunkwriter.is_valid()) {
     // dump all in-memory buffer data to the newly created chunk
-    serialize_storage_from_in_memory_recording();
+    write_storage(_storage, _chunkwriter);
   }
 }
 
-void JfrRecorderService::serialize_storage_from_in_memory_recording() {
-  assert(!JfrStream_lock->owned_by_self(), "not holding stream lock!");
-  MutexLocker stream_lock(JfrStream_lock, Mutex::_no_safepoint_check_flag);
-  _storage.write();
-}
-
 void JfrRecorderService::chunk_rotation() {
+  assert(JfrStream_lock->owned_by_self(), "invariant");
   finalize_current_chunk();
   open_new_chunk();
 }
@@ -375,7 +480,6 @@
 void JfrRecorderService::finalize_current_chunk() {
   assert(_chunkwriter.is_valid(), "invariant");
   write();
-  assert(!_chunkwriter.is_valid(), "invariant");
 }
 
 void JfrRecorderService::write() {
@@ -386,54 +490,20 @@
   post_safepoint_write();
 }
 
-typedef ServiceFunctor<JfrStringPool, &JfrStringPool::write> WriteStringPool;
-typedef ServiceFunctor<JfrStringPool, &JfrStringPool::write_at_safepoint> WriteStringPoolSafepoint;
-typedef WriteCheckpointEvent<WriteStackTraceRepository> WriteStackTraceCheckpoint;
-typedef WriteCheckpointEvent<WriteStringPool> WriteStringPoolCheckpoint;
-typedef WriteCheckpointEvent<WriteStringPoolSafepoint> WriteStringPoolCheckpointSafepoint;
-
-static void write_stacktrace_checkpoint(JfrStackTraceRepository& stack_trace_repo, JfrChunkWriter& chunkwriter, bool clear) {
-  WriteStackTraceRepository write_stacktrace_repo(stack_trace_repo, chunkwriter, clear);
-  WriteStackTraceCheckpoint write_stack_trace_checkpoint(chunkwriter, TYPE_STACKTRACE, write_stacktrace_repo);
-  write_stack_trace_checkpoint.process();
-}
-static void write_stringpool_checkpoint(JfrStringPool& string_pool, JfrChunkWriter& chunkwriter) {
-  WriteStringPool write_string_pool(string_pool);
-  WriteStringPoolCheckpoint write_string_pool_checkpoint(chunkwriter, TYPE_STRING, write_string_pool);
-  write_string_pool_checkpoint.process();
-}
-
-static void write_stringpool_checkpoint_safepoint(JfrStringPool& string_pool, JfrChunkWriter& chunkwriter) {
-  WriteStringPoolSafepoint write_string_pool(string_pool);
-  WriteStringPoolCheckpointSafepoint write_string_pool_checkpoint(chunkwriter, TYPE_STRING, write_string_pool);
-  write_string_pool_checkpoint.process();
-}
-
-//
-// pre-safepoint write sequence
-//
-//  lock stream lock ->
-//    write non-safepoint dependent types ->
-//      write checkpoint epoch transition list->
-//        write stack trace checkpoint ->
-//          write string pool checkpoint ->
-//            write object sample stacktraces ->
-//              write storage ->
-//                release stream lock
-//
 void JfrRecorderService::pre_safepoint_write() {
-  MutexLocker stream_lock(JfrStream_lock, Mutex::_no_safepoint_check_flag);
   assert(_chunkwriter.is_valid(), "invariant");
-  _checkpoint_manager.write_types();
-  _checkpoint_manager.write_epoch_transition_mspace();
-  write_stacktrace_checkpoint(_stack_trace_repository, _chunkwriter, false);
-  write_stringpool_checkpoint(_string_pool, _chunkwriter);
   if (LeakProfiler::is_running()) {
     // Exclusive access to the object sampler instance.
     // The sampler is released (unlocked) later in post_safepoint_write.
     ObjectSampleCheckpoint::on_rotation(ObjectSampler::acquire(), _stack_trace_repository);
   }
-  _storage.write();
+  if (_string_pool.is_modified()) {
+    write_stringpool(_string_pool, _chunkwriter);
+  }
+  write_storage(_storage, _chunkwriter);
+  if (_stack_trace_repository.is_modified()) {
+    write_stacktrace(_stack_trace_repository, _chunkwriter, false);
+  }
 }
 
 void JfrRecorderService::invoke_safepoint_write() {
@@ -441,50 +511,18 @@
   VMThread::execute(&safepoint_task);
 }
 
-//
-// safepoint write sequence
-//
-//   lock stream lock ->
-//       write stacktrace repository ->
-//         write string pool ->
-//           write safepoint dependent types ->
-//             write storage ->
-//                 shift_epoch ->
-//                   update time ->
-//                     lock metadata descriptor ->
-//                       release stream lock
-//
 void JfrRecorderService::safepoint_write() {
   assert(SafepointSynchronize::is_at_safepoint(), "invariant");
-  MutexLocker stream_lock(JfrStream_lock, Mutex::_no_safepoint_check_flag);
-  write_stacktrace_checkpoint(_stack_trace_repository, _chunkwriter, true);
-  write_stringpool_checkpoint_safepoint(_string_pool, _chunkwriter);
-  _checkpoint_manager.write_safepoint_types();
+  if (_string_pool.is_modified()) {
+    write_stringpool_safepoint(_string_pool, _chunkwriter);
+  }
+  _checkpoint_manager.on_rotation();
   _storage.write_at_safepoint();
   _checkpoint_manager.shift_epoch();
-  _chunkwriter.time_stamp_chunk_now();
-  JfrMetadataEvent::lock();
-}
-
-static int64_t write_metadata_event(JfrChunkWriter& chunkwriter) {
-  assert(chunkwriter.is_valid(), "invariant");
-  const int64_t metadata_offset = chunkwriter.current_offset();
-  JfrMetadataEvent::write(chunkwriter, metadata_offset);
-  return metadata_offset;
+  _chunkwriter.set_time_stamp();
+  write_stacktrace(_stack_trace_repository, _chunkwriter, true);
 }
 
-//
-// post-safepoint write sequence
-//
-//   write type set ->
-//     release object sampler ->
-//       lock stream lock ->
-//         write checkpoints ->
-//           write metadata event ->
-//             write chunk header ->
-//               close chunk fd ->
-//                 release stream lock
-//
 void JfrRecorderService::post_safepoint_write() {
   assert(_chunkwriter.is_valid(), "invariant");
   // During the safepoint tasks just completed, the system transitioned to a new epoch.
@@ -497,38 +535,84 @@
     // Note: There is a dependency on write_type_set() above, ensure the release is subsequent.
     ObjectSampler::release();
   }
-  MutexLocker stream_lock(JfrStream_lock, Mutex::_no_safepoint_check_flag);
-  // serialize any outstanding checkpoint memory
-  _checkpoint_manager.write();
   // serialize the metadata descriptor event and close out the chunk
-  _repository.close_chunk(write_metadata_event(_chunkwriter));
-  assert(!_chunkwriter.is_valid(), "invariant");
+  write_metadata(_chunkwriter);
+  _repository.close_chunk();
+}
+
+static JfrBuffer* thread_local_buffer(Thread* t) {
+  assert(t != NULL, "invariant");
+  return t->jfr_thread_local()->native_buffer();
 }
 
-void JfrRecorderService::vm_error_rotation() {
-  if (_chunkwriter.is_valid()) {
-    finalize_current_chunk_on_vm_error();
-    assert(!_chunkwriter.is_valid(), "invariant");
-    _repository.on_vm_error();
+static void reset_buffer(JfrBuffer* buffer, Thread* t) {
+  assert(buffer != NULL, "invariant");
+  assert(t != NULL, "invariant");
+  assert(buffer == thread_local_buffer(t), "invariant");
+  buffer->set_pos(const_cast<u1*>(buffer->top()));
+}
+
+static void reset_thread_local_buffer(Thread* t) {
+  reset_buffer(thread_local_buffer(t), t);
+}
+
+static void write_thread_local_buffer(JfrChunkWriter& chunkwriter, Thread* t) {
+  JfrBuffer * const buffer = thread_local_buffer(t);
+  assert(buffer != NULL, "invariant");
+  if (!buffer->empty()) {
+    chunkwriter.write_unbuffered(buffer->top(), buffer->pos() - buffer->top());
+    reset_buffer(buffer, t);
   }
 }
 
-void JfrRecorderService::finalize_current_chunk_on_vm_error() {
+size_t JfrRecorderService::flush() {
+  assert(JfrStream_lock->owned_by_self(), "invariant");
+  size_t total_elements = flush_metadata(_chunkwriter);
+  const size_t storage_elements = flush_storage(_storage, _chunkwriter);
+  if (0 == storage_elements) {
+    return total_elements;
+  }
+  total_elements += storage_elements;
+  if (_string_pool.is_modified()) {
+    total_elements += flush_stringpool(_string_pool, _chunkwriter);
+  }
+  if (_stack_trace_repository.is_modified()) {
+    total_elements += flush_stacktrace(_stack_trace_repository, _chunkwriter);
+  }
+  if (_checkpoint_manager.is_type_set_required()) {
+    total_elements += flush_typeset(_checkpoint_manager, _chunkwriter);
+  } else if (_checkpoint_manager.is_static_type_set_required()) {
+    // don't tally this, it is only in order to flush the waiting constants
+    _checkpoint_manager.flush_static_type_set();
+  }
+  return total_elements;
+}
+
+typedef Content<EventFlush, JfrRecorderService, &JfrRecorderService::flush> FlushFunctor;
+typedef WriteContent<FlushFunctor> Flush;
+
+void JfrRecorderService::invoke_flush() {
+  assert(JfrStream_lock->owned_by_self(), "invariant");
   assert(_chunkwriter.is_valid(), "invariant");
-  pre_safepoint_write();
-  // Do not attempt safepoint dependent operations during emergency dump.
-  // Optimistically write tagged artifacts.
-  _checkpoint_manager.shift_epoch();
-  // update time
-  _chunkwriter.time_stamp_chunk_now();
-  post_safepoint_write();
-  assert(!_chunkwriter.is_valid(), "invariant");
+  Thread* const t = Thread::current();
+  ResourceMark rm(t);
+  HandleMark hm(t);
+  ++flushpoint_id;
+  reset_thread_local_buffer(t);
+  FlushFunctor flushpoint(*this);
+  Flush fl(_chunkwriter, flushpoint);
+  invoke_with_flush_event(fl);
+  write_thread_local_buffer(_chunkwriter, t);
+  _repository.flush_chunk();
+}
+
+void JfrRecorderService::flushpoint() {
+  MutexLocker lock(JfrStream_lock);
+  invoke_flush();
 }
 
 void JfrRecorderService::process_full_buffers() {
   if (_chunkwriter.is_valid()) {
-    assert(!JfrStream_lock->owned_by_self(), "invariant");
-    MutexLocker stream_lock(JfrStream_lock, Mutex::_no_safepoint_check_flag);
     _storage.write_full();
   }
 }
--- a/src/hotspot/share/jfr/recorder/service/jfrRecorderService.hpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/service/jfrRecorderService.hpp	Wed Oct 30 19:43:52 2019 +0100
@@ -46,11 +46,10 @@
   void open_new_chunk(bool vm_error = false);
   void chunk_rotation();
   void in_memory_rotation();
-  void serialize_storage_from_in_memory_recording();
   void finalize_current_chunk();
-  void finalize_current_chunk_on_vm_error();
   void prepare_for_vm_error_rotation();
   void vm_error_rotation();
+  void invoke_flush();
 
   void clear();
   void pre_safepoint_clear();
@@ -67,7 +66,9 @@
  public:
   JfrRecorderService();
   void start();
+  size_t flush();
   void rotate(int msgs);
+  void flushpoint();
   void process_full_buffers();
   void scavenge();
   void evaluate_chunk_size_for_rotation();
--- a/src/hotspot/share/jfr/recorder/service/jfrRecorderThread.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/service/jfrRecorderThread.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved.
+ * 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
@@ -27,6 +27,7 @@
 #include "classfile/javaClasses.hpp"
 #include "classfile/symbolTable.hpp"
 #include "classfile/systemDictionary.hpp"
+#include "jfr/jfr.hpp"
 #include "jfr/jni/jfrJavaSupport.hpp"
 #include "jfr/recorder/jfrRecorder.hpp"
 #include "jfr/recorder/checkpoint/jfrCheckpointManager.hpp"
@@ -64,7 +65,6 @@
   if (allocation_failed) {
     JfrJavaSupport::throw_out_of_memory_error("Unable to create native recording thread for JFR", CHECK_NULL);
   }
-
   Thread::start(new_thread);
   return new_thread;
 }
@@ -98,8 +98,9 @@
   instanceHandle h_thread_oop(THREAD, (instanceOop)result.get_jobject());
   assert(h_thread_oop.not_null(), "invariant");
   // attempt thread start
-  const Thread* const t = start_thread(h_thread_oop, recorderthread_entry,THREAD);
+  Thread* const t = start_thread(h_thread_oop, recorderthread_entry,THREAD);
   if (!HAS_PENDING_EXCEPTION) {
+    Jfr::exclude_thread(t);
     cp_manager->register_service_thread(t);
     return true;
   }
--- a/src/hotspot/share/jfr/recorder/service/jfrRecorderThreadLoop.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/service/jfrRecorderThreadLoop.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -40,6 +40,7 @@
   #define START (msgs & (MSGBIT(MSG_START)))
   #define SHUTDOWN (msgs & MSGBIT(MSG_SHUTDOWN))
   #define ROTATE (msgs & (MSGBIT(MSG_ROTATE)|MSGBIT(MSG_STOP)))
+  #define FLUSHPOINT (msgs & (MSGBIT(MSG_FLUSHPOINT)))
   #define PROCESS_FULL_BUFFERS (msgs & (MSGBIT(MSG_ROTATE)|MSGBIT(MSG_STOP)|MSGBIT(MSG_FULLBUFFER)))
   #define SCAVENGE (msgs & (MSGBIT(MSG_DEADBUFFER)))
 
@@ -72,6 +73,8 @@
         service.start();
       } else if (ROTATE) {
         service.rotate(msgs);
+      } else if (FLUSHPOINT) {
+        service.flushpoint();
       }
       JfrMsg_lock->lock();
       post_box.notify_waiters();
@@ -90,6 +93,7 @@
   #undef START
   #undef SHUTDOWN
   #undef ROTATE
+  #undef FLUSHPOINT
   #undef PROCESS_FULL_BUFFERS
   #undef SCAVENGE
 }
--- a/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTraceRepository.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTraceRepository.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -62,7 +62,7 @@
 };
 
 bool JfrStackTraceRepository::initialize() {
-  return JfrSerializer::register_serializer(TYPE_FRAMETYPE, false, true, new JfrFrameType());
+  return JfrSerializer::register_serializer(TYPE_FRAMETYPE, true, new JfrFrameType());
 }
 
 void JfrStackTraceRepository::destroy() {
@@ -71,7 +71,16 @@
   _instance = NULL;
 }
 
-size_t JfrStackTraceRepository::write_impl(JfrChunkWriter& sw, bool clear) {
+static traceid last_id = 0;
+
+bool JfrStackTraceRepository::is_modified() const {
+  return last_id != _next_id;
+}
+
+size_t JfrStackTraceRepository::write(JfrChunkWriter& sw, bool clear) {
+  if (_entries == 0) {
+    return 0;
+  }
   MutexLocker lock(JfrStacktrace_lock, Mutex::_no_safepoint_check_flag);
   assert(_entries > 0, "invariant");
   int count = 0;
@@ -93,29 +102,10 @@
     memset(_table, 0, sizeof(_table));
     _entries = 0;
   }
+  last_id = _next_id;
   return count;
 }
 
-size_t JfrStackTraceRepository::write(JfrChunkWriter& sw, bool clear) {
-  return _entries > 0 ? write_impl(sw, clear) : 0;
-}
-
-traceid JfrStackTraceRepository::write(JfrCheckpointWriter& writer, traceid id, unsigned int hash) {
-  assert(JfrStacktrace_lock->owned_by_self(), "invariant");
-  const JfrStackTrace* const trace = lookup(hash, id);
-  assert(trace != NULL, "invariant");
-  assert(trace->hash() == hash, "invariant");
-  assert(trace->id() == id, "invariant");
-  trace->write(writer);
-  return id;
-}
-
-void JfrStackTraceRepository::write_metadata(JfrCheckpointWriter& writer) {
-  JfrFrameType fct;
-  writer.write_type(TYPE_FRAMETYPE);
-  fct.serialize(writer);
-}
-
 size_t JfrStackTraceRepository::clear() {
   MutexLocker lock(JfrStacktrace_lock, Mutex::_no_safepoint_check_flag);
   if (_entries == 0) {
@@ -142,7 +132,7 @@
   if (tl->has_cached_stack_trace()) {
     return tl->cached_stack_trace_id();
   }
-  if (!thread->is_Java_thread() || thread->is_hidden_from_external_view()) {
+  if (!thread->is_Java_thread() || thread->is_hidden_from_external_view() || tl->is_excluded()) {
     return 0;
   }
   JfrStackFrame* frames = tl->stackframes();
--- a/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTraceRepository.hpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/stacktrace/jfrStackTraceRepository.hpp	Wed Oct 30 19:43:52 2019 +0100
@@ -40,7 +40,7 @@
   friend class ObjectSampleCheckpoint;
   friend class ObjectSampler;
   friend class StackTraceBlobInstaller;
-  friend class WriteStackTraceRepository;
+  friend class StackTraceRepository;
 
  private:
   static const u4 TABLE_SIZE = 2053;
@@ -51,19 +51,18 @@
   JfrStackTraceRepository();
   static JfrStackTraceRepository& instance();
   static JfrStackTraceRepository* create();
+  static void destroy();
   bool initialize();
-  static void destroy();
 
-  size_t write_impl(JfrChunkWriter& cw, bool clear);
-  static void write_metadata(JfrCheckpointWriter& cpw);
-  traceid write(JfrCheckpointWriter& cpw, traceid id, unsigned int hash);
+  bool is_modified() const;
   size_t write(JfrChunkWriter& cw, bool clear);
   size_t clear();
 
+  const JfrStackTrace* lookup(unsigned int hash, traceid id) const;
+
   traceid add_trace(const JfrStackTrace& stacktrace);
   static traceid add(const JfrStackTrace& stacktrace);
   traceid record_for(JavaThread* thread, int skip, JfrStackFrame* frames, u4 max_frames);
-  const JfrStackTrace* lookup(unsigned int hash, traceid id) const;
 
  public:
   static traceid record(Thread* thread, int skip = 0);
--- a/src/hotspot/share/jfr/recorder/storage/jfrBuffer.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/storage/jfrBuffer.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -54,10 +54,18 @@
   return true;
 }
 
-void JfrBuffer::reinitialize() {
+void JfrBuffer::reinitialize(bool exclusion /* false */) {
   assert(!lease(), "invariant");
   assert(!transient(), "invariant");
   set_pos(start());
+  if (exclusion != excluded()) {
+    // update
+    if (exclusion) {
+      set_excluded();
+    } else {
+      clear_excluded();
+    }
+  }
   clear_retired();
   set_top(start());
 }
@@ -80,7 +88,7 @@
 const u1* JfrBuffer::stable_top() const {
   const u1* current_top;
   do {
-    current_top = OrderAccess::load_acquire(&_top);
+    current_top = Atomic::load(&_top);
   } while (MUTEX_CLAIM == current_top);
   return current_top;
 }
@@ -107,7 +115,8 @@
   assert(new_top <= end(), "invariant");
   assert(new_top >= start(), "invariant");
   assert(top() == MUTEX_CLAIM, "invariant");
-  OrderAccess::release_store(&_top, new_top);
+  OrderAccess::storestore();
+  _top = new_top;
 }
 
 size_t JfrBuffer::unflushed_size() const {
@@ -118,18 +127,19 @@
   assert(id != NULL, "invariant");
   const void* current_id;
   do {
-    current_id = OrderAccess::load_acquire(&_identity);
+    current_id = Atomic::load(&_identity);
   } while (current_id != NULL || Atomic::cmpxchg(id, &_identity, current_id) != current_id);
 }
 
 bool JfrBuffer::try_acquire(const void* id) {
   assert(id != NULL, "invariant");
-  const void* const current_id = OrderAccess::load_acquire(&_identity);
+  const void* const current_id = Atomic::load(&_identity);
   return current_id == NULL && Atomic::cmpxchg(id, &_identity, current_id) == current_id;
 }
 
 void JfrBuffer::release() {
-  OrderAccess::release_store(&_identity, (const void*)NULL);
+  OrderAccess::storestore();
+  _identity = NULL;
 }
 
 bool JfrBuffer::acquired_by(const void* id) const {
@@ -186,7 +196,8 @@
 enum FLAG {
   RETIRED = 1,
   TRANSIENT = 2,
-  LEASE = 4
+  LEASE = 4,
+  EXCLUDED = 8
 };
 
 bool JfrBuffer::transient() const {
@@ -221,27 +232,35 @@
   assert(!lease(), "invariant");
 }
 
-static u2 load_acquire_flags(const u2* const flags) {
-  return OrderAccess::load_acquire(flags);
+bool JfrBuffer::excluded() const {
+  return (u1)EXCLUDED == (_flags & (u1)EXCLUDED);
 }
 
-static void release_store_flags(u2* const flags, u2 new_flags) {
-  OrderAccess::release_store(flags, new_flags);
+void JfrBuffer::set_excluded() {
+  _flags |= (u1)EXCLUDED;
+  assert(excluded(), "invariant");
+}
+
+void JfrBuffer::clear_excluded() {
+  if (excluded()) {
+    OrderAccess::storestore();
+    _flags ^= (u1)EXCLUDED;
+  }
+  assert(!excluded(), "invariant");
 }
 
 bool JfrBuffer::retired() const {
-  return (u1)RETIRED == (load_acquire_flags(&_flags) & (u1)RETIRED);
+  return (_flags & (u1)RETIRED) == (u1)RETIRED;
 }
 
 void JfrBuffer::set_retired() {
-  const u2 new_flags = load_acquire_flags(&_flags) | (u1)RETIRED;
-  release_store_flags(&_flags, new_flags);
+  OrderAccess::storestore();
+  _flags |= (u1)RETIRED;
 }
 
 void JfrBuffer::clear_retired() {
-  u2 new_flags = load_acquire_flags(&_flags);
-  if ((u1)RETIRED == (new_flags & (u1)RETIRED)) {
-    new_flags ^= (u1)RETIRED;
-    release_store_flags(&_flags, new_flags);
+  if (retired()) {
+    OrderAccess::storestore();
+    _flags ^= (u1)RETIRED;
   }
 }
--- a/src/hotspot/share/jfr/recorder/storage/jfrBuffer.hpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/storage/jfrBuffer.hpp	Wed Oct 30 19:43:52 2019 +0100
@@ -61,7 +61,7 @@
  public:
   JfrBuffer();
   bool initialize(size_t header_size, size_t size, const void* id = NULL);
-  void reinitialize();
+  void reinitialize(bool exclusion = false);
   void concurrent_reinitialization();
   size_t discard();
   JfrBuffer* next() const {
@@ -165,12 +165,15 @@
   bool retired() const;
   void set_retired();
   void clear_retired();
+
+  bool excluded() const;
+  void set_excluded();
+  void clear_excluded();
 };
 
 class JfrAgeNode : public JfrBuffer {
  private:
   JfrBuffer* _retired;
-
  public:
   JfrAgeNode() : _retired(NULL) {}
   void set_retired_buffer(JfrBuffer* retired) {
--- a/src/hotspot/share/jfr/recorder/storage/jfrMemorySpace.hpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/storage/jfrMemorySpace.hpp	Wed Oct 30 19:43:52 2019 +0100
@@ -99,8 +99,8 @@
   template <typename IteratorCallback, typename IteratorType>
   void iterate(IteratorCallback& callback, bool full = true, jfr_iter_direction direction = forward);
 
-  debug_only(bool in_full_list(const Type* t) const { return _full.in_list(t); })
-  debug_only(bool in_free_list(const Type* t) const { return _free.in_list(t); })
+  bool in_full_list(const Type* t) const { return _full.in_list(t); }
+  bool in_free_list(const Type* t) const { return _free.in_list(t); }
 };
 
 #endif // SHARE_JFR_RECORDER_STORAGE_JFRMEMORYSPACE_HPP
--- a/src/hotspot/share/jfr/recorder/storage/jfrMemorySpace.inline.hpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/storage/jfrMemorySpace.inline.hpp	Wed Oct 30 19:43:52 2019 +0100
@@ -141,6 +141,7 @@
   }
   assert(t->empty(), "invariant");
   assert(!t->retired(), "invariant");
+  assert(!t->excluded(), "invariant");
   assert(t->identity() == NULL, "invariant");
   if (!should_populate_cache()) {
     remove_free(t);
--- a/src/hotspot/share/jfr/recorder/storage/jfrStorage.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/storage/jfrStorage.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -26,6 +26,7 @@
 #include "jfr/jfrEvents.hpp"
 #include "jfr/jni/jfrJavaSupport.hpp"
 #include "jfr/recorder/jfrRecorder.hpp"
+#include "jfr/recorder/checkpoint/jfrCheckpointManager.hpp"
 #include "jfr/recorder/repository/jfrChunkWriter.hpp"
 #include "jfr/recorder/service/jfrOptionSet.hpp"
 #include "jfr/recorder/service/jfrPostBox.hpp"
@@ -253,6 +254,18 @@
     assert(buffer->empty(), "invariant");
     return true;
   }
+
+  if (buffer->excluded()) {
+    const bool thread_is_excluded = thread->jfr_thread_local()->is_excluded();
+    buffer->reinitialize(thread_is_excluded);
+    assert(buffer->empty(), "invariant");
+    if (!thread_is_excluded) {
+      // state change from exclusion to inclusion requires a thread checkpoint
+      JfrCheckpointManager::write_thread_checkpoint(thread);
+    }
+    return true;
+  }
+
   BufferPtr const promotion_buffer = get_promotion_buffer(unflushed_size, _global_mspace, *this, promotion_retry, thread);
   if (promotion_buffer == NULL) {
     write_data_loss(buffer, thread);
@@ -301,7 +314,7 @@
   assert(buffer != NULL, "invariant");
   assert(buffer->retired(), "invariant");
   const size_t unflushed_size = buffer->unflushed_size();
-  buffer->reinitialize();
+  buffer->concurrent_reinitialization();
   log_registration_failure(unflushed_size);
 }
 
@@ -470,6 +483,7 @@
   assert(t != NULL, "invariant");
   assert(cur != NULL, "invariant");
   assert(cur->lease(), "invariant");
+  assert(!cur->excluded(), "invariant");
   assert(cur_pos != NULL, "invariant");
   assert(native ? t->jfr_thread_local()->native_buffer() == cur : t->jfr_thread_local()->java_buffer() == cur, "invariant");
   assert(t->jfr_thread_local()->shelved_buffer() != NULL, "invariant");
@@ -496,6 +510,9 @@
   // the case for stable thread local buffers; it is not the case for large buffers.
   if (!cur->empty()) {
     flush_regular_buffer(cur, t);
+    if (cur->excluded()) {
+      return cur;
+    }
   }
   assert(t->jfr_thread_local()->shelved_buffer() == NULL, "invariant");
   if (cur->free_size() >= req) {
@@ -584,28 +601,40 @@
 typedef UnBufferedWriteToChunk<JfrBuffer> WriteOperation;
 typedef MutexedWriteOp<WriteOperation> MutexedWriteOperation;
 typedef ConcurrentWriteOp<WriteOperation> ConcurrentWriteOperation;
-typedef ConcurrentWriteOpExcludeRetired<WriteOperation> ThreadLocalConcurrentWriteOperation;
+
+typedef Retired<JfrBuffer, true> NonRetired;
+typedef Excluded<JfrBuffer, true> NonExcluded;
+typedef CompositeOperation<NonRetired, NonExcluded> BufferPredicate;
+typedef PredicatedMutexedWriteOp<WriteOperation, BufferPredicate> ThreadLocalMutexedWriteOperation;
+typedef PredicatedConcurrentWriteOp<WriteOperation, BufferPredicate> ThreadLocalConcurrentWriteOperation;
 
 size_t JfrStorage::write() {
-  const size_t full_size_processed = write_full();
+  const size_t full_elements = write_full();
   WriteOperation wo(_chunkwriter);
-  ThreadLocalConcurrentWriteOperation tlwo(wo);
+  NonRetired nr;
+  NonExcluded ne;
+  BufferPredicate bp(&nr, &ne);
+  ThreadLocalConcurrentWriteOperation tlwo(wo, bp);
   process_full_list(tlwo, _thread_local_mspace);
   ConcurrentWriteOperation cwo(wo);
   process_free_list(cwo, _global_mspace);
-  return full_size_processed + wo.processed();
+  return full_elements + wo.elements();
 }
 
 size_t JfrStorage::write_at_safepoint() {
   assert(SafepointSynchronize::is_at_safepoint(), "invariant");
   WriteOperation wo(_chunkwriter);
   MutexedWriteOperation writer(wo); // mutexed write mode
-  process_full_list(writer, _thread_local_mspace);
+  NonRetired nr;
+  NonExcluded ne;
+  BufferPredicate bp(&nr, &ne);
+  ThreadLocalMutexedWriteOperation tlmwo(wo, bp);
+  process_full_list(tlmwo, _thread_local_mspace);
   assert(_transient_mspace->is_free_empty(), "invariant");
   process_full_list(writer, _transient_mspace);
   assert(_global_mspace->is_full_empty(), "invariant");
   process_free_list(writer, _global_mspace);
-  return wo.processed();
+  return wo.elements();
 }
 
 typedef DiscardOp<DefaultDiscarder<JfrStorage::Buffer> > DiscardOperation;
@@ -613,14 +642,14 @@
 typedef CompositeOperation<MutexedWriteOperation, ReleaseOperation> FullOperation;
 
 size_t JfrStorage::clear() {
-  const size_t full_size_processed = clear_full();
+  const size_t full_elements = clear_full();
   DiscardOperation discarder(concurrent); // concurrent discard mode
   process_full_list(discarder, _thread_local_mspace);
   assert(_transient_mspace->is_free_empty(), "invariant");
   process_full_list(discarder, _transient_mspace);
   assert(_global_mspace->is_full_empty(), "invariant");
   process_free_list(discarder, _global_mspace);
-  return full_size_processed + discarder.processed();
+  return full_elements + discarder.elements();
 }
 
 static void insert_free_age_nodes(JfrStorageAgeMspace* age_mspace, JfrAgeNode* head, JfrAgeNode* tail, size_t count) {
@@ -711,15 +740,25 @@
   ReleaseOperation ro(_transient_mspace, thread);
   FullOperation cmd(&writer, &ro);
   const size_t count = process_full(cmd, control(), _age_mspace);
-  log(count, writer.processed());
-  return writer.processed();
+  if (0 == count) {
+    assert(0 == writer.elements(), "invariant");
+    return 0;
+  }
+  const size_t size = writer.size();
+  log(count, size);
+  return count;
 }
 
 size_t JfrStorage::clear_full() {
   DiscardOperation discarder(mutexed); // a retired buffer implies mutexed access
   const size_t count = process_full(discarder, control(), _age_mspace);
-  log(count, discarder.processed(), true);
-  return discarder.processed();
+  if (0 == count) {
+    assert(0 == discarder.elements(), "invariant");
+    return 0;
+  }
+  const size_t size = discarder.size();
+  log(count, size, true);
+  return count;
 }
 
 static void scavenge_log(size_t count, size_t amount, size_t current) {
@@ -749,6 +788,10 @@
       assert(!t->lease(), "invariant");
       ++_count;
       _amount += t->total_size();
+      if (t->excluded()) {
+        t->clear_excluded();
+      }
+      assert(!t->excluded(), "invariant");
       t->clear_retired();
       t->release();
       _control.decrement_dead();
@@ -767,6 +810,11 @@
   }
   Scavenger<JfrThreadLocalMspace> scavenger(ctrl, _thread_local_mspace);
   process_full_list(scavenger, _thread_local_mspace);
-  scavenge_log(scavenger.processed(), scavenger.amount(), ctrl.dead_count());
-  return scavenger.processed();
+  const size_t count = scavenger.processed();
+  if (0 == count) {
+    assert(0 == scavenger.amount(), "invariant");
+    return 0;
+  }
+  scavenge_log(count, scavenger.amount(), ctrl.dead_count());
+  return count;
 }
--- a/src/hotspot/share/jfr/recorder/storage/jfrStorage.hpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/storage/jfrStorage.hpp	Wed Oct 30 19:43:52 2019 +0100
@@ -68,7 +68,6 @@
 
   size_t clear();
   size_t clear_full();
-  size_t write();
   size_t write_full();
   size_t write_at_safepoint();
   size_t scavenge();
@@ -89,6 +88,8 @@
   void discard_oldest(Thread* t);
   static JfrStorageControl& control();
 
+  size_t write();
+
   friend class JfrRecorder;
   friend class JfrRecorderService;
   template <typename, template <typename> class, typename>
--- a/src/hotspot/share/jfr/recorder/storage/jfrStorageControl.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/storage/jfrStorageControl.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -26,14 +26,13 @@
 #include "jfr/recorder/storage/jfrStorageControl.hpp"
 #include "runtime/atomic.hpp"
 #include "runtime/mutexLocker.hpp"
-#include "runtime/orderAccess.hpp"
 
 // returns the updated value
 static jlong atomic_add(size_t value, size_t volatile* const dest) {
   size_t compare_value;
   size_t exchange_value;
   do {
-    compare_value = OrderAccess::load_acquire(dest);
+    compare_value = *dest;
     exchange_value = compare_value + value;
   } while (Atomic::cmpxchg(exchange_value, dest, compare_value) != compare_value);
   return exchange_value;
@@ -43,7 +42,7 @@
   size_t compare_value;
   size_t exchange_value;
   do {
-    compare_value = OrderAccess::load_acquire(dest);
+    compare_value = *dest;
     assert(compare_value >= 1, "invariant");
     exchange_value = compare_value - 1;
   } while (Atomic::cmpxchg(exchange_value, dest, compare_value) != compare_value);
@@ -102,7 +101,7 @@
 // concurrent with accuracy requirement
 
 size_t JfrStorageControl::global_lease_count() const {
-  return OrderAccess::load_acquire(&_global_lease_count);
+  return Atomic::load(&_global_lease_count);
 }
 
 size_t JfrStorageControl::increment_leased() {
--- a/src/hotspot/share/jfr/recorder/storage/jfrStorageUtils.hpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/storage/jfrStorageUtils.hpp	Wed Oct 30 19:43:52 2019 +0100
@@ -31,7 +31,21 @@
 #include "jfr/utilities/jfrTypes.hpp"
 #include "runtime/thread.hpp"
 
-template <typename Operation, typename NextOperation>
+class CompositeOperationOr {
+ public:
+  static bool evaluate(bool value) {
+    return !value;
+  }
+};
+
+class CompositeOperationAnd {
+ public:
+  static bool evaluate(bool value) {
+    return value;
+  }
+};
+
+template <typename Operation, typename NextOperation, typename TruthFunction = CompositeOperationAnd>
 class CompositeOperation {
  private:
   Operation* _op;
@@ -41,11 +55,15 @@
     assert(_op != NULL, "invariant");
   }
   typedef typename Operation::Type Type;
-  bool process(Type* t = NULL) {
-    return _next == NULL ? _op->process(t) : _op->process(t) && _next->process(t);
+  bool process(Type* t) {
+    const bool op_result = _op->process(t);
+    return _next == NULL ? op_result : TruthFunction::evaluate(op_result) ? _next->process(t) : op_result;
   }
-  size_t processed() const {
-    return _next == NULL ? _op->processed() : _op->processed() + _next->processed();
+  size_t elements() const {
+    return _next == NULL ? _op->elements() : _op->elements() + _next->elements();
+  }
+  size_t size() const {
+    return _next == NULL ? _op->size() : _op->size() + _next->size();
   }
 };
 
@@ -53,23 +71,71 @@
 class UnBufferedWriteToChunk {
  private:
   JfrChunkWriter& _writer;
-  size_t _processed;
+  size_t _elements;
+  size_t _size;
  public:
   typedef T Type;
-  UnBufferedWriteToChunk(JfrChunkWriter& writer) : _writer(writer), _processed(0) {}
+  UnBufferedWriteToChunk(JfrChunkWriter& writer) : _writer(writer), _elements(0), _size(0) {}
   bool write(Type* t, const u1* data, size_t size);
-  size_t processed() { return _processed; }
+  size_t elements() const { return _elements; }
+  size_t size() const { return _size; }
 };
 
 template <typename T>
 class DefaultDiscarder {
  private:
-  size_t _processed;
+  size_t _elements;
+  size_t _size;
+ public:
+  typedef T Type;
+  DefaultDiscarder() : _elements(0), _size(0) {}
+  bool discard(Type* t, const u1* data, size_t size);
+  size_t elements() const { return _elements; }
+  size_t size() const { return _size; }
+};
+
+template <typename T, bool negation>
+class Retired {
+ public:
+  typedef T Type;
+  bool process(Type* t) {
+    assert(t != NULL, "invariant");
+    return negation ? !t->retired() : t->retired();
+  }
+};
+
+template <typename T, bool negation>
+class Excluded {
  public:
   typedef T Type;
-  DefaultDiscarder() : _processed() {}
-  bool discard(Type* t, const u1* data, size_t size);
-  size_t processed() const { return _processed; }
+  bool process(Type* t) {
+    assert(t != NULL, "invariant");
+    return negation ? !t->excluded() : t->excluded();
+  }
+};
+
+template <typename Operation>
+class MutexedWriteOp {
+ private:
+  Operation& _operation;
+ public:
+  typedef typename Operation::Type Type;
+  MutexedWriteOp(Operation& operation) : _operation(operation) {}
+  bool process(Type* t);
+  size_t elements() const { return _operation.elements(); }
+  size_t size() const { return _operation.size(); }
+};
+
+template <typename Operation, typename Predicate>
+class PredicatedMutexedWriteOp : public MutexedWriteOp<Operation> {
+ private:
+  Predicate& _predicate;
+ public:
+  PredicatedMutexedWriteOp(Operation& operation, Predicate& predicate) :
+    MutexedWriteOp<Operation>(operation), _predicate(predicate) {}
+  bool process(typename Operation::Type* t) {
+    return _predicate.process(t) ? MutexedWriteOp<Operation>::process(t) : true;
+  }
 };
 
 template <typename Operation>
@@ -80,27 +146,20 @@
   typedef typename Operation::Type Type;
   ConcurrentWriteOp(Operation& operation) : _operation(operation) {}
   bool process(Type* t);
-  size_t processed() const { return _operation.processed(); }
+  size_t elements() const { return _operation.elements(); }
+  size_t size() const { return _operation.size(); }
 };
 
-template <typename Operation>
-class ConcurrentWriteOpExcludeRetired : private ConcurrentWriteOp<Operation> {
+template <typename Operation, typename Predicate>
+class PredicatedConcurrentWriteOp : public ConcurrentWriteOp<Operation> {
+ private:
+  Predicate& _predicate;
  public:
-  typedef typename Operation::Type Type;
-  ConcurrentWriteOpExcludeRetired(Operation& operation) : ConcurrentWriteOp<Operation>(operation) {}
-  bool process(Type* t);
-  size_t processed() const { return ConcurrentWriteOp<Operation>::processed(); }
-};
-
-template <typename Operation>
-class MutexedWriteOp {
- private:
-  Operation& _operation;
- public:
-  typedef typename Operation::Type Type;
-  MutexedWriteOp(Operation& operation) : _operation(operation) {}
-  bool process(Type* t);
-  size_t processed() const { return _operation.processed(); }
+  PredicatedConcurrentWriteOp(Operation& operation, Predicate& predicate) :
+    ConcurrentWriteOp<Operation>(operation), _predicate(predicate) {}
+  bool process(typename Operation::Type* t) {
+    return _predicate.process(t) ? ConcurrentWriteOp<Operation>::process(t) : true;
+  }
 };
 
 template <typename Operation>
@@ -126,7 +185,8 @@
   typedef typename Operation::Type Type;
   DiscardOp(jfr_operation_mode mode = concurrent) : _operation(), _mode(mode) {}
   bool process(Type* t);
-  size_t processed() const { return _operation.processed(); }
+  size_t elements() const { return _operation.elements(); }
+  size_t size() const { return _operation.size(); }
 };
 
 #endif // SHARE_JFR_RECORDER_STORAGE_JFRSTORAGEUTILS_HPP
--- a/src/hotspot/share/jfr/recorder/storage/jfrStorageUtils.inline.hpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/storage/jfrStorageUtils.inline.hpp	Wed Oct 30 19:43:52 2019 +0100
@@ -31,13 +31,15 @@
 template <typename T>
 inline bool UnBufferedWriteToChunk<T>::write(T* t, const u1* data, size_t size) {
   _writer.write_unbuffered(data, size);
-  _processed += size;
+  ++_elements;
+  _size += size;
   return true;
 }
 
 template <typename T>
 inline bool DefaultDiscarder<T>::discard(T* t, const u1* data, size_t size) {
-  _processed += size;
+  ++_elements;
+  _size += size;
   return true;
 }
 
@@ -55,15 +57,6 @@
 }
 
 template <typename Operation>
-inline bool ConcurrentWriteOpExcludeRetired<Operation>::process(typename Operation::Type* t) {
-  if (t->retired()) {
-    assert(t->empty(), "invariant");
-    return true;
-  }
-  return ConcurrentWriteOp<Operation>::process(t);
-}
-
-template <typename Operation>
 inline bool MutexedWriteOp<Operation>::process(typename Operation::Type* t) {
   assert(t != NULL, "invariant");
   const u1* const current_top = t->top();
--- a/src/hotspot/share/jfr/recorder/storage/jfrVirtualMemory.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/storage/jfrVirtualMemory.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -26,7 +26,6 @@
 #include "jfr/recorder/storage/jfrVirtualMemory.hpp"
 #include "memory/virtualspace.hpp"
 #include "runtime/globals.hpp"
-#include "runtime/orderAccess.hpp"
 #include "runtime/os.hpp"
 #include "services/memTracker.hpp"
 #include "utilities/globalDefinitions.hpp"
--- a/src/hotspot/share/jfr/recorder/stringpool/jfrStringPool.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/stringpool/jfrStringPool.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -33,7 +33,6 @@
 #include "jfr/recorder/stringpool/jfrStringPoolWriter.hpp"
 #include "jfr/utilities/jfrTypes.hpp"
 #include "logging/log.hpp"
-#include "runtime/atomic.hpp"
 #include "runtime/mutexLocker.hpp"
 #include "runtime/orderAccess.hpp"
 #include "runtime/safepoint.hpp"
@@ -42,12 +41,42 @@
 typedef JfrStringPool::Buffer* BufferPtr;
 
 static JfrStringPool* _instance = NULL;
+static uint64_t store_generation = 0;
+static uint64_t serialized_generation = 0;
+
+inline void set_generation(uint64_t value, uint64_t* const dest) {
+  assert(dest != NULL, "invariant");
+  OrderAccess::release_store(dest, value);
+}
+static void increment_store_generation() {
+  const uint64_t current_serialized = OrderAccess::load_acquire(&serialized_generation);
+  const uint64_t current_stored = OrderAccess::load_acquire(&store_generation);
+  if (current_serialized == current_stored) {
+    set_generation(current_serialized + 1, &store_generation);
+  }
+}
+
+static bool increment_serialized_generation() {
+  const uint64_t current_stored = OrderAccess::load_acquire(&store_generation);
+  const uint64_t current_serialized = OrderAccess::load_acquire(&serialized_generation);
+  if (current_stored != current_serialized) {
+    set_generation(current_stored, &serialized_generation);
+    return true;
+  }
+  return false;
+}
+
+bool JfrStringPool::is_modified() {
+  return increment_serialized_generation();
+}
 
 JfrStringPool& JfrStringPool::instance() {
   return *_instance;
 }
 
 JfrStringPool* JfrStringPool::create(JfrChunkWriter& cw) {
+  store_generation = 0;
+  serialized_generation = 0;
   assert(_instance == NULL, "invariant");
   _instance = new JfrStringPool(cw);
   return _instance;
@@ -131,12 +160,16 @@
 bool JfrStringPool::add(bool epoch, jlong id, jstring string, JavaThread* jt) {
   assert(jt != NULL, "invariant");
   const bool current_epoch = JfrTraceIdEpoch::epoch();
-  if (current_epoch == epoch) {
+  if (current_epoch != epoch) {
+    return current_epoch;
+  }
+  {
     JfrStringPoolWriter writer(jt);
     writer.write(id);
     writer.write(string);
     writer.inc_nof_strings();
   }
+  increment_store_generation();
   return current_epoch;
 }
 
@@ -163,9 +196,10 @@
   size_t processed() { return _strings_processed; }
 };
 
-template <typename Type>
+template <typename T>
 class StringPoolDiscarderStub {
  public:
+  typedef T Type;
   bool write(Type* buffer, const u1* data, size_t size) {
     // stub only, discard happens at higher level
     return true;
@@ -197,6 +231,7 @@
 }
 
 size_t JfrStringPool::clear() {
+  increment_serialized_generation();
   DiscardOperation discard_operation;
   ExclusiveDiscardOperation edo(discard_operation);
   StringPoolReleaseOperation spro(_free_list_mspace, Thread::current(), false);
--- a/src/hotspot/share/jfr/recorder/stringpool/jfrStringPool.hpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/recorder/stringpool/jfrStringPool.hpp	Wed Oct 30 19:43:52 2019 +0100
@@ -71,6 +71,7 @@
   static JfrStringPool* create(JfrChunkWriter& cw);
   bool initialize();
   static void destroy();
+  static bool is_modified();
 
   friend class JfrRecorder;
   friend class JfrRecorderService;
--- a/src/hotspot/share/jfr/support/jfrThreadLocal.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/support/jfrThreadLocal.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -55,6 +55,7 @@
   _stack_trace_hash(0),
   _stackdepth(0),
   _entering_suspend_flag(0),
+  _excluded(false),
   _dead(false) {}
 
 u8 JfrThreadLocal::add_data_lost(u8 value) {
@@ -84,9 +85,13 @@
 void JfrThreadLocal::on_start(Thread* t) {
   assert(t != NULL, "invariant");
   assert(Thread::current() == t, "invariant");
+  JfrJavaSupport::on_thread_start(t);
   if (JfrRecorder::is_recording()) {
-    if (t->is_Java_thread()) {
-      send_java_thread_start_event((JavaThread*)t);
+    if (!t->jfr_thread_local()->is_excluded()) {
+      JfrCheckpointManager::write_thread_checkpoint(t);
+      if (t->is_Java_thread()) {
+        send_java_thread_start_event((JavaThread*)t);
+      }
     }
   }
 }
@@ -103,48 +108,68 @@
   }
 }
 
+void JfrThreadLocal::release(Thread* t) {
+  if (has_java_event_writer()) {
+    assert(t->is_Java_thread(), "invariant");
+    JfrJavaSupport::destroy_global_jni_handle(java_event_writer());
+    _java_event_writer = NULL;
+  }
+  if (has_native_buffer()) {
+    JfrStorage::release_thread_local(native_buffer(), t);
+    _native_buffer = NULL;
+  }
+  if (has_java_buffer()) {
+    JfrStorage::release_thread_local(java_buffer(), t);
+    _java_buffer = NULL;
+  }
+  if (_stackframes != NULL) {
+    FREE_C_HEAP_ARRAY(JfrStackFrame, _stackframes);
+    _stackframes = NULL;
+  }
+}
+
 void JfrThreadLocal::release(JfrThreadLocal* tl, Thread* t) {
   assert(tl != NULL, "invariant");
   assert(t != NULL, "invariant");
   assert(Thread::current() == t, "invariant");
   assert(!tl->is_dead(), "invariant");
   assert(tl->shelved_buffer() == NULL, "invariant");
-  if (tl->has_native_buffer()) {
-    JfrStorage::release_thread_local(tl->native_buffer(), t);
-  }
-  if (tl->has_java_buffer()) {
-    JfrStorage::release_thread_local(tl->java_buffer(), t);
-  }
-  if (tl->has_java_event_writer()) {
-    assert(t->is_Java_thread(), "invariant");
-    JfrJavaSupport::destroy_global_jni_handle(tl->java_event_writer());
-  }
-  FREE_C_HEAP_ARRAY(JfrStackFrame, tl->_stackframes);
   tl->_dead = true;
+  tl->release(t);
 }
 
 void JfrThreadLocal::on_exit(Thread* t) {
   assert(t != NULL, "invariant");
   JfrThreadLocal * const tl = t->jfr_thread_local();
   assert(!tl->is_dead(), "invariant");
-  if (t->is_Java_thread()) {
-    JavaThread* const jt = (JavaThread*)t;
-    ObjectSampleCheckpoint::on_thread_exit(jt);
-    send_java_thread_end_events(tl->thread_id(), jt);
+  if (JfrRecorder::is_recording()) {
+    if (t->is_Java_thread()) {
+      JavaThread* const jt = (JavaThread*)t;
+      ObjectSampleCheckpoint::on_thread_exit(jt);
+      send_java_thread_end_events(tl->thread_id(), jt);
+    }
   }
   release(tl, Thread::current()); // because it could be that Thread::current() != t
 }
 
+static JfrBuffer* acquire_buffer(bool excluded) {
+  JfrBuffer* const buffer = JfrStorage::acquire_thread_local(Thread::current());
+  if (buffer != NULL && excluded) {
+    buffer->set_excluded();
+  }
+  return buffer;
+}
+
 JfrBuffer* JfrThreadLocal::install_native_buffer() const {
   assert(!has_native_buffer(), "invariant");
-  _native_buffer = JfrStorage::acquire_thread_local(Thread::current());
+  _native_buffer = acquire_buffer(_excluded);
   return _native_buffer;
 }
 
 JfrBuffer* JfrThreadLocal::install_java_buffer() const {
   assert(!has_java_buffer(), "invariant");
   assert(!has_java_event_writer(), "invariant");
-  _java_buffer = JfrStorage::acquire_thread_local(Thread::current());
+  _java_buffer = acquire_buffer(_excluded);
   return _java_buffer;
 }
 
@@ -162,6 +187,18 @@
   return in_ByteSize(offset_of(JfrThreadLocal, _java_event_writer));
 }
 
+void JfrThreadLocal::exclude(Thread* t) {
+  assert(t != NULL, "invariant");
+  t->jfr_thread_local()->_excluded = true;
+  t->jfr_thread_local()->release(t);
+}
+
+void JfrThreadLocal::include(Thread* t) {
+  assert(t != NULL, "invariant");
+  t->jfr_thread_local()->_excluded = false;
+  t->jfr_thread_local()->release(t);
+}
+
 u4 JfrThreadLocal::stackdepth() const {
   return _stackdepth != 0 ? _stackdepth : (u4)JfrOptionSet::stackdepth();
 }
--- a/src/hotspot/share/jfr/support/jfrThreadLocal.hpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/support/jfrThreadLocal.hpp	Wed Oct 30 19:43:52 2019 +0100
@@ -50,12 +50,13 @@
   unsigned int _stack_trace_hash;
   mutable u4 _stackdepth;
   volatile jint _entering_suspend_flag;
+  bool _excluded;
   bool _dead;
 
   JfrBuffer* install_native_buffer() const;
   JfrBuffer* install_java_buffer() const;
   JfrStackFrame* install_stackframes() const;
-
+  void release(Thread* t);
   static void release(JfrThreadLocal* tl, Thread* t);
 
  public:
@@ -203,6 +204,10 @@
     _trace_id = id;
   }
 
+  bool is_excluded() const {
+    return _excluded;
+  }
+
   bool is_dead() const {
     return _dead;
   }
@@ -211,6 +216,9 @@
   void set_thread_blob(const JfrBlobHandle& handle);
   const JfrBlobHandle& thread_blob() const;
 
+  static void exclude(Thread* t);
+  static void include(Thread* t);
+
   static void on_start(Thread* t);
   static void on_exit(Thread* t);
 
--- a/src/hotspot/share/jfr/support/jfrTraceIdExtension.hpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/support/jfrTraceIdExtension.hpp	Wed Oct 30 19:43:52 2019 +0100
@@ -69,7 +69,7 @@
 
   jbyte* meta_addr() const {
 #ifdef VM_LITTLE_ENDIAN
-    return (jbyte*)(&_flags) + 1;
+    return ((jbyte*)&_flags) + 1;
 #else
     return (jbyte*)&_flags;
 #endif
--- a/src/hotspot/share/jfr/utilities/jfrAllocation.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/utilities/jfrAllocation.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -28,7 +28,6 @@
 #include "logging/log.hpp"
 #include "memory/allocation.inline.hpp"
 #include "runtime/atomic.hpp"
-#include "runtime/orderAccess.hpp"
 #include "runtime/vm_version.hpp"
 #include "utilities/debug.hpp"
 #include "utilities/macros.hpp"
@@ -40,7 +39,7 @@
   jlong compare_value;
   jlong exchange_value;
   do {
-    compare_value = OrderAccess::load_acquire(dest);
+    compare_value = *dest;
     exchange_value = compare_value + value;
   } while (Atomic::cmpxchg(exchange_value, dest, compare_value) != compare_value);
   return exchange_value;
--- a/src/hotspot/share/jfr/utilities/jfrDoublyLinkedList.hpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/utilities/jfrDoublyLinkedList.hpp	Wed Oct 30 19:43:52 2019 +0100
@@ -48,8 +48,8 @@
   void prepend(T* const node);
   void append(T* const node);
   void append_list(T* const head_node, T* const tail_node, size_t count);
-  debug_only(bool in_list(const T* const target_node) const;)
-  debug_only(bool locate(const T* start_node, const T* const target_node) const;)
+  bool in_list(const T* const target_node) const;
+  bool locate(const T* start_node, const T* const target_node) const;
 };
 
 template <typename T>
@@ -153,7 +153,6 @@
   return node;
 }
 
-#ifdef ASSERT
 template <typename T>
 bool JfrDoublyLinkedList<T>::locate(const T* node, const T* const target) const {
   assert(target != NULL, "invariant");
@@ -182,7 +181,6 @@
   }
   assert(count_param == count, "invariant");
 }
-#endif // ASSERT
 
 template <typename T>
 void JfrDoublyLinkedList<T>::append_list(T* const head_node, T* const tail_node, size_t count) {
--- a/src/hotspot/share/jfr/utilities/jfrLogTagSets.hpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/utilities/jfrLogTagSets.hpp	Wed Oct 30 19:43:52 2019 +0100
@@ -53,6 +53,7 @@
   JFR_LOG_TAG(jfr, system, bytecode) \
   JFR_LOG_TAG(jfr, system, parser) \
   JFR_LOG_TAG(jfr, system, metadata) \
+  JFR_LOG_TAG(jfr, system, streaming) \
   JFR_LOG_TAG(jfr, metadata) \
   JFR_LOG_TAG(jfr, event) \
   JFR_LOG_TAG(jfr, setting) \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hotspot/share/jfr/utilities/jfrThreadIterator.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,87 @@
+/*
+ * 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_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;
+  _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;
+  _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	Wed Oct 30 19:43:52 2019 +0100
@@ -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
--- a/src/hotspot/share/jfr/utilities/jfrTypes.hpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/utilities/jfrTypes.hpp	Wed Oct 30 19:43:52 2019 +0100
@@ -26,11 +26,14 @@
 #define SHARE_JFR_UTILITIES_JFRTYPES_HPP
 
 #include "jfrfiles/jfrEventIds.hpp"
+#include "utilities/globalDefinitions.hpp"
 
 typedef u8 traceid;
 typedef int fio_fd;
+
 const int invalid_fd = -1;
 const jlong invalid_offset = -1;
+const int64_t invalid_time = -1;
 const u4 STACK_DEPTH_DEFAULT = 64;
 const u4 MIN_STACK_DEPTH = 1;
 const u4 MAX_STACK_DEPTH = 2048;
@@ -48,4 +51,12 @@
   TIMED
 };
 
+enum JfrCheckpointType {
+  GENERIC,
+  FLUSH,
+  HEADER,
+  STATICS = 4,
+  THREADS = 8
+};
+
 #endif // SHARE_JFR_UTILITIES_JFRTYPES_HPP
--- a/src/hotspot/share/jfr/writers/jfrJavaEventWriter.hpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/writers/jfrJavaEventWriter.hpp	Wed Oct 30 19:43:52 2019 +0100
@@ -32,9 +32,9 @@
 class Thread;
 
 class JfrJavaEventWriter : AllStatic {
-  friend class JfrCheckpointThreadClosure;
+  friend class JfrNotifyClosure;
+  friend class JfrJavaEventWriterNotifyOperation;
   friend class JfrJavaEventWriterNotificationClosure;
-  friend class JfrJavaEventWriterNotifyOperation;
   friend class JfrRecorder;
  private:
   static bool initialize();
--- a/src/hotspot/share/jfr/writers/jfrStorageAdapter.hpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/writers/jfrStorageAdapter.hpp	Wed Oct 30 19:43:52 2019 +0100
@@ -82,7 +82,7 @@
     assert(_thread != NULL, "invariant");
     Flush f(_storage, used, requested, _thread);
     _storage = f.result();
-    return _storage != NULL;
+    return _storage != NULL && !_storage->excluded();
   }
 
   void release() {
@@ -236,7 +236,8 @@
   void release() {}
   bool flush(size_t used, size_t requested) {
     // don't flush/expand a buffer that is not our own
-    return false;
+    _pos = _start;
+    return true;
   }
 };
 
--- a/src/hotspot/share/jfr/writers/jfrWriterHost.inline.hpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/jfr/writers/jfrWriterHost.inline.hpp	Wed Oct 30 19:43:52 2019 +0100
@@ -114,10 +114,7 @@
 template <typename BE, typename IE, typename WriterPolicyImpl >
 template <typename T>
 inline void WriterHost<BE, IE, WriterPolicyImpl>::be_write(T value) {
-  u1* const pos = ensure_size(sizeof(T));
-  if (pos) {
-    this->set_current_pos(BE::be_write(&value, 1, pos));
-  }
+  be_write(&value, 1);
 }
 
 template <typename BE, typename IE, typename WriterPolicyImpl >
--- a/src/hotspot/share/logging/logTag.hpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/logging/logTag.hpp	Wed Oct 30 19:43:52 2019 +0100
@@ -148,6 +148,7 @@
   LOG_TAG(startuptime) \
   LOG_TAG(state) \
   LOG_TAG(stats) \
+  LOG_TAG(streaming) \
   LOG_TAG(stringdedup) \
   LOG_TAG(stringtable) \
   LOG_TAG(symboltable) \
--- a/src/hotspot/share/runtime/mutexLocker.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/hotspot/share/runtime/mutexLocker.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -312,7 +312,7 @@
 #if INCLUDE_JFR
   def(JfrMsg_lock                  , PaddedMonitor, leaf,        true,  _safepoint_check_always);
   def(JfrBuffer_lock               , PaddedMutex  , leaf,        true,  _safepoint_check_never);
-  def(JfrStream_lock               , PaddedMutex  , leaf+1,      true,  _safepoint_check_never);      // ensure to rank lower than 'safepoint'
+  def(JfrStream_lock               , PaddedMutex  , nonleaf + 1, false, _safepoint_check_always);
   def(JfrStacktrace_lock           , PaddedMutex  , special,     true,  _safepoint_check_never);
   def(JfrThreadSampler_lock        , PaddedMonitor, leaf,        true,  _safepoint_check_never);
 #endif
--- a/src/jdk.jfr/share/classes/jdk/jfr/Recording.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/Recording.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -413,6 +413,36 @@
         internal.setMaxSize(maxSize);
     }
 
+        /**
+         * Determines how often events are made available for streaming.
+         *
+         * @param interval the interval at which events are made available for streaming.
+         *
+         * @throws IllegalArgumentException if {@code interval} is negative
+         *
+         * @throws IllegalStateException if the recording is in the {@code CLOSED} state
+         *
+         * @since 14
+         */
+        public void setFlushInterval(Duration interval) {
+            Objects.nonNull(interval);
+            if (interval.isNegative()) {
+                throw new IllegalArgumentException("Stream interval can't be negative");
+            }
+            internal.setFlushInterval(interval);
+        }
+
+    /**
+     * Returns how often events are made available for streaming purposes.
+     *
+     * @return the flush interval, or {@code null} if no interval has been set
+     *
+     * @since 14
+     */
+    public Duration getFlushInterval() {
+        return internal.getFlushInterval();
+    }
+
     /**
      * Determines how far back data is kept in the disk repository.
      * <p>
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/ChunkParser.java	Wed Oct 30 16:14:56 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,174 +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 java.util.Collection;
-import java.util.List;
-
-import jdk.jfr.EventType;
-import jdk.jfr.internal.LogLevel;
-import jdk.jfr.internal.LogTag;
-import jdk.jfr.internal.Logger;
-import jdk.jfr.internal.MetadataDescriptor;
-import jdk.jfr.internal.Type;
-import jdk.jfr.internal.consumer.ChunkHeader;
-import jdk.jfr.internal.consumer.RecordingInput;
-
-/**
- * Parses a chunk.
- *
- */
-final class ChunkParser {
-    private static final long CONSTANT_POOL_TYPE_ID = 1;
-    private final RecordingInput input;
-    private final LongMap<Parser> parsers;
-    private final ChunkHeader chunkHeader;
-    private final long absoluteChunkEnd;
-    private final MetadataDescriptor metadata;
-    private final LongMap<Type> typeMap;
-    private final TimeConverter timeConverter;
-
-    public ChunkParser(RecordingInput input) throws IOException {
-        this(new ChunkHeader(input));
-    }
-
-    private ChunkParser(ChunkHeader header) throws IOException {
-        this.input = header.getInput();
-        this.chunkHeader = header;
-        this.metadata = header.readMetadata();
-        this.absoluteChunkEnd = header.getEnd();
-        this.timeConverter = new TimeConverter(chunkHeader, metadata.getGMTOffset());
-
-        ParserFactory factory = new ParserFactory(metadata, timeConverter);
-        LongMap<ConstantMap> constantPools = factory.getConstantPools();
-        parsers = factory.getParsers();
-        typeMap = factory.getTypeMap();
-
-        fillConstantPools(parsers, constantPools);
-        constantPools.forEach(ConstantMap::setIsResolving);
-        constantPools.forEach(ConstantMap::resolve);
-        constantPools.forEach(ConstantMap::setResolved);
-
-        input.position(chunkHeader.getEventStart());
-    }
-
-    public RecordedEvent readEvent() throws IOException {
-        while (input.position() < absoluteChunkEnd) {
-            long pos = input.position();
-            int size = input.readInt();
-            if (size == 0) {
-                throw new IOException("Event can't have zero size");
-            }
-            long typeId = input.readLong();
-            if (typeId > CONSTANT_POOL_TYPE_ID) { // also skips metadata (id=0)
-                Parser ep = parsers.get(typeId);
-                if (ep instanceof EventParser) {
-                    return (RecordedEvent) ep.parse(input);
-                }
-            }
-            input.position(pos + size);
-        }
-        return null;
-    }
-
-    private void fillConstantPools(LongMap<Parser> typeParser, LongMap<ConstantMap> constantPools) throws IOException {
-        long nextCP = chunkHeader.getAbsoluteChunkStart();
-        long deltaToNext = chunkHeader.getConstantPoolPosition();
-        while (deltaToNext != 0) {
-            nextCP += deltaToNext;
-            input.position(nextCP);
-            final long position = nextCP;
-            int size = input.readInt(); // size
-            long typeId = input.readLong();
-            if (typeId != CONSTANT_POOL_TYPE_ID) {
-                throw new IOException("Expected check point event (id = 1) at position " + nextCP + ", but found type id = " + typeId);
-            }
-            input.readLong(); // timestamp
-            input.readLong(); // duration
-            deltaToNext = input.readLong();
-            final long delta = deltaToNext;
-            boolean flush = input.readBoolean();
-            int poolCount = input.readInt();
-            Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.TRACE, () -> {
-                return "New constant pool: startPosition=" + position + ", size=" + size + ", deltaToNext=" + delta + ", flush=" + flush + ", poolCount=" + poolCount;
-            });
-
-            for (int i = 0; i < poolCount; i++) {
-                long id = input.readLong(); // type id
-                ConstantMap pool = constantPools.get(id);
-                Type type = typeMap.get(id);
-                if (pool == null) {
-                    Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Found constant pool(" + id + ") that is never used");
-                    if (type == null) {
-                        throw new IOException("Error parsing constant pool type " + getName(id) + " at position " + input.position() + " at check point between [" + nextCP + ", " + nextCP + size + "]");
-                    }
-                    pool = new ConstantMap(ObjectFactory.create(type, timeConverter), type.getName());
-                    constantPools.put(type.getId(), pool);
-                }
-                Parser parser = typeParser.get(id);
-                if (parser == null) {
-                    throw new IOException("Could not find constant pool type with id = " + id);
-                }
-                try {
-                    int count = input.readInt();
-                    Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.TRACE, () -> "Constant: " + getName(id) + "[" + count + "]");
-                    for (int j = 0; j < count; j++) {
-                        long key = input.readLong();
-                        Object value = parser.parse(input);
-                        pool.put(key, value);
-                    }
-                } catch (Exception e) {
-                    throw new IOException("Error parsing constant pool type " + getName(id) + " at position " + input.position() + " at check point between [" + nextCP + ", " + nextCP + size + "]", e);
-                }
-            }
-            if (input.position() != nextCP + size) {
-                throw new IOException("Size of check point event doesn't match content");
-            }
-        }
-    }
-
-    private String getName(long id) {
-        Type type = typeMap.get(id);
-        return type == null ? ("unknown(" + id + ")") : type.getName();
-    }
-
-    public Collection<Type> getTypes() {
-        return metadata.getTypes();
-    }
-
-    public List<EventType> getEventTypes() {
-        return metadata.getEventTypes();
-    }
-
-    public boolean isLastChunk() {
-        return chunkHeader.isLastChunk();
-    }
-
-    public ChunkParser nextChunkParser() throws IOException {
-        return new ChunkParser(chunkHeader.nextHeader());
-    }
-}
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/ConstantMap.java	Wed Oct 30 16:14:56 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,139 +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.util.ArrayList;
-import java.util.List;
-
-/**
- * Holds mapping between a set of keys and their corresponding object.
- *
- * If the type is a known type, i.e. {@link RecordedThread}, an
- * {@link ObjectFactory} can be supplied which will instantiate a typed object.
- */
-final class ConstantMap {
-    private final static class Reference {
-        private final long key;
-        private final ConstantMap pool;
-
-        Reference(ConstantMap pool, long key) {
-            this.pool = pool;
-            this.key = key;
-        }
-
-        Object resolve() {
-            return pool.get(key);
-        }
-    }
-
-    private final ObjectFactory<?> factory;
-    private final LongMap<Object> objects;
-
-    private LongMap<Boolean> isResolving;
-    private boolean allResolved;
-    private String name;
-
-    ConstantMap(ObjectFactory<?> factory, String name) {
-        this.name = name;
-        this.objects = new LongMap<>();
-        this.factory = factory;
-    }
-
-    Object get(long id) {
-        // fast path, all objects in pool resolved
-        if (allResolved) {
-            return objects.get(id);
-        }
-        // referenced from a pool, deal with this later
-        if (isResolving == null) {
-            return new Reference(this, id);
-        }
-
-        Boolean beingResolved = isResolving.get(id);
-
-        // we are resolved (but not the whole pool)
-        if (Boolean.FALSE.equals(beingResolved)) {
-            return objects.get(id);
-        }
-
-        // resolving ourself, abort to avoid infinite recursion
-        if (Boolean.TRUE.equals(beingResolved)) {
-            return null;
-        }
-
-        // resolve me!
-        isResolving.put(id, Boolean.TRUE);
-        Object resolved = resolve(objects.get(id));
-        isResolving.put(id, Boolean.FALSE);
-        if (factory != null) {
-            Object factorized = factory.createObject(id, resolved);
-            objects.put(id, factorized);
-            return factorized;
-        } else {
-            objects.put(id, resolved);
-            return resolved;
-        }
-    }
-
-    private static Object resolve(Object o) {
-        if (o instanceof Reference) {
-            return resolve(((Reference) o).resolve());
-        }
-        if (o != null && o.getClass().isArray()) {
-            final Object[] array = (Object[]) o;
-            for (int i = 0; i < array.length; i++) {
-                array[i] = resolve(array[i]);
-            }
-            return array;
-        }
-        return o;
-    }
-
-    public void resolve() {
-        List<Long> keyList = new ArrayList<>();
-        objects.keys().forEachRemaining(keyList::add);
-        for (Long l : keyList) {
-            get(l);
-        }
-    }
-
-    public void put(long key, Object value) {
-        objects.put(key, value);
-    }
-
-    public void setIsResolving() {
-        isResolving = new LongMap<>();
-    }
-
-    public void setResolved() {
-        allResolved = true;
-        isResolving = null; // pool finished, release memory
-    }
-
-    public String getName() {
-        return name;
-    }
-}
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/EventParser.java	Wed Oct 30 16:14:56 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,72 +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 static jdk.jfr.internal.EventInstrumentation.FIELD_DURATION;
-
-import java.io.IOException;
-import java.util.List;
-
-import jdk.jfr.EventType;
-import jdk.jfr.ValueDescriptor;
-import jdk.jfr.internal.consumer.RecordingInput;
-
-/**
- * Parses an event and returns a {@link RecordedEvent}.
- *
- */
-final class EventParser extends Parser {
-    private final Parser[] parsers;
-    private final EventType eventType;
-    private final TimeConverter timeConverter;
-    private final boolean hasDuration;
-    private final List<ValueDescriptor> valueDescriptors;
-
-    EventParser(TimeConverter timeConverter, EventType type, Parser[] parsers) {
-        this.timeConverter = timeConverter;
-        this.parsers = parsers;
-        this.eventType = type;
-        this.hasDuration = type.getField(FIELD_DURATION) != null;
-        this.valueDescriptors = type.getFields();
-    }
-
-    @Override
-    public Object parse(RecordingInput input) throws IOException {
-        Object[] values = new Object[parsers.length];
-        for (int i = 0; i < parsers.length; i++) {
-            values[i] = parsers[i].parse(input);
-        }
-        Long startTicks = (Long) values[0];
-        long startTime = timeConverter.convertTimestamp(startTicks);
-        if (hasDuration) {
-            long durationTicks = (Long) values[1];
-            long endTime = timeConverter.convertTimestamp(startTicks + durationTicks);
-            return new RecordedEvent(eventType, valueDescriptors, values, startTime, endTime, timeConverter);
-        } else {
-            return new RecordedEvent(eventType, valueDescriptors, values, startTime, startTime, timeConverter);
-        }
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/EventStream.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,364 @@
+/*
+ * 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.security.AccessControlContext;
+import java.security.AccessController;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Objects;
+import java.util.function.Consumer;
+
+import jdk.jfr.internal.SecuritySupport;
+import jdk.jfr.internal.Utils;
+import jdk.jfr.internal.consumer.EventDirectoryStream;
+import jdk.jfr.internal.consumer.EventFileStream;
+import jdk.jfr.internal.consumer.FileAccess;
+
+/**
+ * Represents a stream of events.
+ * <p>
+ * A stream is a sequence of events and the way to interact with a stream is to
+ * register actions. The {@code EventStream} interface is not to be implemented
+ * and future versions of the JDK may prevent this completely.
+ * <p>
+ * To receive a notification when an event arrives, register an action using the
+ * {@link #onEvent(Consumer)} method. To filter the stream for an event with a
+ * specific name, use {@link #onEvent(String, Consumer)} method.
+ * <p>
+ * By default, the same {@code RecordedEvent} object can be used to
+ * represent two or more distinct events. That object can be delivered
+ * multiple times to the same action as well as to other actions. To use an
+ * event object after the action is completed, the
+ * {@link #setReuse(boolean)} method should be set to {@code false} so a
+ * new object is allocated for each event.
+ * <p>
+ * Events are delivered in batches. To receive a notification when a batch is
+ * complete, register an action using the {@link #onFlush(Runnable)} method.
+ * This is an opportunity to aggregate or push data to external systems while
+ * the Java Virtual Machine (JVM) is preparing the next batch.
+ * <p>
+ * Events within a batch are sorted chronologically by their end time.
+ * Well-ordering of events is only maintained for events available to the JVM at
+ * the point of flush, i.e. for the set of events delivered as a unit in a
+ * single batch. Events delivered in a batch could therefore be out-of-order
+ * compared to events delivered in a previous batch, but never out-of-order with
+ * events within the same batch. If ordering is not a concern, sorting can be
+ * disabled using the {@link #setOrdered(boolean)} method.
+ * <p>
+ * To dispatch events to registered actions, the stream must be started. To
+ * start processing in the current thread, invoke the {@link #start()} method.
+ * To process actions asynchronously in a separate thread, invoke the
+ * {@link #startAsync()} method. To await completion of the stream, use the
+ * awaitTermination {@link #awaitTermination()} or the
+ * {@link #awaitTermination(Duration)} method.
+ * <p>
+ * When a stream ends it is automatically closed. To manually stop processing of
+ * events, close the stream by invoking the {@link #close()} method. A stream
+ * can also be automatically closed in exceptional circumstances, for example if
+ * the JVM that is being monitored exits. To receive a notification in any of
+ * these occasions, use the {@link #onClose(Runnable)} method to register an
+ * action.
+ * <p>
+ * If an unexpected exception occurs in an action, it is possible to catch the
+ * exception in an error handler. An error handler can be registered using the
+ * {@link #onError(Runnable)} method. If no error handler is registered, the
+ * default behavior is to print the exception and its backtrace to the standard
+ * error stream.
+ * <p>
+ * The following example shows how an {@code EventStream} can be used to listen
+ * to events on a JVM running Flight Recorder
+ *
+ * <pre>
+ * <code>
+ * try (var es = EventStream.openRepository()) {
+ *   es.onEvent("jdk.CPULoad", event -> {
+ *     System.out.println("CPU Load " + event.getEndTime());
+ *     System.out.println(" Machine total: " + 100 * event.getFloat("machineTotal") + "%");
+ *     System.out.println(" JVM User: " + 100 * event.getFloat("jvmUser") + "%");
+ *     System.out.println(" JVM System: " + 100 * event.getFloat("jvmSystem") + "%");
+ *     System.out.println();
+ *   });
+ *   es.onEvent("jdk.GarbageCollection", event -> {
+ *     System.out.println("Garbage collection: " + event.getLong("gcId"));
+ *     System.out.println(" Cause: " + event.getString("cause"));
+ *     System.out.println(" Total pause: " + event.getDuration("sumOfPauses"));
+ *     System.out.println(" Longest pause: " + event.getDuration("longestPause"));
+ *     System.out.println();
+ *   });
+ *   es.start();
+ * }
+ * </code>
+ * </pre>
+ * <p>
+ * To start recording together with the stream, see {@link RecordingStream}.
+ *
+ * @since 14
+ */
+public interface EventStream extends AutoCloseable {
+    /**
+     * Creates a stream from the repository of the current Java Virtual Machine
+     * (JVM).
+     * <p>
+     * By default, the stream starts with the next event flushed by Flight
+     * Recorder.
+     *
+     * @return an event stream, not {@code null}
+     *
+     * @throws IOException if a stream can't be opened, or an I/O error occurs
+     *         when trying to access the repository
+     *
+     * @throws SecurityException if a security manager exists and the caller
+     *         does not have
+     *         {@code FlightRecorderPermission("accessFlightRecorder")}
+     */
+    public static EventStream openRepository() throws IOException {
+        Utils.checkAccessFlightRecorder();
+        return new EventDirectoryStream(AccessController.getContext(), null, SecuritySupport.PRIVILIGED, false);
+    }
+
+    /**
+     * Creates an event stream from a disk repository.
+     * <p>
+     * By default, the stream starts with the next event flushed by Flight
+     * Recorder.
+     *
+     * @param directory location of the disk repository, not {@code null}
+     *
+     * @return an event stream, not {@code null}
+     *
+     * @throws IOException if a stream can't be opened, or an I/O error occurs
+     *         when trying to access the repository
+     *
+     * @throws SecurityException if a security manager exists and its
+     *         {@code checkRead} method denies read access to the directory, or
+     *         files in the directory.
+     */
+    public static EventStream openRepository(Path directory) throws IOException {
+        Objects.nonNull(directory);
+        AccessControlContext acc = AccessController.getContext();
+        return new EventDirectoryStream(acc, directory, FileAccess.UNPRIVILIGED, false);
+    }
+
+    /**
+     * Creates an event stream from a file.
+     * <p>
+     * By default, the stream starts with the first event in the file.
+     *
+     * @param file location of the file, not {@code null}
+     *
+     * @return an event stream, not {@code null}
+     *
+     * @throws IOException if the file can't be opened, or an I/O error occurs
+     *         during reading
+     *
+     * @throws SecurityException if a security manager exists and its
+     *         {@code checkRead} method denies read access to the file
+     */
+    static EventStream openFile(Path file) throws IOException {
+        return new EventFileStream(AccessController.getContext(), file);
+    }
+
+    /**
+     * Registers an action to perform on all events in the stream.
+     *
+     * @param action an action to perform on each {@code RecordedEvent}, not
+     *        {@code null}
+     */
+    void onEvent(Consumer<RecordedEvent> action);
+
+    /**
+     * Registers an action to perform on all events matching a name.
+     *
+     * @param eventName the name of the event, not {@code null}
+     *
+     * @param action an action to perform on each {@code RecordedEvent} matching
+     *        the event name, not {@code null}
+     */
+    void onEvent(String eventName, Consumer<RecordedEvent> action);
+
+    /**
+     * Registers an action to perform after the stream has been flushed.
+     *
+     * @param action an action to perform after the stream has been
+     *        flushed, not {@code null}
+     */
+    void onFlush(Runnable action);
+
+    /**
+     * Registers an action to perform if an exception occurs.
+     * <p>
+     * if an action is not registered, an exception stack trace is printed to
+     * standard error.
+     * <p>
+     * Registering an action overrides the default behavior. If multiple actions
+     * have been registered, they are performed in the order of registration.
+     * <p>
+     * If this method itself throws an exception, resulting behavior is
+     * undefined.
+     *
+     * @param action an action to perform if an exception occurs, not
+     *        {@code null}
+     */
+    void onError(Consumer<Throwable> action);
+
+    /**
+     * Registers an action to perform when the stream is closed.
+     * <p>
+     * If the stream is already closed, the action will be performed immediately
+     * in the current thread.
+     *
+     * @param action an action to perform after the stream is closed, not
+     *        {@code null}
+     * @see #close()
+     */
+    void onClose(Runnable action);
+
+    /**
+     * Releases all resources associated with this stream.
+     * <p>
+     * Closing a previously closed stream has no effect.
+     */
+    void close();
+
+    /**
+     * Unregisters an action.
+     * <p>
+     * If the action has been registered multiple times, all instances are
+     * unregistered.
+     *
+     * @param action the action to unregister, not {@code null}
+     *
+     * @return {@code true} if the action was unregistered, {@code false}
+     *         otherwise
+     *
+     * @see #onEvent(Consumer)
+     * @see #onEvent(String, Consumer)
+     * @see #onFlush(Runnable)
+     * @see #onClose(Runnable)
+     * @see #onError(Consumer)
+     */
+    boolean remove(Object action);
+
+    /**
+     * Specifies that the event object in an {@link #onEvent(Consumer)} action
+     * can be reused.
+     * <p>
+     * If reuse is set to {@code true), an action should not keep a reference
+     * to the event object after the action has completed.
+     *
+     * @param reuse {@code true} if an event object can be reused, {@code false}
+     * otherwise
+     */
+    void setReuse(boolean reuse);
+
+    /**
+     * Specifies that events arrives in chronological order, sorted by the time
+     * they were committed to the stream.
+     *
+     * @param ordered if event objects arrive in chronological order to
+     *        {@code #onEvent(Consumer)}
+     */
+    void setOrdered(boolean ordered);
+
+    /**
+     * Specifies the start time of the stream.
+     * <p>
+     * The start time must be set before starting the stream
+     *
+     * @param startTime the start time, not {@code null}
+     *
+     * @throws IllegalStateException if the stream is already started
+     *
+     * @see #start()
+     * @see #startAsync()
+     */
+    void setStartTime(Instant startTime);
+
+    /**
+     * Specifies the end time of the stream.
+     * <p>
+     * The end time must be set before starting the stream.
+     * <p>
+     * At end time, the stream is closed.
+     *
+     * @param endTime the end time, not {@code null}
+     *
+     * @throws IllegalStateException if the stream is already started
+     *
+     * @see #start()
+     * @see #startAsync()
+     */
+    void setEndTime(Instant endTime);
+
+    /**
+     * Start processing of actions.
+     * <p>
+     * Actions are performed in the current thread.
+     *
+     * @throws IllegalStateException if the stream is already started or closed
+     */
+    void start();
+
+    /**
+     * Start asynchronous processing of actions.
+     * <p>
+     * Actions are performed in a single separate thread.
+     *
+     * @throws IllegalStateException if the stream is already started or closed
+     */
+    void startAsync();
+
+    /**
+     * Blocks until all actions are completed, or the stream is closed, or the
+     * timeout occurs, or the current thread is interrupted, whichever happens
+     * first.
+     *
+     * @param timeout the maximum time to wait, not {@code null}
+     *
+     * @throws IllegalArgumentException if timeout is negative
+     * @throws InterruptedException if interrupted while waiting
+     *
+     * @see #start()
+     * @see #startAsync()
+     * @see Thread#interrupt()
+     */
+    void awaitTermination(Duration timeout) throws InterruptedException;
+
+    /**
+     * Blocks until all actions are completed, or the stream is closed, or the
+     * current thread is interrupted, whichever happens first.
+     *
+     * @throws InterruptedException if interrupted while waiting
+     *
+     * @see #start()
+     * @see #startAsync()
+     * @see Thread#interrupt()
+     */
+    void awaitTermination() throws InterruptedException;
+}
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/LongMap.java	Wed Oct 30 16:14:56 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +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.util.HashMap;
-import java.util.Iterator;
-
-/**
- * Commonly used data structure for looking up objects given an id (long value)
- *
- * TODO: Implement without using Map and Long objects, to minimize allocation
- *
- * @param <T>
- */
-final class LongMap<T> implements Iterable<T> {
-    private final HashMap<Long, T> map;
-
-    LongMap() {
-        map = new HashMap<>(101);
-    }
-
-    void put(long id, T object) {
-        map.put(id, object);
-    }
-
-    T get(long id) {
-        return map.get(id);
-    }
-
-    @Override
-    public Iterator<T> iterator() {
-        return map.values().iterator();
-    }
-
-    Iterator<Long> keys() {
-        return map.keySet().iterator();
-    }
-}
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/ObjectFactory.java	Wed Oct 30 16:14:56 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,85 +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.util.List;
-
-import jdk.jfr.ValueDescriptor;
-import jdk.jfr.internal.Type;
-
-/**
- * Abstract factory for creating specialized types
- */
-abstract class ObjectFactory<T> {
-
-    final static String TYPE_PREFIX_VERSION_1 = "com.oracle.jfr.types.";
-    final static String TYPE_PREFIX_VERSION_2 = Type.TYPES_PREFIX;
-    final static String STACK_FRAME_VERSION_1 = TYPE_PREFIX_VERSION_1 + "StackFrame";
-    final static String STACK_FRAME_VERSION_2 = TYPE_PREFIX_VERSION_2 + "StackFrame";
-
-    public static ObjectFactory<?> create(Type type, TimeConverter timeConverter) {
-        switch (type.getName()) {
-        case "java.lang.Thread":
-            return RecordedThread.createFactory(type, timeConverter);
-        case TYPE_PREFIX_VERSION_1 + "StackFrame":
-        case TYPE_PREFIX_VERSION_2 + "StackFrame":
-            return RecordedFrame.createFactory(type, timeConverter);
-        case TYPE_PREFIX_VERSION_1 + "Method":
-        case TYPE_PREFIX_VERSION_2 + "Method":
-            return RecordedMethod.createFactory(type, timeConverter);
-        case TYPE_PREFIX_VERSION_1 + "ThreadGroup":
-        case TYPE_PREFIX_VERSION_2 + "ThreadGroup":
-            return RecordedThreadGroup.createFactory(type, timeConverter);
-        case TYPE_PREFIX_VERSION_1 + "StackTrace":
-        case TYPE_PREFIX_VERSION_2 + "StackTrace":
-            return RecordedStackTrace.createFactory(type, timeConverter);
-        case TYPE_PREFIX_VERSION_1 + "ClassLoader":
-        case TYPE_PREFIX_VERSION_2 + "ClassLoader":
-            return RecordedClassLoader.createFactory(type, timeConverter);
-        case "java.lang.Class":
-            return RecordedClass.createFactory(type, timeConverter);
-        }
-        return null;
-    }
-
-    private final List<ValueDescriptor> valueDescriptors;
-
-    ObjectFactory(Type type) {
-        this.valueDescriptors = type.getFields();
-    }
-
-    T createObject(long id, Object value) {
-        if (value == null) {
-            return null;
-        }
-        if (value instanceof Object[]) {
-            return createTyped(valueDescriptors, id, (Object[]) value);
-        }
-        throw new InternalError("Object factory must have struct type");
-    }
-
-    abstract T createTyped(List<ValueDescriptor> valueDescriptors, long id, Object[] values);
-}
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/Parser.java	Wed Oct 30 16:14:56 2019 +0100
+++ /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;
-}
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/ParserFactory.java	Wed Oct 30 16:14:56 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,303 +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 java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-import jdk.jfr.EventType;
-import jdk.jfr.ValueDescriptor;
-import jdk.jfr.internal.MetadataDescriptor;
-import jdk.jfr.internal.PrivateAccess;
-import jdk.jfr.internal.Type;
-import jdk.jfr.internal.consumer.RecordingInput;
-
-/**
- * Class that create parsers suitable for reading events and constant pools
- */
-final class ParserFactory {
-    private final LongMap<Parser> parsers = new LongMap<>();
-    private final TimeConverter timeConverter;
-    private final LongMap<Type> types = new LongMap<>();
-    private final LongMap<ConstantMap> constantPools;
-
-    public ParserFactory(MetadataDescriptor metadata, TimeConverter timeConverter) throws IOException {
-        this.constantPools = new LongMap<>();
-        this.timeConverter = timeConverter;
-        for (Type t : metadata.getTypes()) {
-            types.put(t.getId(), t);
-        }
-        for (Type t : types) {
-            if (!t.getFields().isEmpty()) { // Avoid primitives
-                CompositeParser cp = createCompositeParser(t);
-                if (t.isSimpleType()) { // Reduce to nested parser
-                   parsers.put(t.getId(), cp.parsers[0]);
-                }
-
-            }
-        }
-        // Override event types with event parsers
-        for (EventType t : metadata.getEventTypes()) {
-            parsers.put(t.getId(), createEventParser(t));
-        }
-    }
-
-    public LongMap<Parser> getParsers() {
-        return parsers;
-    }
-
-    public LongMap<ConstantMap> getConstantPools() {
-        return constantPools;
-    }
-
-    public LongMap<Type> getTypeMap() {
-        return types;
-    }
-
-    private EventParser createEventParser(EventType eventType) throws IOException {
-        List<Parser> parsers = new ArrayList<Parser>();
-        for (ValueDescriptor f : eventType.getFields()) {
-            parsers.add(createParser(f));
-        }
-        return new EventParser(timeConverter, eventType, parsers.toArray(new Parser[0]));
-    }
-
-    private Parser createParser(ValueDescriptor v) throws IOException {
-        boolean constantPool = PrivateAccess.getInstance().isConstantPool(v);
-        if (v.isArray()) {
-            Type valueType = PrivateAccess.getInstance().getType(v);
-            ValueDescriptor element = PrivateAccess.getInstance().newValueDescriptor(v.getName(), valueType, v.getAnnotationElements(), 0, constantPool, null);
-            return new ArrayParser(createParser(element));
-        }
-        long id = v.getTypeId();
-        Type type = types.get(id);
-        if (type == null) {
-            throw new IOException("Type '" + v.getTypeName() + "' is not defined");
-        }
-        if (constantPool) {
-            ConstantMap pool = constantPools.get(id);
-            if (pool == null) {
-                pool = new ConstantMap(ObjectFactory.create(type, timeConverter), type.getName());
-                constantPools.put(id, pool);
-            }
-            return new ConstantMapValueParser(pool);
-        }
-        Parser parser = parsers.get(id);
-        if (parser == null) {
-            if (!v.getFields().isEmpty()) {
-                return createCompositeParser(type);
-            } else {
-                return registerParserType(type, createPrimitiveParser(type));
-            }
-        }
-        return parser;
-    }
-
-    private Parser createPrimitiveParser(Type type) throws IOException {
-        switch (type.getName()) {
-        case "int":
-            return new IntegerParser();
-        case "long":
-            return new LongParser();
-        case "float":
-            return new FloatParser();
-        case "double":
-            return new DoubleParser();
-        case "char":
-            return new CharacterParser();
-        case "boolean":
-            return new BooleanParser();
-        case "short":
-            return new ShortParser();
-        case "byte":
-            return new ByteParser();
-        case "java.lang.String":
-            ConstantMap pool = new ConstantMap(ObjectFactory.create(type, timeConverter), type.getName());
-            constantPools.put(type.getId(), pool);
-            return new StringParser(pool);
-        default:
-            throw new IOException("Unknown primitive type " + type.getName());
-        }
-    }
-
-    private Parser registerParserType(Type t, Parser parser) {
-        Parser p = parsers.get(t.getId());
-        // check if parser exists (known type)
-        if (p != null) {
-            return p;
-        }
-        parsers.put(t.getId(), parser);
-        return parser;
-    }
-
-    private CompositeParser createCompositeParser(Type type) throws IOException {
-        List<ValueDescriptor> vds = type.getFields();
-        Parser[] parsers = new Parser[vds.size()];
-        CompositeParser composite = new CompositeParser(parsers);
-        // need to pre-register so recursive types can be handled
-        registerParserType(type, composite);
-
-        int index = 0;
-        for (ValueDescriptor vd : vds) {
-            parsers[index++] = createParser(vd);
-        }
-        return composite;
-    }
-
-    private static final class BooleanParser extends Parser {
-        @Override
-        public Object parse(RecordingInput input) throws IOException {
-            return input.readBoolean() ? Boolean.TRUE : Boolean.FALSE;
-        }
-    }
-
-    private static final class ByteParser extends Parser {
-        @Override
-        public Object parse(RecordingInput input) throws IOException {
-            return Byte.valueOf(input.readByte());
-        }
-    }
-
-    private static final class LongParser extends Parser {
-        @Override
-        public Object parse(RecordingInput input) throws IOException {
-            return Long.valueOf(input.readLong());
-        }
-    }
-
-    private static final class IntegerParser extends Parser {
-        @Override
-        public Object parse(RecordingInput input) throws IOException {
-            return Integer.valueOf(input.readInt());
-        }
-    }
-
-    private static final class ShortParser extends Parser {
-        @Override
-        public Object parse(RecordingInput input) throws IOException {
-            return Short.valueOf(input.readShort());
-        }
-    }
-
-    private static final class CharacterParser extends Parser {
-        @Override
-        public Object parse(RecordingInput input) throws IOException {
-            return Character.valueOf(input.readChar());
-        }
-    }
-
-    private static final class FloatParser extends Parser {
-        @Override
-        public Object parse(RecordingInput input) throws IOException {
-            return Float.valueOf(input.readFloat());
-        }
-    }
-
-    private static final class DoubleParser extends Parser {
-        @Override
-        public Object parse(RecordingInput input) throws IOException {
-            return Double.valueOf(input.readDouble());
-        }
-    }
-
-    private static final class StringParser extends Parser {
-        private final ConstantMap stringConstantMap;
-        private String last;
-
-        StringParser(ConstantMap stringConstantMap) {
-            this.stringConstantMap = stringConstantMap;
-        }
-
-        @Override
-        public Object parse(RecordingInput input) throws IOException {
-            String s = parseEncodedString(input);
-            if (!Objects.equals(s, last)) {
-                last = s;
-            }
-            return last;
-        }
-
-        private String parseEncodedString(RecordingInput input) throws IOException {
-            byte encoding = input.readByte();
-            if (encoding == RecordingInput.STRING_ENCODING_CONSTANT_POOL) {
-                long id = input.readLong();
-                return (String) stringConstantMap.get(id);
-            } else {
-                return input.readEncodedString(encoding);
-            }
-        }
-    }
-
-    private final static class ArrayParser extends Parser {
-        private final Parser elementParser;
-
-        public ArrayParser(Parser elementParser) {
-            this.elementParser = elementParser;
-        }
-
-        @Override
-        public Object parse(RecordingInput input) throws IOException {
-            final int size = input.readInt();
-            final Object[] array = new Object[size];
-            for (int i = 0; i < size; i++) {
-                array[i] = elementParser.parse(input);
-            }
-            return array;
-        }
-    }
-
-    private final static class CompositeParser extends Parser {
-        private final Parser[] parsers;
-
-        public CompositeParser(Parser[] valueParsers) {
-            this.parsers = valueParsers;
-        }
-
-        @Override
-        public Object parse(RecordingInput input) throws IOException {
-            final Object[] values = new Object[parsers.length];
-            for (int i = 0; i < values.length; i++) {
-                values[i] = parsers[i].parse(input);
-            }
-            return values;
-        }
-    }
-
-    private static final class ConstantMapValueParser extends Parser {
-        private final ConstantMap pool;
-
-        ConstantMapValueParser(ConstantMap pool) {
-            this.pool = pool;
-        }
-
-        @Override
-        public Object parse(RecordingInput input) throws IOException {
-            return pool.get(input.readLong());
-        }
-    }
-}
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedClass.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedClass.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -26,10 +26,8 @@
 package jdk.jfr.consumer;
 
 import java.lang.reflect.Modifier;
-import java.util.List;
 
-import jdk.jfr.ValueDescriptor;
-import jdk.jfr.internal.Type;
+import jdk.jfr.internal.consumer.ObjectContext;
 
 /**
  * A recorded Java type, such as a class or an interface.
@@ -37,21 +35,11 @@
  * @since 9
  */
 public final class RecordedClass extends RecordedObject {
-
-    static ObjectFactory<RecordedClass> createFactory(Type type, TimeConverter timeConverter) {
-        return new ObjectFactory<RecordedClass>(type) {
-            @Override
-            RecordedClass createTyped(List<ValueDescriptor> desc, long id, Object[] object) {
-                return new RecordedClass(desc, id, object, timeConverter);
-            }
-        };
-    }
-
     private final long uniqueId;
 
     // package private
-    private RecordedClass(List<ValueDescriptor> descriptors, long id, Object[] values, TimeConverter timeConverter) {
-        super(descriptors, values, timeConverter);
+    RecordedClass(ObjectContext objectContext, long id, Object[] values) {
+        super(objectContext, values);
         this.uniqueId = id;
     }
 
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedClassLoader.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedClassLoader.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -25,10 +25,7 @@
 
 package jdk.jfr.consumer;
 
-import java.util.List;
-
-import jdk.jfr.ValueDescriptor;
-import jdk.jfr.internal.Type;
+import jdk.jfr.internal.consumer.ObjectContext;
 
 /**
  * A recorded Java class loader.
@@ -36,21 +33,11 @@
  * @since 9
  */
 public final class RecordedClassLoader extends RecordedObject {
-
-    static ObjectFactory<RecordedClassLoader> createFactory(Type type, TimeConverter timeConverter) {
-        return new ObjectFactory<RecordedClassLoader>(type) {
-            @Override
-            RecordedClassLoader createTyped(List<ValueDescriptor> desc, long id, Object[] object) {
-                return new RecordedClassLoader(desc, id, object, timeConverter);
-            }
-        };
-    }
-
     private final long uniqueId;
 
     // package private
-    private RecordedClassLoader(List<ValueDescriptor> descriptors, long id, Object[] values, TimeConverter timeConverter) {
-        super(descriptors, values, timeConverter);
+    RecordedClassLoader(ObjectContext objectContext, long id, Object[] values) {
+        super(objectContext, values);
         this.uniqueId = id;
     }
 
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedEvent.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedEvent.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -32,6 +32,7 @@
 import jdk.jfr.EventType;
 import jdk.jfr.ValueDescriptor;
 import jdk.jfr.internal.EventInstrumentation;
+import jdk.jfr.internal.consumer.ObjectContext;
 
 /**
  * A recorded event.
@@ -39,17 +40,14 @@
  * @since 9
  */
 public final class RecordedEvent extends RecordedObject {
-    private final EventType eventType;
-    private final long startTime;
-    // package private needed for efficient sorting
-    final long endTime;
+    long startTimeTicks;
+    long endTimeTicks;
 
     // package private
-    RecordedEvent(EventType type, List<ValueDescriptor> vds, Object[] values, long startTime, long endTime, TimeConverter timeConverter) {
-        super(vds, values, timeConverter);
-        this.eventType = type;
-        this.startTime = startTime;
-        this.endTime = endTime;
+    RecordedEvent(ObjectContext objectContext, Object[] values, long startTimeTicks, long endTimeTicks) {
+        super(objectContext, values);
+        this.startTimeTicks = startTimeTicks;
+        this.endTimeTicks = endTimeTicks;
     }
 
     /**
@@ -78,7 +76,7 @@
      * @return the event type, not {@code null}
      */
     public EventType getEventType() {
-        return eventType;
+        return objectContext.eventType;
     }
 
     /**
@@ -89,7 +87,7 @@
      * @return the start time, not {@code null}
      */
     public Instant getStartTime() {
-        return Instant.ofEpochSecond(0, startTime);
+        return Instant.ofEpochSecond(0, getStartTimeNanos());
     }
 
     /**
@@ -100,7 +98,7 @@
      * @return the end time, not {@code null}
      */
     public Instant getEndTime() {
-        return Instant.ofEpochSecond(0, endTime);
+        return Instant.ofEpochSecond(0, getEndTimeNanos());
     }
 
     /**
@@ -109,7 +107,7 @@
      * @return the duration in nanoseconds, not {@code null}
      */
     public Duration getDuration() {
-        return Duration.ofNanos(endTime - startTime);
+        return Duration.ofNanos(getEndTimeNanos() - getStartTimeNanos());
     }
 
     /**
@@ -119,6 +117,31 @@
      */
     @Override
     public List<ValueDescriptor> getFields() {
-        return getEventType().getFields();
+        return objectContext.fields;
+    }
+
+    protected final Object objectAt(int index) {
+        if (index == 0) {
+            return startTimeTicks;
+        }
+        if (hasDuration()) {
+            if (index == 1) {
+                return endTimeTicks - startTimeTicks;
+            }
+            return objects[index - 2];
+        }
+        return objects[index - 1];
+    }
+
+    private boolean hasDuration() {
+        return objects.length + 2 == objectContext.fields.size();
+    }
+
+    private long getStartTimeNanos() {
+        return objectContext.convertTimestamp(startTimeTicks);
+    }
+
+    private long getEndTimeNanos() {
+        return objectContext.convertTimestamp(endTimeTicks);
     }
 }
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedFrame.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedFrame.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -26,10 +26,8 @@
 package jdk.jfr.consumer;
 
 import java.lang.reflect.Modifier;
-import java.util.List;
 
-import jdk.jfr.ValueDescriptor;
-import jdk.jfr.internal.Type;
+import jdk.jfr.internal.consumer.ObjectContext;
 
 /**
  * A recorded frame in a stack trace.
@@ -37,19 +35,9 @@
  * @since 9
  */
 public final class RecordedFrame extends RecordedObject {
-
-    static ObjectFactory<RecordedFrame> createFactory(Type type, TimeConverter timeConverter) {
-        return new ObjectFactory<RecordedFrame>(type) {
-            @Override
-            RecordedFrame createTyped(List<ValueDescriptor> desc, long id, Object[] object) {
-                return new RecordedFrame(desc, object, timeConverter);
-            }
-        };
-    }
-
     // package private
-    RecordedFrame(List<ValueDescriptor> desc, Object[] objects, TimeConverter timeConverter) {
-        super(desc, objects, timeConverter);
+    RecordedFrame(ObjectContext objectContext, Object[] values) {
+        super(objectContext, values);
     }
 
     /**
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedMethod.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedMethod.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -26,10 +26,8 @@
 package jdk.jfr.consumer;
 
 import java.lang.reflect.Modifier;
-import java.util.List;
 
-import jdk.jfr.ValueDescriptor;
-import jdk.jfr.internal.Type;
+import jdk.jfr.internal.consumer.ObjectContext;
 
 /**
  * A recorded method.
@@ -38,17 +36,9 @@
  */
 public final class RecordedMethod extends RecordedObject {
 
-    static ObjectFactory<RecordedMethod> createFactory(Type type, TimeConverter timeConverter) {
-        return new ObjectFactory<RecordedMethod>(type) {
-            @Override
-            RecordedMethod createTyped(List<ValueDescriptor> desc, long id, Object[] object) {
-                return new RecordedMethod(desc, object, timeConverter);
-            }
-        };
-    }
-
-    private RecordedMethod(List<ValueDescriptor> descriptors, Object[] objects, TimeConverter timeConverter) {
-        super(descriptors, objects, timeConverter);
+    // package private
+    RecordedMethod(ObjectContext objectContext, Object[] values) {
+        super(objectContext, values);
     }
 
     /**
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedObject.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedObject.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -25,18 +25,24 @@
 
 package jdk.jfr.consumer;
 
+import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.time.Duration;
 import java.time.Instant;
 import java.time.OffsetDateTime;
+import java.util.Comparator;
 import java.util.List;
 import java.util.Objects;
 
 import jdk.jfr.Timespan;
 import jdk.jfr.Timestamp;
 import jdk.jfr.ValueDescriptor;
+import jdk.jfr.internal.consumer.JdkJfrConsumer;
+import jdk.jfr.internal.consumer.ObjectFactory;
 import jdk.jfr.internal.PrivateAccess;
+import jdk.jfr.internal.Type;
+import jdk.jfr.internal.consumer.ObjectContext;
 import jdk.jfr.internal.tool.PrettyWriter;
 
 /**
@@ -51,6 +57,89 @@
  */
 public class RecordedObject {
 
+    static{
+        JdkJfrConsumer access = new JdkJfrConsumer() {
+            public List<Type> readTypes(RecordingFile file) throws IOException {
+                return file.readTypes();
+            }
+
+            public boolean isLastEventInChunk(RecordingFile file) {
+                return file.isLastEventInChunk();
+            }
+
+            @Override
+            public Object getOffsetDataTime(RecordedObject event, String name) {
+                return event.getOffsetDateTime(name);
+            }
+
+            @Override
+            public RecordedClass newRecordedClass(ObjectContext objectContext, long id, Object[] values) {
+                return new RecordedClass(objectContext, id, values);
+            }
+
+            @Override
+            public RecordedClassLoader newRecordedClassLoader(ObjectContext objectContext, long id, Object[] values) {
+                return new RecordedClassLoader(objectContext, id, values);
+            }
+
+            @Override
+            public Comparator<? super RecordedEvent> eventComparator() {
+                return new Comparator<RecordedEvent>()  {
+                    @Override
+                    public int compare(RecordedEvent e1, RecordedEvent e2) {
+                        return Long.compare(e1.endTimeTicks, e2.endTimeTicks);
+                    }
+                };
+            }
+
+            @Override
+            public RecordedStackTrace newRecordedStackTrace(ObjectContext objectContext, Object[] values) {
+                return new RecordedStackTrace(objectContext, values);
+            }
+
+            @Override
+            public RecordedThreadGroup newRecordedThreadGroup(ObjectContext objectContext, Object[] values) {
+                return new RecordedThreadGroup(objectContext, values);
+            }
+
+            @Override
+            public RecordedFrame newRecordedFrame(ObjectContext objectContext, Object[] values) {
+                return new RecordedFrame(objectContext, values);
+            }
+
+            @Override
+            public RecordedThread newRecordedThread(ObjectContext objectContext, long id, Object[] values) {
+                return new RecordedThread(objectContext, id, values);
+            }
+
+            @Override
+            public RecordedMethod newRecordedMethod(ObjectContext objectContext, Object[] values) {
+                return new RecordedMethod(objectContext, values);
+            }
+
+            @Override
+            public RecordedEvent newRecordedEvent(ObjectContext objectContext, Object[] values, long startTimeTicks, long endTimeTicks) {
+                return new RecordedEvent(objectContext, values, startTimeTicks, endTimeTicks);
+            }
+
+            @Override
+            public void setStartTicks(RecordedEvent event, long startTicks) {
+               event.startTimeTicks = startTicks;
+            }
+
+            @Override
+            public void setEndTicks(RecordedEvent event, long endTicks) {
+               event.endTimeTicks = endTicks;
+            }
+
+            @Override
+            public Object[] eventValues(RecordedEvent event) {
+                return event.objects;
+            }
+        };
+        JdkJfrConsumer.setAccess(access);
+    }
+
     private final static class UnsignedValue {
         private final Object o;
 
@@ -63,15 +152,13 @@
         }
     }
 
-    private final Object[] objects;
-    private final List<ValueDescriptor> descriptors;
-    private final TimeConverter timeConverter;
+    final Object[] objects;
+    final ObjectContext objectContext;
 
     // package private, not to be subclassed outside this package
-    RecordedObject(List<ValueDescriptor> descriptors, Object[] objects, TimeConverter timeConverter) {
-        this.descriptors = descriptors;
+    RecordedObject(ObjectContext objectContext, Object[] objects) {
+        this.objectContext = objectContext;
         this.objects = objects;
-        this.timeConverter = timeConverter;
     }
 
     // package private
@@ -101,7 +188,7 @@
      */
     public boolean hasField(String name) {
         Objects.requireNonNull(name);
-        for (ValueDescriptor v : descriptors) {
+        for (ValueDescriptor v : objectContext.fields) {
             if (v.getName().equals(name)) {
                 return true;
             }
@@ -109,7 +196,7 @@
         int dotIndex = name.indexOf(".");
         if (dotIndex > 0) {
             String structName = name.substring(0, dotIndex);
-            for (ValueDescriptor v : descriptors) {
+            for (ValueDescriptor v : objectContext.fields) {
                 if (!v.getFields().isEmpty() && v.getName().equals(structName)) {
                     RecordedObject child = getValue(structName);
                     if (child != null) {
@@ -169,12 +256,16 @@
         return t;
     }
 
+    protected Object objectAt(int index) {
+        return objects[index];
+    }
+
     private Object getValue(String name, boolean allowUnsigned) {
         Objects.requireNonNull(name);
         int index = 0;
-        for (ValueDescriptor v : descriptors) {
+        for (ValueDescriptor v : objectContext.fields) {
             if (name.equals(v.getName())) {
-                Object object = objects[index];
+                Object object = objectAt(index);
                 if (object == null) {
                     // error or missing
                     return null;
@@ -200,7 +291,7 @@
                         return structifyArray(v, array, 0);
                     }
                     // struct
-                    return new RecordedObject(v.getFields(), (Object[]) object, timeConverter);
+                    return new RecordedObject(objectContext.getInstance(v), (Object[]) object);
                 }
             }
             index++;
@@ -209,7 +300,7 @@
         int dotIndex = name.indexOf(".");
         if (dotIndex > 0) {
             String structName = name.substring(0, dotIndex);
-            for (ValueDescriptor v : descriptors) {
+            for (ValueDescriptor v : objectContext.fields) {
                 if (!v.getFields().isEmpty() && v.getName().equals(structName)) {
                     RecordedObject child = getValue(structName);
                     String subName = name.substring(dotIndex + 1);
@@ -261,7 +352,7 @@
     private <T> T getTypedValue(String name, String typeName) {
         Objects.requireNonNull(name);
         // Validate name and type first
-        getValueDescriptor(descriptors, name, typeName);
+        getValueDescriptor(objectContext.fields, name, typeName);
         return getValue(name);
     }
 
@@ -270,15 +361,16 @@
             return null;
         }
         Object[] structArray = new Object[array.length];
+        ObjectContext objContext = objectContext.getInstance(v);
         for (int i = 0; i < structArray.length; i++) {
             Object arrayElement = array[i];
             if (dimension == 0) {
                 // No general way to handle structarrays
                 // without invoking ObjectFactory for every instance (which may require id)
                 if (isStackFrameType(v.getTypeName())) {
-                    structArray[i] = new RecordedFrame(v.getFields(), (Object[]) arrayElement, timeConverter);
+                    structArray[i] = new RecordedFrame(objContext, (Object[]) arrayElement);
                 } else {
-                    structArray[i] = new RecordedObject(v.getFields(), (Object[]) arrayElement, timeConverter);
+                    structArray[i] = new RecordedObject(objContext, (Object[]) arrayElement);
                 }
             } else {
                 structArray[i] = structifyArray(v, (Object[]) arrayElement, dimension - 1);
@@ -303,7 +395,7 @@
      * @return the fields, not {@code null}
      */
     public List<ValueDescriptor> getFields() {
-        return descriptors;
+        return objectContext.fields;
     }
 
     /**
@@ -725,7 +817,7 @@
     }
 
     private Duration getDuration(long timespan, String name) throws InternalError {
-        ValueDescriptor v = getValueDescriptor(descriptors, name, null);
+        ValueDescriptor v = getValueDescriptor(objectContext.fields, name, null);
         if (timespan == Long.MIN_VALUE) {
             return Duration.ofSeconds(Long.MIN_VALUE, 0);
         }
@@ -741,7 +833,7 @@
             case Timespan.NANOSECONDS:
                 return Duration.ofNanos(timespan);
             case Timespan.TICKS:
-                return Duration.ofNanos(timeConverter.convertTimespan(timespan));
+                return Duration.ofNanos(objectContext.convertTimespan(timespan));
             }
             throw new IllegalArgumentException("Attempt to get " + v.getTypeName() + " field \"" + name + "\" with illegal timespan unit " + ts.value());
         }
@@ -804,7 +896,7 @@
     }
 
     private Instant getInstant(long timestamp, String name) {
-        ValueDescriptor v = getValueDescriptor(descriptors, name, null);
+        ValueDescriptor v = getValueDescriptor(objectContext.fields, name, null);
         Timestamp ts = v.getAnnotation(Timestamp.class);
         if (ts != null) {
             if (timestamp == Long.MIN_VALUE) {
@@ -814,7 +906,7 @@
             case Timestamp.MILLISECONDS_SINCE_EPOCH:
                 return Instant.ofEpochMilli(timestamp);
             case Timestamp.TICKS:
-                return Instant.ofEpochSecond(0, timeConverter.convertTimestamp(timestamp));
+                return Instant.ofEpochSecond(0, objectContext.convertTimestamp(timestamp));
             }
             throw new IllegalArgumentException("Attempt to get " + v.getTypeName() + " field \"" + name + "\" with illegal timestamp unit " + ts.value());
         }
@@ -889,12 +981,12 @@
     }
 
     // package private for now. Used by EventWriter
-    OffsetDateTime getOffsetDateTime(String name) {
+    private OffsetDateTime getOffsetDateTime(String name) {
         Instant instant = getInstant(name);
         if (instant.equals(Instant.MIN)) {
             return OffsetDateTime.MIN;
         }
-        return OffsetDateTime.ofInstant(getInstant(name), timeConverter.getZoneOffset());
+        return OffsetDateTime.ofInstant(getInstant(name), objectContext.getZoneOffset());
     }
 
     private static IllegalArgumentException newIllegalArgumentException(String name, String typeName) {
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedStackTrace.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedStackTrace.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -29,8 +29,7 @@
 import java.util.Collections;
 import java.util.List;
 
-import jdk.jfr.ValueDescriptor;
-import jdk.jfr.internal.Type;
+import jdk.jfr.internal.consumer.ObjectContext;
 
 /**
  * A recorded stack trace.
@@ -38,18 +37,9 @@
  * @since 9
  */
 public final class RecordedStackTrace extends RecordedObject {
-
-    static ObjectFactory<RecordedStackTrace> createFactory(Type type, TimeConverter timeConverter) {
-        return new ObjectFactory<RecordedStackTrace>(type) {
-            @Override
-            RecordedStackTrace createTyped(List<ValueDescriptor> desc, long id, Object[] object) {
-                return new RecordedStackTrace(desc, object, timeConverter);
-            }
-        };
-    }
-
-    private RecordedStackTrace(List<ValueDescriptor> desc, Object[] values, TimeConverter timeConverter) {
-        super(desc, values, timeConverter);
+    // package private
+    RecordedStackTrace(ObjectContext objectContext, Object[] values) {
+        super(objectContext, values);
     }
 
     /**
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedThread.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedThread.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -25,10 +25,7 @@
 
 package jdk.jfr.consumer;
 
-import java.util.List;
-
-import jdk.jfr.ValueDescriptor;
-import jdk.jfr.internal.Type;
+import jdk.jfr.internal.consumer.ObjectContext;
 
 /**
  * A recorded thread.
@@ -36,20 +33,11 @@
  * @since 9
  */
 public final class RecordedThread extends RecordedObject {
-
-    static ObjectFactory<RecordedThread> createFactory(Type type, TimeConverter timeConverter) {
-        return new ObjectFactory<RecordedThread>(type) {
-            @Override
-            RecordedThread createTyped(List<ValueDescriptor> desc, long id, Object[] object) {
-                return new RecordedThread(desc, id, object, timeConverter);
-            }
-        };
-    }
-
     private final long uniqueId;
 
-    private RecordedThread(List<ValueDescriptor> descriptors, long id, Object[] values,  TimeConverter timeConverter) {
-        super(descriptors, values, timeConverter);
+    // package private
+    RecordedThread(ObjectContext objectContext, long id, Object[] values) {
+        super(objectContext, values);
         this.uniqueId = id;
     }
 
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedThreadGroup.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedThreadGroup.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -25,10 +25,7 @@
 
 package jdk.jfr.consumer;
 
-import java.util.List;
-
-import jdk.jfr.ValueDescriptor;
-import jdk.jfr.internal.Type;
+import jdk.jfr.internal.consumer.ObjectContext;
 
 /**
  * A recorded Java thread group.
@@ -36,18 +33,9 @@
  * @since 9
  */
 public final class RecordedThreadGroup extends RecordedObject {
-
-    static ObjectFactory<RecordedThreadGroup> createFactory(Type type, TimeConverter timeConverter) {
-        return new ObjectFactory<RecordedThreadGroup>(type) {
-            @Override
-            RecordedThreadGroup createTyped(List<ValueDescriptor> desc, long id, Object[] object) {
-                return new RecordedThreadGroup(desc, object, timeConverter);
-            }
-        };
-    }
-
-    private RecordedThreadGroup(List<ValueDescriptor> descriptors, Object[] objects, TimeConverter timeConverter) {
-        super(descriptors, objects, timeConverter);
+    // package private
+    RecordedThreadGroup(ObjectContext objectContext, Object[] values) {
+        super(objectContext, values);
     }
 
     /**
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordingFile.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordingFile.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -32,7 +32,6 @@
 import java.nio.file.NoSuchFileException;
 import java.nio.file.Path;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 
@@ -40,8 +39,9 @@
 import jdk.jfr.internal.MetadataDescriptor;
 import jdk.jfr.internal.Type;
 import jdk.jfr.internal.consumer.ChunkHeader;
+import jdk.jfr.internal.consumer.ChunkParser;
+import jdk.jfr.internal.consumer.FileAccess;
 import jdk.jfr.internal.consumer.RecordingInput;
-import jdk.jfr.internal.consumer.RecordingInternals;
 
 /**
  * A recording file.
@@ -62,27 +62,6 @@
  * @since 9
  */
 public final class RecordingFile implements Closeable {
-    static{
-        RecordingInternals.INSTANCE = new RecordingInternals() {
-            public List<Type> readTypes(RecordingFile file) throws IOException {
-                return file.readTypes();
-            }
-
-            public boolean isLastEventInChunk(RecordingFile file) {
-                return file.isLastEventInChunk;
-            }
-
-            @Override
-            public Object getOffsetDataTime(RecordedObject event, String name) {
-                return event.getOffsetDateTime(name);
-            }
-
-            @Override
-            public void sort(List<RecordedEvent> events) {
-               Collections.sort(events, (e1, e2) -> Long.compare(e1.endTime, e2.endTime));
-            }
-        };
-    }
 
     private boolean isLastEventInChunk;
     private final File file;
@@ -104,7 +83,7 @@
      */
     public RecordingFile(Path file) throws IOException {
         this.file = file.toFile();
-        this.input = new RecordingInput(this.file);
+        this.input = new RecordingInput(this.file, FileAccess.UNPRIVILIGED);
         findNext();
     }
 
@@ -154,14 +133,15 @@
      */
     public List<EventType> readEventTypes() throws IOException {
         ensureOpen();
+        MetadataDescriptor previous = null;
         List<EventType> types = new ArrayList<>();
         HashSet<Long> foundIds = new HashSet<>();
-        try (RecordingInput ri = new RecordingInput(file)) {
+        try (RecordingInput ri = new RecordingInput(file, FileAccess.UNPRIVILIGED)) {
             ChunkHeader ch = new ChunkHeader(ri);
-            aggregateEventTypeForChunk(ch, types, foundIds);
+            aggregateEventTypeForChunk(ch, null, types, foundIds);
             while (!ch.isLastChunk()) {
                 ch = ch.nextHeader();
-                aggregateEventTypeForChunk(ch, types, foundIds);
+                previous = aggregateEventTypeForChunk(ch, previous, types, foundIds);
             }
         }
         return types;
@@ -169,37 +149,41 @@
 
     List<Type> readTypes() throws IOException  {
         ensureOpen();
+        MetadataDescriptor previous = null;
         List<Type> types = new ArrayList<>();
         HashSet<Long> foundIds = new HashSet<>();
-        try (RecordingInput ri = new RecordingInput(file)) {
+        try (RecordingInput ri = new RecordingInput(file, FileAccess.UNPRIVILIGED)) {
             ChunkHeader ch = new ChunkHeader(ri);
-            aggregateTypeForChunk(ch, types, foundIds);
+            ch.awaitFinished();
+            aggregateTypeForChunk(ch, null, types, foundIds);
             while (!ch.isLastChunk()) {
                 ch = ch.nextHeader();
-                aggregateTypeForChunk(ch, types, foundIds);
+                previous = aggregateTypeForChunk(ch, previous, types, foundIds);
             }
         }
         return types;
     }
 
-    private void aggregateTypeForChunk(ChunkHeader ch, List<Type> types, HashSet<Long> foundIds) throws IOException {
-        MetadataDescriptor m = ch.readMetadata();
+    private MetadataDescriptor aggregateTypeForChunk(ChunkHeader ch, MetadataDescriptor previous, List<Type> types, HashSet<Long> foundIds) throws IOException {
+        MetadataDescriptor m = ch.readMetadata(previous);
         for (Type t : m.getTypes()) {
             if (!foundIds.contains(t.getId())) {
                 types.add(t);
                 foundIds.add(t.getId());
             }
         }
+        return m;
     }
 
-    private static void aggregateEventTypeForChunk(ChunkHeader ch, List<EventType> types, HashSet<Long> foundIds) throws IOException {
-        MetadataDescriptor m = ch.readMetadata();
+    private static MetadataDescriptor aggregateEventTypeForChunk(ChunkHeader ch,  MetadataDescriptor previous, List<EventType> types, HashSet<Long> foundIds) throws IOException {
+        MetadataDescriptor m = ch.readMetadata(previous);
         for (EventType t : m.getEventTypes()) {
             if (!foundIds.contains(t.getId())) {
                 types.add(t);
                 foundIds.add(t.getId());
             }
         }
+        return m;
     }
 
     /**
@@ -246,6 +230,17 @@
         }
     }
 
+    // package protected
+    File getFile() {
+        return file;
+    }
+
+    // package protected
+    boolean isLastEventInChunk() {
+        return isLastEventInChunk;
+    }
+
+
     // either sets next to an event or sets eof to true
     private void findNext() throws IOException {
         while (nextEvent == null) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordingStream.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,362 @@
+/*
+ * 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.time.Instant;
+import java.util.Map;
+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.SecuritySupport;
+import jdk.jfr.internal.Utils;
+import jdk.jfr.internal.consumer.EventDirectoryStream;
+
+/**
+ * A recording stream produces events from the current JVM (Java Virtual
+ * Machine).
+ * <p>
+ * The following example shows how to record events using the default
+ * configuration and print the Garbage Collection, CPU Load and JVM Information
+ * event to standard out.
+ * <pre>
+ * <code>
+ * Configuration c = Configuration.getConfiguration("default");
+ * try (var rs = new RecordingStream(c)) {
+ *     rs.onEvent("jdk.GarbageCollection", System.out::println);
+ *     rs.onEvent("jdk.CPULoad", System.out::println);
+ *     rs.onEvent("jdk.JVMInformation", System.out::println);
+ *     rs.start();
+ *   }
+ * }
+ * </code>
+ * </pre>
+ *
+ * @since 14
+ */
+public final class RecordingStream implements AutoCloseable, EventStream {
+
+    private final Recording recording;
+    private final EventDirectoryStream directoryStream;
+
+    /**
+     * Creates an event stream for the current JVM (Java Virtual Machine).
+     *
+     * @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.directoryStream = new EventDirectoryStream(acc, null, SecuritySupport.PRIVILIGED, true);
+        } catch (IOException ioe) {
+            this.recording.close();
+            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>
+     * var c = Configuration.getConfiguration("default");
+     * try (var rs = new RecordingStream(c)) {
+     *   rs.onEvent(System.out::println);
+     *   rs.start();
+     * }
+     * </code>
+     * </pre>
+     *
+     * @param configuration configuration that contains the settings to 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);
+    }
+
+    /**
+     * Replaces all settings for this recording stream.
+     * <p>
+     * The following example records 20 seconds using the "default" configuration
+     * and then changes settings to the "profile" configuration.
+     *
+     * <pre>
+     * <code>
+     *     Configuration defaultConfiguration = Configuration.getConfiguration("default");
+     *     Configuration profileConfiguration = Configuration.getConfiguration("profile");
+     *     try (var rs = new RecordingStream(defaultConfiguration) {
+     *        rs.onEvent(System.out::println);
+     *        rs.startAsync();
+     *        Thread.sleep(20_000);
+     *        rs.setSettings(profileConfiguration.getSettings());
+     *        Thread.sleep(20_000);
+     *     }
+     * </code>
+     * </pre>
+     *
+     * @param settings the settings to set, not {@code null}
+     *
+     * @see Recording#setSettings(Map)
+     */
+    public void setSettings(Map<String, String> settings) {
+        recording.setSettings(settings);
+    };
+
+    /**
+     * 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 are
+     * 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.
+     * <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} 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 for the stream.
+     * <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.
+     * <p>
+     * The size is measured in bytes.
+     *
+     * @param maxSize the amount of data to retain, {@code 0} if infinite
+     *
+     * @throws IllegalArgumentException if {@code maxSize} is negative
+     *
+     * @throws IllegalStateException if the recording is in {@code CLOSED} state
+     */
+    public void setMaxSize(long maxSize) {
+        recording.setMaxSize(maxSize);
+    }
+
+    /**
+     * Determines how often events are made available for streaming.
+     *
+     * @param interval the interval at which events are made available to the
+     *        stream, no {@code null}
+     *
+     * @throws IllegalArgumentException if {@code interval} is negative
+     *
+     * @throws IllegalStateException if the stream is closed
+     */
+    public void setFlushInterval(Duration interval) {
+        recording.setFlushInterval(interval);
+    }
+
+    @Override
+    public void setReuse(boolean reuse) {
+        directoryStream.setReuse(reuse);
+    }
+
+    @Override
+    public void setOrdered(boolean ordered) {
+        directoryStream.setOrdered(ordered);
+    }
+
+    @Override
+    public void setStartTime(Instant startTime) {
+        directoryStream.setStartTime(startTime);
+    }
+
+    @Override
+    public void setEndTime(Instant endTime) {
+        directoryStream.setEndTime(endTime);
+    }
+
+    @Override
+    public void onEvent(String eventName, Consumer<RecordedEvent> action) {
+        directoryStream.onEvent(eventName, action);
+    }
+
+    @Override
+    public void onEvent(Consumer<RecordedEvent> action) {
+        directoryStream.onEvent(action);
+    }
+
+    @Override
+    public void onFlush(Runnable action) {
+        directoryStream.onFlush(action);
+    }
+
+    @Override
+    public void onClose(Runnable action) {
+        directoryStream.onClose(action);
+    }
+
+    @Override
+    public void onError(Consumer<Throwable> action) {
+        directoryStream.onError(action);
+    }
+
+    @Override
+    public void close() {
+        recording.close();
+        directoryStream.close();
+    }
+
+    @Override
+    public boolean remove(Object action) {
+        return directoryStream.remove(action);
+    }
+
+    @Override
+    public void start() {
+        PlatformRecording pr = PrivateAccess.getInstance().getPlatformRecording(recording);
+        long startNanos = pr.start();
+        directoryStream.start(startNanos);
+    }
+
+    @Override
+    public void startAsync() {
+        PlatformRecording pr = PrivateAccess.getInstance().getPlatformRecording(recording);
+        long startNanos = pr.start();
+        directoryStream.startAsync(startNanos);
+    }
+
+    @Override
+    public void awaitTermination(Duration timeout) throws InterruptedException {
+        directoryStream.awaitTermination(timeout);
+    }
+
+    @Override
+    public void awaitTermination() throws InterruptedException {
+        directoryStream.awaitTermination();
+    }
+}
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/TimeConverter.java	Wed Oct 30 16:14:56 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,72 +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.time.DateTimeException;
-import java.time.ZoneOffset;
-
-import jdk.jfr.internal.LogLevel;
-import jdk.jfr.internal.LogTag;
-import jdk.jfr.internal.Logger;
-import jdk.jfr.internal.consumer.ChunkHeader;
-
-/**
- * Converts ticks to nanoseconds
- */
-final class TimeConverter {
-    private final long startTicks;
-    private final long startNanos;
-    private final double divisor;
-    private final ZoneOffset zoneOffet;
-
-    TimeConverter(ChunkHeader chunkHeader, int rawOffset) {
-        this.startTicks = chunkHeader.getStartTicks();
-        this.startNanos = chunkHeader.getStartNanos();
-        this.divisor = chunkHeader.getTicksPerSecond() / 1000_000_000L;
-        this.zoneOffet = zoneOfSet(rawOffset);
-    }
-
-    private ZoneOffset zoneOfSet(int rawOffset) {
-        try {
-            return ZoneOffset.ofTotalSeconds(rawOffset / 1000);
-        } catch (DateTimeException dte) {
-            Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Could not create ZoneOffset from raw offset " + rawOffset);
-        }
-        return ZoneOffset.UTC;
-    }
-
-    public long convertTimestamp(long ticks) {
-        return startNanos + (long) ((ticks - startTicks) / divisor);
-    }
-
-    public long convertTimespan(long ticks) {
-        return (long) (ticks / divisor);
-    }
-
-    public ZoneOffset getZoneOffset() {
-        return zoneOffet;
-    }
-}
--- a/src/jdk.jfr/share/classes/jdk/jfr/events/ActiveRecordingEvent.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/events/ActiveRecordingEvent.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -40,6 +40,13 @@
 @StackTrace(false)
 public final class ActiveRecordingEvent extends AbstractJDKEvent {
 
+    public static final ThreadLocal<ActiveRecordingEvent> EVENT = new ThreadLocal<ActiveRecordingEvent>() {
+        @Override
+        protected ActiveRecordingEvent initialValue() {
+            return new ActiveRecordingEvent();
+        }
+    };
+
     @Label("Id")
     public long id;
 
@@ -53,6 +60,10 @@
     @Timespan(Timespan.MILLISECONDS)
     public long maxAge;
 
+    @Label("Flush Interval")
+    @Timespan(Timespan.MILLISECONDS)
+    public long flushInterval;
+
     @Label("Max Size")
     @DataAmount
     public long maxSize;
--- a/src/jdk.jfr/share/classes/jdk/jfr/events/ActiveSettingEvent.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/events/ActiveSettingEvent.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -37,6 +37,13 @@
 @StackTrace(false)
 public final class ActiveSettingEvent extends AbstractJDKEvent {
 
+    public static final ThreadLocal<ActiveSettingEvent> EVENT = new ThreadLocal<ActiveSettingEvent>() {
+        @Override
+        protected ActiveSettingEvent initialValue() {
+            return new ActiveSettingEvent();
+        }
+    };
+
     @Label("Event Id")
     public long id;
 
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -32,11 +32,7 @@
 import java.lang.reflect.Modifier;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
 
 import jdk.internal.module.Modules;
 import jdk.jfr.AnnotationElement;
@@ -59,7 +55,14 @@
 // holds SettingControl instances that need to be released
 // when a class is unloaded (to avoid memory leaks).
 public final class EventControl {
-
+    final static class NamedControl {
+        public final String name;
+        public final Control control;
+        NamedControl(String name, Control control) {
+            this.name = name;
+            this.control = control;
+        }
+    }
     static final String FIELD_SETTING_PREFIX = "setting";
     private static final Type TYPE_ENABLED = TypeLibrary.createType(EnabledSetting.class);
     private static final Type TYPE_THRESHOLD = TypeLibrary.createType(ThresholdSetting.class);
@@ -67,24 +70,24 @@
     private static final Type TYPE_PERIOD = TypeLibrary.createType(PeriodSetting.class);
     private static final Type TYPE_CUTOFF = TypeLibrary.createType(CutoffSetting.class);
 
-    private final List<SettingInfo> settingInfos = new ArrayList<>();
-    private final Map<String, Control> eventControls = new HashMap<>(5);
+    private final ArrayList<SettingInfo> settingInfos = new ArrayList<>();
+    private final ArrayList<NamedControl> namedControls = new ArrayList<>(5);
     private final PlatformEventType type;
     private final String idName;
 
     EventControl(PlatformEventType eventType) {
-        eventControls.put(Enabled.NAME, defineEnabled(eventType));
+        addControl(Enabled.NAME, defineEnabled(eventType));
         if (eventType.hasDuration()) {
-            eventControls.put(Threshold.NAME, defineThreshold(eventType));
+            addControl(Threshold.NAME, defineThreshold(eventType));
         }
         if (eventType.hasStackTrace()) {
-            eventControls.put(StackTrace.NAME, defineStackTrace(eventType));
+            addControl(StackTrace.NAME, defineStackTrace(eventType));
         }
         if (eventType.hasPeriod()) {
-            eventControls.put(Period.NAME, definePeriod(eventType));
+            addControl(Period.NAME, definePeriod(eventType));
         }
         if (eventType.hasCutoff()) {
-            eventControls.put(Cutoff.NAME, defineCutoff(eventType));
+            addControl(Cutoff.NAME, defineCutoff(eventType));
         }
 
         ArrayList<AnnotationElement> aes = new ArrayList<>(eventType.getAnnotationElements());
@@ -99,6 +102,19 @@
         this.idName = String.valueOf(eventType.getId());
     }
 
+    private boolean hasControl(String name) {
+        for (NamedControl nc : namedControls) {
+            if (name.equals(nc.name)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void addControl(String name, Control control) {
+        namedControls.add(new NamedControl(name, control));
+    }
+
     static void remove(PlatformEventType type, List<AnnotationElement> aes, Class<? extends java.lang.annotation.Annotation> clazz) {
         long id = Type.getTypeId(clazz);
         for (AnnotationElement a : type.getAnnotationElements()) {
@@ -131,7 +147,8 @@
                             if (n != null) {
                                 name = n.value();
                             }
-                            if (!eventControls.containsKey(name)) {
+
+                            if (!hasControl(name)) {
                                 defineSetting((Class<? extends SettingControl>) settingClass, m, type, name);
                             }
                         }
@@ -163,7 +180,7 @@
                     }
                 }
                 aes.trimToSize();
-                eventControls.put(settingName, si.settingControl);
+                addControl(settingName, si.settingControl);
                 eventType.add(PrivateAccess.getInstance().newSettingDescriptor(settingType, settingName, defaultValue, aes));
                 settingInfos.add(si);
             }
@@ -247,9 +264,9 @@
     }
 
     void disable() {
-        for (Control c : eventControls.values()) {
-            if (c instanceof EnabledSetting) {
-                c.setValueSafe("false");
+        for (NamedControl nc : namedControls) {
+            if (nc.control instanceof EnabledSetting) {
+                nc.control.setValueSafe("false");
                 return;
             }
         }
@@ -259,24 +276,23 @@
         if (!type.isRegistered()) {
             return;
         }
-        for (Map.Entry<String, Control> entry : eventControls.entrySet()) {
-            Control c = entry.getValue();
-            if (Utils.isSettingVisible(c, type.hasEventHook())) {
-                String value = c.getLastValue();
+        ActiveSettingEvent event = ActiveSettingEvent.EVENT.get();
+        for (NamedControl nc : namedControls) {
+            if (Utils.isSettingVisible(nc.control, type.hasEventHook())) {
+                String value = nc.control.getLastValue();
                 if (value == null) {
-                    value = c.getDefaultValue();
+                    value = nc.control.getDefaultValue();
                 }
-                ActiveSettingEvent ase = new ActiveSettingEvent();
-                ase.id = type.getId();
-                ase.name = entry.getKey();
-                ase.value = value;
-                ase.commit();
+                event.id = type.getId();
+                event.name = nc.name;
+                event.value = value;
+                event.commit();
             }
         }
     }
 
-    public Set<Entry<String, Control>> getEntries() {
-        return eventControls.entrySet();
+    public ArrayList<NamedControl> getNamedControls() {
+        return namedControls;
     }
 
     public PlatformEventType getEventType() {
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventInstrumentation.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventInstrumentation.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -363,74 +363,72 @@
             methodVisitor.visitMaxs(0, 0);
         });
 
-        // MyEvent#commit() - Java event writer
         updateMethod(METHOD_COMMIT, methodVisitor -> {
-                // if (!isEnable()) {
-                // return;
-                // }
-                methodVisitor.visitCode();
-                methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
-                methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, getInternalClassName(), METHOD_IS_ENABLED.getName(), METHOD_IS_ENABLED.getDescriptor(), false);
-                Label l0 = new Label();
-                methodVisitor.visitJumpInsn(Opcodes.IFNE, l0);
-                methodVisitor.visitInsn(Opcodes.RETURN);
-                methodVisitor.visitLabel(l0);
-                methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
-                // if (startTime == 0) {
-                // startTime = EventWriter.timestamp();
-                // } else {
-                methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
-                methodVisitor.visitFieldInsn(Opcodes.GETFIELD, getInternalClassName(), FIELD_START_TIME, "J");
-                methodVisitor.visitInsn(Opcodes.LCONST_0);
-                methodVisitor.visitInsn(Opcodes.LCMP);
-                Label durationalEvent = new Label();
-                methodVisitor.visitJumpInsn(Opcodes.IFNE, durationalEvent);
+            // if (!isEnable()) {
+            // return;
+            // }
+            methodVisitor.visitCode();
+            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
+            methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, getInternalClassName(), METHOD_IS_ENABLED.getName(), METHOD_IS_ENABLED.getDescriptor(), false);
+            Label l0 = new Label();
+            methodVisitor.visitJumpInsn(Opcodes.IFNE, l0);
+            methodVisitor.visitInsn(Opcodes.RETURN);
+            methodVisitor.visitLabel(l0);
+            methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+            // if (startTime == 0) {
+            // startTime = EventWriter.timestamp();
+            // } else {
+            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
+            methodVisitor.visitFieldInsn(Opcodes.GETFIELD, getInternalClassName(), FIELD_START_TIME, "J");
+            methodVisitor.visitInsn(Opcodes.LCONST_0);
+            methodVisitor.visitInsn(Opcodes.LCMP);
+            Label durationalEvent = new Label();
+            methodVisitor.visitJumpInsn(Opcodes.IFNE, durationalEvent);
+            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
+            methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, TYPE_EVENT_HANDLER.getInternalName(), METHOD_TIME_STAMP.getName(), METHOD_TIME_STAMP.getDescriptor(), false);
+            methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, getInternalClassName(), FIELD_START_TIME, "J");
+            Label commit = new Label();
+            methodVisitor.visitJumpInsn(Opcodes.GOTO, commit);
+            // if (duration == 0) {
+            // duration = EventWriter.timestamp() - startTime;
+            // }
+            // }
+            methodVisitor.visitLabel(durationalEvent);
+            methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
+            methodVisitor.visitFieldInsn(Opcodes.GETFIELD, getInternalClassName(), FIELD_DURATION, "J");
+            methodVisitor.visitInsn(Opcodes.LCONST_0);
+            methodVisitor.visitInsn(Opcodes.LCMP);
+            methodVisitor.visitJumpInsn(Opcodes.IFNE, commit);
+            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
+            methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, TYPE_EVENT_HANDLER.getInternalName(), METHOD_TIME_STAMP.getName(), METHOD_TIME_STAMP.getDescriptor(), false);
+            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
+            methodVisitor.visitFieldInsn(Opcodes.GETFIELD, getInternalClassName(), FIELD_START_TIME, "J");
+            methodVisitor.visitInsn(Opcodes.LSUB);
+            methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, getInternalClassName(), FIELD_DURATION, "J");
+            methodVisitor.visitLabel(commit);
+            // if (shouldCommit()) {
+            methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+            methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
+            methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, getInternalClassName(), METHOD_EVENT_SHOULD_COMMIT.getName(), METHOD_EVENT_SHOULD_COMMIT.getDescriptor(), false);
+            Label end = new Label();
+            // eventHandler.write(...);
+            // }
+            methodVisitor.visitJumpInsn(Opcodes.IFEQ, end);
+            getEventHandler(methodVisitor);
+
+            methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, eventHandlerXInternalName);
+            for (FieldInfo fi : fieldInfos) {
                 methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
-                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, TYPE_EVENT_HANDLER.getInternalName(), METHOD_TIME_STAMP.getName(),
-                        METHOD_TIME_STAMP.getDescriptor(), false);
-                methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, getInternalClassName(), FIELD_START_TIME, "J");
-                Label commit = new Label();
-                methodVisitor.visitJumpInsn(Opcodes.GOTO, commit);
-                // if (duration == 0) {
-                // duration = EventWriter.timestamp() - startTime;
-                // }
-                // }
-                methodVisitor.visitLabel(durationalEvent);
-                methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
-                methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
-                methodVisitor.visitFieldInsn(Opcodes.GETFIELD, getInternalClassName(), FIELD_DURATION, "J");
-                methodVisitor.visitInsn(Opcodes.LCONST_0);
-                methodVisitor.visitInsn(Opcodes.LCMP);
-                methodVisitor.visitJumpInsn(Opcodes.IFNE, commit);
-                methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
-                methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, TYPE_EVENT_HANDLER.getInternalName(), METHOD_TIME_STAMP.getName(), METHOD_TIME_STAMP.getDescriptor(), false);
-                methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
-                methodVisitor.visitFieldInsn(Opcodes.GETFIELD, getInternalClassName(), FIELD_START_TIME, "J");
-                methodVisitor.visitInsn(Opcodes.LSUB);
-                methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, getInternalClassName(), FIELD_DURATION, "J");
-                methodVisitor.visitLabel(commit);
-                // if (shouldCommit()) {
-                methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
-                methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
-                methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, getInternalClassName(), METHOD_EVENT_SHOULD_COMMIT.getName(), METHOD_EVENT_SHOULD_COMMIT.getDescriptor(), false);
-                Label end = new Label();
-                // eventHandler.write(...);
-                // }
-                methodVisitor.visitJumpInsn(Opcodes.IFEQ, end);
-                getEventHandler(methodVisitor);
+                methodVisitor.visitFieldInsn(Opcodes.GETFIELD, fi.internalClassName, fi.fieldName, fi.fieldDescriptor);
+            }
 
-                methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, eventHandlerXInternalName);
-                for (FieldInfo fi : fieldInfos) {
-                    methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
-                    methodVisitor.visitFieldInsn(Opcodes.GETFIELD, fi.internalClassName, fi.fieldName, fi.fieldDescriptor);
-                }
-
-                methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, eventHandlerXInternalName, writeMethod.getName(), writeMethod.getDescriptor(), false);
-                methodVisitor.visitLabel(end);
-                methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
-                methodVisitor.visitInsn(Opcodes.RETURN);
-                methodVisitor.visitEnd();
-            });
+            methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, eventHandlerXInternalName, writeMethod.getName(), writeMethod.getDescriptor(), false);
+            methodVisitor.visitLabel(end);
+            methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+            methodVisitor.visitInsn(Opcodes.RETURN);
+            methodVisitor.visitEnd();
+        });
 
         // MyEvent#shouldCommit()
         updateMethod(METHOD_EVENT_SHOULD_COMMIT, methodVisitor -> {
@@ -469,6 +467,7 @@
         });
     }
 
+
     private void getEventHandler(MethodVisitor methodVisitor) {
         if (untypedEventHandler) {
             methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, getInternalClassName(), FIELD_EVENT_HANDLER, TYPE_OBJECT.getDescriptor());
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventWriter.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventWriter.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -26,7 +26,7 @@
 package jdk.jfr.internal;
 
 import jdk.internal.misc.Unsafe;
-import jdk.jfr.internal.consumer.RecordingInput;
+import jdk.jfr.internal.consumer.StringParser;
 
 /**
  * Class must reside in a package with package restriction.
@@ -115,18 +115,18 @@
 
     public void putString(String s, StringPool pool) {
         if (s == null) {
-            putByte(RecordingInput.STRING_ENCODING_NULL);
+            putByte(StringParser.Encoding.NULL.byteValue());
             return;
         }
         int length = s.length();
         if (length == 0) {
-            putByte(RecordingInput.STRING_ENCODING_EMPTY_STRING);
+            putByte(StringParser.Encoding.EMPTY_STRING.byteValue());
             return;
         }
         if (length > StringPool.MIN_LIMIT && length < StringPool.MAX_LIMIT) {
             long l = StringPool.addString(s);
             if (l > 0) {
-                putByte(RecordingInput.STRING_ENCODING_CONSTANT_POOL);
+                putByte(StringParser.Encoding.CONSTANT_POOL.byteValue());
                 putLong(l);
                 return;
             }
@@ -138,7 +138,7 @@
     private void putStringValue(String s) {
         int length = s.length();
         if (isValidForSize(1 + 5 + 3 * length)) {
-            putUncheckedByte(RecordingInput.STRING_ENCODING_CHAR_ARRAY); // 1 byte
+            putUncheckedByte(StringParser.Encoding.CHAR_ARRAY.byteValue()); // 1 byte
             putUncheckedInt(length); // max 5 bytes
             for (int i = 0; i < length; i++) {
                 putUncheckedChar(s.charAt(i)); // max 3 bytes
@@ -197,11 +197,7 @@
         if (currentPosition + requestedSize > maxPosition) {
             flushOnEnd = flush(usedSize(), requestedSize);
             // retry
-            if (currentPosition + requestedSize > maxPosition) {
-                Logger.log(LogTag.JFR_SYSTEM,
-                           LogLevel.WARN, () ->
-                               "Unable to commit. Requested size " + requestedSize + " too large");
-                valid = false;
+            if (!valid) {
                 return false;
             }
         }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/FilePurger.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,73 @@
+/*
+ * 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.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;
+        }
+    }
+}
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 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
@@ -270,7 +270,6 @@
      *
      * @param file the file where data should be written, or null if it should
      *        not be copied out (in memory).
-     *
      * @throws IOException
      */
     public native void setOutput(String file);
@@ -460,6 +459,16 @@
     public static native boolean flush(EventWriter writer, int uncommittedSize, int requestedSize);
 
     /**
+     * Flushes all thread buffers to disk and the constant pool data needed to read
+     * them.
+     * <p>
+     * When the method returns, the chunk header should be updated with valid
+     * pointers to the metadata event, last check point event, correct file size and
+     * the generation id.
+     *
+     */
+    public native void flush();
+    /**
      * Sets the location of the disk repository, to be used at an emergency
      * dump.
      *
@@ -523,4 +532,30 @@
      * @return if it is time to perform a chunk rotation
      */
     public native boolean shouldRotateDisk();
+
+    /**
+     * Exclude a thread from the jfr system
+     *
+     */
+    public native void exclude(Thread thread);
+
+    /**
+     * Include a thread back into the jfr system
+     *
+     */
+    public native void include(Thread thread);
+
+    /**
+     * Test if a thread ius currently excluded from the jfr system.
+     *
+     * @return is thread currently excluded
+     */
+    public native boolean isExcluded(Thread thread);
+
+    /**
+     * Get the start time in nanos from the header of the current chunk
+     *
+     *@return start time of the recording in nanos, -1 in case of in-memory
+     */
+    public native long getChunkStartNanos();
 }
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/LogTag.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/LogTag.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -63,21 +63,25 @@
      */
     JFR_SYSTEM_METADATA(6),
     /**
+     *  Covers streaming (for Hotspot developers)
+     */
+    JFR_SYSTEM_STREAMING(7),
+    /**
      *  Covers metadata for Java user (for Hotspot developers)
      */
-    JFR_METADATA(7),
+    JFR_METADATA(8),
     /**
      * Covers events (for users of the JDK)
      */
-    JFR_EVENT(8),
+    JFR_EVENT(9),
     /**
      * Covers setting (for users of the JDK)
      */
-    JFR_SETTING(9),
+    JFR_SETTING(10),
     /**
      * Covers usage of jcmd with JFR
      */
-    JFR_DCMD(10);
+    JFR_DCMD(11);
 
     /* set from native side */
     volatile int tagSetLevel = 100; // prevent logging if JVM log system has not been initialized
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/LongMap.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,257 @@
+/*
+ * 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.internal;
+
+import java.util.BitSet;
+import java.util.function.Consumer;
+import java.util.function.LongConsumer;
+
+@SuppressWarnings("unchecked")
+public final class LongMap<T> {
+    private static final int MAXIMUM_CAPACITY = 1 << 30;
+    private static final long[] EMPTY_KEYS = new long[0];
+    private static final Object[] EMPTY_OBJECTS = new Object[0];
+    private static final int DEFAULT_SIZE = 32;
+    private static final Object NULL_OBJECT = new Object();
+
+    private final int bitCount;
+    private BitSet bitSet;
+    private long[] keys = EMPTY_KEYS;
+    private T[] objects = (T[]) EMPTY_OBJECTS;
+    private int count;
+    private int shift;
+
+    public LongMap() {
+        this.bitCount = 0;
+    }
+
+    public LongMap(int markBits) {
+        this.bitCount = markBits;
+        this.bitSet = new BitSet();
+    }
+
+    // Should be 2^n
+    private void initialize(int capacity) {
+        keys = new long[capacity];
+        objects = (T[]) new Object[capacity];
+        shift = 64 - (31 - Integer.numberOfLeadingZeros(capacity));
+    }
+
+    public void claimBits() {
+        // flip last bit back and forth to make bitset expand to max size
+        int lastBit = bitSetIndex(objects.length - 1, bitCount -1);
+        bitSet.flip(lastBit);
+        bitSet.flip(lastBit);
+    }
+
+    public void setId(long id, int bitIndex) {
+        int bitSetIndex = bitSetIndex(tableIndexOf(id), bitIndex);
+        bitSet.set(bitSetIndex, true);
+    }
+
+    public void clearId(long id, int bitIndex) {
+        int bitSetIndex = bitSetIndex(tableIndexOf(id), bitIndex);
+        bitSet.set(bitSetIndex, false);
+    }
+
+    public boolean isSetId(long id, int bitIndex) {
+        int bitSetIndex = bitSetIndex(tableIndexOf(id), bitIndex);
+        return bitSet.get(bitSetIndex);
+    }
+
+    private int bitSetIndex(int tableIndex, int bitIndex) {
+        return bitCount * tableIndex + bitIndex;
+    }
+
+    private int tableIndexOf(long id) {
+        int index = index(id);
+        while (true) {
+            if (objects[index] == null) {
+                throw new InternalError("Unknown id");
+            }
+            if (keys[index] == id) {
+                return index;
+            }
+            index++;
+            if (index == keys.length) {
+                index = 0;
+            }
+        }
+    }
+
+    public boolean hasKey(long id) {
+        int index = index(id);
+        while (true) {
+            if (objects[index] == null) {
+               return false;
+            }
+            if (keys[index] == id) {
+                return true;
+            }
+            index++;
+            if (index == keys.length) {
+                index = 0;
+            }
+        }
+    }
+
+    public void expand(int size) {
+        int l = 4 * size / 3;
+        if (l <= keys.length) {
+            return;
+        }
+        int n = tableSizeFor(l);
+        LongMap<T> temp = new LongMap<>(bitCount);
+        temp.initialize(n);
+        // Optimization, avoid growing while copying bits
+        if (bitCount > 0 && !bitSet.isEmpty()) {
+           temp.claimBits();
+           claimBits();
+        }
+        for (int tIndex = 0; tIndex < keys.length; tIndex++) {
+            T o = objects[tIndex];
+            if (o != null) {
+                long key = keys[tIndex];
+                temp.put(key, o);
+                if (bitCount != 0) {
+                    for (int bIndex = 0; bIndex < bitCount; bIndex++) {
+                        boolean bitValue = isSetId(key, bIndex);
+                        if (bitValue) {
+                            temp.setId(key, bIndex);
+                        }
+                    }
+                }
+            }
+        }
+        keys = temp.keys;
+        objects = temp.objects;
+        shift = temp.shift;
+        bitSet = temp.bitSet;
+    }
+
+    public void put(long id, T object) {
+        if (keys == EMPTY_KEYS) {
+            // Lazy initialization
+            initialize(DEFAULT_SIZE);
+        }
+        if (object == null) {
+            object = (T) NULL_OBJECT;
+        }
+
+        int index = index(id);
+        // probe for empty slot
+        while (true) {
+            if (objects[index] == null) {
+                keys[index] = id;
+                objects[index] = object;
+                count++;
+                // Don't expand lazy since it
+                // can cause resize when replacing
+                // an object.
+                if (count > 3 * keys.length / 4) {
+                    expand(2 * keys.length);
+                }
+                return;
+            }
+            // if it already exists, replace
+            if (keys[index] == id) {
+                objects[index] = object;
+                return;
+            }
+            index++;
+            if (index == keys.length) {
+                index = 0;
+            }
+        }
+    }
+    public T getAt(int tableIndex) {
+        T o =  objects[tableIndex];
+        return o == NULL_OBJECT ? null : o;
+    }
+
+    public T get(long id) {
+        if (keys == EMPTY_KEYS) {
+            return null;
+        }
+        int index = index(id);
+        while (true) {
+            if (objects[index] == null) {
+                return null;
+            }
+            if (keys[index] == id) {
+                return getAt(index);
+            }
+            index++;
+            if (index == keys.length) {
+                index = 0;
+            }
+        }
+    }
+
+    private int index(long id) {
+        return (int) ((id * -7046029254386353131L) >>> shift);
+    }
+
+    // Copied from HashMap::tableSizeFor
+    private static final int tableSizeFor(int cap) {
+        int n = -1 >>> Integer.numberOfLeadingZeros(cap - 1);
+        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
+    }
+
+    public void forEachKey(LongConsumer keyTraverser) {
+        for (int i = 0; i < keys.length; i++) {
+            if (objects[i] != null) {
+                keyTraverser.accept(keys[i]);
+            }
+        }
+    }
+
+    public void forEach(Consumer<T> consumer) {
+        for (int i = 0; i < keys.length; i++) {
+            T o = objects[i];
+            if (o != null) {
+                consumer.accept(o);
+            }
+        }
+    }
+
+    public int size() {
+        return count;
+    }
+
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < objects.length; i++) {
+            sb.append(i);
+            sb.append(": id=");
+            sb.append(keys[i]);
+            sb.append(" ");
+            sb.append(objects[i]);
+            sb.append("\n");
+        }
+        return sb.toString();
+    }
+}
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataDescriptor.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataDescriptor.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -25,7 +25,6 @@
 
 package jdk.jfr.internal;
 
-import java.io.DataInput;
 import java.io.DataOutput;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -35,6 +34,7 @@
 import java.util.TimeZone;
 
 import jdk.jfr.EventType;
+import jdk.jfr.internal.consumer.RecordingInput;
 
 /**
  * Metadata about a chunk
@@ -214,6 +214,7 @@
     long gmtOffset;
     String locale;
     Element root;
+    public long metadataId;
 
     // package private
     MetadataDescriptor() {
@@ -252,7 +253,7 @@
         return locale;
     }
 
-    public static MetadataDescriptor read(DataInput input) throws IOException {
+    public static MetadataDescriptor read(RecordingInput input) throws IOException {
         MetadataReader r = new MetadataReader(input);
         return r.getDescriptor();
     }
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataReader.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataReader.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -49,6 +49,8 @@
 import jdk.jfr.SettingDescriptor;
 import jdk.jfr.ValueDescriptor;
 import jdk.jfr.internal.MetadataDescriptor.Element;
+import jdk.jfr.internal.consumer.RecordingInput;
+import jdk.jfr.internal.consumer.StringParser;
 
 /**
  * Parses metadata.
@@ -61,12 +63,13 @@
     private final MetadataDescriptor descriptor;
     private final Map<Long, Type> types = new HashMap<>();
 
-    public MetadataReader(DataInput input) throws IOException {
+    public MetadataReader(RecordingInput input) throws IOException {
         this.input = input;
         int size = input.readInt();
         this.pool = new ArrayList<>(size);
+        StringParser p = new StringParser(null, false);
         for (int i = 0; i < size; i++) {
-            this.pool.add(input.readUTF());
+            this.pool.add((String) p.parse(input));
         }
         descriptor = new MetadataDescriptor();
         Element root = createElement();
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataRepository.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataRepository.java	Wed Oct 30 19:43:52 2019 +0100
@@ -46,6 +46,7 @@
 import jdk.jfr.Threshold;
 import jdk.jfr.ValueDescriptor;
 import jdk.jfr.internal.RequestEngine.RequestHook;
+import jdk.jfr.internal.consumer.RepositoryFiles;
 import jdk.jfr.internal.handlers.EventHandler;
 
 public final class MetadataRepository {
@@ -207,10 +208,14 @@
     }
 
     public synchronized List<EventControl> getEventControls() {
-        List<EventControl> controls = new ArrayList<>();
+        List<Class<? extends jdk.internal.event.Event>> eventClasses = jvm.getAllEventClasses();
+        ArrayList<EventControl> controls = new ArrayList<>(eventClasses.size() + nativeControls.size());
         controls.addAll(nativeControls);
-        for (EventHandler eh : getEventHandlers()) {
-            controls.add(eh.getEventControl());
+        for (Class<? extends jdk.internal.event.Event> clazz : eventClasses) {
+            EventHandler eh = Utils.getHandler(clazz);
+            if (eh != null) {
+                controls.add(eh.getEventControl());
+            }
         }
         return controls;
     }
@@ -263,7 +268,9 @@
             storeDescriptorInJVM();
         }
         jvm.setOutput(filename);
-
+        if (filename != null) {
+            RepositoryFiles.notifyNewFile();
+        }
         unregisterUnloaded();
         if (unregistered) {
             if (typeLibrary.clearUnregistered()) {
@@ -309,4 +316,11 @@
         throw new InternalError("Mirror class must have annotation " + MirrorEvent.class.getName());
     }
 
+    public synchronized void flush() {
+        if (staleMetadata) {
+            storeDescriptorInJVM();
+        }
+        jvm.flush();
+    }
+
 }
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataWriter.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataWriter.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -53,7 +53,7 @@
 import jdk.jfr.ValueDescriptor;
 import jdk.jfr.internal.MetadataDescriptor.Attribute;
 import jdk.jfr.internal.MetadataDescriptor.Element;
-import jdk.jfr.internal.consumer.RecordingInput;
+import jdk.jfr.internal.consumer.StringParser;
 
 /**
  * Class responsible for converting a list of types into a format that can be
@@ -94,10 +94,10 @@
 
     private void writeString(DataOutput out, String s) throws IOException {
         if (s == null ) {
-            out.writeByte(RecordingInput.STRING_ENCODING_NULL);
+            out.writeByte(StringParser.Encoding.NULL.byteValue());
             return;
         }
-        out.writeByte(RecordingInput.STRING_ENCODING_CHAR_ARRAY); // encoding UTF-16
+        out.writeByte(StringParser.Encoding.CHAR_ARRAY.byteValue()); // encoding UTF-16
         int length = s.length();
         writeInt(out, length);
             for (int i = 0; i < length; i++) {
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 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
@@ -69,7 +69,7 @@
         super(name, Type.SUPER_TYPE_EVENT, id);
         this.dynamicSettings = dynamicSettings;
         this.isJVM = Type.isDefinedByJVM(id);
-        this.isMethodSampling = name.equals(Type.EVENT_NAME_PREFIX + "ExecutionSample") || name.equals(Type.EVENT_NAME_PREFIX + "NativeMethodSample");
+        this.isMethodSampling = isJVM && (name.equals(Type.EVENT_NAME_PREFIX + "ExecutionSample") || name.equals(Type.EVENT_NAME_PREFIX + "NativeMethodSample"));
         this.isJDK = isJDK;
         this.stackTraceOffset = stackTraceOffset(name, isJDK);
     }
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecorder.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecorder.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -31,7 +31,6 @@
 import static jdk.jfr.internal.LogTag.JFR;
 import static jdk.jfr.internal.LogTag.JFR_SYSTEM;
 
-import java.io.IOException;
 import java.security.AccessControlContext;
 import java.security.AccessController;
 import java.time.Duration;
@@ -59,6 +58,7 @@
 
 public final class PlatformRecorder {
 
+
     private final List<PlatformRecording> recordings = new ArrayList<>();
     private final static List<SecureRecorderListener> changeListeners = new ArrayList<>();
     private final Repository repository;
@@ -96,6 +96,7 @@
             Thread t = SecuritySupport.createThreadWitNoPermissions("Permissionless thread", ()-> {
                 result.add(new Timer("JFR Recording Scheduler", true));
             });
+            jvm.exclude(t);
             t.start();
             t.join();
             return result.get(0);
@@ -204,7 +205,7 @@
         repository.clear();
     }
 
-    synchronized void start(PlatformRecording recording) {
+    synchronized long start(PlatformRecording recording) {
         // State can only be NEW or DELAYED because of previous checks
         Instant now = Instant.now();
         recording.setStartTime(now);
@@ -215,14 +216,17 @@
         }
         boolean toDisk = recording.isToDisk();
         boolean beginPhysical = true;
+        long streamInterval = recording.getStreamIntervalMillis();
         for (PlatformRecording s : getRecordings()) {
             if (s.getState() == RecordingState.RUNNING) {
                 beginPhysical = false;
                 if (s.isToDisk()) {
                     toDisk = true;
                 }
+                streamInterval = Math.min(streamInterval, s.getStreamIntervalMillis());
             }
         }
+        long startNanos = -1;
         if (beginPhysical) {
             RepositoryChunk newChunk = null;
             if (toDisk) {
@@ -233,6 +237,7 @@
             }
             currentChunk = newChunk;
             jvm.beginRecording_();
+            startNanos = jvm.getChunkStartNanos();
             recording.setState(RecordingState.RUNNING);
             updateSettings();
             writeMetaEvents();
@@ -242,6 +247,7 @@
                 newChunk = repository.newChunk(now);
                 RequestEngine.doChunkEnd();
                 MetadataRepository.getInstance().setOutput(newChunk.getUnfishedFile().toString());
+                startNanos = jvm.getChunkStartNanos();
             }
             recording.setState(RecordingState.RUNNING);
             updateSettings();
@@ -251,8 +257,12 @@
             }
             currentChunk = newChunk;
         }
+        if (toDisk) {
+            RequestEngine.setFlushInterval(streamInterval);
+        }
+        RequestEngine.doChunkBegin();
 
-        RequestEngine.doChunkBegin();
+        return startNanos;
     }
 
     synchronized void stop(PlatformRecording recording) {
@@ -267,6 +277,7 @@
         Instant now = Instant.now();
         boolean toDisk = false;
         boolean endPhysical = true;
+        long streamInterval = Long.MAX_VALUE;
         for (PlatformRecording s : getRecordings()) {
             RecordingState rs = s.getState();
             if (s != recording && RecordingState.RUNNING == rs) {
@@ -274,6 +285,7 @@
                 if (s.isToDisk()) {
                     toDisk = true;
                 }
+                streamInterval = Math.min(streamInterval, s.getStreamIntervalMillis());
             }
         }
         OldObjectSample.emit(recording);
@@ -309,6 +321,13 @@
             currentChunk = newChunk;
             RequestEngine.doChunkBegin();
         }
+
+        if (toDisk) {
+            RequestEngine.setFlushInterval(streamInterval);
+        } else {
+            RequestEngine.setFlushInterval(Long.MAX_VALUE);
+        }
+
         recording.setState(RecordingState.STOPPED);
     }
 
@@ -338,6 +357,18 @@
         MetadataRepository.getInstance().setSettings(list);
     }
 
+    public synchronized void rotateIfRecordingToDisk() {
+        boolean disk = false;
+        for (PlatformRecording s : getRecordings()) {
+            if (RecordingState.RUNNING == s.getState() && s.isToDisk()) {
+                disk = true;
+            }
+        }
+        if (disk) {
+            rotateDisk();
+        }
+    }
+
     synchronized void rotateDisk() {
         Instant now = Instant.now();
         RepositoryChunk newChunk = repository.newChunk(now);
@@ -395,14 +426,14 @@
                 r.appendChunk(chunk);
             }
         }
+        FilePurger.purge();
     }
 
     private void writeMetaEvents() {
-
         if (activeRecordingEvent.isEnabled()) {
+            ActiveRecordingEvent event = ActiveRecordingEvent.EVENT.get();
             for (PlatformRecording r : getRecordings()) {
                 if (r.getState() == RecordingState.RUNNING && r.shouldWriteMetadataEvent()) {
-                    ActiveRecordingEvent event = new ActiveRecordingEvent();
                     event.id = r.getId();
                     event.name = r.getName();
                     WriteableUserPath p = r.getDestination();
@@ -415,6 +446,8 @@
                     event.maxSize = size == null ? Long.MAX_VALUE : size;
                     Instant start = r.getStartTime();
                     event.recordingStart = start == null ? Long.MAX_VALUE : start.toEpochMilli();
+                    Duration fi = r.getFlushInterval();
+                    event.flushInterval = fi == null ? Long.MAX_VALUE : fi.toMillis();
                     event.commit();
                 }
             }
@@ -448,7 +481,7 @@
                 JVM.FILE_DELTA_CHANGE.wait(duration < 10 ? 10 : duration);
             }
         } catch (InterruptedException e) {
-            e.printStackTrace();
+            // Ignore
         }
     }
 
@@ -550,4 +583,7 @@
         target.setStopTime(endTime);
         target.setInternalDuration(Duration.between(startTime, endTime));
     }
+
+
+
 }
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecording.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformRecording.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -84,6 +84,7 @@
     private TimerTask startTask;
     private AccessControlContext noDestinationDumpOnExitAccessControlContext;
     private boolean shuoldWriteActiveRecordingEvent = true;
+    private Duration flushInterval = Duration.ofSeconds(1);
 
     PlatformRecording(PlatformRecorder recorder, long id) {
         // Typically the access control context is taken
@@ -98,9 +99,10 @@
         this.name = String.valueOf(id);
     }
 
-    public void start() {
+    public long start() {
         RecordingState oldState;
         RecordingState newState;
+        long startNanos = -1;
         synchronized (recorder) {
             oldState = getState();
             if (!Utils.isBefore(state, RecordingState.RUNNING)) {
@@ -111,7 +113,7 @@
                 startTask = null;
                 startTime = null;
             }
-            recorder.start(this);
+            startNanos = recorder.start(this);
             Logger.log(LogTag.JFR, LogLevel.INFO, () -> {
                 // Only print non-default values so it easy to see
                 // which options were added
@@ -143,6 +145,8 @@
             newState = getState();
         }
         notifyIfStateChanged(oldState, newState);
+
+        return startNanos;
     }
 
     public boolean stop(String reason) {
@@ -780,4 +784,28 @@
     public SafePath getDumpOnExitDirectory()  {
         return this.dumpOnExitDirectory;
     }
+
+    public void setFlushInterval(Duration interval) {
+        synchronized (recorder) {
+            if (getState() == RecordingState.CLOSED) {
+                throw new IllegalStateException("Can't set stream interval when recording is closed");
+            }
+            this.flushInterval = interval;
+        }
+    }
+
+    public Duration getFlushInterval() {
+        synchronized (recorder) {
+            return flushInterval;
+        }
+    }
+
+    public long getStreamIntervalMillis() {
+        synchronized (recorder) {
+            if (flushInterval != null) {
+                return flushInterval.toMillis();
+            }
+            return Long.MAX_VALUE;
+        }
+    }
 }
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/Repository.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/Repository.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved.
+ * 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
@@ -43,6 +43,7 @@
 
     public final static DateTimeFormatter REPO_DATE_FORMAT = DateTimeFormatter
             .ofPattern("yyyy_MM_dd_HH_mm_ss");
+    private static final String JFR_REPOSITORY_LOCATION_PROPERTY = "jdk.jfr.repository";
 
     private final Set<SafePath> cleanupDirectories = new HashSet<>();
     private SafePath baseLocation;
@@ -55,8 +56,7 @@
         return instance;
     }
 
-    public synchronized void setBasePath(SafePath baseLocation) throws Exception {
-
+    public synchronized void setBasePath(SafePath baseLocation) throws IOException {
         if(baseLocation.equals(this.baseLocation)) {
             Logger.log(LogTag.JFR, LogLevel.INFO, "Same base repository path " + baseLocation.toString() + " is set");
             return;
@@ -74,7 +74,7 @@
         this.baseLocation = baseLocation;
     }
 
-    synchronized void ensureRepository() throws Exception {
+    public synchronized void ensureRepository() throws IOException {
         if (baseLocation == null) {
             setBasePath(SecuritySupport.JAVA_IO_TMPDIR);
         }
@@ -96,7 +96,7 @@
         }
     }
 
-    private static SafePath createRepository(SafePath basePath) throws Exception {
+    private static SafePath createRepository(SafePath basePath) throws IOException {
         SafePath canonicalBaseRepositoryPath = createRealBasePath(basePath);
         SafePath f = null;
 
@@ -113,13 +113,14 @@
         }
 
         if (i == MAX_REPO_CREATION_RETRIES) {
-            throw new Exception("Unable to create JFR repository directory using base location (" + basePath + ")");
+            throw new IOException("Unable to create JFR repository directory using base location (" + basePath + ")");
         }
         SafePath canonicalRepositoryPath = SecuritySupport.toRealPath(f);
+        SecuritySupport.setProperty(JFR_REPOSITORY_LOCATION_PROPERTY, canonicalRepositoryPath.toString());
         return canonicalRepositoryPath;
     }
 
-    private static SafePath createRealBasePath(SafePath safePath) throws Exception {
+    private static SafePath createRealBasePath(SafePath safePath) throws IOException {
         if (SecuritySupport.exists(safePath)) {
             if (!SecuritySupport.isWritable(safePath)) {
                 throw new IOException("JFR repository directory (" + safePath.toString() + ") exists, but isn't writable");
@@ -159,7 +160,7 @@
                 SecuritySupport.clearDirectory(p);
                 Logger.log(LogTag.JFR, LogLevel.INFO, "Removed repository " + p);
             } catch (IOException e) {
-                Logger.log(LogTag.JFR, LogLevel.ERROR, "Repository " + p + " could not be removed at shutdown: " + e.getMessage());
+                Logger.log(LogTag.JFR, LogLevel.INFO, "Repository " + p + " could not be removed at shutdown: " + e.getMessage());
             }
         }
     }
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/RepositoryChunk.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/RepositoryChunk.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved.
+ * 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
@@ -63,10 +63,10 @@
                 LocalDateTime.ofInstant(startTime, z.getZone()));
         this.startTime = startTime;
         this.repositoryPath = path;
-        this.unFinishedFile = findFileName(repositoryPath, fileName, ".part");
+        this.unFinishedFile = findFileName(repositoryPath, fileName, ".jfr");
         this.file = findFileName(repositoryPath, fileName, ".jfr");
         this.unFinishedRAF = SecuritySupport.createRandomAccessFile(unFinishedFile);
-        SecuritySupport.touch(file);
+ //       SecuritySupport.touch(file);
     }
 
     private static SafePath findFileName(SafePath directory, String name, String extension) throws Exception {
@@ -105,8 +105,6 @@
     private static long finish(SafePath unFinishedFile, SafePath file) throws IOException {
         Objects.requireNonNull(unFinishedFile);
         Objects.requireNonNull(file);
-        SecuritySupport.delete(file);
-        SecuritySupport.moveReplace(unFinishedFile, file);
         return SecuritySupport.getFileSize(file);
     }
 
@@ -123,9 +121,11 @@
             SecuritySupport.delete(f);
             Logger.log(LogTag.JFR, LogLevel.DEBUG, () -> "Repository chunk " + f + " deleted");
         } catch (IOException e) {
-            Logger.log(LogTag.JFR, LogLevel.ERROR, ()  -> "Repository chunk " + f + " could not be deleted: " + e.getMessage());
+            // Probably happens because file is being streamed
+            // on Windows where files in use can't be removed.
+            Logger.log(LogTag.JFR, LogLevel.DEBUG, ()  -> "Repository chunk " + f + " could not be deleted: " + e.getMessage());
             if (f != null) {
-                SecuritySupport.deleteOnExit(f);
+                FilePurger.add(f);
             }
         }
     }
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/RequestEngine.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/RequestEngine.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -30,6 +30,7 @@
 import java.security.PrivilegedAction;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.CopyOnWriteArrayList;
@@ -96,6 +97,8 @@
 
     private final static List<RequestHook> entries = new CopyOnWriteArrayList<>();
     private static long lastTimeMillis;
+    private static long flushInterval = Long.MAX_VALUE;
+    private static long streamDelta;
 
     public static void addHook(AccessControlContext acc, PlatformEventType type, Runnable hook) {
         Objects.requireNonNull(acc);
@@ -209,7 +212,9 @@
             lastTimeMillis = now;
             return 0;
         }
-        for (RequestHook he : entries) {
+        Iterator<RequestHook> hookIterator = entries.iterator();
+        while(hookIterator.hasNext()) {
+            RequestHook he = hookIterator.next();
             long left = 0;
             PlatformEventType es = he.type;
             // Not enabled, skip.
@@ -228,7 +233,6 @@
                 // for wait > period
                 r_delta = 0;
                 he.execute();
-                ;
             }
 
             // calculate time left
@@ -250,7 +254,39 @@
                 min = left;
             }
         }
+        // Flush should happen after all periodic events has been emitted
+        // Repeat of the above algorithm, but using the stream interval.
+        if (flushInterval != Long.MAX_VALUE) {
+            long r_period = flushInterval;
+            long r_delta = streamDelta;
+            r_delta += delta;
+            if (r_delta >= r_period) {
+                r_delta = 0;
+                MetadataRepository.getInstance().flush();
+                Utils.notifyFlush();
+            }
+            long left = (r_period - r_delta);
+            if (left < 0) {
+                left = 0;
+            }
+            streamDelta = r_delta;
+            if (min == 0 || left < min) {
+                min = left;
+            }
+        }
+
         lastTimeMillis = now;
         return min;
     }
+
+    static void setFlushInterval(long millis) {
+        // Don't accept shorter interval than 1 s.
+        long interval = millis < 1000 ? 1000  : millis;
+        flushInterval = interval;
+        if (interval < flushInterval) {
+            synchronized (JVM.FILE_DELTA_CHANGE) {
+                JVM.FILE_DELTA_CHANGE.notifyAll();
+            }
+        }
+    }
 }
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/SecuritySupport.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/SecuritySupport.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -38,6 +38,7 @@
 import java.lang.reflect.ReflectPermission;
 import java.nio.channels.FileChannel;
 import java.nio.channels.ReadableByteChannel;
+import java.nio.file.DirectoryStream;
 import java.nio.file.FileVisitResult;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -65,6 +66,7 @@
 import jdk.jfr.FlightRecorderListener;
 import jdk.jfr.FlightRecorderPermission;
 import jdk.jfr.Recording;
+import jdk.jfr.internal.consumer.FileAccess;
 
 /**
  * Contains JFR code that does
@@ -75,7 +77,7 @@
     private final static MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
     private final static Module JFR_MODULE = Event.class.getModule();
     public  final static SafePath JFC_DIRECTORY = getPathInProperty("java.home", "lib/jfr");
-
+    public final static FileAccess PRIVILIGED = new Privileged();
     static final SafePath USER_HOME = getPathInProperty("user.home", null);
     static final SafePath JAVA_IO_TMPDIR = getPathInProperty("java.io.tmpdir", null);
 
@@ -150,7 +152,7 @@
      * a malicious provider.
      *
      */
-    public static final class SafePath {
+    public static final class SafePath implements Comparable<SafePath> {
         private final Path path;
         private final String text;
 
@@ -168,11 +170,20 @@
             return path;
         }
 
+        public File toFile() {
+            return path.toFile();
+        }
+
         public String toString() {
             return text;
         }
 
         @Override
+        public int compareTo(SafePath that) {
+            return that.text.compareTo(this.text);
+        }
+
+        @Override
         public boolean equals(Object other) {
             if(other != null && other instanceof SafePath){
                 return this.toPath().equals(((SafePath) other).toPath());
@@ -303,6 +314,10 @@
         doPrivileged(() ->  MetadataRepository.getInstance().registerMirror(eventClass), new FlightRecorderPermission(Utils.REGISTER_EVENT));
     }
 
+    public static void setProperty(String propertyName, String value) {
+        doPrivileged(() -> System.setProperty(propertyName, value), new PropertyPermission(propertyName, "write"));
+    }
+
     static boolean getBooleanProperty(String propertyName) {
         return doPrivilegedWithReturn(() -> Boolean.getBoolean(propertyName), new PropertyPermission(propertyName, "read"));
     }
@@ -343,7 +358,7 @@
         doPriviligedIO(() -> Files.walkFileTree(safePath.toPath(), new DirectoryCleaner()));
     }
 
-    static SafePath toRealPath(SafePath safePath) throws Exception {
+    static SafePath toRealPath(SafePath safePath) throws IOException {
         return new SafePath(doPrivilegedIOWithReturn(() -> safePath.toPath().toRealPath()));
     }
 
@@ -369,7 +384,8 @@
     }
 
     public static boolean exists(SafePath safePath) throws IOException {
-        return doPrivilegedIOWithReturn(() -> Files.exists(safePath.toPath()));
+        // Files.exist(path) is allocation intensive
+        return doPrivilegedIOWithReturn(() -> safePath.toPath().toFile().exists());
     }
 
     public static boolean isDirectory(SafePath safePath) throws IOException {
@@ -421,7 +437,7 @@
     }
 
     static Class<?> defineClass(Class<?> lookupClass, byte[] bytes) {
-        return AccessController.doPrivileged(new PrivilegedAction<>() {
+        return AccessController.doPrivileged(new PrivilegedAction<Class<?>>() {
             @Override
             public Class<?> run() {
                 try {
@@ -433,7 +449,7 @@
         });
     }
 
-    static Thread createThreadWitNoPermissions(String threadName, Runnable runnable) {
+    public static Thread createThreadWitNoPermissions(String threadName, Runnable runnable) {
         return doPrivilegedWithReturn(() -> new Thread(runnable, threadName), new Permission[0]);
     }
 
@@ -444,4 +460,32 @@
     public static SafePath getAbsolutePath(SafePath path) throws IOException {
         return new SafePath(doPrivilegedIOWithReturn((()-> path.toPath().toAbsolutePath())));
     }
+
+    private final static class Privileged extends FileAccess {
+        @Override
+        public RandomAccessFile openRAF(File f, String mode) throws IOException {
+            return doPrivilegedIOWithReturn( () -> new RandomAccessFile(f, mode));
+        }
+
+        @Override
+        public  DirectoryStream<Path> newDirectoryStream(Path directory)  throws IOException  {
+            return doPrivilegedIOWithReturn( () -> Files.newDirectoryStream(directory));
+        }
+
+        @Override
+        public  String getAbsolutePath(File f) throws IOException {
+            return doPrivilegedIOWithReturn( () -> f.getAbsolutePath());
+        }
+        @Override
+        public long length(File f) throws IOException {
+            return doPrivilegedIOWithReturn( () -> f.length());
+        }
+
+        @Override
+        public  long fileSize(Path p) throws IOException {
+            return doPrivilegedIOWithReturn( () -> Files.size(p));
+        }
+    }
+
+
 }
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/SettingsManager.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/SettingsManager.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -33,10 +33,10 @@
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.Set;
 import java.util.StringJoiner;
 
+import jdk.jfr.internal.EventControl.NamedControl;
 import jdk.jfr.internal.handlers.EventHandler;
 
 final class SettingsManager {
@@ -213,18 +213,21 @@
 
     void setEventControl(EventControl ec) {
         InternalSetting is = getInternalSetting(ec);
-        Logger.log(LogTag.JFR_SETTING, LogLevel.INFO, "Applied settings for " + ec.getEventType().getLogName() + " {");
-        for (Entry<String, Control> entry : ec.getEntries()) {
+        boolean shouldLog = Logger.shouldLog(LogTag.JFR_SETTING, LogLevel.INFO);
+        if (shouldLog) {
+            Logger.log(LogTag.JFR_SETTING, LogLevel.INFO, "Applied settings for " + ec.getEventType().getLogName() + " {");
+        }
+        for (NamedControl nc: ec.getNamedControls()) {
             Set<String> values = null;
-            String settingName = entry.getKey();
+            String settingName = nc.name;
             if (is != null) {
                 values = is.getValues(settingName);
             }
-            Control control = entry.getValue();
+            Control control = nc.control;
             if (values != null) {
                 control.apply(values);
                 String after = control.getLastValue();
-                if (Logger.shouldLog(LogTag.JFR_SETTING, LogLevel.INFO)) {
+                if (shouldLog) {
                     if (Utils.isSettingVisible(control, ec.getEventType().hasEventHook())) {
                         if (values.size() > 1) {
                             StringJoiner sj = new StringJoiner(", ", "{", "}");
@@ -241,14 +244,16 @@
                 }
             } else {
                 control.setDefault();
-                if (Logger.shouldLog(LogTag.JFR_SETTING, LogLevel.INFO)) {
+                if (shouldLog) {
                     String message = "  " + settingName + "=\"" + control.getLastValue() + "\"";
                     Logger.log(LogTag.JFR_SETTING, LogLevel.INFO, message);
                 }
             }
         }
         ec.writeActiveSettingEvent();
-        Logger.log(LogTag.JFR_SETTING, LogLevel.INFO, "}");
+        if (shouldLog) {
+            Logger.log(LogTag.JFR_SETTING, LogLevel.INFO, "}");
+        }
     }
 
     private InternalSetting getInternalSetting(EventControl ec) {
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/TypeLibrary.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/TypeLibrary.java	Wed Oct 30 19:43:52 2019 +0100
@@ -407,6 +407,7 @@
     // Purpose of this method is to mark types that are reachable
     // from registered event types. Those types that are not reachable can
     // safely be removed
+    // Returns true if type was removed
     public boolean clearUnregistered() {
         Logger.log(LogTag.JFR_METADATA, LogLevel.TRACE, "Cleaning out obsolete metadata");
         List<Type> registered = new ArrayList<>();
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/Utils.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/Utils.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -43,6 +43,7 @@
 import java.lang.reflect.Modifier;
 import java.nio.file.Path;
 import java.time.Duration;
+import java.time.Instant;
 import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -65,17 +66,17 @@
 
 public final class Utils {
 
+    private static final Object flushObject = new Object();
     private static final String INFINITY = "infinity";
-
-    private static Boolean SAVE_GENERATED;
-
     public static final String EVENTS_PACKAGE_NAME = "jdk.jfr.events";
     public static final String INSTRUMENT_PACKAGE_NAME = "jdk.jfr.internal.instrument";
     public static final String HANDLERS_PACKAGE_NAME = "jdk.jfr.internal.handlers";
     public static final String REGISTER_EVENT = "registerEvent";
     public static final String ACCESS_FLIGHT_RECORDER = "accessFlightRecorder";
+    private final static String LEGACY_EVENT_NAME_PREFIX = "com.oracle.jdk.";
 
-    private final static String LEGACY_EVENT_NAME_PREFIX = "com.oracle.jdk.";
+    private static Boolean SAVE_GENERATED;
+
 
     public static void checkAccessFlightRecorder() throws SecurityException {
         SecurityManager sm = System.getSecurityManager();
@@ -595,4 +596,33 @@
         String idText = recording == null ? "" :  "-id-" + Long.toString(recording.getId());
         return "hotspot-" + "pid-" + pid + idText + "-" + date + ".jfr";
     }
+
+    public static void takeNap(long millis) {
+        try {
+            Thread.sleep(millis);
+        } catch (InterruptedException e) {
+            // ok
+        }
+    }
+
+    public static void notifyFlush() {
+        synchronized (flushObject) {
+            flushObject.notifyAll();
+        }
+    }
+
+    public static void waitFlush(long timeOut) {
+        synchronized (flushObject) {
+            try {
+                flushObject.wait(timeOut);
+            } catch (InterruptedException e) {
+                // OK
+            }
+        }
+
+    }
+
+    public static long timeToNanos(Instant timestamp) {
+        return timestamp.getEpochSecond() * 1_000_000_000L + timestamp.getNano();
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/AbstractEventStream.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,273 @@
+/*
+ * 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.internal.consumer;
+
+import java.io.IOException;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Consumer;
+
+import jdk.jfr.consumer.EventStream;
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.jfr.internal.LogLevel;
+import jdk.jfr.internal.LogTag;
+import jdk.jfr.internal.Logger;
+import jdk.jfr.internal.SecuritySupport;
+
+/*
+ * Purpose of this class is to simplify the implementation of
+ * an event stream.
+ */
+abstract class AbstractEventStream implements EventStream {
+    private final static AtomicLong counter = new AtomicLong(1);
+
+    private final Object terminated = new Object();
+    private final boolean active;
+    private final Runnable flushOperation = () -> dispatcher().runFlushActions();
+    private final AccessControlContext accessControllerContext;
+    private final StreamConfiguration configuration = new StreamConfiguration();
+
+    private volatile Thread thread;
+    private Dispatcher dispatcher;
+
+    private volatile boolean closed;
+
+    AbstractEventStream(AccessControlContext acc, boolean active) throws IOException {
+        this.accessControllerContext = Objects.requireNonNull(acc);
+        this.active = active;
+    }
+
+    @Override
+    abstract public void start();
+
+    @Override
+    abstract public void startAsync();
+
+    @Override
+    abstract public void close();
+
+    protected final Dispatcher dispatcher() {
+        if (configuration.hasChanged()) {
+            synchronized (configuration) {
+                dispatcher = new Dispatcher(configuration);
+            }
+        }
+        return dispatcher;
+    }
+
+    @Override
+    public final void setOrdered(boolean ordered) {
+        configuration.setOrdered(ordered);
+    }
+
+    @Override
+    public final void setReuse(boolean reuse) {
+        configuration.setReuse(reuse);
+    }
+
+    @Override
+    public final void setStartTime(Instant startTime) {
+        Objects.nonNull(startTime);
+        synchronized (configuration) {
+            if (configuration.started) {
+                throw new IllegalStateException("Stream is already started");
+            }
+            if (startTime.isBefore(Instant.EPOCH)) {
+                startTime = Instant.EPOCH;
+            }
+            configuration.setStartTime(startTime);
+        }
+    }
+
+    @Override
+    public final void setEndTime(Instant endTime) {
+        Objects.requireNonNull(endTime);
+        synchronized (configuration) {
+            if (configuration.started) {
+                throw new IllegalStateException("Stream is already started");
+            }
+            configuration.setEndTime(endTime);
+        }
+    }
+
+    @Override
+    public final void onEvent(Consumer<RecordedEvent> action) {
+        Objects.requireNonNull(action);
+        configuration.addEventAction(action);
+    }
+
+    @Override
+    public final void onEvent(String eventName, Consumer<RecordedEvent> action) {
+        Objects.requireNonNull(eventName);
+        Objects.requireNonNull(action);
+        configuration.addEventAction(eventName, action);
+    }
+
+    @Override
+    public final void onFlush(Runnable action) {
+        Objects.requireNonNull(action);
+        configuration.addFlushAction(action);
+    }
+
+    @Override
+    public final void onClose(Runnable action) {
+        Objects.requireNonNull(action);
+        configuration.addCloseAction(action);
+    }
+
+    @Override
+    public final void onError(Consumer<Throwable> action) {
+        Objects.requireNonNull(action);
+        configuration.addErrorAction(action);
+    }
+
+    @Override
+    public final boolean remove(Object action) {
+        Objects.requireNonNull(action);
+        return configuration.remove(action);
+    }
+
+    @Override
+    public final void awaitTermination() throws InterruptedException {
+        awaitTermination(Duration.ofMillis(0));
+    }
+
+    @Override
+    public final void awaitTermination(Duration timeout) throws InterruptedException {
+        Objects.requireNonNull(timeout);
+        if (timeout.isNegative()) {
+            throw new IllegalArgumentException("timeout value is negative");
+        }
+
+        long base = System.currentTimeMillis();
+        long now = 0;
+
+        long millis;
+        try {
+            millis = Math.multiplyExact(timeout.getSeconds(), 1000);
+        } catch (ArithmeticException a) {
+            millis = Long.MAX_VALUE;
+        }
+        int nanos = timeout.toNanosPart();
+        if (nanos == 0 && millis == 0) {
+            synchronized (terminated) {
+                while (!isClosed()) {
+                    terminated.wait(0);
+                }
+            }
+        } else {
+            while (!isClosed()) {
+                long delay = millis - now;
+                if (delay <= 0) {
+                    break;
+                }
+                synchronized (terminated) {
+                    terminated.wait(delay, nanos);
+                }
+                now = System.currentTimeMillis() - base;
+            }
+        }
+    }
+
+    protected abstract void process() throws IOException;
+
+    protected final void setClosed(boolean closed) {
+        this.closed = closed;
+    }
+
+    protected final boolean isClosed() {
+        return closed;
+    }
+
+    public final void startAsync(long startNanos) {
+        startInternal(startNanos);
+        Runnable r = () -> run(accessControllerContext);
+        thread = SecuritySupport.createThreadWitNoPermissions(nextThreadName(), r);
+        thread.start();
+    }
+
+    public final void start(long startNanos) {
+        startInternal(startNanos);
+        thread = Thread.currentThread();
+        run(accessControllerContext);
+    }
+
+    protected final Runnable getFlushOperation() {
+        return flushOperation;
+    }
+
+    private void startInternal(long startNanos) {
+        synchronized (configuration) {
+            if (configuration.started) {
+                throw new IllegalStateException("Event stream can only be started once");
+            }
+            if (active && configuration.startTime == null) {
+                configuration.setStartNanos(startNanos);
+            }
+            configuration.setStarted(true);
+        }
+    }
+
+    private void execute() {
+        try {
+            process();
+        } catch (IOException ioe) {
+            // This can happen if a chunk file is removed, or
+            // a file is access that has been closed
+            // This is "normal" behavior for streaming and the
+            // stream will be closed when this happens.
+        } finally {
+            Logger.log(LogTag.JFR_SYSTEM_STREAMING, LogLevel.DEBUG, "Execution of stream ended.");
+            try {
+                close();
+            } finally {
+                synchronized (terminated) {
+                    terminated.notifyAll();
+                }
+            }
+        }
+    }
+
+    private void run(AccessControlContext accessControlContext) {
+        AccessController.doPrivileged(new PrivilegedAction<Void>() {
+            @Override
+            public Void run() {
+                execute();
+                return null;
+            }
+        }, accessControlContext);
+    }
+
+    private String nextThreadName() {
+        counter.incrementAndGet();
+        return "JFR Event Stream " + counter;
+    }
+}
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ChunkHeader.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ChunkHeader.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -25,48 +25,60 @@
 
 package jdk.jfr.internal.consumer;
 
-import java.io.DataInput;
 import java.io.IOException;
 
 import jdk.jfr.internal.LogLevel;
 import jdk.jfr.internal.LogTag;
 import jdk.jfr.internal.Logger;
 import jdk.jfr.internal.MetadataDescriptor;
+import jdk.jfr.internal.Utils;
 
 public final class ChunkHeader {
+    private static final long HEADER_SIZE = 68;
+    private static final byte UPDATING_CHUNK_HEADER = (byte) 255;
+    private static final long CHUNK_SIZE_POSITION = 8;
+    private static final long DURATION_NANOS_POSITION = 40;
+    private static final long FILE_STATE_POSITION = 64;
     private static final long METADATA_TYPE_ID = 0;
     private static final byte[] FILE_MAGIC = { 'F', 'L', 'R', '\0' };
 
     private final short major;
     private final short minor;
-    private final long chunkSize;
     private final long chunkStartTicks;
     private final long ticksPerSecond;
     private final long chunkStartNanos;
-    private final long metadataPosition;
- //   private final long absoluteInitialConstantPoolPosition;
-    private final long absoluteChunkEnd;
-    private final long absoluteEventStart;
     private final long absoluteChunkStart;
-    private final boolean lastChunk;
     private final RecordingInput input;
-    private final long durationNanos;
     private final long id;
-    private long constantPoolPosition;
+    private long absoluteEventStart;
+    private long chunkSize = 0;
+    private long constantPoolPosition = 0;
+    private long metadataPosition = 0;
+    private long durationNanos;
+    private long absoluteChunkEnd;
+    private boolean isFinished;
+    private boolean finished;
 
     public ChunkHeader(RecordingInput input) throws IOException {
         this(input, 0, 0);
     }
 
     private ChunkHeader(RecordingInput input, long absoluteChunkStart, long id) throws IOException {
+        this.absoluteChunkStart = absoluteChunkStart;
+        this.absoluteEventStart = absoluteChunkStart + HEADER_SIZE;
+        if (input.getFileSize() < HEADER_SIZE) {
+            throw new IOException("Not a complete Chunk header");
+        }
+        input.setValidSize(absoluteChunkStart + HEADER_SIZE);
         input.position(absoluteChunkStart);
         if (input.position() >= input.size()) {
-            throw new IOException("Chunk contains no data");
+           throw new IOException("Chunk contains no data");
         }
         verifyMagic(input);
         this.input = input;
         this.id = id;
-        Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk " + id);
+        Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: " + id);
+        Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: file=" + input.getFilename());
         Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: startPosition=" + absoluteChunkStart);
         major = input.readRawShort();
         Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: major=" + major);
@@ -75,11 +87,11 @@
         if (major != 1 && major != 2) {
             throw new IOException("File version " + major + "." + minor + ". Only Flight Recorder files of version 1.x and 2.x can be read by this JDK.");
         }
-        chunkSize = input.readRawLong();
+        input.readRawLong(); // chunk size
         Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: chunkSize=" + chunkSize);
-        this.constantPoolPosition = input.readRawLong();
+        input.readRawLong(); // constant pool position
         Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: constantPoolPosition=" + constantPoolPosition);
-        metadataPosition = input.readRawLong();
+        input.readRawLong(); // metadata position
         Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: metadataPosition=" + metadataPosition);
         chunkStartNanos = input.readRawLong(); // nanos since epoch
         Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: startNanos=" + chunkStartNanos);
@@ -91,21 +103,98 @@
         Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: ticksPerSecond=" + ticksPerSecond);
         input.readRawInt(); // features, not used
 
-        // set up boundaries
-        this.absoluteChunkStart = absoluteChunkStart;
-        absoluteChunkEnd = absoluteChunkStart + chunkSize;
-        lastChunk = input.size() == absoluteChunkEnd;
-        absoluteEventStart = input.position();
+        refresh();
+        input.position(absoluteEventStart);
+    }
 
-        // read metadata
-        input.position(absoluteEventStart);
+    void refresh() throws IOException {
+        while (true) {
+            byte fileState1;
+            input.positionPhysical(absoluteChunkStart + FILE_STATE_POSITION);
+            while ((fileState1 = input.readPhysicalByte()) == UPDATING_CHUNK_HEADER) {
+                Utils.takeNap(1);
+                input.positionPhysical(absoluteChunkStart + FILE_STATE_POSITION);
+            }
+            input.positionPhysical(absoluteChunkStart + CHUNK_SIZE_POSITION);
+            long chunkSize = input.readPhysicalLong();
+            long constantPoolPosition = input.readPhysicalLong();
+            long metadataPosition = input.readPhysicalLong();
+            input.positionPhysical(absoluteChunkStart + DURATION_NANOS_POSITION);
+            long durationNanos = input.readPhysicalLong();
+            input.positionPhysical(absoluteChunkStart + FILE_STATE_POSITION);
+            byte fileState2 =  input.readPhysicalByte();
+            if (fileState1 == fileState2) { // valid header
+                finished = fileState1 == 0;
+                if (metadataPosition != 0) {
+                    Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Setting input size to " + (absoluteChunkStart + chunkSize));
+                    if (finished) {
+                        // This assumes that the whole recording
+                        // is finished if the first chunk is.
+                        // This is a limitation we may want to
+                        // remove, but greatly improves performance as
+                        // data can be read across chunk boundaries
+                        // of multi-chunk files and only once.
+                        input.setValidSize(input.getFileSize());
+                    } else {
+                        input.setValidSize(absoluteChunkStart + chunkSize);
+                    }
+                    this.chunkSize = chunkSize;
+                    Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: chunkSize=" + chunkSize);
+                    this.constantPoolPosition = constantPoolPosition;
+                    Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: constantPoolPosition=" + constantPoolPosition);
+                    this.metadataPosition = metadataPosition;
+                    Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: metadataPosition=" + metadataPosition);
+                    this.durationNanos = durationNanos;
+                    Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: durationNanos =" + durationNanos);
+                    isFinished = fileState2 == 0;
+                    Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: generation=" + fileState2);
+                    Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: finished=" + isFinished);
+                    Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Chunk: fileSize=" + input.size());
+                    absoluteChunkEnd = absoluteChunkStart + chunkSize;
+                    return;
+                }
+            }
+        }
+    }
+
+    public void awaitFinished() throws IOException {
+        if (finished) {
+            return;
+        }
+        long pos = input.position();
+        try {
+            input.positionPhysical(absoluteChunkStart + FILE_STATE_POSITION);
+            while (true) {
+                byte filestate = input.readPhysicalByte();
+                if (filestate == 0) {
+                    finished = true;
+                    return;
+                }
+                Utils.takeNap(1);
+            }
+        } finally {
+            input.position(pos);
+        }
+    }
+
+    public boolean isLastChunk() throws IOException {
+        awaitFinished();
+        // streaming files only have one chunk
+        return input.getFileSize() == absoluteChunkEnd;
+   }
+
+    public boolean isFinished() throws IOException {
+        return isFinished;
     }
 
     public ChunkHeader nextHeader() throws IOException {
         return new ChunkHeader(input, absoluteChunkEnd, id + 1);
     }
+    public MetadataDescriptor readMetadata() throws IOException {
+        return readMetadata(null);
+    }
 
-    public MetadataDescriptor readMetadata() throws IOException {
+    public MetadataDescriptor readMetadata(MetadataDescriptor previous) throws IOException {
         input.position(absoluteChunkStart + metadataPosition);
         input.readInt(); // size
         long id = input.readLong(); // event type id
@@ -115,15 +204,15 @@
         input.readLong(); // start time
         input.readLong(); // duration
         long metadataId = input.readLong();
-        Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.TRACE, "Metadata id=" + metadataId);
-        // No need to read if metadataId == lastMetadataId, but we
-        // do it for verification purposes.
-        return MetadataDescriptor.read(input);
+        if (previous != null && metadataId == previous.metadataId) {
+            return previous;
+        }
+        Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.TRACE, "New metadata id = " + metadataId);
+        MetadataDescriptor m =  MetadataDescriptor.read(input);
+        m.metadataId = metadataId;
+        return m;
     }
 
-    public boolean isLastChunk() {
-        return lastChunk;
-    }
 
     public short getMajor() {
         return major;
@@ -137,13 +226,22 @@
         return absoluteChunkStart;
     }
 
+    public long getAbsoluteEventStart() {
+        return absoluteEventStart;
+    }
     public long getConstantPoolPosition() {
         return constantPoolPosition;
     }
 
+    public long getMetataPosition() {
+        return metadataPosition;
+    }
     public long getStartTicks() {
         return chunkStartTicks;
     }
+    public long getChunkSize() {
+        return chunkSize;
+    }
 
     public double getTicksPerSecond() {
         return ticksPerSecond;
@@ -169,7 +267,7 @@
         return input;
     }
 
-    private static void verifyMagic(DataInput input) throws IOException {
+    private static void verifyMagic(RecordingInput input) throws IOException {
         for (byte c : FILE_MAGIC) {
             if (input.readByte() != c) {
                 throw new IOException("Not a Flight Recorder file");
@@ -181,4 +279,7 @@
         return absoluteEventStart;
     }
 
+    static long headerSize() {
+        return HEADER_SIZE;
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ChunkParser.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,451 @@
+/*
+ * Copyright (c) 2016, 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.internal.consumer;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import java.util.StringJoiner;
+
+import jdk.jfr.EventType;
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.jfr.consumer.RecordedObject;
+import jdk.jfr.internal.LogLevel;
+import jdk.jfr.internal.LogTag;
+import jdk.jfr.internal.Logger;
+import jdk.jfr.internal.LongMap;
+import jdk.jfr.internal.MetadataDescriptor;
+import jdk.jfr.internal.Type;
+import jdk.jfr.internal.Utils;
+
+/**
+ * Parses a chunk.
+ *
+ */
+public final class ChunkParser {
+
+    static final class ParserConfiguration {
+        private final boolean reuse;
+        private final boolean ordered;
+        private final ParserFilter eventFilter;
+
+        long filterStart;
+        long filterEnd;
+
+        ParserConfiguration(long filterStart, long filterEnd, boolean reuse, boolean ordered, ParserFilter filter) {
+            this.filterStart = filterStart;
+            this.filterEnd = filterEnd;
+            this.reuse = reuse;
+            this.ordered = ordered;
+            this.eventFilter = filter;
+        }
+
+        public ParserConfiguration() {
+            this(0, Long.MAX_VALUE, false, false, ParserFilter.ACCEPT_ALL);
+        }
+
+        public boolean isOrdered() {
+            return ordered;
+        }
+    }
+
+    private enum CheckPointType {
+        // Checkpoint that finishes a flush segment
+        FLUSH(1),
+        // Checkpoint contains chunk header information in the first pool
+        CHUNK_HEADER(2),
+        // Checkpoint contains only statics that will not change from chunk to chunk
+        STATICS(4),
+        // Checkpoint contains thread related information
+        THREAD(8);
+        private final int mask;
+        private CheckPointType(int mask) {
+            this.mask = mask;
+        }
+
+        private boolean is(int flags) {
+            return (mask & flags) != 0;
+        }
+    }
+
+    private static final long CONSTANT_POOL_TYPE_ID = 1;
+    private static final String CHUNKHEADER = "jdk.types.ChunkHeader";
+    private final RecordingInput input;
+    private final ChunkHeader chunkHeader;
+    private final MetadataDescriptor metadata;
+    private final TimeConverter timeConverter;
+    private final MetadataDescriptor previousMetadata;
+    private final LongMap<ConstantLookup> constantLookups;
+
+    private LongMap<Type> typeMap;
+    private LongMap<Parser> parsers;
+    private boolean chunkFinished;
+
+    private Runnable flushOperation;
+    private ParserConfiguration configuration;
+
+    public ChunkParser(RecordingInput input) throws IOException {
+        this(input, new ParserConfiguration());
+    }
+
+    ChunkParser(RecordingInput input, ParserConfiguration pc) throws IOException {
+       this(new ChunkHeader(input), null, pc);
+    }
+
+    private ChunkParser(ChunkParser previous) throws IOException {
+        this(new ChunkHeader(previous.input), previous, new ParserConfiguration());
+     }
+
+    private ChunkParser(ChunkHeader header, ChunkParser previous, ParserConfiguration pc) throws IOException {
+        this.configuration = pc;
+        this.input = header.getInput();
+        this.chunkHeader = header;
+        if (previous == null) {
+            this.constantLookups = new LongMap<>();
+            this.previousMetadata = null;
+        } else {
+            this.constantLookups = previous.constantLookups;
+            this.previousMetadata = previous.metadata;
+            this.configuration = previous.configuration;
+        }
+        this.metadata = header.readMetadata(previousMetadata);
+        this.timeConverter = new TimeConverter(chunkHeader, metadata.getGMTOffset());
+        if (metadata != previousMetadata) {
+            ParserFactory factory = new ParserFactory(metadata, constantLookups, timeConverter);
+            parsers = factory.getParsers();
+            typeMap = factory.getTypeMap();
+            updateConfiguration();
+        } else {
+            parsers = previous.parsers;
+            typeMap = previous.typeMap;
+        }
+        constantLookups.forEach(c -> c.newPool());
+        fillConstantPools(0);
+        constantLookups.forEach(c -> c.getLatestPool().setResolving());
+        constantLookups.forEach(c -> c.getLatestPool().resolve());
+        constantLookups.forEach(c -> c.getLatestPool().setResolved());
+
+        input.position(chunkHeader.getEventStart());
+    }
+
+    public ChunkParser nextChunkParser() throws IOException {
+        return new ChunkParser(chunkHeader.nextHeader(), this, configuration);
+    }
+
+    private void updateConfiguration() {
+        updateConfiguration(configuration, false);
+    }
+
+    void updateConfiguration(ParserConfiguration configuration, boolean resetEventCache) {
+        this.configuration = configuration;
+        parsers.forEach(p -> {
+            if (p instanceof EventParser) {
+                EventParser ep = (EventParser) p;
+                if (resetEventCache) {
+                    ep.resetCache();
+                }
+                String name = ep.getEventType().getName();
+                ep.setOrdered(configuration.ordered);
+                ep.setReuse(configuration.reuse);
+                ep.setFilterStart(configuration.filterStart);
+                ep.setFilterEnd(configuration.filterEnd);
+                long threshold = configuration.eventFilter.getThreshold(name);
+                if (threshold >= 0) {
+                    ep.setEnabled(true);
+                    ep.setThresholdNanos(threshold);
+                } else {
+                    ep.setEnabled(false);
+                    ep.setThresholdNanos(Long.MAX_VALUE);
+                }
+            }
+        });
+    }
+
+    /**
+     * Reads an event and returns null when segment or chunk ends.
+     *
+     * @param awaitNewEvents wait for new data.
+     */
+    RecordedEvent readStreamingEvent(boolean awaitNewEvents) throws IOException {
+        long absoluteChunkEnd = chunkHeader.getEnd();
+        while (true) {
+            RecordedEvent event = readEvent();
+            if (event != null) {
+                return event;
+            }
+            if (!awaitNewEvents) {
+                return null;
+            }
+            long lastValid = absoluteChunkEnd;
+            long metadataPoistion = chunkHeader.getMetataPosition();
+            long contantPosition = chunkHeader.getConstantPoolPosition();
+            chunkFinished = awaitUpdatedHeader(absoluteChunkEnd);
+            if (chunkFinished) {
+                Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "At chunk end");
+                return null;
+            }
+            absoluteChunkEnd = chunkHeader.getEnd();
+            // Read metadata and constant pools for the next segment
+            if (chunkHeader.getMetataPosition() != metadataPoistion) {
+                Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Found new metadata in chunk. Rebuilding types and parsers");
+                MetadataDescriptor metadata = chunkHeader.readMetadata(previousMetadata);
+                ParserFactory factory = new ParserFactory(metadata, constantLookups, timeConverter);
+                parsers = factory.getParsers();
+                typeMap = factory.getTypeMap();
+                updateConfiguration();;
+            }
+            if (contantPosition != chunkHeader.getConstantPoolPosition()) {
+                Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Found new constant pool data. Filling up pools with new values");
+                constantLookups.forEach(c -> c.getLatestPool().setAllResolved(false));
+                fillConstantPools(contantPosition + chunkHeader.getAbsoluteChunkStart());
+                constantLookups.forEach(c -> c.getLatestPool().setResolving());
+                constantLookups.forEach(c -> c.getLatestPool().resolve());
+                constantLookups.forEach(c -> c.getLatestPool().setResolved());
+            }
+            input.position(lastValid);
+        }
+    }
+
+    /**
+     * Reads an event and returns null when the chunk ends
+     */
+    public RecordedEvent readEvent() throws IOException {
+        long absoluteChunkEnd = chunkHeader.getEnd();
+        while (input.position() < absoluteChunkEnd) {
+            long pos = input.position();
+            int size = input.readInt();
+            if (size == 0) {
+                throw new IOException("Event can't have zero size");
+            }
+            long typeId = input.readLong();
+            Parser p = parsers.get(typeId);
+            if (p instanceof EventParser) {
+                // Fast path
+                EventParser ep = (EventParser) p;
+                RecordedEvent event = ep.parse(input);
+                if (event != null) {
+                    input.position(pos + size);
+                    return event;
+                }
+                // Not accepted by filter
+            } else {
+                if (typeId == 1) { // checkpoint event
+                    if (flushOperation != null) {
+                        parseCheckpoint();
+                    }
+                } else {
+                    if (typeId != 0) { // Not metadata event
+                        Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Unknown event type " + typeId);
+                    }
+                }
+            }
+            input.position(pos + size);
+        }
+        return null;
+    }
+
+    private void parseCheckpoint() throws IOException {
+        // Content has been parsed previously. This
+        // is to trigger flush
+        input.readLong(); // timestamp
+        input.readLong(); // duration
+        input.readLong(); // delta
+        byte typeFlags = input.readByte();
+        if (CheckPointType.FLUSH.is(typeFlags)) {
+            flushOperation.run();
+        }
+    }
+
+    private boolean awaitUpdatedHeader(long absoluteChunkEnd) throws IOException {
+        if (Logger.shouldLog(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO)) {
+            Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Waiting for more data (streaming). Read so far: " + chunkHeader.getChunkSize() + " bytes");
+        }
+        while (true) {
+            chunkHeader.refresh();
+            if (absoluteChunkEnd != chunkHeader.getEnd()) {
+                return false;
+            }
+            if (chunkHeader.isFinished()) {
+                return true;
+            }
+            Utils.waitFlush(1000);
+        }
+    }
+
+    private void fillConstantPools(long abortCP) throws IOException {
+        long thisCP = chunkHeader.getConstantPoolPosition() + chunkHeader.getAbsoluteChunkStart();
+        long lastCP = -1;
+        long delta = -1;
+        boolean logTrace = Logger.shouldLog(LogTag.JFR_SYSTEM_PARSER, LogLevel.TRACE);
+        while (thisCP != abortCP && delta != 0) {
+            input.position(thisCP);
+            lastCP = thisCP;
+            int size = input.readInt(); // size
+            long typeId = input.readLong();
+            if (typeId != CONSTANT_POOL_TYPE_ID) {
+                throw new IOException("Expected check point event (id = 1) at position " + lastCP + ", but found type id = " + typeId);
+            }
+            input.readLong(); // timestamp
+            input.readLong(); // duration
+            delta = input.readLong();
+            thisCP += delta;
+            boolean flush = input.readBoolean();
+            int poolCount = input.readInt();
+            final long logLastCP = lastCP;
+            final long logDelta = delta;
+            if (logTrace) {
+                Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.TRACE, () -> {
+                    return "New constant pool: startPosition=" + logLastCP + ", size=" + size + ", deltaToNext=" + logDelta + ", flush=" + flush + ", poolCount=" + poolCount;
+                });
+            }
+            for (int i = 0; i < poolCount; i++) {
+                long id = input.readLong(); // type id
+                ConstantLookup lookup = constantLookups.get(id);
+                Type type = typeMap.get(id);
+                if (lookup == null) {
+                    if (type == null) {
+                        throw new IOException(
+                                "Error parsing constant pool type " + getName(id) + " at position " + input.position() + " at check point between [" + lastCP + ", " + lastCP + size + "]");
+                    }
+                    if (type.getName() != CHUNKHEADER) {
+                        Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Found constant pool(" + id + ") that is never used");
+                    }
+                    ConstantMap pool = new ConstantMap(ObjectFactory.create(type, timeConverter), type.getName());
+                    lookup = new ConstantLookup(pool, type);
+                    constantLookups.put(type.getId(), lookup);
+                }
+                Parser parser = parsers.get(id);
+                if (parser == null) {
+                    throw new IOException("Could not find constant pool type with id = " + id);
+                }
+                try {
+                    int count = input.readInt();
+                    if (count == 0) {
+                        throw new InternalError("Pool " + type.getName() + " must contain at least one element ");
+                    }
+                    if (logTrace) {
+                        Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.TRACE, "Constant Pool " + i + ": " + type.getName());
+                    }
+                    for (int j = 0; j < count; j++) {
+                        long key = input.readLong();
+                        Object resolved = lookup.getPreviousResolved(key);
+                        if (resolved == null) {
+                            Object v = parser.parse(input);
+                            logConstant(key, v, false);
+                            lookup.getLatestPool().put(key, v);
+                        } else {
+                            parser.skip(input);
+                            logConstant(key, resolved, true);
+                            lookup.getLatestPool().putResolved(key, resolved);
+                        }
+                    }
+                } catch (Exception e) {
+                    throw new IOException("Error parsing constant pool type " + getName(id) + " at position " + input.position() + " at check point between [" + lastCP + ", " + lastCP + size + "]",
+                            e);
+                }
+            }
+            if (input.position() != lastCP + size) {
+                throw new IOException("Size of check point event doesn't match content");
+            }
+        }
+    }
+
+    private void logConstant(long key, Object v, boolean preresolved) {
+        if (!Logger.shouldLog(LogTag.JFR_SYSTEM_PARSER, LogLevel.TRACE)) {
+            return;
+        }
+        String valueText;
+        if (v.getClass().isArray()) {
+            Object[] array = (Object[]) v;
+            StringJoiner sj = new StringJoiner(", ", "{", "}");
+            for (int i = 0; i < array.length; i++) {
+                sj.add(textify(array[i]));
+            }
+            valueText = sj.toString();
+        } else {
+            valueText = textify(v);
+        }
+        String suffix  = preresolved ? " (presolved)" :"";
+        Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.TRACE, "Constant: " + key + " = " + valueText + suffix);
+    }
+
+    private String textify(Object o) {
+        if (o == null) { // should not happen
+            return "null";
+        }
+        if (o instanceof String) {
+            return "\"" + String.valueOf(o) + "\"";
+        }
+        if (o instanceof RecordedObject) {
+            return o.getClass().getName();
+        }
+        if (o.getClass().isArray()) {
+            Object[] array = (Object[]) o;
+            if (array.length > 0) {
+                return textify(array[0]) + "[]"; // can it be recursive?
+            }
+        }
+        return String.valueOf(o);
+    }
+
+    private String getName(long id) {
+        Type type = typeMap.get(id);
+        return type == null ? ("unknown(" + id + ")") : type.getName();
+    }
+
+    public Collection<Type> getTypes() {
+        return metadata.getTypes();
+    }
+
+    public List<EventType> getEventTypes() {
+        return metadata.getEventTypes();
+    }
+
+    public boolean isLastChunk() throws IOException {
+        return chunkHeader.isLastChunk();
+    }
+
+    ChunkParser newChunkParser() throws IOException {
+        return new ChunkParser(this);
+    }
+
+    public boolean isChunkFinished() {
+        return chunkFinished;
+    }
+
+    public void setFlushOperation(Runnable flushOperation) {
+        this.flushOperation = flushOperation;
+    }
+
+    public long getChunkDuration() {
+        return chunkHeader.getDurationNanos();
+    }
+
+    public long getStartNanos() {
+        return chunkHeader.getStartNanos();
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ConstantLookup.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+package jdk.jfr.internal.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);
+    }
+
+    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/internal/consumer/ConstantMap.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 2016, 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.internal.consumer;
+
+import jdk.jfr.internal.LongMap;
+
+/**
+ * Holds mapping between a set of keys and their corresponding object.
+ *
+ * If the type is a known type, i.e. {@link RecordedThread}, an
+ * {@link ObjectFactory} can be supplied which will instantiate a typed object.
+ */
+final class ConstantMap {
+
+    private static final int RESOLUTION_FINISHED = 0;
+    private static final int RESOLUTION_STARTED = 1;
+    public static final ConstantMap EMPTY = new ConstantMap();
+
+    // A temporary placeholder, so objects can
+    // reference themselves (directly, or indirectly),
+    // when making a transition from numeric id references
+    // to normal Java references.
+    private final static class Reference {
+        private final long key;
+        private final ConstantMap pool;
+
+        Reference(ConstantMap pool, long key) {
+            this.pool = pool;
+            this.key = key;
+        }
+
+        Object resolve() {
+            return pool.get(key);
+        }
+
+        public String toString() {
+            return "ref: " + pool.name + "[" + key + "]";
+        }
+    }
+
+    final ObjectFactory<?> factory;
+    final String name;
+
+    private final LongMap<Object> objects;
+
+    private boolean resolving;
+    private boolean allResolved;
+
+    private ConstantMap() {
+        this(null, "<empty>");
+        allResolved = true;
+    }
+
+    ConstantMap(ObjectFactory<?> factory, String name) {
+        this.name = name;
+        this.objects = new LongMap<>(2);
+        this.factory = factory;
+    }
+
+    Object get(long id) {
+        // fast path, all objects in pool resolved
+        if (allResolved) {
+            return objects.get(id);
+        }
+        // referenced from a pool, deal with this later
+        if (!resolving) {
+            return new Reference(this, id);
+        }
+
+        // should always have a value
+        Object value = objects.get(id);
+        if (value == null) {
+            // unless is 0 which is used to represent null
+            if (id == 0) {
+                return null;
+            }
+            throw new InternalError("Missing object id=" + id + " in pool " + name + ". All ids should reference object");
+        }
+
+        // id is resolved (but not the whole pool)
+        if (objects.isSetId(id, RESOLUTION_FINISHED)) {
+            return value;
+        }
+
+        // resolving ourself, abort to avoid infinite recursion
+        if (objects.isSetId(id, RESOLUTION_STARTED)) {
+            return null;
+        }
+
+        // mark id as STARTED if we should
+        // come back during object resolution
+        objects.setId(id, RESOLUTION_STARTED);
+
+        // resolve object!
+        Object resolved = resolve(value);
+
+        // mark id as FINISHED
+        objects.setId(id, RESOLUTION_FINISHED);
+
+        // if a factory exists, convert to RecordedThread.
+        // RecordedClass etc. and store back results
+        if (factory != null) {
+            Object factorized = factory.createObject(id, resolved);
+            objects.put(id, factorized);
+            return factorized;
+        } else {
+            objects.put(id, resolved);
+            return resolved;
+        }
+    }
+
+    private static Object resolve(Object o) {
+        if (o instanceof Reference) {
+            return resolve(((Reference) o).resolve());
+        }
+        if (o != null && o.getClass().isArray()) {
+            final Object[] array = (Object[]) o;
+            for (int i = 0; i < array.length; i++) {
+                Object element = array[i];
+                array[i] = resolve(element);
+            }
+            return array;
+        }
+        return o;
+    }
+
+    public void resolve() {
+        objects.forEachKey(k -> get(k));
+    }
+
+    public void put(long key, Object value) {
+        objects.put(key, value);
+    }
+
+    public void setResolving() {
+        resolving = true;
+        allResolved = false;
+    }
+
+    public void setResolved() {
+        allResolved = true;
+        resolving = false;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public Object getResolved(long id) {
+        return objects.get(id);
+    }
+
+    public void putResolved(long id, Object object) {
+        objects.put(id, object);
+        objects.setId(id, RESOLUTION_FINISHED);
+    }
+
+    public void setAllResolved(boolean allResolved) {
+        this.allResolved = allResolved;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/Dispatcher.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,188 @@
+/*
+ * 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.internal.consumer;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+import jdk.jfr.EventType;
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.jfr.internal.LongMap;
+import jdk.jfr.internal.consumer.ChunkParser.ParserConfiguration;
+
+final class Dispatcher {
+
+    final static class EventDispatcher {
+        private final static EventDispatcher[] NO_DISPATCHERS = new EventDispatcher[0];
+
+        private final String eventName;
+        private final Consumer<RecordedEvent> action;
+
+        public EventDispatcher(String eventName, Consumer<RecordedEvent> action) {
+            this.eventName = eventName;
+            this.action = action;
+        }
+
+        private void offer(RecordedEvent event) {
+            action.accept(event);
+        }
+
+        private boolean accepts(EventType eventType) {
+            return (eventName == null || eventType.getName().equals(eventName));
+        }
+
+        public Consumer<RecordedEvent> getAction() {
+            return action;
+        }
+    }
+
+    private final Consumer<Throwable>[] errorActions;
+    private final Runnable[] flushActions;
+    private final Runnable[] closeActions;
+    private final EventDispatcher[] dispatchers;
+    private final LongMap<EventDispatcher[]> dispatcherLookup = new LongMap<>();
+    final ParserConfiguration parserConfiguration;
+    final Instant startTime;
+    final Instant endTime;
+    final long startNanos;
+    final long endNanos;
+
+    // Cache
+    private EventType cacheEventType;
+    private EventDispatcher[] cacheDispatchers;
+
+    @SuppressWarnings({"rawtypes", "unchecked"})
+    public Dispatcher(StreamConfiguration c) {
+        this.flushActions = c.flushActions.toArray(new Runnable[0]);
+        this.closeActions = c.closeActions.toArray(new Runnable[0]);
+        this.errorActions = c.errorActions.toArray(new Consumer[0]);
+        this.dispatchers = c.eventActions.toArray(new EventDispatcher[0]);
+        this.parserConfiguration = new ParserConfiguration(0, Long.MAX_VALUE, c.reuse, c.ordered, buildFilter(dispatchers));
+        this.startTime = c.startTime;
+        this.endTime = c.endTime;
+        this.startNanos = c.startNanos;
+        this.endNanos = c.endNanos;
+    }
+
+    public void runFlushActions() {
+        Runnable[] flushActions = this.flushActions;
+        for (int i = 0; i < flushActions.length; i++) {
+            try {
+                flushActions[i].run();
+            } catch (Exception e) {
+                handleError(e);
+            }
+        }
+    }
+
+    public void runCloseActions() {
+        Runnable[] closeActions = this.closeActions;
+        for (int i = 0; i < closeActions.length; i++) {
+            try {
+                closeActions[i].run();
+            } catch (Exception e) {
+                handleError(e);
+            }
+        }
+    }
+
+    private static ParserFilter buildFilter(EventDispatcher[] dispatchers) {
+        ParserFilter ef = new ParserFilter();
+        for (EventDispatcher ed : dispatchers) {
+            String name = ed.eventName;
+            if (name == null) {
+                return ParserFilter.ACCEPT_ALL;
+            }
+            ef.setThreshold(name, 0);
+        }
+        return ef;
+    }
+
+    void dispatch(RecordedEvent event) {
+        EventType type = event.getEventType();
+        EventDispatcher[] dispatchers = null;
+        if (type == cacheEventType) {
+            dispatchers = cacheDispatchers;
+        } else {
+            dispatchers = dispatcherLookup.get(type.getId());
+            if (dispatchers == null) {
+                List<EventDispatcher> list = new ArrayList<>();
+                for (EventDispatcher e : this.dispatchers) {
+                    if (e.accepts(type)) {
+                        list.add(e);
+                    }
+                }
+                dispatchers = list.isEmpty() ? EventDispatcher.NO_DISPATCHERS : list.toArray(new EventDispatcher[0]);
+                dispatcherLookup.put(type.getId(), dispatchers);
+            }
+            cacheDispatchers = dispatchers;
+        }
+        // Expected behavior if exception occurs in onEvent:
+        //
+        // Synchronous:
+        //  - User has added onError action:
+        //     Catch exception, call onError and continue with next event
+        //     Let Errors propagate to caller of EventStream::start
+        //  - Default action
+        //     Catch exception, e.printStackTrace() and continue with next event
+        //     Let Errors propagate to caller of EventStream::start
+        //
+        // Asynchronous
+        //  - User has added onError action
+        //     Catch exception, call onError and continue with next event
+        //     Let Errors propagate, shutdown thread and stream
+        //  - Default action
+        //    Catch exception, e.printStackTrace() and continue with next event
+        //    Let Errors propagate and shutdown thread and stream
+        //
+        for (int i = 0; i < dispatchers.length; i++) {
+            try {
+                dispatchers[i].offer(event);
+            } catch (Exception e) {
+                handleError(e);
+            }
+        }
+    }
+
+    private void handleError(Throwable e) {
+        Consumer<?>[] consumers = this.errorActions;
+        if (consumers.length == 0) {
+            defaultErrorHandler(e);
+            return;
+        }
+        for (int i = 0; i < consumers.length; i++) {
+            @SuppressWarnings("unchecked")
+            Consumer<Throwable> consumer = (Consumer<Throwable>) consumers[i];
+            consumer.accept(e);
+        }
+    }
+
+    private void defaultErrorHandler(Throwable e) {
+        e.printStackTrace();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventDirectoryStream.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,209 @@
+/*
+ * 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.internal.consumer;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.security.AccessControlContext;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Objects;
+
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.jfr.internal.JVM;
+import jdk.jfr.internal.Utils;
+import jdk.jfr.internal.consumer.ChunkParser.ParserConfiguration;
+
+/**
+ * Implementation of an {@code EventStream}} that operates against a directory
+ * with chunk files.
+ *
+ */
+public final class EventDirectoryStream extends AbstractEventStream {
+
+    private final static Comparator<? super RecordedEvent> EVENT_COMPARATOR = JdkJfrConsumer.instance().eventComparator();
+
+    private final RepositoryFiles repositoryFiles;
+    private final boolean active;
+    private final FileAccess fileAccess;
+
+    private ChunkParser currentParser;
+    private long currentChunkStartNanos;
+    private RecordedEvent[] sortedCache;
+    private int threadExclusionLevel = 0;
+
+    public EventDirectoryStream(AccessControlContext acc, Path p, FileAccess fileAccess, boolean active) throws IOException {
+        super(acc, active);
+        this.fileAccess = Objects.requireNonNull(fileAccess);
+        this.active = active;
+        this.repositoryFiles = new RepositoryFiles(fileAccess, p);
+    }
+
+    @Override
+    public void close() {
+        setClosed(true);
+        dispatcher().runCloseActions();
+        repositoryFiles.close();
+    }
+
+    @Override
+    public void start() {
+        start(Utils.timeToNanos(Instant.now()));
+    }
+
+    @Override
+    public void startAsync() {
+        startAsync(Utils.timeToNanos(Instant.now()));
+    }
+
+    @Override
+    protected void process() throws IOException {
+        JVM jvm = JVM.getJVM();
+        Thread t = Thread.currentThread();
+        try {
+            if (jvm.isExcluded(t)) {
+                threadExclusionLevel++;
+            } else {
+                jvm.exclude(t);
+            }
+            processRecursionSafe();
+        } finally {
+            if (threadExclusionLevel > 0) {
+                threadExclusionLevel--;
+            } else {
+                jvm.include(t);
+            }
+        }
+    }
+
+    protected void processRecursionSafe() throws IOException {
+        Dispatcher disp = dispatcher();
+
+        Path path;
+        boolean validStartTime = active || disp.startTime != null;
+        if (validStartTime) {
+            path = repositoryFiles.firstPath(disp.startNanos);
+        } else {
+            path = repositoryFiles.lastPath();
+        }
+        if (path == null) { // closed
+            return;
+        }
+        currentChunkStartNanos = repositoryFiles.getTimestamp(path);
+        try (RecordingInput input = new RecordingInput(path.toFile(), fileAccess)) {
+            currentParser = new ChunkParser(input, disp.parserConfiguration);
+            long segmentStart = currentParser.getStartNanos() + currentParser.getChunkDuration();
+            long filterStart = validStartTime ? disp.startNanos : segmentStart;
+            long filterEnd = disp.endTime != null ? disp.endNanos: Long.MAX_VALUE;
+
+            while (!isClosed()) {
+                boolean awaitnewEvent = false;
+                while (!isClosed() && !currentParser.isChunkFinished()) {
+                    disp = dispatcher();
+                    ParserConfiguration pc = disp.parserConfiguration;
+                    pc.filterStart = filterStart;
+                    pc.filterEnd = filterEnd;
+                    currentParser.updateConfiguration(pc, true);
+                    currentParser.setFlushOperation(getFlushOperation());
+                    if (pc.isOrdered()) {
+                        awaitnewEvent = processOrdered(disp, awaitnewEvent);
+                    } else {
+                        awaitnewEvent = processUnordered(disp, awaitnewEvent);
+                    }
+                    if (currentParser.getStartNanos() + currentParser.getChunkDuration() > filterEnd) {
+                        close();
+                        return;
+                    }
+                }
+
+                if (isClosed()) {
+                    return;
+                }
+                long durationNanos = currentParser.getChunkDuration();
+                if (durationNanos == 0) {
+                    // Avoid reading the same chunk again and again if
+                    // duration is 0 ns
+                    durationNanos++;
+                }
+                path = repositoryFiles.nextPath(currentChunkStartNanos + durationNanos);
+                if (path == null) {
+                    return; // stream closed
+                }
+                currentChunkStartNanos = repositoryFiles.getTimestamp(path);
+                input.setFile(path);
+                currentParser = currentParser.newChunkParser();
+                // TODO: Optimization. No need filter when we reach new chunk
+                // Could set start = 0;
+            }
+        }
+    }
+
+    private boolean processOrdered(Dispatcher c, boolean awaitNewEvents) throws IOException {
+        if (sortedCache == null) {
+            sortedCache = new RecordedEvent[100_000];
+        }
+        int index = 0;
+        while (true) {
+            RecordedEvent e = currentParser.readStreamingEvent(awaitNewEvents);
+            if (e == null) {
+                // wait for new event with next call to
+                // readStreamingEvent()
+                awaitNewEvents = true;
+                break;
+            }
+            awaitNewEvents = false;
+            if (index == sortedCache.length) {
+                sortedCache = Arrays.copyOf(sortedCache, sortedCache.length * 2);
+            }
+            sortedCache[index++] = e;
+        }
+
+        // no events found
+        if (index == 0 && currentParser.isChunkFinished()) {
+            return awaitNewEvents;
+        }
+        // at least 2 events, sort them
+        if (index > 1) {
+            Arrays.sort(sortedCache, 0, index, EVENT_COMPARATOR);
+        }
+        for (int i = 0; i < index; i++) {
+            c.dispatch(sortedCache[i]);
+        }
+        return awaitNewEvents;
+    }
+
+    private boolean processUnordered(Dispatcher c, boolean awaitNewEvents) throws IOException {
+        while (true) {
+            RecordedEvent e = currentParser.readStreamingEvent(awaitNewEvents);
+            if (e == null) {
+                return true;
+            } else {
+                c.dispatch(e);
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventFileStream.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,147 @@
+/*
+ * 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.internal.consumer;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.security.AccessControlContext;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Objects;
+
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.jfr.internal.consumer.Dispatcher;
+import jdk.jfr.internal.consumer.FileAccess;
+import jdk.jfr.internal.consumer.RecordingInput;
+
+/**
+ * Implementation of an event stream that operates against a recording file.
+ *
+ */
+public final class EventFileStream extends AbstractEventStream {
+    private final static Comparator<? super RecordedEvent> EVENT_COMPARATOR = JdkJfrConsumer.instance().eventComparator();
+
+    private final RecordingInput input;
+
+    private ChunkParser currentParser;
+    private RecordedEvent[] cacheSorted;
+
+    public EventFileStream(AccessControlContext acc, Path path) throws IOException {
+        super(acc, false);
+        Objects.requireNonNull(path);
+        this.input = new RecordingInput(path.toFile(), FileAccess.UNPRIVILIGED);
+    }
+
+    @Override
+    public void start() {
+        start(0);
+    }
+
+    @Override
+    public void startAsync() {
+        startAsync(0);
+    }
+
+    @Override
+    public void close() {
+        setClosed(true);
+        dispatcher().runCloseActions();
+        try {
+            input.close();
+        } catch (IOException e) {
+            // ignore
+        }
+    }
+
+    @Override
+    protected void process() throws IOException {
+        Dispatcher disp = dispatcher();
+        long start = 0;
+        long end = Long.MAX_VALUE;
+        if (disp.startTime != null) {
+            start = disp.startNanos;
+        }
+        if (disp.endTime != null) {
+            end = disp.endNanos;
+        }
+
+        currentParser = new ChunkParser(input, disp.parserConfiguration);
+        while (!isClosed()) {
+            if (currentParser.getStartNanos() > end) {
+                close();
+                return;
+            }
+            disp = dispatcher();
+            disp.parserConfiguration.filterStart = start;
+            disp.parserConfiguration.filterEnd = end;
+            currentParser.updateConfiguration(disp.parserConfiguration, true);
+            currentParser.setFlushOperation(getFlushOperation());
+            if (disp.parserConfiguration.isOrdered()) {
+                processOrdered(disp);
+            } else {
+                processUnordered(disp);
+            }
+            if (isClosed() || currentParser.isLastChunk()) {
+                return;
+            }
+            currentParser = currentParser.nextChunkParser();
+        }
+    }
+
+    private void processOrdered(Dispatcher c) throws IOException {
+        if (cacheSorted == null) {
+            cacheSorted = new RecordedEvent[10_000];
+        }
+        RecordedEvent event;
+        int index = 0;
+        while (true) {
+            event = currentParser.readEvent();
+            if (event == null) {
+                Arrays.sort(cacheSorted, 0, index, EVENT_COMPARATOR);
+                for (int i = 0; i < index; i++) {
+                    c.dispatch(cacheSorted[i]);
+                }
+                return;
+            }
+            if (index == cacheSorted.length) {
+                RecordedEvent[] tmp = cacheSorted;
+                cacheSorted = new RecordedEvent[2 * tmp.length];
+                System.arraycopy(tmp, 0, cacheSorted, 0, tmp.length);
+            }
+            cacheSorted[index++] = event;
+        }
+    }
+
+    private void processUnordered(Dispatcher c) throws IOException {
+        while (!isClosed()) {
+            RecordedEvent event = currentParser.readEvent();
+            if (event == null) {
+                return;
+            }
+            c.dispatch(event);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/EventParser.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,198 @@
+/*
+ * Copyright (c) 2016, 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.internal.consumer;
+
+import static jdk.jfr.internal.EventInstrumentation.FIELD_DURATION;
+
+import java.io.IOException;
+import java.util.List;
+
+import jdk.jfr.EventType;
+import jdk.jfr.ValueDescriptor;
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.jfr.internal.consumer.Parser;
+import jdk.jfr.internal.consumer.RecordingInput;
+
+/**
+ * Parses an event and returns a {@link RecordedEvent}.
+ *
+ */
+final class EventParser extends Parser {
+
+    private static final JdkJfrConsumer PRIVATE_ACCESS = JdkJfrConsumer.instance();
+
+    private final Parser[] parsers;
+    private final EventType eventType;
+    private final TimeConverter timeConverter;
+    private final boolean hasDuration;
+    private final List<ValueDescriptor> valueDescriptors;
+    private final int startIndex;
+    private final int length;
+    private final RecordedEvent unorderedEvent;
+    private final ObjectContext objectContext;
+
+    private RecordedEvent[] cached;
+    private int cacheIndex;
+
+    private boolean enabled = true;
+    private boolean ordered;
+    private long filterStart;
+    private long filterEnd = Long.MAX_VALUE;
+    private long thresholdNanos = -1;
+
+    EventParser(TimeConverter timeConverter, EventType type, Parser[] parsers) {
+        this.timeConverter = timeConverter;
+        this.parsers = parsers;
+        this.eventType = type;
+        this.hasDuration = type.getField(FIELD_DURATION) != null;
+        this.startIndex = hasDuration ? 2 : 1;
+        this.length = parsers.length - startIndex;
+        this.valueDescriptors = type.getFields();
+        this.objectContext = new ObjectContext(type, valueDescriptors, timeConverter);
+        this.unorderedEvent = PRIVATE_ACCESS.newRecordedEvent(objectContext, new Object[length], 0L, 0L);
+    }
+
+    private RecordedEvent cachedEvent() {
+        if (ordered) {
+            if (cacheIndex == cached.length) {
+                RecordedEvent[] old = cached;
+                cached = new RecordedEvent[cached.length * 2];
+                System.arraycopy(old, 0, cached, 0, old.length);
+            }
+            RecordedEvent event = cached[cacheIndex];
+            if (event == null) {
+                event = PRIVATE_ACCESS.newRecordedEvent(objectContext, new Object[length], 0L, 0L);
+                cached[cacheIndex] = event;
+            }
+            cacheIndex++;
+            return event;
+        } else {
+            return unorderedEvent;
+        }
+    }
+
+    public EventType getEventType() {
+        return eventType;
+    }
+
+    public void setThresholdNanos(long thresholdNanos) {
+        this.thresholdNanos = thresholdNanos;
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public RecordedEvent parse(RecordingInput input) throws IOException {
+        if (!enabled) {
+            return null;
+        }
+
+        long startTicks = input.readLong();
+        long endTicks = startTicks;
+        if (hasDuration) {
+            long durationTicks = input.readLong();
+            if (thresholdNanos > 0L) {
+                if (timeConverter.convertTimespan(durationTicks) < thresholdNanos) {
+                    return null;
+                }
+            }
+            endTicks += durationTicks;
+        }
+        if (filterStart != 0L || filterEnd != Long.MAX_VALUE) {
+            long eventEnd = timeConverter.convertTimestamp(endTicks);
+            if (eventEnd < filterStart) {
+                return null;
+            }
+            if (eventEnd > filterEnd) {
+                return null;
+            }
+        }
+
+        if (cached != null) {
+            RecordedEvent event = cachedEvent();
+            JdkJfrConsumer access = PRIVATE_ACCESS;
+            access.setStartTicks(event, startTicks);
+            access.setEndTicks(event, endTicks);
+            Object[] values = access.eventValues(event);
+            for (int i = 0; i < values.length; i++) {
+                values[i] = parsers[startIndex + i].parse(input);
+            }
+            return event;
+        }
+
+        Object[] values = new Object[length];
+        for (int i = 0; i < values.length; i++) {
+            values[i] = parsers[startIndex + i].parse(input);
+        }
+        return PRIVATE_ACCESS.newRecordedEvent(objectContext, values, startTicks, endTicks);
+    }
+
+    @Override
+    public void skip(RecordingInput input) throws IOException {
+        throw new InternalError("Should not call this method. More efficent to read event size and skip ahead");
+    }
+
+    public void resetCache() {
+        cacheIndex = 0;
+    }
+
+    private boolean hasReuse() {
+        return cached != null;
+    }
+
+    public void setReuse(boolean reuse) {
+        if (reuse == hasReuse()) {
+            return;
+        }
+        if (reuse) {
+            cached = new RecordedEvent[2];
+            cacheIndex = 0;
+        } else {
+            cached = null;
+        }
+    }
+
+    public void setFilterStart(long filterStart) {
+        this.filterStart = filterStart;
+    }
+
+    public void setFilterEnd(long filterEnd) {
+        this.filterEnd = filterEnd;
+    }
+
+    public void setOrdered(boolean ordered) {
+        if (this.ordered == ordered) {
+            return;
+        }
+        this.ordered = ordered;
+        this.cacheIndex = 0;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/FileAccess.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,73 @@
+/*
+ * 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.
+ */
+
+package jdk.jfr.internal.consumer;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+// Protected by modular boundaries.
+public abstract class FileAccess {
+    public final static FileAccess UNPRIVILIGED = new UnPriviliged();
+
+    public abstract RandomAccessFile openRAF(File f, String mode) throws IOException;
+
+    public abstract DirectoryStream<Path> newDirectoryStream(Path repository) throws IOException;
+
+    public abstract String getAbsolutePath(File f) throws IOException;
+
+    public abstract long length(File f) throws IOException;
+
+    public abstract long fileSize(Path p) throws IOException;
+
+    private static class UnPriviliged extends FileAccess {
+        @Override
+        public RandomAccessFile openRAF(File f, String mode) throws IOException {
+            return new RandomAccessFile(f, mode);
+        }
+
+        @Override
+        public DirectoryStream<Path> newDirectoryStream(Path dir) throws IOException {
+            return Files.newDirectoryStream(dir);
+        }
+
+        @Override
+        public String getAbsolutePath(File f) throws IOException {
+            return f.getAbsolutePath();
+        }
+
+        @Override
+        public long length(File f) throws IOException {
+            return f.length();
+        }
+
+        @Override
+        public long fileSize(Path p) throws IOException {
+            return Files.size(p);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/JdkJfrConsumer.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,101 @@
+/*
+ * 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.internal.consumer;
+
+import java.io.IOException;
+import java.util.Comparator;
+import java.util.List;
+
+import jdk.jfr.consumer.RecordedClass;
+import jdk.jfr.consumer.RecordedClassLoader;
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.jfr.consumer.RecordedFrame;
+import jdk.jfr.consumer.RecordedMethod;
+import jdk.jfr.consumer.RecordedObject;
+import jdk.jfr.consumer.RecordedStackTrace;
+import jdk.jfr.consumer.RecordedThread;
+import jdk.jfr.consumer.RecordedThreadGroup;
+import jdk.jfr.consumer.RecordingFile;
+import jdk.jfr.internal.Type;
+/*
+ * Purpose of this class is to give package private access to
+ * the jdk.jfr.consumer package
+ */
+public abstract class JdkJfrConsumer {
+
+    private static JdkJfrConsumer instance;
+
+    // Initialization will trigger setAccess being called
+    private static void forceInitializetion() {
+        try {
+            Class<?> c = RecordedObject.class;
+            Class.forName(c.getName(), true, c.getClassLoader());
+        } catch (ClassNotFoundException e) {
+            throw new InternalError("Should not happen");
+        }
+    }
+
+    public static void setAccess(JdkJfrConsumer access) {
+        instance = access;
+    }
+
+    public static JdkJfrConsumer instance() {
+        if (instance == null) {
+            forceInitializetion();
+        }
+        return instance;
+    }
+
+    public abstract List<Type> readTypes(RecordingFile file) throws IOException;
+
+    public abstract boolean isLastEventInChunk(RecordingFile file);
+
+    public abstract Object getOffsetDataTime(RecordedObject event, String name);
+
+    public abstract RecordedClass newRecordedClass(ObjectContext objectContext, long id, Object[] values);
+
+    public abstract RecordedClassLoader newRecordedClassLoader(ObjectContext objectContext, long id, Object[] values);
+
+    public abstract RecordedStackTrace newRecordedStackTrace(ObjectContext objectContext, Object[] values);
+
+    public abstract RecordedThreadGroup newRecordedThreadGroup(ObjectContext objectContext, Object[] values);
+
+    public abstract RecordedFrame newRecordedFrame(ObjectContext objectContext, Object[] values);
+
+    public abstract RecordedThread newRecordedThread(ObjectContext objectContext, long id, Object[] values);
+
+    public abstract RecordedMethod newRecordedMethod(ObjectContext objectContext, Object[] values);
+
+    public abstract RecordedEvent newRecordedEvent(ObjectContext objectContext, Object[] objects, long l, long m);
+
+    public abstract Comparator<? super RecordedEvent> eventComparator();
+
+    public abstract void setStartTicks(RecordedEvent event, long startTicks);
+
+    public abstract void setEndTicks(RecordedEvent event, long endTicks);
+
+    public abstract Object[] eventValues(RecordedEvent event);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ObjectContext.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,77 @@
+/*
+ * 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.internal.consumer;
+
+import java.time.ZoneId;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import jdk.jfr.EventType;
+import jdk.jfr.ValueDescriptor;
+
+public final class ObjectContext {
+    private final Map<ValueDescriptor, ObjectContext> contextLookup;
+    private final TimeConverter timeConverter;
+
+    public final EventType eventType;
+    public final List<ValueDescriptor> fields;
+
+    ObjectContext(EventType eventType, List<ValueDescriptor> fields, TimeConverter timeConverter) {
+        this.contextLookup = new HashMap<>();
+        this.eventType = eventType;
+        this.fields = fields;
+        this.timeConverter = timeConverter;
+    }
+
+    private ObjectContext(ObjectContext parent, ValueDescriptor descriptor) {
+        this.eventType = parent.eventType;
+        this.contextLookup = parent.contextLookup;
+        this.timeConverter = parent.timeConverter;
+        this.fields = descriptor.getFields();
+    }
+
+    public ObjectContext getInstance(ValueDescriptor descriptor) {
+        ObjectContext context = contextLookup.get(descriptor);
+        if (context == null) {
+            context = new ObjectContext(this, descriptor);
+            contextLookup.put(descriptor, context);
+        }
+        return context;
+    }
+
+    public long convertTimestamp(long ticks) {
+        return timeConverter.convertTimestamp(ticks);
+    }
+
+    public long convertTimespan(long ticks) {
+        return timeConverter.convertTimespan(ticks);
+    }
+
+    public ZoneId getZoneOffset() {
+        return timeConverter.getZoneOffset();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ObjectFactory.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2016, 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.internal.consumer;
+
+import jdk.jfr.consumer.RecordedClass;
+import jdk.jfr.consumer.RecordedClassLoader;
+import jdk.jfr.consumer.RecordedFrame;
+import jdk.jfr.consumer.RecordedMethod;
+import jdk.jfr.consumer.RecordedStackTrace;
+import jdk.jfr.consumer.RecordedThread;
+import jdk.jfr.consumer.RecordedThreadGroup;
+import jdk.jfr.internal.Type;
+
+/**
+ * Abstract factory for creating specialized types
+ */
+public abstract class ObjectFactory<T> {
+    private static final JdkJfrConsumer PRIVATE_ACCESS = JdkJfrConsumer.instance();
+
+    private final static String TYPE_PREFIX_VERSION_1 = "com.oracle.jfr.types.";
+    private final static String TYPE_PREFIX_VERSION_2 = Type.TYPES_PREFIX;
+    public final static String STACK_FRAME_VERSION_1 = TYPE_PREFIX_VERSION_1 + "StackFrame";
+    public final static String STACK_FRAME_VERSION_2 = TYPE_PREFIX_VERSION_2 + "StackFrame";
+
+    static ObjectFactory<?> create(Type type, TimeConverter timeConverter) {
+        switch (type.getName()) {
+        case "java.lang.Thread":
+            return createThreadFactory(type, timeConverter);
+        case TYPE_PREFIX_VERSION_1 + "StackFrame":
+        case TYPE_PREFIX_VERSION_2 + "StackFrame":
+            return createFrameFactory(type, timeConverter);
+        case TYPE_PREFIX_VERSION_1 + "Method":
+        case TYPE_PREFIX_VERSION_2 + "Method":
+            return createMethodFactory(type, timeConverter);
+        case TYPE_PREFIX_VERSION_1 + "ThreadGroup":
+        case TYPE_PREFIX_VERSION_2 + "ThreadGroup":
+            return createdThreadGroupFactory(type, timeConverter);
+        case TYPE_PREFIX_VERSION_1 + "StackTrace":
+        case TYPE_PREFIX_VERSION_2 + "StackTrace":
+            return createStackTraceFactory(type, timeConverter);
+        case TYPE_PREFIX_VERSION_1 + "ClassLoader":
+        case TYPE_PREFIX_VERSION_2 + "ClassLoader":
+            return createClassLoaderFactory(type, timeConverter);
+        case "java.lang.Class":
+            return createClassFactory(type, timeConverter);
+        }
+        return null;
+    }
+
+    private static ObjectFactory<RecordedClass> createClassFactory(Type type, TimeConverter timeConverter) {
+        return new ObjectFactory<RecordedClass>(type, timeConverter) {
+            @Override
+            RecordedClass createTyped(ObjectContext objectContext, long id, Object[] values) {
+                return PRIVATE_ACCESS.newRecordedClass(objectContext, id, values);
+            }
+        };
+    }
+
+    private static ObjectFactory<?> createClassLoaderFactory(Type type, TimeConverter timeConverter) {
+        return new ObjectFactory<RecordedClassLoader>(type, timeConverter) {
+            @Override
+            RecordedClassLoader createTyped(ObjectContext objectContext, long id, Object[] values) {
+                return PRIVATE_ACCESS.newRecordedClassLoader(objectContext, id, values);
+            }
+        };
+    }
+
+    private static ObjectFactory<RecordedStackTrace> createStackTraceFactory(Type type, TimeConverter timeConverter) {
+        return new ObjectFactory<RecordedStackTrace>(type, timeConverter) {
+            @Override
+            RecordedStackTrace createTyped(ObjectContext objectContext, long id, Object[] values) {
+                return PRIVATE_ACCESS.newRecordedStackTrace(objectContext, values);
+            }
+        };
+    }
+
+    private static ObjectFactory<RecordedThreadGroup> createdThreadGroupFactory(Type type, TimeConverter timeConverter) {
+        return new ObjectFactory<RecordedThreadGroup>(type, timeConverter) {
+            @Override
+            RecordedThreadGroup createTyped(ObjectContext objectContext, long id, Object[] values) {
+                return PRIVATE_ACCESS.newRecordedThreadGroup(objectContext, values);
+            }
+        };
+    }
+
+    private static ObjectFactory<RecordedMethod> createMethodFactory(Type type, TimeConverter timeConverter) {
+        return new ObjectFactory<RecordedMethod>(type, timeConverter) {
+            @Override
+            RecordedMethod createTyped(ObjectContext objectContext, long id, Object[] values) {
+                return PRIVATE_ACCESS.newRecordedMethod(objectContext, values);
+            }
+        };
+    }
+
+    private static ObjectFactory<RecordedFrame> createFrameFactory(Type type, TimeConverter timeConverter) {
+        return new ObjectFactory<RecordedFrame>(type, timeConverter) {
+            @Override
+            RecordedFrame createTyped(ObjectContext objectContext, long id, Object[] values) {
+                return PRIVATE_ACCESS.newRecordedFrame(objectContext, values);
+            }
+        };
+    }
+
+    private static ObjectFactory<RecordedThread> createThreadFactory(Type type, TimeConverter timeConverter) {
+        return new ObjectFactory<RecordedThread>(type, timeConverter) {
+            @Override
+            RecordedThread createTyped(ObjectContext objectContext, long id, Object[] values) {
+                return PRIVATE_ACCESS.newRecordedThread(objectContext, id, values);
+            }
+        };
+    }
+
+    private final ObjectContext objectContext;
+
+    private ObjectFactory(Type type, TimeConverter timeConverter) {
+        this.objectContext = new ObjectContext(null, type.getFields(), timeConverter);
+    }
+
+    T createObject(long id, Object value) {
+        if (value == null) {
+            return null;
+        }
+        if (value instanceof Object[]) {
+            return createTyped(objectContext, id, (Object[]) value);
+        }
+        throw new InternalError("Object factory must have struct type. Type was " + value.getClass().getName());
+    }
+
+    abstract T createTyped(ObjectContext objectContextm, long id, Object[] values);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/Parser.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2016, 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.internal.consumer;
+
+import java.io.IOException;
+
+/**
+ * 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
+     */
+    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/ParserFactory.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,382 @@
+/*
+ * Copyright (c) 2016, 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.internal.consumer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import jdk.jfr.EventType;
+import jdk.jfr.ValueDescriptor;
+import jdk.jfr.internal.LongMap;
+import jdk.jfr.internal.MetadataDescriptor;
+import jdk.jfr.internal.PrivateAccess;
+import jdk.jfr.internal.Type;
+import jdk.jfr.internal.consumer.Parser;
+import jdk.jfr.internal.consumer.RecordingInput;
+
+/**
+ * Class that create parsers suitable for reading events and constant pools
+ */
+final class ParserFactory {
+    private final LongMap<Parser> parsers = new LongMap<>();
+    private final TimeConverter timeConverter;
+    private final LongMap<Type> types = new LongMap<>();
+    private final LongMap<ConstantLookup> constantLookups;
+
+    public ParserFactory(MetadataDescriptor metadata, LongMap<ConstantLookup> constantLookups, TimeConverter timeConverter) throws IOException {
+        this.constantLookups = constantLookups;
+        this.timeConverter = timeConverter;
+        for (Type t : metadata.getTypes()) {
+            types.put(t.getId(), t);
+        }
+        // Add to separate list
+        // so createCompositeParser can throw
+        // IOException outside lambda
+        List<Type> typeList = new ArrayList<>();
+        types.forEach(typeList::add);
+        for (Type t : typeList) {
+            if (!t.getFields().isEmpty()) { // Avoid primitives
+                CompositeParser cp = createCompositeParser(t, false);
+                if (t.isSimpleType()) { // Reduce to nested parser
+                    parsers.put(t.getId(), cp.parsers[0]);
+                }
+            }
+        }
+        // Override event types with event parsers
+        for (EventType t : metadata.getEventTypes()) {
+            parsers.put(t.getId(), createEventParser(t));
+        }
+    }
+
+    public LongMap<Parser> getParsers() {
+        return parsers;
+    }
+
+    public LongMap<Type> getTypeMap() {
+        return types;
+    }
+
+    private EventParser createEventParser(EventType eventType) throws IOException {
+        List<Parser> parsers = new ArrayList<Parser>();
+        for (ValueDescriptor f : eventType.getFields()) {
+            parsers.add(createParser(f, true));
+        }
+        return new EventParser(timeConverter, eventType, parsers.toArray(new Parser[0]));
+    }
+
+    private Parser createParser(ValueDescriptor v, boolean event) throws IOException {
+        boolean constantPool = PrivateAccess.getInstance().isConstantPool(v);
+        if (v.isArray()) {
+            Type valueType = PrivateAccess.getInstance().getType(v);
+            ValueDescriptor element = PrivateAccess.getInstance().newValueDescriptor(v.getName(), valueType, v.getAnnotationElements(), 0, constantPool, null);
+            return new ArrayParser(createParser(element, event));
+        }
+        long id = v.getTypeId();
+        Type type = types.get(id);
+        if (type == null) {
+            throw new IOException("Type '" + v.getTypeName() + "' is not defined");
+        }
+        if (constantPool) {
+            ConstantLookup lookup = constantLookups.get(id);
+            if (lookup == null) {
+                ConstantMap pool = new ConstantMap(ObjectFactory.create(type, timeConverter), type.getName());
+                lookup = new ConstantLookup(pool, type);
+                constantLookups.put(id, lookup);
+            }
+            if (event) {
+                return new EventValueConstantParser(lookup);
+            }
+            return new ConstantValueParser(lookup);
+        }
+        Parser parser = parsers.get(id);
+        if (parser == null) {
+            if (!v.getFields().isEmpty()) {
+                return createCompositeParser(type, event);
+            } else {
+                return registerParserType(type, createPrimitiveParser(type, constantPool));
+            }
+        }
+        return parser;
+    }
+
+    private Parser createPrimitiveParser(Type type, boolean event) throws IOException {
+        switch (type.getName()) {
+        case "int":
+            return new IntegerParser();
+        case "long":
+            return new LongParser();
+        case "float":
+            return new FloatParser();
+        case "double":
+            return new DoubleParser();
+        case "char":
+            return new CharacterParser();
+        case "boolean":
+            return new BooleanParser();
+        case "short":
+            return new ShortParser();
+        case "byte":
+            return new ByteParser();
+        case "java.lang.String":
+            ConstantMap pool = new ConstantMap(ObjectFactory.create(type, timeConverter), type.getName());
+            ConstantLookup lookup = new ConstantLookup(pool, type);
+            constantLookups.put(type.getId(), lookup);
+            return new StringParser(lookup, event);
+        default:
+            throw new IOException("Unknown primitive type " + type.getName());
+        }
+    }
+
+    private Parser registerParserType(Type t, Parser parser) {
+        Parser p = parsers.get(t.getId());
+        // check if parser exists (known type)
+        if (p != null) {
+            return p;
+        }
+        parsers.put(t.getId(), parser);
+        return parser;
+    }
+
+    private CompositeParser createCompositeParser(Type type, boolean event) throws IOException {
+        List<ValueDescriptor> vds = type.getFields();
+        Parser[] parsers = new Parser[vds.size()];
+        CompositeParser composite = new CompositeParser(parsers);
+        // need to pre-register so recursive types can be handled
+        registerParserType(type, composite);
+
+        int index = 0;
+        for (ValueDescriptor vd : vds) {
+            parsers[index++] = createParser(vd, event);
+        }
+        return composite;
+    }
+
+    private static final class BooleanParser extends Parser {
+        @Override
+        public Object parse(RecordingInput input) throws IOException {
+            return input.readBoolean() ? Boolean.TRUE : Boolean.FALSE;
+        }
+
+        @Override
+        public void skip(RecordingInput input) throws IOException {
+            input.skipBytes(1);
+        }
+    }
+
+    private static final class ByteParser extends Parser {
+        @Override
+        public Object parse(RecordingInput input) throws IOException {
+            return Byte.valueOf(input.readByte());
+        }
+
+        @Override
+        public void skip(RecordingInput input) throws IOException {
+            input.skipBytes(1);
+        }
+    }
+
+    private static final class LongParser extends Parser {
+        private Object lastLongObject = Long.valueOf(0);
+        private long last = 0;
+
+        @Override
+        public Object parse(RecordingInput input) throws IOException {
+            long l = input.readLong();
+            if (l == last) {
+                return lastLongObject;
+            }
+            last = l;
+            lastLongObject = Long.valueOf(l);
+            return lastLongObject;
+        }
+
+        @Override
+        public void skip(RecordingInput input) throws IOException {
+            input.readLong();
+        }
+    }
+
+    private static final class IntegerParser extends Parser {
+        private Integer lastIntegergObject = Integer.valueOf(0);
+        private int last = 0;
+
+        @Override
+        public Object parse(RecordingInput input) throws IOException {
+            int i = input.readInt();
+            if (i != last) {
+                last = i;
+                lastIntegergObject = Integer.valueOf(i);
+            }
+            return lastIntegergObject;
+        }
+
+        @Override
+        public void skip(RecordingInput input) throws IOException {
+            input.readInt();
+        }
+    }
+
+    private static final class ShortParser extends Parser {
+        @Override
+        public Object parse(RecordingInput input) throws IOException {
+            return Short.valueOf(input.readShort());
+        }
+
+        @Override
+        public void skip(RecordingInput input) throws IOException {
+            input.readShort();
+        }
+    }
+
+    private static final class CharacterParser extends Parser {
+        @Override
+        public Object parse(RecordingInput input) throws IOException {
+            return Character.valueOf(input.readChar());
+        }
+
+        @Override
+        public void skip(RecordingInput input) throws IOException {
+            input.readChar();
+        }
+    }
+
+    private static final class FloatParser extends Parser {
+        @Override
+        public Object parse(RecordingInput input) throws IOException {
+            return Float.valueOf(input.readFloat());
+        }
+
+        @Override
+        public void skip(RecordingInput input) throws IOException {
+            input.skipBytes(Float.SIZE);
+        }
+    }
+
+    private static final class DoubleParser extends Parser {
+        @Override
+        public Object parse(RecordingInput input) throws IOException {
+            return Double.valueOf(input.readDouble());
+        }
+
+        @Override
+        public void skip(RecordingInput input) throws IOException {
+            input.skipBytes(Double.SIZE);
+        }
+    }
+
+    private final static class ArrayParser extends Parser {
+        private final Parser elementParser;
+
+        public ArrayParser(Parser elementParser) {
+            this.elementParser = elementParser;
+        }
+
+        @Override
+        public Object parse(RecordingInput input) throws IOException {
+            final int size = input.readInt();
+            final Object[] array = new Object[size];
+            for (int i = 0; i < size; i++) {
+                array[i] = elementParser.parse(input);
+            }
+            return array;
+        }
+
+        @Override
+        public void skip(RecordingInput input) throws IOException {
+            final int size = input.readInt();
+            for (int i = 0; i < size; i++) {
+                elementParser.skip(input);
+            }
+        }
+    }
+
+    private final static class CompositeParser extends Parser {
+        private final Parser[] parsers;
+
+        public CompositeParser(Parser[] valueParsers) {
+            this.parsers = valueParsers;
+        }
+
+        @Override
+        public Object parse(RecordingInput input) throws IOException {
+            final Object[] values = new Object[parsers.length];
+            for (int i = 0; i < values.length; i++) {
+                values[i] = parsers[i].parse(input);
+            }
+            return values;
+        }
+
+        @Override
+        public void skip(RecordingInput input) throws IOException {
+            for (int i = 0; i < parsers.length; i++) {
+                parsers[i].skip(input);
+            }
+        }
+    }
+
+    private static final class EventValueConstantParser extends Parser {
+        private final ConstantLookup lookup;
+        private Object lastValue = 0;
+        private long lastKey = -1;
+        EventValueConstantParser(ConstantLookup lookup) {
+            this.lookup = lookup;
+        }
+
+        @Override
+        public Object parse(RecordingInput input) throws IOException {
+            long key = input.readLong();
+            if (key == lastKey) {
+                return lastValue;
+            }
+            lastKey = key;
+            lastValue = lookup.getCurrentResolved(key);
+            return lastValue;
+        }
+
+        @Override
+        public void skip(RecordingInput input) throws IOException {
+            input.readLong();
+        }
+    }
+
+    private static final class ConstantValueParser extends Parser {
+        private final ConstantLookup lookup;
+        ConstantValueParser(ConstantLookup lookup) {
+            this.lookup = lookup;
+        }
+
+        @Override
+        public Object parse(RecordingInput input) throws IOException {
+            return lookup.getCurrent(input.readLong());
+        }
+
+        @Override
+        public void skip(RecordingInput input) throws IOException {
+            input.readLong();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ParserFilter.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,80 @@
+/*
+ * 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.internal.consumer;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.StringJoiner;
+
+final class ParserFilter {
+    public static final ParserFilter ACCEPT_ALL = new ParserFilter(true, Map.of());
+
+    private final Map<String, Long> thresholds;
+    private final boolean acceptAll;
+
+    public ParserFilter() {
+        this(false, new HashMap<>());
+    }
+
+    private ParserFilter(boolean acceptAll, Map<String, Long> thresholds) {
+        this.acceptAll = acceptAll;
+        this.thresholds = thresholds;
+    }
+
+    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 0L;
+        }
+        Long l = thresholds.get(eventName);
+        if (l != null) {
+            return l;
+        }
+        return -1;
+    }
+
+    public String toString() {
+        if (acceptAll) {
+            return "ACCEPT ALL";
+        }
+
+        StringJoiner sb = new StringJoiner(", ");
+        for (String key : thresholds.keySet().toArray(new String[0])) {
+            Long value = thresholds.get(key);
+            sb.add(key + " = " + value);
+        }
+        return sb.toString();
+    }
+}
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/RecordingInput.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/RecordingInput.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -30,61 +30,82 @@
 import java.io.File;
 import java.io.IOException;
 import java.io.RandomAccessFile;
-import java.nio.charset.Charset;
+import java.nio.file.Path;
 
 public final class RecordingInput implements DataInput, AutoCloseable {
 
-    public static final byte STRING_ENCODING_NULL = 0;
-    public static final byte STRING_ENCODING_EMPTY_STRING = 1;
-    public static final byte STRING_ENCODING_CONSTANT_POOL = 2;
-    public static final byte STRING_ENCODING_UTF8_BYTE_ARRAY = 3;
-    public static final byte STRING_ENCODING_CHAR_ARRAY = 4;
-    public static final byte STRING_ENCODING_LATIN1_BYTE_ARRAY = 5;
-
-    private final static int DEFAULT_BLOCK_SIZE = 16 * 1024 * 1024;
-    private final static Charset UTF8 = Charset.forName("UTF-8");
-    private final static Charset LATIN1 = Charset.forName("ISO-8859-1");
+    private final static int DEFAULT_BLOCK_SIZE = 64_000;
 
     private static final class Block {
         private byte[] bytes = new byte[0];
         private long blockPosition;
+        private long blockPositionEnd;
 
         boolean contains(long position) {
-            return position >= blockPosition && position < blockPosition + bytes.length;
+            return position >= blockPosition && position < blockPositionEnd;
         }
 
         public void read(RandomAccessFile file, int amount) throws IOException {
             blockPosition = file.getFilePointer();
             // reuse byte array, if possible
-            if (amount != bytes.length) {
+            if (amount > bytes.length) {
                 bytes = new byte[amount];
             }
-            file.readFully(bytes);
+            this.blockPositionEnd = blockPosition + amount;
+            file.readFully(bytes, 0, amount);
         }
 
         public byte get(long position) {
             return bytes[(int) (position - blockPosition)];
         }
+
+        public void reset() {
+            blockPosition = 0;
+            blockPositionEnd = 0;
+        }
     }
-
-    private final RandomAccessFile file;
-    private final long size;
+    private final int blockSize;
+    private final FileAccess fileAccess;
+    private RandomAccessFile file;
+    private String filename;
     private Block currentBlock = new Block();
     private Block previousBlock = new Block();
     private long position;
-    private final int blockSize;
+    private long size = -1; // Fail fast if setSize(...) has not been called
+                            // before parsing
+
+    RecordingInput(File f, FileAccess fileAccess, int blockSize) throws IOException {
+        this.blockSize = blockSize;
+        this.fileAccess = fileAccess;
+        initialize(f);
+    }
 
-    private RecordingInput(File f, int blockSize) throws IOException {
-        this.size = f.length();
-        this.blockSize = blockSize;
-        this.file = new RandomAccessFile(f, "r");
-        if (size < 8) {
-            throw new IOException("Not a valid Flight Recorder file. File length is only " + size + " bytes.");
+    private void initialize(File f) throws IOException {
+        this.filename = fileAccess.getAbsolutePath(f);
+        this.file = fileAccess.openRAF(f, "r");
+        this.position = 0;
+        this.size = -1;
+        this.currentBlock.reset();
+        previousBlock.reset();
+        if (fileAccess.length(f) < 8) {
+            throw new IOException("Not a valid Flight Recorder file. File length is only " + fileAccess.length(f) + " bytes.");
         }
     }
 
-    public RecordingInput(File f) throws IOException {
-        this(f, DEFAULT_BLOCK_SIZE);
+    public RecordingInput(File f, FileAccess fileAccess) throws IOException {
+        this(f, fileAccess, DEFAULT_BLOCK_SIZE);
+    }
+
+    void positionPhysical(long position) throws IOException {
+        file.seek(position);
+    }
+
+    byte readPhysicalByte() throws IOException {
+        return file.readByte();
+    }
+
+    long readPhysicalLong() throws IOException {
+        return file.readLong();
     }
 
     @Override
@@ -109,7 +130,7 @@
         readFully(dst, 0, dst.length);
     }
 
-    public final short readRawShort() throws IOException {
+    short readRawShort() throws IOException {
         // copied from java.io.Bits
         byte b0 = readByte();
         byte b1 = readByte();
@@ -117,18 +138,18 @@
     }
 
     @Override
-    public final double readDouble() throws IOException {
+    public double readDouble() throws IOException {
         // copied from java.io.Bits
         return Double.longBitsToDouble(readRawLong());
     }
 
     @Override
-    public final float readFloat() throws IOException {
+    public float readFloat() throws IOException {
         // copied from java.io.Bits
         return Float.intBitsToFloat(readRawInt());
     }
 
-    public final int readRawInt() throws IOException {
+    int readRawInt() throws IOException {
         // copied from java.io.Bits
         byte b0 = readByte();
         byte b1 = readByte();
@@ -137,7 +158,7 @@
         return ((b3 & 0xFF)) + ((b2 & 0xFF) << 8) + ((b1 & 0xFF) << 16) + ((b0) << 24);
     }
 
-    public final long readRawLong() throws IOException {
+    long readRawLong() throws IOException {
         // copied from java.io.Bits
         byte b0 = readByte();
         byte b1 = readByte();
@@ -150,20 +171,20 @@
         return ((b7 & 0xFFL)) + ((b6 & 0xFFL) << 8) + ((b5 & 0xFFL) << 16) + ((b4 & 0xFFL) << 24) + ((b3 & 0xFFL) << 32) + ((b2 & 0xFFL) << 40) + ((b1 & 0xFFL) << 48) + (((long) b0) << 56);
     }
 
-    public final long position() throws IOException {
+    public final long position() {
         return position;
     }
 
     public final void position(long newPosition) throws IOException {
         if (!currentBlock.contains(newPosition)) {
             if (!previousBlock.contains(newPosition)) {
-                if (newPosition > size()) {
-                    throw new EOFException("Trying to read at " + newPosition + ", but file is only " + size() + " bytes.");
+                if (newPosition > size) {
+                    throw new EOFException("Trying to read at " + newPosition + ", but file is only " + size + " bytes.");
                 }
                 long blockStart = trimToFileSize(calculateBlockStart(newPosition));
                 file.seek(blockStart);
                 // trim amount to file size
-                long amount = Math.min(size() - blockStart, blockSize);
+                long amount = Math.min(size - blockStart, blockSize);
                 previousBlock.read(file, (int) amount);
             }
             // swap previous and current
@@ -191,11 +212,12 @@
         return newPosition - blockSize / 2;
     }
 
-    public final long size() throws IOException {
+    long size() {
         return size;
     }
 
-    public final void close() throws IOException {
+    @Override
+    public void close() throws IOException {
         file.close();
     }
 
@@ -245,34 +267,7 @@
     // 4, means ""
     @Override
     public String readUTF() throws IOException {
-        return readEncodedString(readByte());
-    }
-
-    public String readEncodedString(byte encoding) throws IOException {
-        if (encoding == STRING_ENCODING_NULL) {
-            return null;
-        }
-        if (encoding == STRING_ENCODING_EMPTY_STRING) {
-            return "";
-        }
-        int size = readInt();
-        if (encoding == STRING_ENCODING_CHAR_ARRAY) {
-            char[] c = new char[size];
-            for (int i = 0; i < size; i++) {
-                c[i] = readChar();
-            }
-            return new String(c);
-        }
-        byte[] bytes = new byte[size];
-        readFully(bytes); // TODO: optimize, check size, and copy only if needed
-        if (encoding == STRING_ENCODING_UTF8_BYTE_ARRAY) {
-            return new String(bytes, UTF8);
-        }
-
-        if (encoding == STRING_ENCODING_LATIN1_BYTE_ARRAY) {
-            return new String(bytes, LATIN1);
-        }
-        throw new IOException("Unknown string encoding " + encoding);
+        throw new UnsupportedOperationException("Use StringParser");
     }
 
     @Override
@@ -292,48 +287,152 @@
 
     @Override
     public long readLong() throws IOException {
-        // can be optimized by branching checks, but will do for now
+        final byte[] bytes = currentBlock.bytes;
+        final int index = (int) (position - currentBlock.blockPosition);
+
+        if (index + 8 < bytes.length && index >= 0) {
+            byte b0 = bytes[index];
+            long ret = (b0 & 0x7FL);
+            if (b0 >= 0) {
+                position += 1;
+                return ret;
+            }
+            int b1 = bytes[index + 1];
+            ret += (b1 & 0x7FL) << 7;
+            if (b1 >= 0) {
+                position += 2;
+                return ret;
+            }
+            int b2 = bytes[index + 2];
+            ret += (b2 & 0x7FL) << 14;
+            if (b2 >= 0) {
+                position += 3;
+                return ret;
+            }
+            int b3 = bytes[index + 3];
+            ret += (b3 & 0x7FL) << 21;
+            if (b3 >= 0) {
+                position += 4;
+                return ret;
+            }
+            int b4 = bytes[index + 4];
+            ret += (b4 & 0x7FL) << 28;
+            if (b4 >= 0) {
+                position += 5;
+                return ret;
+            }
+            int b5 = bytes[index + 5];
+            ret += (b5 & 0x7FL) << 35;
+            if (b5 >= 0) {
+                position += 6;
+                return ret;
+            }
+            int b6 = bytes[index + 6];
+            ret += (b6 & 0x7FL) << 42;
+            if (b6 >= 0) {
+                position += 7;
+                return ret;
+            }
+            int b7 = bytes[index + 7];
+            ret += (b7 & 0x7FL) << 49;
+            if (b7 >= 0) {
+                position += 8;
+                return ret;
+            }
+            int b8 = bytes[index + 8];// read last byte raw
+            position += 9;
+            return ret + (((long) (b8 & 0XFF)) << 56);
+        } else {
+            return readLongSlow();
+        }
+    }
+
+    private long readLongSlow() throws IOException {
         byte b0 = readByte();
         long ret = (b0 & 0x7FL);
         if (b0 >= 0) {
             return ret;
         }
+
         int b1 = readByte();
         ret += (b1 & 0x7FL) << 7;
         if (b1 >= 0) {
             return ret;
         }
+
         int b2 = readByte();
         ret += (b2 & 0x7FL) << 14;
         if (b2 >= 0) {
             return ret;
         }
+
         int b3 = readByte();
         ret += (b3 & 0x7FL) << 21;
         if (b3 >= 0) {
             return ret;
         }
+
         int b4 = readByte();
         ret += (b4 & 0x7FL) << 28;
         if (b4 >= 0) {
             return ret;
         }
+
         int b5 = readByte();
         ret += (b5 & 0x7FL) << 35;
         if (b5 >= 0) {
             return ret;
         }
+
         int b6 = readByte();
         ret += (b6 & 0x7FL) << 42;
         if (b6 >= 0) {
             return ret;
         }
+
         int b7 = readByte();
         ret += (b7 & 0x7FL) << 49;
         if (b7 >= 0) {
             return ret;
+
         }
+
         int b8 = readByte(); // read last byte raw
         return ret + (((long) (b8 & 0XFF)) << 56);
     }
+
+    public void setValidSize(long size) {
+        if (size > this.size) {
+            this.size = size;
+        }
+    }
+
+    public long getFileSize() throws IOException {
+        return file.length();
+    }
+
+    public String getFilename() {
+        return filename;
+    }
+
+    // Purpose of this method is to reuse block cache from a
+    // previous RecordingInput
+    public void setFile(Path path) throws IOException {
+        try {
+            file.close();
+        } catch (IOException e) {
+            // perhaps deleted
+        }
+        file = null;
+        initialize(path.toFile());
+    }
+/*
+
+
+
+
+
+ *
+ *
+ */
 }
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/RecordingInternals.java	Wed Oct 30 16:14:56 2019 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,47 +0,0 @@
-/*
- * Copyright (c) 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;
-import java.util.List;
-
-import jdk.jfr.consumer.RecordedEvent;
-import jdk.jfr.consumer.RecordedObject;
-import jdk.jfr.consumer.RecordingFile;
-import jdk.jfr.internal.Type;
-
-public abstract class RecordingInternals {
-
-    public static RecordingInternals INSTANCE;
-
-    public abstract boolean isLastEventInChunk(RecordingFile file);
-
-    public abstract Object getOffsetDataTime(RecordedObject event, String name);
-
-    public abstract List<Type> readTypes(RecordingFile file) throws IOException;
-
-    public abstract void sort(List<RecordedEvent> events);
-
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/RepositoryFiles.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,230 @@
+/*
+ * 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.internal.consumer;
+
+import java.io.IOException;
+import java.nio.file.DirectoryStream;
+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.NavigableMap;
+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.SecuritySupport.SafePath;
+
+public final class RepositoryFiles {
+    private static final Object WAIT_OBJECT = new Object();
+    public static void notifyNewFile() {
+        synchronized (WAIT_OBJECT) {
+            WAIT_OBJECT.notifyAll();
+        }
+    }
+
+    private final FileAccess fileAccess;
+    private final NavigableMap<Long, Path> pathSet = new TreeMap<>();
+    private final Map<Path, Long> pathLookup = new HashMap<>();
+    private final Path repository;
+    private final Object waitObject;
+
+    private volatile boolean closed;
+
+    RepositoryFiles(FileAccess fileAccess, Path repository) {
+        this.repository = repository;
+        this.fileAccess = fileAccess;
+        this.waitObject = repository == null ? WAIT_OBJECT : new Object();
+    }
+
+    long getTimestamp(Path p) {
+        return pathLookup.get(p);
+    }
+
+    Path lastPath() {
+        if (waitForPaths()) {
+            return pathSet.lastEntry().getValue();
+        }
+        return null; // closed
+    }
+
+    Path firstPath(long startTimeNanos) {
+        if (waitForPaths()) {
+            // Pick closest chunk before timestamp
+            Long time = pathSet.floorKey(startTimeNanos);
+            if (time != null) {
+                startTimeNanos = time;
+            }
+            return path(startTimeNanos);
+        }
+        return null; // closed
+    }
+
+    private boolean waitForPaths() {
+        while (!closed) {
+            try {
+                if (updatePaths()) {
+                    break;
+                }
+            } catch (IOException e) {
+                Logger.log(LogTag.JFR_SYSTEM_STREAMING, LogLevel.DEBUG, "IOException during repository file scan " + e.getMessage());
+                // This can happen if a chunk is being removed
+                // between the file was discovered and an instance
+                // was accessed, or if new file has been written yet
+                // Just ignore, and retry later.
+            }
+            nap();
+        }
+        return !closed;
+    }
+
+    Path nextPath(long startTimeNanos) {
+        if (closed) {
+            return null;
+        }
+        // Try to get the 'exact' path first
+        // to avoid skipping files if repository
+        // is updated while DirectoryStream
+        // is traversing it
+        Path path = pathSet.get(startTimeNanos);
+        if (path != null) {
+            return path;
+        }
+        // Update paths
+        try {
+            updatePaths();
+        } catch (IOException e) {
+            // ignore
+        }
+        // try to get the next file
+        return path(startTimeNanos);
+    }
+
+    private Path path(long timestamp) {
+        if (closed) {
+            return null;
+        }
+        while (true) {
+            SortedMap<Long, Path> after = pathSet.tailMap(timestamp);
+            if (!after.isEmpty()) {
+                Path path = after.get(after.firstKey());
+                if (Logger.shouldLog(LogTag.JFR_SYSTEM_STREAMING, LogLevel.TRACE)) {
+                    Logger.log(LogTag.JFR_SYSTEM_STREAMING, LogLevel.TRACE, "Return path " + path + " for start time nanos " + timestamp);
+                }
+                return path;
+            }
+            if (!waitForPaths()) {
+                return null; // closed
+            }
+        }
+    }
+
+    private void nap() {
+        try {
+            synchronized (waitObject) {
+                waitObject.wait(1000);
+            }
+        } catch (InterruptedException e) {
+            // ignore
+        }
+    }
+
+    private boolean updatePaths() throws IOException {
+        boolean foundNew = false;
+        Path repoPath = repository;
+        if (repoPath == null) {
+            // Always get the latest repository if 'jcmd JFR.configure
+            // repositorypath=...' has been executed
+            SafePath sf = Repository.getRepository().getRepositoryPath();
+            if (sf == null) {
+                return false; // not initialized
+            }
+            repoPath = sf.toPath();
+        }
+
+        try (DirectoryStream<Path> dirStream = fileAccess.newDirectoryStream(repoPath)) {
+            List<Path> added = new ArrayList<>();
+            Set<Path> current = new HashSet<>();
+            for (Path p : dirStream) {
+                if (!pathLookup.containsKey(p)) {
+                    String s = p.toString();
+                    if (s.endsWith(".jfr")) {
+                        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 : pathLookup.keySet()) {
+                if (!current.contains(p)) {
+                    removed.add(p);
+                }
+            }
+
+            for (Path remove : removed) {
+                Long time = pathLookup.get(remove);
+                pathSet.remove(time);
+                pathLookup.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 = fileAccess.fileSize(p);
+                if (size >= ChunkHeader.headerSize()) {
+                    long startNanos = readStartTime(p);
+                    pathSet.put(startNanos, p);
+                    pathLookup.put(p, startNanos);
+                    foundNew = true;
+                }
+            }
+            return foundNew;
+        }
+    }
+
+    private long readStartTime(Path p) throws IOException {
+        try (RecordingInput in = new RecordingInput(p.toFile(), fileAccess, 100)) {
+            Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Parsing header for chunk start time");
+            ChunkHeader c = new ChunkHeader(in);
+            return c.getStartNanos();
+        }
+    }
+
+    void close() {
+        synchronized (waitObject) {
+            this.closed = true;
+            waitObject.notify();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/StreamConfiguration.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,124 @@
+/*
+ * 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.internal.consumer;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.jfr.internal.Utils;
+import jdk.jfr.internal.consumer.Dispatcher.EventDispatcher;
+
+final class StreamConfiguration {
+    final List<Runnable> closeActions = new ArrayList<>();
+    final List<Runnable> flushActions = new ArrayList<>();
+    final List<EventDispatcher> eventActions = new ArrayList<>();
+    final List<Consumer<Throwable>> errorActions = new ArrayList<>();
+
+    boolean reuse = true;
+    boolean ordered = true;
+    Instant startTime = null;
+    Instant endTime = null;
+    boolean started = false;
+    long startNanos = 0;
+    long endNanos = Long.MAX_VALUE;
+
+    private volatile boolean changed = true;
+
+    public synchronized boolean remove(Object action) {
+        boolean removed = false;
+        removed |= flushActions.removeIf(e -> e == action);
+        removed |= closeActions.removeIf(e -> e == action);
+        removed |= errorActions.removeIf(e -> e == action);
+        removed |= eventActions.removeIf(e -> e.getAction() == action);
+        if (removed) {
+            changed = true;
+        }
+        return removed;
+    }
+
+    public synchronized void addEventAction(String name, Consumer<RecordedEvent> consumer) {
+        eventActions.add(new EventDispatcher(name, consumer));
+        changed = true;
+    }
+
+    public void addEventAction(Consumer<RecordedEvent> action) {
+        addEventAction(null, action);
+    }
+
+    public synchronized void addFlushAction(Runnable action) {
+        flushActions.add(action);
+        changed = true;
+    }
+
+    public synchronized void addCloseAction(Runnable action) {
+        closeActions.add(action);
+        changed = true;
+    }
+
+    public synchronized void addErrorAction(Consumer<Throwable> action) {
+        errorActions.add(action);
+        changed = true;
+    }
+
+    public synchronized void setReuse(boolean reuse) {
+        this.reuse = reuse;
+        changed = true;
+    }
+
+    public synchronized void setOrdered(boolean ordered) {
+        this.ordered = ordered;
+        changed = true;
+    }
+
+    public synchronized void setEndTime(Instant endTime) {
+        this.endTime = endTime;
+        this.endNanos = Utils.timeToNanos(endTime);
+        changed = true;
+    }
+
+    public synchronized void setStartTime(Instant startTime) {
+        this.startTime = startTime;
+        this.startNanos = Utils.timeToNanos(startTime);
+        changed = true;
+    }
+
+    public synchronized void setStartNanos(long startNanos) {
+        this.startNanos = startNanos;
+        changed = true;
+    }
+
+    public synchronized void setStarted(boolean started) {
+        this.started = started;
+        changed = true;
+    }
+
+    public boolean hasChanged() {
+        return changed;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/StringParser.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,222 @@
+/*
+ * 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.internal.consumer;
+
+import java.io.IOException;
+import java.nio.charset.Charset;
+
+public final class StringParser extends Parser {
+
+    public enum Encoding {
+        NULL(0),
+        EMPTY_STRING(1),
+        CONSTANT_POOL(2),
+        UT8_BYTE_ARRAY(3),
+        CHAR_ARRAY(4),
+        LATIN1_BYTE_ARRAY(5);
+
+        private byte byteValue;
+
+        private Encoding(int byteValue) {
+            this.byteValue = (byte) byteValue;
+        }
+
+        public byte byteValue() {
+            return byteValue;
+        }
+
+        public boolean is(byte value) {
+            return value == byteValue;
+        }
+
+    }
+    private final static Charset UTF8 = Charset.forName("UTF-8");
+    private final static Charset LATIN1 = Charset.forName("ISO-8859-1");
+
+    private 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];
+            }
+        }
+    }
+
+    private 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.CONSTANT_POOL.is(encoding)) {
+            long key = input.readLong();
+            if (event) {
+                return stringLookup.getCurrentResolved(key);
+            } else {
+                return stringLookup.getCurrent(key);
+            }
+        }
+        if (Encoding.NULL.is(encoding)) {
+            return null;
+        }
+        if (Encoding.EMPTY_STRING.is(encoding)) {
+            return "";
+        }
+        if (Encoding.CHAR_ARRAY.is(encoding)) {
+            return charArrayParser.parse(input);
+        }
+        if (Encoding.UT8_BYTE_ARRAY.is(encoding)) {
+            return utf8parser.parse(input);
+        }
+        if (Encoding.LATIN1_BYTE_ARRAY.is(encoding)) {
+            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.CONSTANT_POOL.is(encoding)) {
+            input.readLong();
+            return;
+        }
+        if (Encoding.EMPTY_STRING.is(encoding)) {
+            return;
+        }
+        if (Encoding.NULL.is(encoding)) {
+            return;
+        }
+        if (Encoding.CHAR_ARRAY.is(encoding)) {
+            charArrayParser.skip(input);
+            return;
+        }
+        if (Encoding.UT8_BYTE_ARRAY.is(encoding)) {
+            utf8parser.skip(input);
+            return;
+        }
+        if (Encoding.LATIN1_BYTE_ARRAY.is(encoding)) {
+            latin1parser.skip(input);
+            return;
+        }
+        throw new IOException("Unknown string encoding " + encoding);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/TimeConverter.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2016, 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.internal.consumer;
+
+import java.time.DateTimeException;
+import java.time.ZoneOffset;
+
+import jdk.jfr.internal.LogLevel;
+import jdk.jfr.internal.LogTag;
+import jdk.jfr.internal.Logger;
+import jdk.jfr.internal.consumer.ChunkHeader;
+
+/**
+ * Converts ticks to nanoseconds
+ */
+final class TimeConverter {
+    private final long startTicks;
+    private final long startNanos;
+    private final double divisor;
+    private final ZoneOffset zoneOffet;
+
+    TimeConverter(ChunkHeader chunkHeader, int rawOffset) {
+        this.startTicks = chunkHeader.getStartTicks();
+        this.startNanos = chunkHeader.getStartNanos();
+        this.divisor = chunkHeader.getTicksPerSecond() / 1000_000_000L;
+        this.zoneOffet = zoneOfSet(rawOffset);
+    }
+
+    public long convertTimestamp(long ticks) {
+        return startNanos + (long) ((ticks - startTicks) / divisor);
+    }
+
+    public long convertTimespan(long ticks) {
+        return (long) (ticks / divisor);
+    }
+
+    public ZoneOffset getZoneOffset() {
+        return zoneOffet;
+    }
+
+    private ZoneOffset zoneOfSet(int rawOffset) {
+        try {
+            return ZoneOffset.ofTotalSeconds(rawOffset / 1000);
+        } catch (DateTimeException dte) {
+            Logger.log(LogTag.JFR_SYSTEM_PARSER, LogLevel.INFO, "Could not create ZoneOffset from raw offset " + rawOffset);
+        }
+        return ZoneOffset.UTC;
+    }
+}
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdConfigure.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdConfigure.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -27,10 +27,12 @@
 
 
 
+import jdk.jfr.FlightRecorder;
 import jdk.jfr.internal.LogLevel;
 import jdk.jfr.internal.LogTag;
 import jdk.jfr.internal.Logger;
 import jdk.jfr.internal.Options;
+import jdk.jfr.internal.PrivateAccess;
 import jdk.jfr.internal.Repository;
 import jdk.jfr.internal.SecuritySupport.SafePath;
 
@@ -89,6 +91,9 @@
                 SafePath s = new SafePath(repositoryPath);
                 Repository.getRepository().setBasePath(s);
                 Logger.log(LogTag.JFR, LogLevel.INFO, "Base repository path set to " + repositoryPath);
+                if (FlightRecorder.isInitialized()) {
+                    PrivateAccess.getInstance().getPlatformRecorder().rotateIfRecordingToDisk();;
+                }
             } catch (Exception e) {
                 throw new DCmdException("Could not use " + repositoryPath + " as repository. " + e.getMessage(), e);
             }
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdStart.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/dcmd/DCmdStart.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2018, Oracle and/or its affiliates. All rights reserved.
+ * 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
@@ -81,7 +81,7 @@
      * @throws DCmdException if recording could not be started
      */
     @SuppressWarnings("resource")
-    public String execute(String name, String[] settings, Long delay, Long duration, Boolean disk, String path, Long maxAge, Long maxSize, Boolean dumpOnExit, Boolean pathToGcRoots) throws DCmdException {
+    public String execute(String name, String[] settings, Long delay, Long duration, Boolean disk, String path, Long maxAge, Long maxSize, Long flush, Boolean dumpOnExit, Boolean pathToGcRoots) throws DCmdException {
         if (Logger.shouldLog(LogTag.JFR_DCMD, LogLevel.DEBUG)) {
             Logger.log(LogTag.JFR_DCMD, LogLevel.DEBUG, "Executing DCmdStart: name=" + name +
                     ", settings=" + Arrays.asList(settings) +
@@ -90,6 +90,7 @@
                     ", disk=" + disk+
                     ", filename=" + path +
                     ", maxage=" + maxAge +
+                    ", flush=" + flush +
                     ", maxsize=" + maxSize +
                     ", dumponexit =" + dumpOnExit +
                     ", path-to-gc-roots=" + pathToGcRoots);
@@ -136,6 +137,12 @@
             }
         }
 
+        if (flush != null) {
+            if (Boolean.FALSE.equals(disk)) {
+                throw new DCmdException("Flush can only be set for recordings that are to disk.");
+            }
+        }
+
         if (!FlightRecorder.isInitialized() && delay == null) {
             initializeWithForcedInstrumentation(s);
         }
@@ -148,6 +155,7 @@
         if (disk != null) {
             recording.setToDisk(disk.booleanValue());
         }
+
         recording.setSettings(s);
         SafePath safePath = null;
 
@@ -177,6 +185,10 @@
             recording.setMaxAge(Duration.ofNanos(maxAge));
         }
 
+        if (flush != null) {
+            recording.setFlushInterval(Duration.ofNanos(flush));
+        }
+
         if (maxSize != null) {
             recording.setMaxSize(maxSize);
         }
@@ -222,6 +234,7 @@
             print("Use jcmd " + getPid() + " JFR." + cmd + " " + recordingspecifier + " " + fileOption + "to copy recording data to file.");
             println();
         }
+
         return getResult();
     }
 
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Disassemble.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Disassemble.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -41,6 +41,7 @@
 import java.util.List;
 
 import jdk.jfr.internal.consumer.ChunkHeader;
+import jdk.jfr.internal.consumer.FileAccess;
 import jdk.jfr.internal.consumer.RecordingInput;
 
 final class Disassemble extends Command {
@@ -163,7 +164,7 @@
     }
 
     private List<Long> findChunkSizes(Path p) throws IOException {
-        try (RecordingInput input = new RecordingInput(p.toFile())) {
+        try (RecordingInput input = new RecordingInput(p.toFile(), FileAccess.UNPRIVILIGED)) {
             List<Long> sizes = new ArrayList<>();
             ChunkHeader ch = new ChunkHeader(input);
             sizes.add(ch.getSize());
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/EventPrintWriter.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/EventPrintWriter.java	Wed Oct 30 19:43:52 2019 +0100
@@ -30,6 +30,7 @@
 import java.io.PrintWriter;
 import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -42,7 +43,7 @@
 import jdk.jfr.consumer.RecordedEvent;
 import jdk.jfr.consumer.RecordedObject;
 import jdk.jfr.consumer.RecordingFile;
-import jdk.jfr.internal.consumer.RecordingInternals;
+import jdk.jfr.internal.consumer.JdkJfrConsumer;
 
 abstract class EventPrintWriter extends StructuredWriter {
 
@@ -52,6 +53,7 @@
 
     protected static final String STACK_TRACE_FIELD = "stackTrace";
     protected static final String EVENT_THREAD_FIELD = "eventThread";
+    private static final JdkJfrConsumer PRIVATE_ACCESS = JdkJfrConsumer.instance();
 
     private Predicate<EventType> eventFilter = x -> true;
     private int stackDepth;
@@ -74,8 +76,8 @@
                 if (acceptEvent(event)) {
                     events.add(event);
                 }
-                if (RecordingInternals.INSTANCE.isLastEventInChunk(file)) {
-                    RecordingInternals.INSTANCE.sort(events);
+                if (PRIVATE_ACCESS.isLastEventInChunk(file)) {
+                    Collections.sort(events, PRIVATE_ACCESS.eventComparator());
                     print(events);
                     events.clear();
                 }
@@ -121,7 +123,7 @@
         case TIMESPAN:
             return object.getDuration(v.getName());
         case TIMESTAMP:
-            return RecordingInternals.INSTANCE.getOffsetDataTime(object, v.getName());
+            return PRIVATE_ACCESS.getOffsetDataTime(object, v.getName());
         default:
             return object.getValue(v.getName());
         }
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Metadata.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Metadata.java	Wed Oct 30 19:43:52 2019 +0100
@@ -35,10 +35,12 @@
 
 import jdk.jfr.consumer.RecordingFile;
 import jdk.jfr.internal.Type;
-import jdk.jfr.internal.consumer.RecordingInternals;
+import jdk.jfr.internal.consumer.JdkJfrConsumer;
 
 final class Metadata extends Command {
 
+    private final static JdkJfrConsumer PRIVATE_ACCESS = JdkJfrConsumer.instance();
+
     private static class TypeComparator implements Comparator<Type> {
 
         @Override
@@ -89,6 +91,7 @@
         }
     }
 
+
     @Override
     public String getName() {
         return "metadata";
@@ -125,7 +128,7 @@
             PrettyWriter prettyWriter = new PrettyWriter(pw);
             prettyWriter.setShowIds(showIds);
             try (RecordingFile rf = new RecordingFile(file)) {
-                List<Type> types = RecordingInternals.INSTANCE.readTypes(rf);
+                List<Type> types = PRIVATE_ACCESS.readTypes(rf);
                 Collections.sort(types, new TypeComparator());
                 for (Type type : types) {
                     prettyWriter.printType(type);
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Summary.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Summary.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 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
@@ -42,6 +42,7 @@
 import jdk.jfr.internal.MetadataDescriptor;
 import jdk.jfr.internal.Type;
 import jdk.jfr.internal.consumer.ChunkHeader;
+import jdk.jfr.internal.consumer.FileAccess;
 import jdk.jfr.internal.consumer.RecordingInput;
 
 final class Summary extends Command {
@@ -91,7 +92,7 @@
         long totalDuration = 0;
         long chunks = 0;
 
-        try (RecordingInput input = new RecordingInput(p.toFile())) {
+        try (RecordingInput input = new RecordingInput(p.toFile(), FileAccess.UNPRIVILIGED)) {
             ChunkHeader first = new ChunkHeader(input);
             ChunkHeader ch = first;
             String eventPrefix = Type.EVENT_NAME_PREFIX;
--- a/src/jdk.jfr/share/conf/jfr/default.jfc	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/conf/jfr/default.jfc	Wed Oct 30 19:43:52 2019 +0100
@@ -406,7 +406,7 @@
       <setting name="enabled" control="gc-enabled-normal">true</setting>
       <setting name="threshold">0 ms</setting>
     </event>
- 
+
     <event name="jdk.G1BasicIHOP">
       <setting name="enabled" control="gc-enabled-normal">true</setting>
     </event>
@@ -670,6 +670,36 @@
       <setting name="enabled">true</setting>
     </event>
 
+    <event name="jdk.Flush">
+      <setting name="enabled">true</setting>
+      <setting name="threshold">0 ns</setting>
+    </event>
+
+    <event name="jdk.FlushStorage">
+      <setting name="enabled">true</setting>
+      <setting name="threshold">0 ns</setting>
+    </event>
+
+    <event name="jdk.FlushStacktrace">
+      <setting name="enabled">true</setting>
+      <setting name="threshold">0 ns</setting>
+    </event>
+
+    <event name="jdk.FlushStringPool">
+      <setting name="enabled">true</setting>
+      <setting name="threshold">0 ns</setting>
+    </event>
+
+    <event name="jdk.FlushMetadata">
+      <setting name="enabled">true</setting>
+      <setting name="threshold">0 ns</setting>
+    </event>
+
+    <event name="jdk.FlushTypeSet">
+      <setting name="enabled">true</setting>
+      <setting name="threshold">0 ns</setting>
+    </event>
+
     <event name="jdk.DataLoss">
       <setting name="enabled">true</setting>
     </event>
--- a/src/jdk.jfr/share/conf/jfr/profile.jfc	Wed Oct 30 16:14:56 2019 +0100
+++ b/src/jdk.jfr/share/conf/jfr/profile.jfc	Wed Oct 30 19:43:52 2019 +0100
@@ -406,7 +406,7 @@
       <setting name="enabled" control="gc-enabled-normal">true</setting>
       <setting name="threshold">0 ms</setting>
     </event>
-    
+
     <event name="jdk.G1BasicIHOP">
       <setting name="enabled" control="gc-enabled-normal">true</setting>
     </event>
@@ -670,6 +670,36 @@
       <setting name="enabled">true</setting>
     </event>
 
+    <event name="jdk.Flush">
+      <setting name="enabled">true</setting>
+      <setting name="threshold">0 ns</setting>
+    </event>
+
+    <event name="jdk.FlushStorage">
+      <setting name="enabled">true</setting>
+      <setting name="threshold">0 ns</setting>
+    </event>
+
+    <event name="jdk.FlushStacktrace">
+      <setting name="enabled">true</setting>
+      <setting name="threshold">0 ns</setting>
+    </event>
+
+    <event name="jdk.FlushStringPool">
+      <setting name="enabled">true</setting>
+      <setting name="threshold">0 ns</setting>
+    </event>
+
+    <event name="jdk.FlushMetadata">
+      <setting name="enabled">true</setting>
+      <setting name="threshold">0 ns</setting>
+    </event>
+
+    <event name="jdk.FlushTypeSet">
+      <setting name="enabled">true</setting>
+      <setting name="threshold">0 ns</setting>
+    </event>
+
     <event name="jdk.DataLoss">
       <setting name="enabled">true</setting>
     </event>
--- a/test/hotspot/gtest/jfr/test_networkUtilization.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/test/hotspot/gtest/jfr/test_networkUtilization.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, 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
@@ -51,7 +51,7 @@
 namespace {
 
   class MockFastUnorderedElapsedCounterSource : public ::FastUnorderedElapsedCounterSource {
-  public:
+   public:
     static jlong current_ticks;
     static Type now() {
       return current_ticks;
@@ -65,7 +65,7 @@
   typedef TimeInterval<CounterRepresentation, MockFastUnorderedElapsedCounterSource> MockJfrTickspan;
 
   class MockJfrCheckpointWriter {
-  public:
+   public:
     traceid current;
     std::map<traceid, std::string> ids;
 
@@ -78,44 +78,97 @@
     void write_key(traceid id) {
       current = id;
     }
-    void write(const char* data) {
-      ids[current] = data;
-    }
+    void write_type(JfrTypeId id) {}
+    MockJfrCheckpointWriter() {}
+    void write(const char* data) {}
     void set_context(const JfrCheckpointContext ctx) { }
-    void write_count(u4 nof_entries, jlong offset) { }
+    void write_count(u4 nof_entries) { }
   };
 
   class MockJfrSerializer {
-  public:
-    static MockJfrSerializer* current;
-
-    static bool register_serializer(JfrTypeId id, bool require_safepoint, bool permit_cache, MockJfrSerializer* serializer) {
-      current = serializer;
+   public:
+    static bool register_serializer(JfrTypeId id, bool permit_cache, MockJfrSerializer* serializer) {
       return true;
     }
+    virtual void on_rotation() {}
+    virtual void serialize(MockJfrCheckpointWriter& writer) {}
+  };
 
-    virtual void serialize(MockJfrCheckpointWriter& writer) = 0;
+  struct MockNetworkInterface {
+    std::string name;
+    uint64_t bytes_in;
+    uint64_t bytes_out;
+    traceid id;
+    MockNetworkInterface(std::string name, uint64_t bytes_in, uint64_t bytes_out, traceid id) :
+      name(name), bytes_in(bytes_in), bytes_out(bytes_out), id(id) {}
+
+    bool operator==(const MockNetworkInterface& rhs) const {
+      return name == rhs.name;
+    }
+  };
+
+  class NetworkInterface : public ::NetworkInterface {
+   public:
+    NetworkInterface(const char* name, uint64_t bytes_in, uint64_t bytes_out, NetworkInterface* next) :
+      ::NetworkInterface(name, bytes_in, bytes_out, next) {}
+    NetworkInterface* next(void) const {
+      return reinterpret_cast<NetworkInterface*>(::NetworkInterface::next());
+    }
   };
 
-  MockJfrSerializer* MockJfrSerializer::current;
+  class MockJfrOSInterface {
+    static std::list<MockNetworkInterface> _interfaces;
+   public:
+    MockJfrOSInterface() {}
+    static int network_utilization(NetworkInterface** network_interfaces) {
+      *network_interfaces = NULL;
+      for (std::list<MockNetworkInterface>::const_iterator i = _interfaces.begin();
+           i != _interfaces.end();
+           ++i) {
+        NetworkInterface* cur = new NetworkInterface(i->name.c_str(), i->bytes_in, i->bytes_out, *network_interfaces);
+        *network_interfaces = cur;
+      }
+      return OS_OK;
+    }
+    static MockNetworkInterface& add_interface(const std::string& name, traceid id) {
+      MockNetworkInterface iface(name, 0, 0, id);
+      _interfaces.push_front(iface);
+      return _interfaces.front();
+    }
+    static void remove_interface(const MockNetworkInterface& iface) {
+      _interfaces.remove(iface);
+    }
+    static void clear_interfaces() {
+      _interfaces.clear();
+    }
+    static const MockNetworkInterface& get_interface(traceid id) {
+      std::list<MockNetworkInterface>::const_iterator i = _interfaces.begin();
+      for (; i != _interfaces.end(); ++i) {
+        if (i->id == id) {
+          break;
+        }
+      }
+      return *i;
+    }
+  };
 
-  class MockEventNetworkUtilization : public ::EventNetworkUtilization
-  {
-  public:
+  std::list<MockNetworkInterface> MockJfrOSInterface::_interfaces;
+
+  class MockEventNetworkUtilization : public ::EventNetworkUtilization {
+   public:
     std::string iface;
     s8 readRate;
     s8 writeRate;
     static std::vector<MockEventNetworkUtilization> committed;
     MockJfrCheckpointWriter writer;
 
-  public:
+   public:
     MockEventNetworkUtilization(EventStartTime timing=TIMED) :
-    ::EventNetworkUtilization(timing) {
-    }
+    ::EventNetworkUtilization(timing) {}
 
     void set_networkInterface(traceid new_value) {
-      MockJfrSerializer::current->serialize(writer);
-      iface = writer.ids[new_value];
+      const MockNetworkInterface& entry  = MockJfrOSInterface::get_interface(new_value);
+      iface = entry.name;
     }
     void set_readRate(s8 new_value) {
       readRate = new_value;
@@ -129,7 +182,6 @@
     }
 
     void set_starttime(const MockJfrTicks& time) {}
-
     void set_endtime(const MockJfrTicks& time) {}
 
     static const MockEventNetworkUtilization& get_committed(const std::string& name) {
@@ -149,62 +201,6 @@
 
   jlong MockFastUnorderedElapsedCounterSource::current_ticks;
 
-  struct MockNetworkInterface {
-    std::string name;
-    uint64_t bytes_in;
-    uint64_t bytes_out;
-    MockNetworkInterface(std::string name, uint64_t bytes_in, uint64_t bytes_out)
-    : name(name),
-    bytes_in(bytes_in),
-    bytes_out(bytes_out) {
-
-    }
-    bool operator==(const MockNetworkInterface& rhs) const {
-      return name == rhs.name;
-    }
-  };
-
-  class NetworkInterface : public ::NetworkInterface {
-  public:
-    NetworkInterface(const char* name, uint64_t bytes_in, uint64_t bytes_out, NetworkInterface* next)
-    : ::NetworkInterface(name, bytes_in, bytes_out, next) {
-    }
-    NetworkInterface* next(void) const {
-      return reinterpret_cast<NetworkInterface*>(::NetworkInterface::next());
-    }
-  };
-
-  class MockJfrOSInterface {
-    static std::list<MockNetworkInterface> _interfaces;
-
-  public:
-    MockJfrOSInterface() {
-    }
-    static int network_utilization(NetworkInterface** network_interfaces) {
-      *network_interfaces = NULL;
-      for (std::list<MockNetworkInterface>::const_iterator i = _interfaces.begin();
-           i != _interfaces.end();
-           ++i) {
-        NetworkInterface* cur = new NetworkInterface(i->name.c_str(), i->bytes_in, i->bytes_out, *network_interfaces);
-        *network_interfaces = cur;
-      }
-      return OS_OK;
-    }
-    static MockNetworkInterface& add_interface(const std::string& name) {
-      MockNetworkInterface iface(name, 0, 0);
-      _interfaces.push_back(iface);
-      return _interfaces.back();
-    }
-    static void remove_interface(const MockNetworkInterface& iface) {
-      _interfaces.remove(iface);
-    }
-    static void clear_interfaces() {
-      _interfaces.clear();
-    }
-  };
-
-  std::list<MockNetworkInterface> MockJfrOSInterface::_interfaces;
-
 // Reincluding source files in the anonymous namespace unfortunately seems to
 // behave strangely with precompiled headers (only when using gcc though)
 #ifndef DONT_USE_PRECOMPILED_HEADER
@@ -248,7 +244,7 @@
 
 TEST_VM_F(JfrTestNetworkUtilization, RequestFunctionBasic) {
 
-  MockNetworkInterface& eth0 = MockJfrOSInterface::add_interface("eth0");
+  MockNetworkInterface& eth0 = MockJfrOSInterface::add_interface("eth0", 1);
   JfrNetworkUtilization::send_events();
   ASSERT_EQ(0u, MockEventNetworkUtilization::committed.size());
 
@@ -265,9 +261,9 @@
 
 TEST_VM_F(JfrTestNetworkUtilization, RequestFunctionMultiple) {
 
-  MockNetworkInterface& eth0 = MockJfrOSInterface::add_interface("eth0");
-  MockNetworkInterface& eth1 = MockJfrOSInterface::add_interface("eth1");
-  MockNetworkInterface& ppp0 = MockJfrOSInterface::add_interface("ppp0");
+  MockNetworkInterface& eth0 = MockJfrOSInterface::add_interface("eth0", 2);
+  MockNetworkInterface& eth1 = MockJfrOSInterface::add_interface("eth1", 3);
+  MockNetworkInterface& ppp0 = MockJfrOSInterface::add_interface("ppp0", 4);
   JfrNetworkUtilization::send_events();
   ASSERT_EQ(0u, MockEventNetworkUtilization::committed.size());
 
@@ -296,8 +292,8 @@
 }
 
 TEST_VM_F(JfrTestNetworkUtilization, InterfaceRemoved) {
-  MockNetworkInterface& eth0 = MockJfrOSInterface::add_interface("eth0");
-  MockNetworkInterface& eth1 = MockJfrOSInterface::add_interface("eth1");
+  MockNetworkInterface& eth0 = MockJfrOSInterface::add_interface("eth0", 5);
+  MockNetworkInterface& eth1 = MockJfrOSInterface::add_interface("eth1", 6);
   JfrNetworkUtilization::send_events();
   ASSERT_EQ(0u, MockEventNetworkUtilization::committed.size());
 
@@ -333,7 +329,7 @@
 }
 
 TEST_VM_F(JfrTestNetworkUtilization, InterfaceReset) {
-  MockNetworkInterface& eth0 = MockJfrOSInterface::add_interface("eth0");
+  MockNetworkInterface& eth0 = MockJfrOSInterface::add_interface("eth0", 7);
   JfrNetworkUtilization::send_events();
   ASSERT_EQ(0u, MockEventNetworkUtilization::committed.size());
 
--- a/test/hotspot/gtest/jfr/test_threadCpuLoad.cpp	Wed Oct 30 16:14:56 2019 +0100
+++ b/test/hotspot/gtest/jfr/test_threadCpuLoad.cpp	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 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
@@ -36,11 +36,10 @@
 #include "jfr/jfrEvents.hpp"
 #include "jfr/support/jfrThreadId.hpp"
 #include "jfr/support/jfrThreadLocal.hpp"
+#include "jfr/utilities/jfrThreadIterator.hpp"
 #include "jfr/utilities/jfrTime.hpp"
 #include "utilities/globalDefinitions.hpp"
 #include "runtime/os.hpp"
-#include "runtime/thread.inline.hpp"
-#include "runtime/threadSMR.inline.hpp"
 
 #include "unittest.hpp"
 
@@ -81,11 +80,18 @@
     MockJavaThread() : ::JavaThread() {}
   };
 
-  class MockJavaThreadIteratorWithHandle
+  class MockJfrJavaThreadIterator
   {
   public:
     MockJavaThread* next() { return NULL; }
-    int length() { return 0; }
+    bool has_next() const { return false; }
+  };
+
+  class MockJfrJavaThreadIteratorAdapter
+  {
+  public:
+    MockJavaThread* next() { return NULL; }
+    bool has_next() const { return false; }
   };
 
 // Reincluding source files in the anonymous namespace unfortunately seems to
@@ -97,7 +103,8 @@
 #define os MockOs
 #define EventThreadCPULoad MockEventThreadCPULoad
 #define JavaThread MockJavaThread
-#define JavaThreadIteratorWithHandle MockJavaThreadIteratorWithHandle
+#define JfrJavaThreadIterator MockJfrJavaThreadIterator
+#define JfrJavaThreadIteratorAdapter MockJfrJavaThreadIteratorAdapter
 
 #include "jfr/periodic/jfrThreadCPULoadEvent.hpp"
 #include "jfr/periodic/jfrThreadCPULoadEvent.cpp"
@@ -105,7 +112,8 @@
 #undef os
 #undef EventThreadCPULoad
 #undef JavaThread
-#undef JavaThreadIteratorWithHandle
+#define JfrJavaThreadIterator MockJfrJavaThreadIterator
+#define JfrJavaThreadIteratorAdapter MockJfrJavaThreadIteratorAdapter
 
 } // anonymous namespace
 
--- a/test/jdk/ProblemList.txt	Wed Oct 30 16:14:56 2019 +0100
+++ b/test/jdk/ProblemList.txt	Wed Oct 30 19:43:52 2019 +0100
@@ -884,7 +884,7 @@
 jdk/jfr/event/io/EvilInstrument.java                            8221331    generic-all
 jdk/jfr/event/runtime/TestNetworkUtilizationEvent.java          8228990,8229370    generic-all
 jdk/jfr/event/compiler/TestCodeSweeper.java                     8225209    generic-all
-jdk/jfr/event/oldobject/TestLargeRootSet.java                   8205651    generic-all
+jdk/jfr/api/consumer/recordingstream/TestSetStartTime.java      8233217    generic-all
 
 ############################################################################
 
--- a/test/jdk/jdk/jfr/api/consumer/TestReadTwice.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/test/jdk/jdk/jfr/api/consumer/TestReadTwice.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, 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
@@ -26,7 +26,6 @@
 package jdk.jfr.api.consumer;
 
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.util.LinkedList;
 import java.util.List;
 
--- a/test/jdk/jdk/jfr/api/consumer/TestRecordingFile.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/test/jdk/jdk/jfr/api/consumer/TestRecordingFile.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, 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
@@ -55,7 +55,7 @@
  * @key jfr
  * @requires vm.hasJFR
  * @library /test/lib
- * @run main/othervm jdk.jfr.api.consumer.TestRecordingFile
+ * @run main/othervm -Xlog:jfr*=info jdk.jfr.api.consumer.TestRecordingFile
  */
 public class TestRecordingFile {
 
@@ -210,12 +210,12 @@
            assertHasEventType(types, "Event2");
            assertMissingEventType(types, "Event3");
        }
-       try (RecordingFile f = new RecordingFile(twoEventTypes)) {
+       try (RecordingFile f = new RecordingFile(threeEventTypes)) {
            List<EventType> types = f.readEventTypes();
            assertUniqueEventTypes(types);
            assertHasEventType(types, "Event1");
            assertHasEventType(types, "Event2");
-           assertMissingEventType(types, "Event3");
+           assertHasEventType(types, "Event3");
        }
 
     }
--- a/test/jdk/jdk/jfr/api/consumer/TestRecordingInternals.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/test/jdk/jdk/jfr/api/consumer/TestRecordingInternals.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * 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
@@ -60,6 +60,7 @@
                 Asserts.assertEquals(id.toString(), rt.getThreadGroup().getName(), "Thread group name should match id");
                 Asserts.assertEquals(id, Integer.valueOf(i), "Chunks out of order");
                 i++;
+                System.out.println(i + " OK ");
             }
         }
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/filestream/TestMultipleChunk.java	Wed Oct 30 19:43:52 2019 +0100
@@ -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.filestream;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.concurrent.atomic.AtomicLong;
+
+import jdk.jfr.Event;
+import jdk.jfr.Recording;
+import jdk.jfr.consumer.EventStream;
+
+/**
+ * @test
+ * @summary Verifies that it is possible to stream contents from a multichunked file
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm jdk.jfr.api.consumer.filestream.TestMultipleChunk
+ */
+public class TestMultipleChunk {
+
+    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);
+            AtomicLong counter = new AtomicLong();
+            try (EventStream es = EventStream.openFile(path)) {
+                es.onEvent(e -> {
+                    counter.incrementAndGet();
+                });
+                es.start();
+                if (counter.get() != 6) {
+                    throw new Exception("Expected 6 event, but got " + counter.get());
+                }
+            }
+        }
+    }
+
+    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/filestream/TestOrdered.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,152 @@
+/*
+ * 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.filestream;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+
+import jdk.jfr.Event;
+import jdk.jfr.Recording;
+import jdk.jfr.consumer.EventStream;
+
+/**
+ * @test
+ * @summary Test EventStream::setOrdered(...)
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm jdk.jfr.api.consumer.filestream.TestOrdered
+ */
+public class TestOrdered {
+
+    static class OrderedEvent extends Event {
+    }
+
+    static class Emitter extends Thread {
+        private final CyclicBarrier barrier;
+
+        public Emitter(CyclicBarrier barrier) {
+            this.barrier = barrier;
+        }
+
+        @Override
+        public void run() {
+            OrderedEvent e1 = new OrderedEvent();
+            e1.commit();
+            try {
+                barrier.await();
+            } catch (Exception e) {
+                e.printStackTrace();
+                throw new Error("Unexpected exception in barrier");
+            }
+            OrderedEvent e2 = new OrderedEvent();
+            e2.commit();
+        }
+    }
+
+    private static final int THREAD_COUNT = 4;
+    private static final boolean[] BOOLEAN_STATES = { false, true };
+
+    public static void main(String... args) throws Exception {
+        Path p = makeUnorderedRecording();
+
+        testSetOrderedTrue(p);
+        testSetOrderedFalse(p);
+    }
+
+    private static void testSetOrderedTrue(Path p) throws Exception {
+        for (boolean reuse : BOOLEAN_STATES) {
+            AtomicReference<Instant> timestamp = new AtomicReference<>(Instant.MIN);
+            try (EventStream es = EventStream.openFile(p)) {
+                es.setReuse(reuse);
+                es.setOrdered(true);
+                es.onEvent(e -> {
+                    Instant endTime = e.getEndTime();
+                    if (endTime.isBefore(timestamp.get())) {
+                        throw new Error("Events are not ordered! Reuse = " + reuse);
+                    }
+                    timestamp.set(endTime);
+                });
+                es.start();
+            }
+        }
+    }
+
+    private static void testSetOrderedFalse(Path p) throws Exception {
+        for (boolean reuse : BOOLEAN_STATES) {
+            AtomicReference<Instant> timestamp = new AtomicReference<>(Instant.MIN);
+            AtomicBoolean unoreded = new AtomicBoolean(false);
+            try (EventStream es = EventStream.openFile(p)) {
+                es.setReuse(reuse);
+                es.setOrdered(false);
+                es.onEvent(e -> {
+                    Instant endTime = e.getEndTime();
+                    if (endTime.isBefore(timestamp.get())) {
+                        unoreded.set(true);
+                        es.close();
+                    }
+                    timestamp.set(endTime);
+                });
+                es.start();
+                if (!unoreded.get()) {
+                    throw new Exception("Expected at least some events to be out of order! Reuse = " + reuse);
+                }
+            }
+        }
+    }
+
+    private static Path makeUnorderedRecording() throws Exception {
+        CyclicBarrier barrier = new CyclicBarrier(THREAD_COUNT + 1);
+
+        try (Recording r = new Recording()) {
+            r.start();
+            List<Emitter> emitters = new ArrayList<>();
+            for (int i = 0; i < THREAD_COUNT; i++) {
+                Emitter e = new Emitter(barrier);
+                e.start();
+                emitters.add(e);
+            }
+            // Thread buffers should now have one event each
+            barrier.await();
+            // Add another event to each thread buffer, so
+            // events are bound to come out of order when they
+            // are flushed
+            for (Emitter e : emitters) {
+                e.join();
+            }
+            r.stop();
+            Path p = Files.createTempFile("recording", ".jfr");
+            r.dump(p);
+            return p;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/filestream/TestReuse.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,128 @@
+/*
+ * 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.filestream;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import jdk.jfr.Event;
+import jdk.jfr.Recording;
+import jdk.jfr.consumer.EventStream;
+import jdk.jfr.consumer.RecordedEvent;
+
+/**
+ * @test
+ * @summary Test EventStream::setReuse(...)
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm jdk.jfr.api.consumer.filestream.TestReuse
+ */
+public class TestReuse {
+
+    static class ReuseEvent extends Event {
+    }
+
+    private static final boolean[] BOOLEAN_STATES = { false, true };
+
+    public static void main(String... args) throws Exception {
+        Path p = makeRecording();
+
+        testSetReuseTrue(p);
+        testSetReuseFalse(p);
+    }
+
+    private static void testSetReuseFalse(Path p) throws Exception {
+        for (boolean ordered : BOOLEAN_STATES) {
+            AtomicBoolean fail = new AtomicBoolean(false);
+            Map<RecordedEvent, RecordedEvent> identity = new IdentityHashMap<>();
+            try (EventStream es = EventStream.openFile(p)) {
+                es.setOrdered(ordered);
+                es.setReuse(false);
+                es.onEvent(e -> {
+                    if (identity.containsKey(e)) {
+                        fail.set(true);
+                        es.close();
+                    }
+                    identity.put(e, e);
+                });
+                es.start();
+            }
+            if (fail.get()) {
+                throw new Exception("Unexpected reuse! Ordered = " + ordered);
+            }
+
+        }
+    }
+
+    private static void testSetReuseTrue(Path p) throws Exception {
+        for (boolean ordered : BOOLEAN_STATES) {
+            AtomicBoolean success = new AtomicBoolean(false);
+            Map<RecordedEvent, RecordedEvent> events = new IdentityHashMap<>();
+            try (EventStream es = EventStream.openFile(p)) {
+                es.setOrdered(ordered);
+                es.setReuse(true);
+                es.onEvent(e -> {
+                    if(events.containsKey(e)) {
+                        success.set(true);;
+                        es.close();
+                    }
+                    events.put(e,e);
+                });
+                es.start();
+            }
+            if (!success.get()) {
+                throw new Exception("No reuse! Ordered = " + ordered);
+            }
+        }
+
+    }
+
+    private static Path makeRecording() throws IOException {
+        try (Recording r = new Recording()) {
+            r.start();
+            for (int i = 0; i < 5; i++) {
+                ReuseEvent e = new ReuseEvent();
+                e.commit();
+            }
+            Recording rotation = new Recording();
+            rotation.start();
+            for (int i = 0; i < 5; i++) {
+                ReuseEvent e = new ReuseEvent();
+                e.commit();
+            }
+            r.stop();
+            rotation.close();
+            Path p = Files.createTempFile("recording", ".jfr");
+            r.dump(p);
+            return p;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/EventProducer.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,58 @@
+/*
+ * 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.api.consumer.recordingstream.TestStart.StartEvent;
+
+class EventProducer extends Thread {
+    private final Object lock = new Object();
+    private boolean killed = false;
+    public void run() {
+        while (true) {
+            StartEvent s = new StartEvent();
+            s.commit();
+            synchronized (lock) {
+                try {
+                    lock.wait(10);
+                    if (killed) {
+                        return; // end thread
+                    }
+                } catch (InterruptedException e) {
+                    // ignore
+                }
+            }
+        }
+    }
+    public void kill()  {
+        synchronized (lock) {
+            this.killed = true;
+            lock.notifyAll();
+        }
+        try {
+            join();
+        } catch (InterruptedException e) {
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestAwaitTermination.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,78 @@
+/*
+ * 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(() -> {
+                try {
+                    r.awaitTermination();
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+            });
+            r.close();
+            c.get();
+        }
+    }
+
+    private static void testAwaitTimeOut() throws InterruptedException, ExecutionException {
+        try (RecordingStream r = new RecordingStream()) {
+            r.startAsync();
+            var c = CompletableFuture.runAsync(() -> {
+                try {
+                    r.awaitTermination(Duration.ofMillis(10));
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+            });
+            c.get();
+            r.close();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestClose.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,128 @@
+/*
+ * 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 {
+        log("Entering testCloseMySelf()");
+        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();
+        log("Leaving testCloseMySelf()");
+    }
+
+    private static void testCloseStreaming() throws Exception {
+        log("Entering testCloseStreaming()");
+        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);
+        log("Leaving testCloseStreaming()");
+    }
+
+    private static void testCloseStarted() {
+        log("Entering testCloseStarted()");
+        RecordingStream r = new RecordingStream();
+        r.startAsync();
+        r.close();
+        log("Leaving testCloseStarted()");
+    }
+
+    private static void testCloseUnstarted() {
+        log("Entering testCloseUnstarted()");
+        RecordingStream r = new RecordingStream();
+        r.close();
+        log("Leaving testCloseUnstarted()");
+    }
+
+    private static void testCloseTwice() {
+        log("Entering testCloseTwice()");
+        RecordingStream r = new RecordingStream();
+        r.startAsync();
+        r.close();
+        r.close();
+        log("Leaving testCloseTwice()");
+    }
+
+    private static void log(String msg) {
+        System.out.println(msg);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestConstructor.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,69 @@
+/*
+ * 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	Wed Oct 30 19:43:52 2019 +0100
@@ -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.api.consumer.recordingstream;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Consumer;
+
+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.TestDisable
+ */
+public class TestDisable {
+
+    private static class DisabledEvent extends Event {
+    }
+
+    private static class EnabledEvent extends Event {
+    }
+
+    public static void main(String... args) throws Exception {
+        testDisableWithClass();
+        testDisableWithEventName();
+    }
+
+    private static void testDisableWithEventName() {
+        test(r -> r.disable(DisabledEvent.class.getName()));
+    }
+
+    private static void testDisableWithClass() {
+        test(r -> r.disable(DisabledEvent.class));
+    }
+
+    private static void test(Consumer<RecordingStream> disablement) {
+        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();
+            });
+            disablement.accept(r);
+            r.startAsync();
+            EnabledEvent e1 = new EnabledEvent();
+            e1.commit();
+            DisabledEvent d1 = new DisabledEvent();
+            d1.commit();
+            EnabledEvent e2 = new EnabledEvent();
+            e2.commit();
+            try {
+                twoEvent.await();
+            } catch (InterruptedException ie) {
+                throw new RuntimeException("Unexpexpected interruption of thread", ie);
+            }
+            if (fail.get()) {
+                throw new RuntimeException("Should not receive a disabled event");
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestEnable.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,79 @@
+/*
+ * 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.function.Consumer;
+
+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 {
+        testEnableWithClass();
+        testEnableWithEventName();
+    }
+
+    private static void testEnableWithEventName() {
+        test(r -> r.enable(EnabledEvent.class.getName()));
+    }
+
+    private static void testEnableWithClass() {
+        test(r -> r.enable(EnabledEvent.class));
+    }
+
+    private static void test(Consumer<RecordingStream> enablement) {
+        CountDownLatch event = new CountDownLatch(1);
+        try (RecordingStream r = new RecordingStream()) {
+            r.onEvent(e -> {
+                event.countDown();
+            });
+            enablement.accept(r);
+            r.startAsync();
+            EnabledEvent e = new EnabledEvent();
+            e.commit();
+            try {
+                event.await();
+            } catch (InterruptedException ie) {
+                throw new RuntimeException("Unexpected interruption of latch", ie);
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestMaxAge.java	Wed Oct 30 19:43:52 2019 +0100
@@ -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();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestOnClose.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,95 @@
+/*
+ * 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.TestOnClose
+ */
+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/TestOnErrorAsync.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,206 @@
+/*
+ * 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 jdk.jfr.api.consumer.recordingstream.TestUtils.TestError;
+import jdk.jfr.api.consumer.recordingstream.TestUtils.TestException;
+import jdk.jfr.api.consumer.security.TestStreamingRemote.TestEvent;
+import jdk.jfr.consumer.RecordingStream;
+
+/**
+ * @test
+ * @summary Tests RecordingStream::onError(...) when using
+ *          RecordingStream:startAsync
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib /test/jdk
+ * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestOnErrorAsync
+ */
+public class TestOnErrorAsync {
+    public static void main(String... args) throws Exception {
+        testDefaultError();
+        testCustomError();
+        testDefaultException();
+        testCustomException();
+        testOnFlushSanity();
+        testOnCloseSanity();
+    }
+
+    private static void testDefaultError() throws Exception {
+        AtomicBoolean closed = new AtomicBoolean();
+        CountDownLatch receivedError = new CountDownLatch(1);
+        try (RecordingStream r = new RecordingStream()) {
+            r.onEvent(e -> {
+                TestError error = new TestError();
+                TestUtils.installUncaughtException(receivedError, error);
+                throw error; // closes stream
+            });
+            r.onClose(() -> {
+                closed.set(true);
+            });
+            r.startAsync();
+            TestEvent e = new TestEvent();
+            e.commit();
+            r.awaitTermination();
+            receivedError.await();
+            if (!closed.get()) {
+                throw new Exception("Expected stream to be closed");
+            }
+        }
+    }
+
+    private static void testCustomError() throws Exception {
+        AtomicBoolean onError = new AtomicBoolean();
+        AtomicBoolean closed = new AtomicBoolean();
+        CountDownLatch receivedError = new CountDownLatch(1);
+        try (RecordingStream r = new RecordingStream()) {
+            r.onEvent(e -> {
+                TestError error = new TestError();
+                TestUtils.installUncaughtException(receivedError, error);
+                throw error; // closes stream
+            });
+            r.onError(e -> {
+                onError.set(true);
+            });
+            r.onClose(() -> {
+                closed.set(true);
+            });
+            r.startAsync();
+            TestEvent e = new TestEvent();
+            e.commit();
+            r.awaitTermination();
+            receivedError.await();
+            if (onError.get()) {
+                throw new Exception("onError handler should not be invoked on java.lang.Error.");
+            }
+            if (!closed.get()) {
+                throw new Exception("Expected stream to be closed");
+            }
+        }
+    }
+
+    private static void testDefaultException() throws Exception {
+        TestException exception = new TestException();
+        AtomicBoolean closed = new AtomicBoolean();
+        AtomicInteger counter = new AtomicInteger();
+        try (RecordingStream r = new RecordingStream()) {
+            r.onEvent(e -> {
+                if (counter.incrementAndGet() == 2) {
+                    r.close();
+                    return;
+                }
+                TestUtils.throwUnchecked(exception);
+            });
+            r.onClose(() -> {
+                closed.set(true);
+            });
+            r.startAsync();
+            TestEvent e1 = new TestEvent();
+            e1.commit();
+            TestEvent e2 = new TestEvent();
+            e2.commit();
+            r.awaitTermination();
+            if (!exception.isPrinted()) {
+                throw new Exception("Expected stack trace from Exception to be printed");
+            }
+            if (!closed.get()) {
+                throw new Exception("Expected stream to be closed");
+            }
+        }
+    }
+
+    private static void testCustomException() throws Exception {
+        TestException exception = new TestException();
+        AtomicBoolean closed = new AtomicBoolean();
+        AtomicBoolean received = new AtomicBoolean();
+        try (RecordingStream r = new RecordingStream()) {
+            r.onEvent(e -> {
+                TestUtils.throwUnchecked(exception);
+            });
+            r.onError(t -> {
+                received.set(t == exception);
+                r.close();
+            });
+            r.onClose(() -> {
+                closed.set(true);
+            });
+            r.startAsync();
+            TestEvent event = new TestEvent();
+            event.commit();
+            r.awaitTermination();
+            if (!received.get()) {
+                throw new Exception("Did not receive expected exception in onError(...)");
+            }
+            if (exception.isPrinted()) {
+                throw new Exception("Expected stack trace from Exception NOT to be printed");
+            }
+            if (!closed.get()) {
+                throw new Exception("Expected stream to be closed");
+            }
+        }
+    }
+
+    private static void testOnFlushSanity() throws Exception {
+        TestException exception = new TestException();
+        CountDownLatch received = new CountDownLatch(1);
+        try (RecordingStream r = new RecordingStream()) {
+            r.onFlush(() -> {
+                TestUtils.throwUnchecked(exception);
+            });
+            r.onError(t -> {
+                if (t == exception) {
+                    received.countDown();
+                }
+            });
+            r.startAsync();
+            received.await();
+       }
+    }
+
+    private static void testOnCloseSanity() throws Exception {
+        TestException exception = new TestException();
+        CountDownLatch received = new CountDownLatch(1);
+        try (RecordingStream r = new RecordingStream()) {
+            r.onFlush(() -> {
+                r.close(); // will trigger onClose
+            });
+            r.onClose(() -> {
+                TestUtils.throwUnchecked(exception); // will trigger onError
+            });
+            r.onError(t -> {
+                if (t == exception) {
+                    received.countDown();
+                }
+            });
+            r.startAsync();
+            received.await();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestOnErrorSync.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,241 @@
+/*
+ * 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.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import jdk.jfr.api.consumer.recordingstream.TestUtils.TestError;
+import jdk.jfr.api.consumer.recordingstream.TestUtils.TestException;
+import jdk.jfr.api.consumer.security.TestStreamingRemote.TestEvent;
+import jdk.jfr.consumer.RecordingStream;
+
+/**
+ * @test
+ * @summary Tests RecordingStream::onError(...) when using RecordingStream:start
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib /test/jdk
+ * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestOnErrorSync
+ */
+public class TestOnErrorSync {
+    public static void main(String... args) throws Exception {
+        testDefaultError();
+        testCustomError();
+        testDefaultException();
+        testCustomException();
+        testOnFlushSanity();
+        testOnCloseSanity();
+    }
+
+    private static void testDefaultError() throws Exception {
+        TestError error = new TestError();
+        AtomicBoolean closed = new AtomicBoolean();
+        Timer t = newEventEmitter();
+        try (RecordingStream r = new RecordingStream()) {
+            r.onEvent(e -> {
+                throw error; // closes stream
+            });
+            r.onClose(() -> {
+                closed.set(true);
+            });
+            try {
+                r.start();
+                throw new Exception("Expected TestError to be thrown");
+            } catch (TestError te) {
+                // as expected
+            }
+            if (!closed.get()) {
+                throw new Exception("Expected stream to be closed");
+            }
+        } finally {
+            t.cancel();
+        }
+    }
+
+    private static void testCustomError() throws Exception {
+        TestError error = new TestError();
+        AtomicBoolean onError = new AtomicBoolean();
+        AtomicBoolean closed = new AtomicBoolean();
+        Timer t = newEventEmitter();
+        try (RecordingStream r = new RecordingStream()) {
+            r.onEvent(e -> {
+                throw error; // closes stream
+            });
+            r.onError(e -> {
+                onError.set(true);
+            });
+            r.onClose(() -> {
+                closed.set(true);
+            });
+            try {
+                r.start();
+                throw new Exception("Expected TestError to be thrown");
+            } catch (TestError terror) {
+                // as expected
+            }
+            if (onError.get()) {
+                throw new Exception("Expected onError(...) NOT to be invoked");
+            }
+            if (!closed.get()) {
+                throw new Exception("Expected stream to be closed");
+            }
+        } finally {
+            t.cancel();
+        }
+    }
+
+    private static void testDefaultException() throws Exception {
+        TestException exception = new TestException();
+        AtomicInteger counter = new AtomicInteger();
+        AtomicBoolean closed = new AtomicBoolean();
+        Timer t = newEventEmitter();
+        try (RecordingStream r = new RecordingStream()) {
+            r.onEvent(e -> {
+                if (counter.incrementAndGet() == 2) {
+                    // Only close if we get a second event after an exception
+                    r.close();
+                    return;
+                }
+                TestUtils.throwUnchecked(exception);
+            });
+            r.onClose(() -> {
+                closed.set(true);
+            });
+            try {
+                r.start();
+            } catch (Exception e) {
+                throw new Exception("Unexpected exception thrown from start()", e);
+            }
+            if (!exception.isPrinted()) {
+                throw new Exception("Expected stack trace from Exception to be printed");
+            }
+            if (!closed.get()) {
+                throw new Exception("Expected stream to be closed");
+            }
+        } finally {
+            t.cancel();
+        }
+    }
+
+    private static void testCustomException() throws Exception {
+        TestException exception = new TestException();
+        AtomicInteger counter = new AtomicInteger();
+        AtomicBoolean onError = new AtomicBoolean();
+        AtomicBoolean closed = new AtomicBoolean();
+        AtomicBoolean received = new AtomicBoolean();
+        Timer t = newEventEmitter();
+        try (RecordingStream r = new RecordingStream()) {
+            r.onEvent(e -> {
+                if (counter.incrementAndGet() == 2) {
+                    // Only close if we get a second event after an exception
+                    r.close();
+                    return;
+                }
+                TestUtils.throwUnchecked(exception);
+            });
+            r.onError(e -> {
+                received.set(e == exception);
+                onError.set(true);
+            });
+            r.onClose(() -> {
+                closed.set(true);
+            });
+            try {
+                r.start();
+            } catch (Exception e) {
+                throw new Exception("Unexpected exception thrown from start()", e);
+            }
+            if (!received.get()) {
+                throw new Exception("Did not receive expected exception in onError(...)");
+            }
+            if (exception.isPrinted()) {
+                throw new Exception("Expected stack trace from Exception NOT to be printed");
+            }
+            if (!onError.get()) {
+                throw new Exception("Expected OnError(...) to be invoked");
+            }
+            if (!closed.get()) {
+                throw new Exception("Expected stream to be closed");
+            }
+        } finally {
+            t.cancel();
+        }
+    }
+
+    private static void testOnFlushSanity() throws Exception {
+        TestException exception = new TestException();
+        AtomicBoolean received = new AtomicBoolean();
+        try (RecordingStream r = new RecordingStream()) {
+            r.onFlush(() -> {
+                TestUtils.throwUnchecked(exception);
+            });
+            r.onError(t -> {
+                received.set(t == exception);
+                r.close();
+            });
+            r.start();
+            if (!received.get()) {
+                throw new Exception("Expected exception in OnFlush to propagate to onError");
+            }
+        }
+    }
+
+    private static void testOnCloseSanity() throws Exception {
+        TestException exception = new TestException();
+        AtomicBoolean received = new AtomicBoolean();
+        try (RecordingStream r = new RecordingStream()) {
+            r.onFlush(() -> {
+                r.close(); // will trigger onClose
+            });
+            r.onClose(() -> {
+                TestUtils.throwUnchecked(exception); // will trigger onError
+            });
+            r.onError(t -> {
+                received.set(t == exception);
+            });
+            r.start();
+            if (!received.get()) {
+                throw new Exception("Expected exception in OnFlush to propagate to onError");
+            }
+        }
+    }
+
+    private static Timer newEventEmitter() {
+        Timer timer = new Timer();
+        TimerTask task = new TimerTask() {
+            @Override
+            public void run() {
+                TestEvent event = new TestEvent();
+                event.commit();
+            }
+        };
+        timer.schedule(task, 0, 100);
+        return timer;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestOnEvent.java	Wed Oct 30 19:43:52 2019 +0100
@@ -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.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() {
+        log("Entering 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
+           }
+        }
+        log("Leaving testOnEventNull()");
+     }
+
+    private static void testTwoEventWithSameName() throws Exception {
+        log("Entering testTwoEventWithSameName()");
+        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();
+        }
+        log("Leaving testTwoEventWithSameName()");
+    }
+
+    private static void testNamedEvent() throws Exception {
+        log("Entering testNamedEvent()");
+        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();
+        }
+        log("Leaving testNamedEvent()");
+    }
+
+    private static void testOnEvent() throws Exception {
+        log("Entering testOnEvent()");
+        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();
+        }
+        log("Leaving testOnEvent()");
+    }
+
+    private static void log(String msg) {
+        System.out.println(msg);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestOnFlush.java	Wed Oct 30 19:43:52 2019 +0100
@@ -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.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() {
+        log("Entering testOnFlushNull()");
+        try (RecordingStream rs = new RecordingStream()) {
+           try {
+               rs.onFlush(null);
+               throw new AssertionError("Expected NullPointerException from onFlush(null");
+           } catch (NullPointerException npe) {
+               // OK; as expected
+           }
+        }
+        log("Leaving testOnFlushNull()");
+     }
+
+    private static void testNoEvent() throws Exception {
+        log("Entering testNoEvent()");
+        CountDownLatch flush = new CountDownLatch(1);
+        try (RecordingStream r = new RecordingStream()) {
+            r.onFlush(() -> {
+                flush.countDown();
+            });
+            r.startAsync();
+            flush.await();
+        }
+        log("Leaving testNoEvent()");
+    }
+
+    private static void testOneEvent() throws InterruptedException {
+        log("Entering testOneEvent()");
+        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();
+        }
+        log("Leaving testOneEvent()");
+    }
+
+    private static void log(String msg) {
+        System.out.println(msg);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestRecursive.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,197 @@
+/*
+ * 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.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import jdk.jfr.Event;
+import jdk.jfr.Recording;
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.jfr.consumer.RecordingStream;
+import jdk.test.lib.jfr.Events;
+
+/**
+ * @test
+ * @summary Tests that events are not emitted in handlers
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib /test/jdk
+ * @build jdk.jfr.api.consumer.recordingstream.EventProducer
+ * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestRecursive
+ */
+public class TestRecursive {
+
+    public static class NotRecorded extends Event {
+    }
+
+    public static class Recorded extends Event {
+    }
+
+    public static class Provoker extends Event {
+    }
+
+    public static void main(String... args) throws Exception {
+        testSync();
+        testAsync();
+        testStreamInStream();
+    }
+
+    private static void testStreamInStream() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        try (Recording r = new Recording()) {
+            r.start();
+            Recorded r1 = new Recorded(); // 1
+            r1.commit();
+            try (RecordingStream rs = new RecordingStream()) {
+                rs.onEvent(e1 -> {
+                    streamInStream();
+                    latch.countDown();
+                });
+                rs.startAsync();
+                Recorded r2 = new Recorded(); // 2
+                r2.commit();
+                latch.await();
+            }
+            Recorded r3 = new Recorded(); // 2
+            r3.commit();
+            r.stop();
+            List<RecordedEvent> events = Events.fromRecording(r);
+            if (count(events, NotRecorded.class) != 0) {
+                throw new Exception("Expected 0 NotRecorded events");
+            }
+            if (count(events, Recorded.class) != 3) {
+                throw new Exception("Expected 3 Recorded events");
+            }
+        }
+    }
+
+    // No events should be recorded in this method
+    private static void streamInStream() {
+        NotRecorded nr1 = new NotRecorded();
+        nr1.commit();
+        CountDownLatch latch = new CountDownLatch(1);
+        try (RecordingStream rs2 = new RecordingStream()) {
+            rs2.onEvent(e2 -> {
+                NotRecorded nr2 = new NotRecorded();
+                nr2.commit();
+                latch.countDown();
+            });
+            NotRecorded nr3 = new NotRecorded();
+            nr3.commit();
+            rs2.startAsync();
+            // run event in separate thread
+            CompletableFuture.runAsync(() -> {
+                Provoker p = new Provoker();
+                p.commit();
+            });
+            try {
+                latch.await();
+            } catch (InterruptedException e) {
+                throw new Error("Unexpected interruption", e);
+            }
+        }
+        NotRecorded nr2 = new NotRecorded();
+        nr2.commit();
+    }
+
+    private static void testSync() throws Exception {
+        try (Recording r = new Recording()) {
+            r.start();
+            EventProducer p = new EventProducer();
+            try (RecordingStream rs = new RecordingStream()) {
+                Recorded e1 = new Recorded();
+                e1.commit();
+                rs.onEvent(e -> {
+                    System.out.println("Emitting NotRecorded event");
+                    NotRecorded event = new NotRecorded();
+                    event.commit();
+                    System.out.println("Stopping event provoker");
+                    p.kill();
+                    System.out.println("Closing recording stream");
+                    rs.close();
+                    return;
+                });
+                p.start();
+                rs.start();
+                Recorded e2 = new Recorded();
+                e2.commit();
+            }
+            r.stop();
+            List<RecordedEvent> events = Events.fromRecording(r);
+            System.out.println(events);
+            if (count(events, NotRecorded.class) != 0) {
+                throw new Exception("Expected 0 NotRecorded events");
+            }
+            if (count(events, Recorded.class) != 2) {
+                throw new Exception("Expected 2 Recorded events");
+            }
+        }
+    }
+
+    private static int count(List<RecordedEvent> events, Class<?> eventClass) {
+        int count = 0;
+        for (RecordedEvent e : events) {
+            if (e.getEventType().getName().equals(eventClass.getName())) {
+                count++;
+            }
+        }
+        System.out.println(count);
+        return count;
+    }
+
+    private static void testAsync() throws InterruptedException, Exception {
+        CountDownLatch latchOne = new CountDownLatch(1);
+        CountDownLatch latchTwo = new CountDownLatch(2);
+        AtomicBoolean fail = new AtomicBoolean();
+        try (RecordingStream r = new RecordingStream()) {
+            r.onEvent(e -> {
+                System.out.println(e);
+                NotRecorded event = new NotRecorded();
+                event.commit();
+                if (e.getEventType().getName().equals(Recorded.class.getName())) {
+                    latchOne.countDown();
+                    latchTwo.countDown();
+                }
+                if (e.getEventType().getName().equals(NotRecorded.class.getName())) {
+                    fail.set(true);
+                }
+            });
+            r.startAsync();
+            Recorded e1 = new Recorded();
+            e1.commit();
+            latchOne.await();
+            Recorded e2 = new Recorded();
+            e2.commit();
+            latchTwo.await();
+            if (fail.get()) {
+                throw new Exception("Unexpected event found");
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestRemove.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,149 @@
+/*
+ * 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() {
+        log("Entering testRemoveNull()");
+        try (RecordingStream rs = new RecordingStream()) {
+           try {
+               rs.remove(null);
+               throw new AssertionError("Expected NullPointerException from remove(null");
+           } catch (NullPointerException npe) {
+               // OK; as expected
+           }
+        }
+        log("Leaving testRemoveNull()");
+     }
+
+    private static void testRemoveOnEvent() throws Exception {
+        log("Entering testRemoveOnEvent()");
+        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!");
+            }
+        }
+        log("Leaving testRemoveOnEvent()");
+    }
+
+    private static void testRemoveOnClose() {
+        log("Entering 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!");
+            }
+        }
+        log("Leaving testRemoveOnClose()");
+    }
+
+    private static void testRemoveOnFlush() throws Exception {
+        log("Entering testRemoveOnFlush()");
+        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!");
+            }
+        }
+        log("Leaving testRemoveOnFlush()");
+    }
+
+    private static void log(String msg) {
+        System.out.println(msg);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestSetEndTime.java	Wed Oct 30 19:43:52 2019 +0100
@@ -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.api.consumer.recordingstream;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import jdk.jfr.Event;
+import jdk.jfr.Name;
+import jdk.jfr.Recording;
+import jdk.jfr.StackTrace;
+import jdk.jfr.consumer.EventStream;
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.jfr.consumer.RecordingFile;
+import jdk.jfr.consumer.RecordingStream;
+
+/**
+ * @test
+ * @summary Tests EventStream::setEndTime
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestSetEndTime
+ */
+public final class TestSetEndTime {
+
+    @Name("Mark")
+    @StackTrace(false)
+    public final static class Mark extends Event {
+        public boolean include;
+        public int id;
+    }
+
+    public static void main(String... args) throws Exception {
+        testEventStream();
+        testRecordingStream();
+    }
+
+    private static void testRecordingStream() throws Exception {
+        while (true) {
+            CountDownLatch closed = new CountDownLatch(1);
+            AtomicInteger count = new AtomicInteger();
+            try (RecordingStream rs = new RecordingStream()) {
+                rs.setFlushInterval(Duration.ofSeconds(1));
+                rs.onEvent(e -> {
+                    count.incrementAndGet();
+                });
+                // when end is reached stream is closed
+                rs.onClose(() -> {
+                    closed.countDown();
+                });
+                Instant endTime = Instant.now().plus(Duration.ofMillis(100));
+                System.out.println("Setting end time: " + endTime);
+                rs.setEndTime(endTime);
+                rs.startAsync();
+                for (int i = 0; i < 50; i++) {
+                    Mark m = new Mark();
+                    m.commit();
+                    Thread.sleep(10);
+                }
+                closed.await();
+                System.out.println("Found events: " + count.get());
+                if (count.get() < 50) {
+                    return;
+                }
+                System.out.println("Found 50 events. Retrying");
+                System.out.println();
+            }
+        }
+    }
+
+    static void testEventStream() throws InterruptedException, IOException, Exception {
+        while (true) {
+            try (Recording r = new Recording()) {
+                r.start();
+
+                Mark event1 = new Mark();
+                event1.id = 1;
+                event1.include = false;
+                event1.commit(); // start time
+
+                nap();
+
+                Mark event2 = new Mark();
+                event2.id = 2;
+                event2.include = true;
+                event2.commit();
+
+                nap();
+
+                Mark event3 = new Mark();
+                event3.id = 3;
+                event3.include = false;
+                event3.commit(); // end time
+
+                Path p = Paths.get("recording.jfr");
+                r.dump(p);
+                Instant start = null;
+                Instant end = null;
+                System.out.println("Find start and end time as instants:");
+                for (RecordedEvent e : RecordingFile.readAllEvents(p)) {
+                    if (e.getInt("id") == 1) {
+                        start = e.getEndTime();
+                        System.out.println("Start  : " + start);
+                    }
+                    if (e.getInt("id") == 2) {
+                        Instant middle = e.getEndTime();
+                        System.out.println("Middle : " + middle);
+                    }
+                    if (e.getInt("id") == 3) {
+                        end = e.getEndTime();
+                        System.out.println("End    : " + end);
+                    }
+                }
+                System.out.println();
+                System.out.println("Opening stream between " + start + " and " + end);
+                AtomicBoolean success = new AtomicBoolean(false);
+                AtomicInteger eventsCount = new AtomicInteger();
+                try (EventStream d = EventStream.openRepository()) {
+                    d.setStartTime(start.plusNanos(1));
+                    // Stream should close when end is reached
+                    d.setEndTime(end.minusNanos(1));
+                    d.onEvent(e -> {
+                        eventsCount.incrementAndGet();
+                        boolean include = e.getBoolean("include");
+                        System.out.println("Event " + e.getEndTime() + " include=" + include);
+                        if (include) {
+                            success.set(true);
+                        }
+                    });
+                    d.start();
+                    if (eventsCount.get() == 1 && success.get()) {
+                        return;
+                    }
+                }
+            }
+        }
+
+    }
+
+    private static void nap() throws InterruptedException {
+        // Ensure we advance at least 1 ns with fast time
+        Thread.sleep(1);
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestSetFlushInterval.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,62 @@
+/*
+ * 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::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.setFlushInterval(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();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestSetMaxAge.java	Wed Oct 30 19:43:52 2019 +0100
@@ -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::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();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestSetMaxSize.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,58 @@
+/*
+ * 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();
+       }
+   }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestSetSettings.java	Wed Oct 30 19:43:52 2019 +0100
@@ -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.HashMap;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+
+import jdk.jfr.Enabled;
+import jdk.jfr.Event;
+import jdk.jfr.Name;
+import jdk.jfr.consumer.RecordingStream;
+
+/**
+ * @test
+ * @summary Tests RecordingStream::setSettings
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm -Xlog:jfr+system+parser jdk.jfr.api.consumer.recordingstream.TestSetSettings
+ */
+public final class TestSetSettings {
+
+    @Name("LateBloomer")
+    @Enabled(false)
+    private final static class LateBloomer extends Event {
+    }
+
+    private static CountDownLatch lateBloomer = new CountDownLatch(1);
+
+    public static void main(String... args) throws Exception {
+        try (RecordingStream r = new RecordingStream()) {
+            r.startAsync();
+            Map<String, String> settings = new HashMap<String, String>();
+            settings.put("LateBloomer#enabled", "true");
+            r.setSettings(settings);
+            r.onEvent("LateBloomer", e -> {
+                lateBloomer.countDown();
+            });
+            LateBloomer event = new LateBloomer();
+            event.commit();
+            lateBloomer.await();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestSetStartTime.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,138 @@
+/*
+ * 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.io.IOException;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import jdk.jfr.Event;
+import jdk.jfr.Name;
+import jdk.jfr.Recording;
+import jdk.jfr.StackTrace;
+import jdk.jfr.consumer.EventStream;
+import jdk.jfr.consumer.RecordingStream;
+
+/**
+ * @test
+ * @summary Tests EventStream::setStartTime
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestSetStartTime
+ */
+public final class TestSetStartTime {
+
+    private final static int SLEEP_TIME_MS = 100;
+
+    @Name("Mark")
+    @StackTrace(false)
+    public final static class Mark extends Event {
+        public boolean before;
+    }
+
+    public static void main(String... args) throws Exception {
+        testEventStream();
+        testRecordingStream();
+    }
+
+    private static void testRecordingStream() throws InterruptedException {
+        AtomicBoolean exit = new AtomicBoolean();
+        int attempt = 1;
+        while (!exit.get()) {
+            System.out.println("Testing RecordingStream:setStartTime(...). Attempt: " + attempt++);
+            AtomicBoolean firstEvent = new AtomicBoolean(true);
+            try (RecordingStream r2 = new RecordingStream()) {
+                Instant t = Instant.now().plus(Duration.ofMillis(SLEEP_TIME_MS / 2));
+                System.out.println("Setting start time: " + t);
+                r2.setStartTime(t);
+                r2.onEvent(e -> {
+                    if (firstEvent.get()) {
+                        firstEvent.set(false);
+                        if (!e.getBoolean("before")) {
+                            // Skipped first event, let's exit
+                            exit.set(true);
+                        }
+                        r2.close();
+                    }
+                });
+                r2.startAsync();
+                Mark m1 = new Mark();
+                m1.before = true;
+                m1.commit();
+                System.out.println("First event emitted: " + Instant.now());
+                Thread.sleep(SLEEP_TIME_MS);
+                Mark m2 = new Mark();
+                m2.before = false;
+                m2.commit();
+                System.out.println("Second event emitted: " + Instant.now());
+                r2.awaitTermination();
+            }
+            System.out.println();
+        }
+    }
+
+    private static void testEventStream() throws InterruptedException, Exception, IOException {
+        AtomicBoolean exit = new AtomicBoolean();
+        int attempt = 1;
+        while (!exit.get()) {
+            System.out.println("Testing EventStream:setStartTime(...). Attempt: " + attempt++);
+            AtomicBoolean firstEvent = new AtomicBoolean(true);
+            try (Recording r = new Recording()) {
+                r.start();
+                Mark event1 = new Mark();
+                event1.before = true;
+                event1.commit();
+                Instant t = Instant.now();
+                System.out.println("First event emitted: " + t);
+                Thread.sleep(SLEEP_TIME_MS);
+                Mark event2 = new Mark();
+                event2.before = false;
+                event2.commit();
+                System.out.println("Second event emitted: " + Instant.now());
+                try (EventStream es = EventStream.openRepository()) {
+                    Instant startTime = t.plus(Duration.ofMillis(SLEEP_TIME_MS / 2));
+                    es.setStartTime(startTime);
+                    System.out.println("Setting start time: " + startTime);
+                    es.onEvent(e -> {
+                        if (firstEvent.get()) {
+                            firstEvent.set(false);
+                            if (!e.getBoolean("before")) {
+                                // Skipped first event, let's exit
+                                exit.set(true);
+                            }
+                            es.close();
+                        }
+                    });
+                    es.startAsync();
+                    es.awaitTermination();
+                }
+            }
+            System.out.println();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestStart.java	Wed Oct 30 19:43:52 2019 +0100
@@ -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.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::start()
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib /test/jdk
+ * @build jdk.jfr.api.consumer.recordingstream.EventProducer
+ * @run main/othervm jdk.jfr.api.consumer.recordingstream.TestStart
+ */
+public class TestStart {
+    static class StartEvent extends Event {
+    }
+    public static void main(String... args) throws Exception {
+        testStart();
+        testStartOnEvent();
+        testStartTwice();
+        testStartClosed();
+    }
+
+    private static void testStartTwice() throws Exception {
+        log("Entering testStartTwice()");
+        CountDownLatch started = new CountDownLatch(1);
+        try (RecordingStream rs = new RecordingStream()) {
+            EventProducer t = new EventProducer();
+            t.start();
+            Thread thread = new Thread() {
+                public void run() {
+                    rs.start();
+                }
+            };
+            thread.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
+            }
+        }
+        log("Leaving testStartTwice()");
+    }
+
+    static void testStart() throws Exception {
+        log("Entering testStart()");
+        CountDownLatch started = new CountDownLatch(1);
+        try (RecordingStream rs = new RecordingStream()) {
+            rs.onEvent(e -> {
+                started.countDown();
+            });
+            EventProducer t = new EventProducer();
+            t.start();
+            Thread thread = new Thread() {
+                public void run() {
+                    rs.start();
+                }
+            };
+            thread.start();
+            started.await();
+            t.kill();
+        }
+        log("Leaving testStart()");
+    }
+
+    static void testStartOnEvent() throws Exception {
+        log("Entering testStartOnEvent()");
+        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())  {
+                        ISE.set(true);
+                        startedTwice.countDown();
+                    }
+                }
+            });
+            EventProducer t = new EventProducer();
+            t.start();
+            Thread thread = new Thread() {
+                public void run() {
+                    rs.start();
+                }
+            };
+            thread.start();
+            startedTwice.await();
+            t.kill();
+            if (!ISE.get()) {
+                throw new AssertionError("Expected IllegalStateException");
+            }
+        }
+        log("Leaving testStartOnEvent()");
+    }
+
+    static void testStartClosed() {
+        log("Entering testStartClosed()");
+        RecordingStream rs = new RecordingStream();
+        rs.close();
+        try {
+            rs.start();
+            throw new AssertionError("Expected IllegalStateException");
+        } catch (IllegalStateException ise) {
+            // OK, as expected.
+        }
+        log("Leaving testStartClosed()");
+    }
+
+    private static void log(String msg) {
+        System.out.println(msg);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/recordingstream/TestStartAsync.java	Wed Oct 30 19:43:52 2019 +0100
@@ -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/recordingstream/TestUtils.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,65 @@
+/*
+ * 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;
+
+public class TestUtils {
+
+    public static final class TestError extends Error {
+        private static final long serialVersionUID = 1L;
+    }
+
+    public static final class TestException extends Exception {
+        private static final long serialVersionUID = 1L;
+        private volatile boolean printed;
+
+        @Override
+        public void printStackTrace() {
+            super.printStackTrace();
+            printed = true;
+        }
+
+        public boolean isPrinted() {
+            return printed;
+        }
+    }
+
+    // Can throw checked exception as unchecked.
+    @SuppressWarnings("unchecked")
+    public static <T extends Throwable> void throwUnchecked(Throwable e) throws T {
+        throw (T) e;
+    }
+
+    public static void installUncaughtException(CountDownLatch receivedError, Throwable expected) {
+        Thread.currentThread().setUncaughtExceptionHandler((thread, throwable) -> {
+            if (throwable == expected) {
+                System.out.println("Received uncaught exception " + expected.getClass());
+                receivedError.countDown();
+            }
+        });
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/security/DriverRecordingDumper.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,49 @@
+/*
+ * 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.security;
+
+import java.nio.file.Paths;
+
+import jdk.jfr.Recording;
+import jdk.test.lib.jfr.EventNames;
+
+/**
+ * Driver that dumps a recording with a JVMInformation event
+ *
+ * Usage:
+ *
+ * @run driver jdk.jfr.api.consumer.security.RecordingDumper <filename>
+ */
+public class DriverRecordingDumper {
+    public static void main(String... args) throws Exception {
+        try (Recording r = new Recording()) {
+            r.enable(EventNames.JVMInformation);
+            r.start();
+            r.stop();
+            r.dump(Paths.get(args[0]));
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/security/TestMissingPermission.java	Wed Oct 30 19:43:52 2019 +0100
@@ -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.security;
+
+import java.io.IOException;
+
+import jdk.jfr.consumer.EventStream;
+import jdk.jfr.consumer.RecordingStream;
+
+/**
+ * @test
+ * @summary Tests that streaming doesn't work if
+ *          FlightRecordingPermission("accessFlightRecorder") is missing
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ *
+ * @run main/othervm/secure=java.lang.SecurityManager/java.security.policy=no-permission.policy
+ *      jdk.jfr.api.consumer.security.TestMissingPermission
+ */
+public class TestMissingPermission {
+
+    public static void main(String... args) throws Exception {
+        testOpenRepository();
+        testRecordingStream();
+    }
+
+    private static void testRecordingStream() throws IOException {
+        try {
+            try (EventStream es = EventStream.openRepository()) {
+                throw new AssertionError("Should not be able to create EventStream without FlightRecorderPermission");
+            }
+        } catch (SecurityException se) {
+            // OK, as expected
+        }
+    }
+
+    private static void testOpenRepository() throws IOException {
+        try {
+            try (RecordingStream es = new RecordingStream()) {
+                throw new AssertionError("Should not be able to create RecordingStream without FlightRecorderPermission");
+            }
+        } catch (SecurityException se) {
+            // OK, as expected
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/security/TestRecordingFile.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,55 @@
+/*
+ * 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.security;
+
+import java.nio.file.Paths;
+
+import jdk.jfr.consumer.RecordingFile;
+
+/**
+ * @test
+ * @summary Test that a recording file can't be opened without permissions
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ *
+ * @run driver jdk.jfr.api.consumer.security.DriverRecordingDumper
+ *      test-recording-file.jfr
+ * @run main/othervm/secure=java.lang.SecurityManager/java.security.policy=no-permission.policy
+ *      jdk.jfr.api.consumer.security.TestRecordingFile
+ *      test-recording-file.jfr
+ */
+public class TestRecordingFile {
+    public static void main(String... args) throws Exception {
+        try {
+            RecordingFile.readAllEvents(Paths.get(args[0]));
+            throw new AssertionError("Expected SecurityException");
+        } catch (SecurityException se) {
+            // OK, as expected
+            return;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/security/TestRecordingStream.java	Wed Oct 30 19:43:52 2019 +0100
@@ -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.security;
+
+import java.time.Instant;
+import java.util.concurrent.CountDownLatch;
+
+import jdk.jfr.consumer.RecordingStream;
+import jdk.test.lib.jfr.EventNames;
+
+/**
+ * @test
+ * @summary Tests that a RecordingStream works using only
+ *          FlightRecordingPermission("accessFlightRecorder")
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ *
+ * @run main/othervm/secure=java.lang.SecurityManager/java.security.policy=local-streaming.policy
+ *      jdk.jfr.api.consumer.security.TestStreamingLocal
+ */
+public class TestRecordingStream {
+
+    public static void main(String... args) throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        try (RecordingStream r = new RecordingStream()) {
+            // Enable JVM event, no write permission needed
+            r.enable(EventNames.JVMInformation);
+            r.setStartTime(Instant.EPOCH);
+            r.onEvent(EventNames.JVMInformation, e -> {
+                latch.countDown();
+            });
+            r.startAsync();
+            latch.await();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/security/TestStreamingFile.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,55 @@
+/*
+ * 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.security;
+
+import java.nio.file.Paths;
+
+import jdk.jfr.consumer.EventStream;
+
+/**
+ * @test
+ * @summary Test that an event file stream can't be opened without permissions
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ *
+ * @run driver jdk.jfr.api.consumer.security.DriverRecordingDumper
+ *      test-streaming-file.jfr
+ * @run main/othervm/secure=java.lang.SecurityManager/java.security.policy=no-permission.policy
+ *      jdk.jfr.api.consumer.security.TestStreamingFile
+ *      test-streaming-file.jfr
+ */
+public class TestStreamingFile {
+
+    public static void main(String... args) throws Exception {
+        try (EventStream es = EventStream.openFile(Paths.get(args[0]))) {
+            throw new AssertionError("Expected SecurityException");
+        } catch (SecurityException se) {
+            // OK, as expected
+            return;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/security/TestStreamingLocal.java	Wed Oct 30 19:43:52 2019 +0100
@@ -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.security;
+
+import java.time.Instant;
+import java.util.concurrent.CountDownLatch;
+
+import jdk.jfr.Recording;
+import jdk.jfr.consumer.EventStream;
+import jdk.test.lib.jfr.EventNames;
+
+/**
+ * @test
+ * @summary Tests that local streaming works using only
+ *          FlightRecordingPermission("accessFlightRecorder")
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ *
+ * @run main/othervm/secure=java.lang.SecurityManager/java.security.policy=local-streaming.policy
+ *      jdk.jfr.api.consumer.security.TestStreamingLocal
+ */
+public class TestStreamingLocal {
+
+    public static void main(String... args) throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        try (Recording r = new Recording()) {
+            // Enable JVM event, no write permission needed
+            r.enable(EventNames.JVMInformation);
+            r.start();
+            try (Recording r2 = new Recording()){
+                r2.start();
+                r2.stop();
+            }
+            r.stop();
+            try (EventStream es = EventStream.openRepository()) {
+                es.setStartTime(Instant.EPOCH);
+                es.onEvent("jdk.JVMInformation", e -> {
+                    latch.countDown();
+                });
+                es.startAsync();
+                latch.await();
+            }
+        }
+
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/security/TestStreamingRemote.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,117 @@
+/*
+ * 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.security;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.Duration;
+import java.time.Instant;
+
+import jdk.jfr.Event;
+import jdk.jfr.Recording;
+import jdk.jfr.consumer.EventStream;
+import jdk.test.lib.process.OutputAnalyzer;
+import jdk.test.lib.process.ProcessTools;
+
+/**
+ * @test
+ * @summary Test that a stream can be opened against a remote repository using
+ *          only file permission
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ *
+ * @run main/othervm jdk.jfr.api.consumer.security.TestStreamingRemote
+ */
+public class TestStreamingRemote {
+
+    private static final String SUCCESS = "Success!";
+
+    public static class TestEvent extends Event {
+    }
+
+    public static class Test {
+        public static void main(String... args) throws Exception {
+            Path repo = Paths.get(args[0]);
+            System.out.println("Repository: " + repo);
+            try (EventStream es = EventStream.openRepository(repo)) {
+                es.setStartTime(Instant.EPOCH);
+                es.onEvent(e -> {
+                    System.out.println(SUCCESS);
+                    es.close();
+                });
+                es.start();
+            }
+        }
+    }
+
+    public static void main(String... args) throws Exception {
+        try (Recording r = new Recording()) {
+            r.setFlushInterval(Duration.ofSeconds(1));
+            r.start();
+            String repository = System.getProperty("jdk.jfr.repository");
+            Path policy = createPolicyFile(repository);
+            TestEvent e = new TestEvent();
+            e.commit();
+            String[] c = new String[4];
+            c[0] = "-Djava.security.manager";
+            c[1] = "-Djava.security.policy=" + escapeBackslashes(policy.toString());
+            c[2] = Test.class.getName();
+            c[3] = repository;
+            OutputAnalyzer oa = ProcessTools.executeTestJvm(c);
+            oa.shouldContain(SUCCESS);
+        }
+    }
+
+    private static Path createPolicyFile(String repository) throws IOException {
+        Path policy = Paths.get("permission.policy").toAbsolutePath();
+        try (PrintWriter pw = new PrintWriter(policy.toFile())) {
+            pw.println("grant {");
+            // All the files and directories the contained in path
+            String dir = escapeBackslashes(repository);
+            String contents = escapeBackslashes(repository + File.separatorChar + "-");
+            pw.println("  permission java.io.FilePermission \"" + dir + "\", \"read\";");
+            pw.println("  permission java.io.FilePermission \"" + contents + "\", \"read\";");
+            pw.println("};");
+            pw.println();
+        }
+        System.out.println("Permission file: " + policy);
+        for (String line : Files.readAllLines(policy)) {
+            System.out.println(line);
+        }
+        System.out.println();
+        return policy;
+    }
+
+    // Needed for Windows
+    private static String escapeBackslashes(String text) {
+        return text.replace("\\", "\\\\");
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/security/local-streaming.policy	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,4 @@
+// Minimum policy to stream locally
+grant {
+permission jdk.jfr.FlightRecorderPermission "accessFlightRecorder";
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/security/no-permission.policy	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,3 @@
+// No permission
+grant {
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestChunkGap.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,111 @@
+/*
+ * 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.Instant;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import jdk.jfr.Event;
+import jdk.jfr.Recording;
+import jdk.jfr.consumer.EventStream;
+
+/**
+ * @test
+ * @summary Tests that a stream can gracefully handle chunk being removed in the
+ *          middle
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm jdk.jfr.api.consumer.streaming.TestFilledChunks
+ */
+public class TestChunkGap {
+
+    static class StartEvent extends Event {
+    }
+
+    static class TestGapEvent extends Event {
+    }
+
+    static class EndEvent extends Event {
+    }
+
+    private final static AtomicInteger count = new AtomicInteger(0);
+
+    public static void main(String... args) throws Exception {
+
+        CountDownLatch gap = new CountDownLatch(1);
+        CountDownLatch receivedEvent = new CountDownLatch(1);
+
+        try (EventStream s = EventStream.openRepository()) {
+            try (Recording r1 = new Recording()) {
+                s.setStartTime(Instant.EPOCH);
+                s.onEvent(e -> {
+                    System.out.println(e);
+                    receivedEvent.countDown();
+                    try {
+                        gap.await();
+                    } catch (InterruptedException e1) {
+                        e1.printStackTrace();
+                    }
+                    count.incrementAndGet();
+                    if (e.getEventType().getName().equals(EndEvent.class.getName())) {
+                        s.close();
+                    }
+                });
+                s.startAsync();
+                r1.start();
+                StartEvent event1 = new StartEvent();
+                event1.commit();
+                receivedEvent.await();
+                r1.stop();
+
+                // create chunk that is removed
+                try (Recording r2 = new Recording()) {
+                    r2.enable(TestGapEvent.class);
+                    r2.start();
+                    TestGapEvent event2 = new TestGapEvent();
+                    event2.commit();
+                    r2.stop();
+                }
+                gap.countDown();
+                try (Recording r3 = new Recording()) {
+                    r3.enable(EndEvent.class);
+                    r3.start();
+                    EndEvent event3 = new EndEvent();
+                    event3.commit();
+                    r3.stop();
+
+                    s.awaitTermination();
+                    if (count.get() != 2) {
+                        throw new AssertionError("Expected 2 event, but got " + count);
+                    }
+                }
+            }
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestEmptyChunks.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,84 @@
+/*
+ * 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);
+        CountDownLatch firstFlush = new CountDownLatch(1);
+        try (RecordingStream es = new RecordingStream()) {
+            es.onEvent(EndEvent.class.getName(), e -> {
+                end.countDown();
+            });
+            es.onFlush(() -> {
+                firstFlush.countDown();
+            });
+            es.startAsync();
+            System.out.println("Invoked startAsync()");
+            // Wait for stream thread to start
+            firstFlush.await();
+            System.out.println("Stream thread active");
+            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	Wed Oct 30 19:43:52 2019 +0100
@@ -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	Wed Oct 30 19:43:52 2019 +0100
@@ -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	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,82 @@
+/*
+ * 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 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 {
+        try (RecordingStream rs = new RecordingStream()) {
+            rs.onEvent(FillEvent.class.getName(), e -> {
+                int id = e.getInt("id");
+                // Some events may get lost due to
+                // buffer being full.
+                if (id > EVENT_COUNT / 2) {
+                    rs.close();
+                }
+            });
+            rs.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 ? "hello, hello, hello, hello, hello!" : "hi!";
+                f.value = r.nextInt(10000);
+                f.id = i;
+                f.commit();
+                if (i % 1_000_000 == 0) {
+                    System.out.println("Emitted " + i + " events");
+                }
+            }
+            rs.awaitTermination();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestFiltering.java	Wed Oct 30 19:43:52 2019 +0100
@@ -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/TestLatestEvent.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,135 @@
+/*
+ * 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.nio.file.Paths;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import jdk.jfr.Event;
+import jdk.jfr.FlightRecorder;
+import jdk.jfr.Name;
+import jdk.jfr.Recording;
+import jdk.jfr.consumer.EventStream;
+import jdk.jfr.consumer.RecordingStream;
+
+/**
+ * @test
+ * @summary Verifies that EventStream::openRepository() read from the latest flush
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm jdk.jfr.api.consumer.streaming.TestLatestEvent
+ */
+public class TestLatestEvent {
+
+    @Name("NotLatest")
+    static class NotLatestEvent extends Event {
+
+        public int id;
+    }
+
+    @Name("Latest")
+    static class LatestEvent extends Event {
+    }
+
+    @Name("MakeChunks")
+    static class MakeChunks extends Event {
+    }
+
+    public static void main(String... args) throws Exception {
+        CountDownLatch notLatestEvent = new CountDownLatch(6);
+        CountDownLatch beginChunks = new CountDownLatch(1);
+
+        try (RecordingStream r = new RecordingStream()) {
+            r.onEvent("MakeChunks", event-> {
+                System.out.println(event);
+                beginChunks.countDown();
+            });
+            r.onEvent("NotLatest", event -> {
+                System.out.println(event);
+                notLatestEvent.countDown();
+            });
+            r.startAsync();
+            MakeChunks e = new MakeChunks();
+            e.commit();
+
+            System.out.println("Waiting for first chunk");
+            beginChunks.await();
+            // Create 5 chunks with events in the repository
+            for (int i = 0; i < 5; i++) {
+                System.out.println("Creating empty chunk");
+                try (Recording r1 = new Recording()) {
+                    r1.start();
+                    NotLatestEvent notLatest = new NotLatestEvent();
+                    notLatest.id = i;
+                    notLatest.commit();
+                    r1.stop();
+                }
+            }
+            System.out.println("All empty chunks created");
+
+            // Create event in new chunk
+            NotLatestEvent notLatest = new NotLatestEvent();
+            notLatest.id = 5;
+            notLatest.commit();
+
+            // This latch ensures thatNotLatest has been
+            // flushed and a new valid position has been written
+            // to the chunk header
+            notLatestEvent.await(80, TimeUnit.SECONDS);
+            if (notLatestEvent.getCount() != 0) {
+               Recording rec =  FlightRecorder.getFlightRecorder().takeSnapshot();
+               Path p = Paths.get("error-not-latest.jfr").toAbsolutePath();
+               rec.dump(p);
+               System.out.println("Dumping repository as a file for inspection at " + p);
+               throw new Exception("Timeout 80 s. Expected 6 event, but got "  + notLatestEvent.getCount());
+            }
+
+            try (EventStream s = EventStream.openRepository()) {
+                System.out.println("EventStream opened");
+                AtomicBoolean foundLatest = new AtomicBoolean();
+                s.onEvent(event -> {
+                    String name = event.getEventType().getName();
+                    System.out.println("Found event " + name);
+                    foundLatest.set(name.equals("Latest"));
+                });
+                s.startAsync();
+                // Must loop here as there is no guarantee
+                // that the parser thread starts before event
+                // is flushed
+                while (!foundLatest.get()) {
+                    LatestEvent latest = new LatestEvent();
+                    latest.commit();
+                    System.out.println("Latest event emitted. Waiting 1 s ...");
+                    Thread.sleep(1000);
+                }
+            }
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestRecordingBefore.java	Wed Oct 30 19:43:52 2019 +0100
@@ -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	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,126 @@
+/*
+ * 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 Tests that a stream can gracefully handle chunk being removed
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm -Xlog:jfr*=info jdk.jfr.api.consumer.streaming.TestRemovedChunks
+ */
+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(5_000_000);
+            s.onEvent(ParkStream.class.getName(), e -> {
+                parkLatch.countDown();
+                await(removalLatch);
+
+            });
+            s.onEvent(IFeelFine.class.getName(), e -> {
+                IFeelFineLatch.countDown();
+            });
+            s.startAsync();
+            // Fill first chunk with data
+            emitData(1_000_000);
+            // Park stream
+            ParkStream ps = new ParkStream();
+            ps.commit();
+            await(parkLatch);
+            // Rotate and emit data that exceeds maxSize
+            for (int i = 0; i< 10;i++) {
+                try (Recording r = new Recording()) {
+                    r.start();
+                    emitData(1_000_000);
+                }
+            }
+            // Emit final event
+            IFeelFine i = new IFeelFine();
+            i.commit();
+            // Wake up parked stream
+            removalLatch.countDown();
+            // Await event things gone bad
+            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/TestRepositoryMigration.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,108 @@
+/*
+ * 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.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.concurrent.CountDownLatch;
+
+import jdk.jfr.Event;
+import jdk.jfr.Recording;
+import jdk.jfr.consumer.EventStream;
+import jdk.jfr.jcmd.JcmdHelper;
+
+/**
+ * @test
+ * @summary Verifies that is possible to stream from a repository that is being
+ *          moved.
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib /test/jdk
+ * @run main/othervm jdk.jfr.api.consumer.streaming.TestRepositoryMigration
+ */
+public class TestRepositoryMigration {
+    static class MigrationEvent extends Event {
+        int id;
+    }
+
+    public static void main(String... args) throws Exception {
+        Path newRepository = Paths.get("new-repository");
+        CountDownLatch event1 = new CountDownLatch(1);
+        CountDownLatch event2 = new CountDownLatch(1);
+
+        try (EventStream es = EventStream.openRepository()) {
+            es.setStartTime(Instant.EPOCH);
+            es.onEvent(e -> {
+                System.out.println(e);
+                if (e.getInt("id") == 1) {
+                    event1.countDown();
+                }
+                if (e.getInt("id") == 2) {
+                    event2.countDown();
+                }
+            });
+            es.startAsync();
+            System.out.println("Started es.startAsync()");
+
+            try (Recording r = new Recording()) {
+                r.setFlushInterval(Duration.ofSeconds(1));
+                r.start();
+                // Chunk in default repository
+                MigrationEvent e1 = new MigrationEvent();
+                e1.id = 1;
+                e1.commit();
+                event1.await();
+                System.out.println("Passed the event1.await()");
+                JcmdHelper.jcmd("JFR.configure", "repositorypath=" + newRepository.toAbsolutePath());
+                // Chunk in new repository
+                MigrationEvent e2 = new MigrationEvent();
+                e2.id = 2;
+                e2.commit();
+                r.stop();
+                event2.await();
+                System.out.println("Passed the event2.await()");
+                // Verify that it happened in new repository
+                if (!Files.exists(newRepository)) {
+                    throw new AssertionError("Could not find repository " + newRepository);
+                }
+                System.out.println("Listing contents in new repository:");
+                boolean empty = true;
+                for (Path p : Files.newDirectoryStream(newRepository)) {
+                    System.out.println(p.toAbsolutePath());
+                    empty = false;
+                }
+                System.out.println();
+                if (empty) {
+                    throw new AssertionError("Could not find contents in new repository location " + newRepository);
+                }
+            }
+        }
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestRepositoryProperty.java	Wed Oct 30 19:43:52 2019 +0100
@@ -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 exist after 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 not exist before JFR is initialized");
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestStartMultiChunk.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,122 @@
+/*
+ * 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	Wed Oct 30 19:43:52 2019 +0100
@@ -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/TestUnstarted.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,48 @@
+/*
+ * 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 jdk.jfr.consumer.EventStream;
+
+/**
+ * @test
+ * @summary Verifies that it is possible to open a stream when a repository doesn't
+ *          exists
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm jdk.jfr.api.consumer.streaming.TestUnstarted
+ */
+public class TestUnstarted {
+    public static void main(String... args) throws Exception {
+        try (EventStream es = EventStream.openRepository()) {
+            es.onEvent(e -> {
+                // ignore
+            });
+            es.startAsync();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/event/TestEventDuration.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,67 @@
+/*
+ * 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.event;
+
+import java.util.List;
+
+import jdk.jfr.Recording;
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.test.lib.jfr.Events;
+import jdk.test.lib.jfr.SimpleEvent;
+
+/**
+ * @test
+ * @summary Tests that a duration is recorded.
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm jdk.jfr.api.event.TestEventDuration
+ */
+public class TestEventDuration {
+
+    public static int counter;
+
+    public static void main(String[] args) throws Exception {
+
+        try(Recording r = new Recording()) {
+            r.start();
+            SimpleEvent e = new SimpleEvent();
+            e.begin();
+            for (int i = 0; i < 10_000;i++) {
+                counter+=i;
+            }
+            e.end();
+            e.commit();
+
+            r.stop();
+            List<RecordedEvent> events = Events.fromRecording(r);
+            if (events.get(0).getDuration().toNanos() < 1) {
+                throw new AssertionError("Expected a duration");
+            }
+        }
+
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/api/recording/time/TestSetFlushInterval.java	Wed Oct 30 19:43:52 2019 +0100
@@ -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.recording.time;
+
+import java.time.Duration;
+import java.util.concurrent.CountDownLatch;
+
+import jdk.jfr.Recording;
+import jdk.jfr.consumer.EventStream;
+import jdk.test.lib.Asserts;
+
+/**
+ * @test
+ * @key jfr
+ * @summary Test Recording::SetFlushInterval(...) and
+ *          Recording::getFlushInterval()
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm jdk.jfr.api.recording.time.TestSetFlushInterval
+ */
+
+public class TestSetFlushInterval {
+
+    public static void main(String[] args) throws Throwable {
+        testSetGet();
+        testSetNull();
+        testFlush();
+    }
+
+    static void testFlush() throws Exception {
+        CountDownLatch flush = new CountDownLatch(1);
+        try (EventStream es = EventStream.openRepository()) {
+            es.onFlush(() -> {
+                flush.countDown();
+            });
+            es.startAsync();
+            try (Recording r = new Recording()) {
+                r.setFlushInterval(Duration.ofSeconds(1));
+                r.start();
+                flush.await();
+            }
+        }
+    }
+
+    static void testSetNull() {
+        try (Recording r = new Recording()) {
+            r.setFlushInterval(null);
+            Asserts.fail("Expected NullPointerException");
+        } catch (NullPointerException npe) {
+            // as expected
+        }
+    }
+
+    static void testSetGet() {
+        try (Recording r = new Recording()) {
+            Duration a = Duration.ofNanos(21378461289374646L);
+            r.setFlushInterval(a);
+            Duration b = r.getFlushInterval();
+            Asserts.assertEQ(a, b);
+        }
+    }
+
+}
--- a/test/jdk/jdk/jfr/event/metadata/TestLookForUntestedEvents.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/test/jdk/jdk/jfr/event/metadata/TestLookForUntestedEvents.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, 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
@@ -81,6 +81,13 @@
         Arrays.asList("DumpReason")
     );
 
+    // Experimental events
+    private static final Set<String> experimentalEvents = new HashSet<>(
+      Arrays.asList(
+                    "Flush", "FlushStorage", "FlushStacktrace",
+                    "FlushStringPool", "FlushMetadata", "FlushTypeSet")
+    );
+
 
     public static void main(String[] args) throws Exception {
         for (EventType type : FlightRecorder.getFlightRecorder().getEventTypes()) {
@@ -138,6 +145,10 @@
             }
         }
 
+        // remove experimental events from eventsFromEventNamesClass since jfrEventTypes
+        // excludes experimental events
+        eventsFromEventNamesClass.removeAll(experimentalEvents);
+
         if (!jfrEventTypes.equals(eventsFromEventNamesClass)) {
             String exceptionMsg = "Events declared in jdk.test.lib.jfr.EventNames differ " +
                          "from events returned by FlightRecorder.getEventTypes()";
--- a/test/jdk/jdk/jfr/event/oldobject/TestLargeRootSet.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/test/jdk/jdk/jfr/event/oldobject/TestLargeRootSet.java	Wed Oct 30 19:43:52 2019 +0100
@@ -26,15 +26,19 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Random;
+import java.util.Vector;
 import java.util.concurrent.BrokenBarrierException;
 import java.util.concurrent.CyclicBarrier;
 
 import jdk.jfr.Recording;
 import jdk.jfr.consumer.RecordedClass;
 import jdk.jfr.consumer.RecordedEvent;
+import jdk.jfr.consumer.RecordedFrame;
+import jdk.jfr.consumer.RecordedMethod;
 import jdk.jfr.consumer.RecordedObject;
+import jdk.jfr.consumer.RecordedStackTrace;
 import jdk.jfr.internal.test.WhiteBox;
-import jdk.test.lib.Asserts;
 import jdk.test.lib.jfr.EventNames;
 import jdk.test.lib.jfr.Events;
 
@@ -50,13 +54,13 @@
 public class TestLargeRootSet {
 
     private static final int THREAD_COUNT = 50;
+    private static final Random RANDOM = new Random(4711);
+    public static Vector<StackObject[]> temporaries = new Vector<>(OldObjects.MIN_SIZE);
 
     private static class RootThread extends Thread {
         private final CyclicBarrier barrier;
         private int maxDepth = OldObjects.MIN_SIZE / THREAD_COUNT;
 
-        public List<StackObject[]> temporaries = new ArrayList<>(maxDepth);
-
         RootThread(CyclicBarrier cb) {
             this.barrier = cb;
         }
@@ -67,8 +71,9 @@
 
         private void buildRootObjects() {
             if (maxDepth-- > 0) {
-                // Allocate array to trigger sampling code path for interpreter / c1
-                StackObject[] stackObject = new StackObject[0];
+                // Allocate array to trigger sampling code path for interpreter
+                // / c1
+                StackObject[] stackObject = new StackObject[RANDOM.nextInt(7)];
                 temporaries.add(stackObject); // make sure object escapes
                 buildRootObjects();
             } else {
@@ -91,37 +96,73 @@
 
     public static void main(String[] args) throws Exception {
         WhiteBox.setWriteAllObjectSamples(true);
-
-        List<RootThread> threads = new ArrayList<>();
-        try (Recording r = new Recording()) {
-            r.enable(EventNames.OldObjectSample).withStackTrace().with("cutoff", "infinity");
-            r.start();
-            CyclicBarrier cb = new CyclicBarrier(THREAD_COUNT + 1);
-            for (int i = 0; i < THREAD_COUNT; i++) {
-                RootThread t = new RootThread(cb);
-                t.start();
-                if (i % 10 == 0) {
-                    // Give threads some breathing room before starting next batch
-                    Thread.sleep(100);
+        int attempt = 1;
+        while (true) {
+            System.out.println();
+            System.out.println();
+            System.out.println("ATTEMPT: " + attempt);
+            System.out.println("====================================");
+            List<RootThread> threads = new ArrayList<>();
+            try (Recording r = new Recording()) {
+                r.enable(EventNames.OldObjectSample).withStackTrace().with("cutoff", "infinity");
+                r.start();
+                CyclicBarrier cb = new CyclicBarrier(THREAD_COUNT + 1);
+                for (int i = 0; i < THREAD_COUNT; i++) {
+                    RootThread t = new RootThread(cb);
+                    t.start();
+                    if (i % 10 == 0) {
+                        // Give threads some breathing room before starting next
+                        // batch
+                        Thread.sleep(100);
+                    }
+                    threads.add(t);
                 }
-                threads.add(t);
-            }
-            cb.await();
-            System.gc();
-            r.stop();
-            cb.await();
-            List<RecordedEvent> events = Events.fromRecording(r);
-            Events.hasEvents(events);
-            for (RecordedEvent e : events) {
-                RecordedObject ro = e.getValue("object");
-                RecordedClass rc = ro.getValue("type");
-                System.out.println(rc.getName());
-                if (rc.getName().equals(StackObject[].class.getName())) {
-                    return; // ok
+                cb.await();
+                System.gc();
+                r.stop();
+                cb.await();
+                List<RecordedEvent> events = Events.fromRecording(r);
+                Events.hasEvents(events);
+                int sample = 0;
+                for (RecordedEvent e : events) {
+                    RecordedObject ro = e.getValue("object");
+                    RecordedClass rc = ro.getValue("type");
+                    System.out.println("Sample: " + sample);
+                    System.out.println(" - allocationTime: " + e.getInstant("allocationTime"));
+                    System.out.println(" - type: " + rc.getName());
+                    RecordedObject root = e.getValue("root");
+                    if (root != null) {
+                        System.out.println(" - root:");
+                        System.out.println("   - description: " + root.getValue("description"));
+                        System.out.println("   - system: " + root.getValue("system"));
+                        System.out.println("   - type: " + root.getValue("type"));
+                    } else {
+                        System.out.println(" - root: N/A");
+                    }
+                    RecordedStackTrace stack = e.getStackTrace();
+                    if (stack != null) {
+                        System.out.println(" - stack:");
+                        int frameCount = 0;
+                        for (RecordedFrame frame : stack.getFrames()) {
+                            RecordedMethod m = frame.getMethod();
+                            System.out.println("      " + m.getType().getName() + "." + m.getName() + "(...)");
+                            frameCount++;
+                            if (frameCount == 10) {
+                                break;
+                            }
+                        }
+                    } else {
+                        System.out.println(" - stack: N/A");
+                    }
+                    System.out.println();
+                    if (rc.getName().equals(StackObject[].class.getName())) {
+                        return; // ok
+                    }
+                    sample++;
                 }
             }
-            Asserts.fail("Could not find root object");
+            attempt++;
         }
     }
+
 }
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/event/runtime/TestFlush.java	Wed Oct 30 19:43:52 2019 +0100
@@ -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/jcmd/TestJcmdStartFlushInterval.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,56 @@
+/*
+ * 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.jcmd;
+
+import java.time.Duration;
+
+import jdk.jfr.FlightRecorder;
+import jdk.jfr.Recording;
+
+/**
+ * @test
+ * @summary Start a recording with a flush interval
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib /test/jdk
+ * @run main/othervm jdk.jfr.jcmd.TestJcmdStartReadOnlyFile
+ */
+public class TestJcmdStartFlushInterval {
+
+    public static void main(String[] args) throws Exception {
+        JcmdHelper.jcmd("JFR.start","flush-interval=1s");
+        for (Recording r : FlightRecorder.getFlightRecorder().getRecordings()) {
+            Duration d = r.getFlushInterval();
+            if (d.equals(Duration.ofSeconds(1))) {
+                return; //OK
+            } else {
+                throw new Exception("Unexpected flush-interval=" + d);
+            }
+        }
+        throw new Exception("No recording found");
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/jvm/TestThreadExclusion.java	Wed Oct 30 19:43:52 2019 +0100
@@ -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();
+        }
+    }
+}
--- a/test/jdk/jdk/jfr/jvm/TestUnsupportedVM.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/test/jdk/jdk/jfr/jvm/TestUnsupportedVM.java	Wed Oct 30 19:43:52 2019 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 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
@@ -30,8 +30,10 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.time.Duration;
 import java.util.ArrayList;
 import java.util.concurrent.Callable;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 import jdk.jfr.AnnotationElement;
 import jdk.jfr.Configuration;
@@ -48,6 +50,7 @@
 import jdk.jfr.RecordingState;
 import jdk.jfr.SettingControl;
 import jdk.jfr.ValueDescriptor;
+import jdk.jfr.consumer.EventStream;
 import jdk.jfr.consumer.RecordedClass;
 import jdk.jfr.consumer.RecordedEvent;
 import jdk.jfr.consumer.RecordedFrame;
@@ -57,6 +60,7 @@
 import jdk.jfr.consumer.RecordedThread;
 import jdk.jfr.consumer.RecordedThreadGroup;
 import jdk.jfr.consumer.RecordingFile;
+import jdk.jfr.consumer.RecordingStream;
 import jdk.management.jfr.ConfigurationInfo;
 import jdk.management.jfr.EventTypeInfo;
 import jdk.management.jfr.FlightRecorderMXBean;
@@ -106,9 +110,11 @@
             RecordingState.class,
             SettingControl.class,
             SettingDescriptorInfo.class,
-            ValueDescriptor.class
+            ValueDescriptor.class,
+            EventStream.class,
+            RecordingStream.class
        };
-    // * @run main/othervm -Dprepare-recording=true jdk.jfr.jvm.TestUnsupportedVM
+
     @Label("My Event")
     @Description("My fine event")
     static class MyEvent extends Event {
@@ -125,7 +131,7 @@
             return;
         }
 
-        System.out.println("jdk.jfr.unsupportedvm=" + System.getProperty("jdk.jfr.unsupportedvm"));
+        System.out.println("jfr.unsupported.vm=" + System.getProperty("jfr.unsupported.vm"));
         // Class FlightRecorder
         if (FlightRecorder.isAvailable()) {
             throw new AssertionError("JFR should not be available on an unsupported VM");
@@ -136,6 +142,7 @@
         }
 
         assertIllegalStateException(() -> FlightRecorder.getFlightRecorder());
+        assertIllegalStateException(() -> new RecordingStream());
         assertSwallow(() -> FlightRecorder.addListener(new FlightRecorderListener() {}));
         assertSwallow(() -> FlightRecorder.removeListener(new FlightRecorderListener() {}));
         assertSwallow(() -> FlightRecorder.register(MyEvent.class));
@@ -174,8 +181,39 @@
         // Only run this part of tests if we are on VM
         // that can produce a recording file
         if (Files.exists(RECORDING_FILE)) {
+            boolean firstFileEvent = true;
             for(RecordedEvent re : RecordingFile.readAllEvents(RECORDING_FILE)) {
-                System.out.println(re);
+                // Print one event
+                if (firstFileEvent) {
+                    System.out.println(re);
+                    firstFileEvent = false;
+                }
+            }
+            AtomicBoolean firstStreamEvent = new AtomicBoolean(true);
+            try (EventStream es = EventStream.openFile(RECORDING_FILE)) {
+                es.onEvent(e -> {
+                    // Print one event
+                    if (firstStreamEvent.get()) {
+                        try {
+                            System.out.println(e);
+                            firstStreamEvent.set(false);
+                        } catch (Throwable t) {
+                            t.printStackTrace();
+                        }
+                    }
+                });
+                es.start();
+                if (firstStreamEvent.get()) {
+                    throw new AssertionError("Didn't print streaming event");
+                }
+            }
+
+            try (EventStream es = EventStream.openRepository()) {
+                es.onEvent(e -> {
+                    System.out.println(e);
+                });
+                es.startAsync();
+                es.awaitTermination(Duration.ofMillis(10));
             }
         }
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/jdk/jfr/startupargs/TestFlushInterval.java	Wed Oct 30 19:43:52 2019 +0100
@@ -0,0 +1,55 @@
+/*
+ * 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.startupargs;
+
+import java.time.Duration;
+
+import jdk.jfr.FlightRecorder;
+import jdk.jfr.Recording;
+
+/**
+ * @test
+ * @summary Start a recording with a flush interval
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib /test/jdk
+ * @run main/othervm -XX:StartFlightRecording=flush-interval=1s jdk.jfr.startupargs.TestFlushInterval
+ */
+public class TestFlushInterval {
+
+    public static void main(String[] args) throws Exception {
+        for (Recording r : FlightRecorder.getFlightRecorder().getRecordings()) {
+            Duration d = r.getFlushInterval();
+            if (d.equals(Duration.ofSeconds(1))) {
+                return; //OK
+            } else {
+                throw new Exception("Unexpected flush-interval " + d);
+            }
+        }
+        throw new Exception("No recording found");
+    }
+
+}
--- a/test/lib/jdk/test/lib/jfr/EventNames.java	Wed Oct 30 16:14:56 2019 +0100
+++ b/test/lib/jdk/test/lib/jfr/EventNames.java	Wed Oct 30 19:43:52 2019 +0100
@@ -189,6 +189,12 @@
     public final static String CPUTimeStampCounter = PREFIX + "CPUTimeStampCounter";
     public final static String ActiveRecording = PREFIX + "ActiveRecording";
     public final static String ActiveSetting = PREFIX + "ActiveSetting";
+    public static final String Flush = PREFIX + "Flush";
+    public static final String FlushStringPool = PREFIX + "FlushStringPool";
+    public static final String FlushStacktrace = PREFIX + "FlushStacktrace";
+    public static final String FlushStorage = PREFIX + "FlushStorage";
+    public static final String FlushMetadata = PREFIX + "FlushMetadata";
+    public static final String FlushTypeSet = PREFIX + "FlushTypeSet";
 
     public static boolean isGcEvent(EventType et) {
         return et.getCategoryNames().contains(GC_CATEGORY);