Move implementation into jdk.jfr.internal.consumer JEP-349-branch
authoregahlin
Mon, 16 Sep 2019 09:45:22 +0200
branchJEP-349-branch
changeset 58145 bc54ed8d908a
parent 58129 7b751fe181a5
child 58146 9f3aadcaa430
Move implementation into jdk.jfr.internal.consumer
src/jdk.jfr/share/classes/jdk/jfr/consumer/AbstractEventStream.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/ChunkParser.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/ConstantLookup.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/ConstantMap.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/Dispatcher.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/EventDirectoryStream.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/EventFileStream.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/EventFilter.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/EventParser.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/EventStream.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/ObjectContext.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/ObjectFactory.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/StreamConfiguration.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/StringParser.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/TimeConverter.java
src/jdk.jfr/share/classes/jdk/jfr/consumer/UseCasesStream.java
src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataReader.java
src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/AbstractEventStream.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/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/ParserFactory.java
src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/RecordingInternals.java
src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/StreamConfiguration.java
src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/StringEncoding.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/tool/EventPrintWriter.java
src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Metadata.java
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/AbstractEventStream.java	Fri Sep 13 18:46:07 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,286 +0,0 @@
-/*
- * 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.security.PrivilegedAction;
-import java.time.Duration;
-import java.time.Instant;
-import java.util.Comparator;
-import java.util.Objects;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.function.Consumer;
-
-import jdk.jfr.internal.JVM;
-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. In particular, it handles:
- *
- * - configuration storage
- * - atomic updates to a configuration
- * - dispatch mechanism
- * - error handling
- * - security
- *
- */
-abstract class AbstractEventStream implements EventStream {
-
-    static final Comparator<? super RecordedEvent> END_TIME = (e1, e2) -> Long.compare(e1.endTimeTicks, e2.endTimeTicks);
-
-    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;
-
-    public 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 Exception;
-
-    protected final void setClosed(boolean closed) {
-        this.closed = closed;
-    }
-
-    protected final boolean isClosed() {
-        return closed;
-    }
-
-    protected final void startAsync(long startNanos) {
-        startInternal(startNanos);
-        Runnable r = () -> run(accessControllerContext);
-        thread = SecuritySupport.createThreadWitNoPermissions(nextThreadName(), r);
-        thread.start();
-    }
-
-    protected 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.setStartNanos(startNanos);
-            }
-            configuration.setStarted(true);
-        }
-    }
-
-    private void execute() {
-        JVM.getJVM().exclude(Thread.currentThread());
-        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
-        } catch (Exception e) {
-            // TODO: Remove before integrating
-            e.printStackTrace();
-        } 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;
-    }
-}
\ No newline at end of file
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/ChunkParser.java	Fri Sep 13 18:46:07 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,436 +0,0 @@
-/*
- * 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.consumer;
-
-import java.io.IOException;
-import java.util.Collection;
-import java.util.List;
-import java.util.StringJoiner;
-
-import jdk.jfr.EventType;
-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;
-import jdk.jfr.internal.consumer.ChunkHeader;
-import jdk.jfr.internal.consumer.InternalEventFilter;
-import jdk.jfr.internal.consumer.Parser;
-import jdk.jfr.internal.consumer.RecordingInput;
-
-/**
- * Parses a chunk.
- *
- */
-final class ChunkParser {
-
-    static final class ParserConfiguration {
-        final boolean reuse;
-        final boolean ordered;
-        final InternalEventFilter eventFilter;
-
-        long filterStart;
-        long filterEnd;
-
-        public ParserConfiguration(long filterStart, long filterEnd, boolean reuse, boolean ordered, InternalEventFilter 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, InternalEventFilter.ACCEPT_ALL);
-        }
-    }
-
-    // Checkpoint that finishes a flush segment
-    static final byte CHECKPOINT_FLUSH_MASK = 1;
-    // Checkpoint contains chunk header information in the first pool
-    static final byte CHECKPOINT_CHUNK_HEADER_MASK = 2;
-    // Checkpoint contains only statics that will not change from chunk to chunk
-    static final byte CHECKPOINT_STATICS_MASK = 4;
-    // Checkpoint contains thread related information
-    static final byte CHECKPOINT_THREADS_MASK = 8;
-
-    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 long pollInterval;
-    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());
-    }
-
-    public ChunkParser(RecordingInput input, ParserConfiguration pc) throws IOException {
-       this(new ChunkHeader(input), null, pc);
-    }
-
-    public 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.pollInterval = 1000;
-            this.constantLookups = new LongMap<>();
-            this.previousMetadata = null;
-        } else {
-            this.constantLookups = previous.constantLookups;
-            this.previousMetadata = previous.metadata;
-            this.pollInterval = previous.pollInterval;
-            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);
-    }
-
-    public 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.
-     */
-    public 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();
-
-            if (typeId != 0) { // Not metadata event
-                Parser p = parsers.get(typeId);
-                if (p instanceof EventParser) {
-                    EventParser ep = (EventParser) p;
-                    RecordedEvent event = ep.parse(input);
-                    if (event != null) {
-                        input.position(pos + size);
-                        return event;
-                    }
-                }
-                if (typeId == 1 && flushOperation != null) { // checkpoint event
-                    parseCheckpoint();
-                }
-            }
-            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 c = input.readByte();
-        if ((c & CHECKPOINT_FLUSH_MASK)== 1) {
-            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(pollInterval);
-        }
-    }
-
-    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();
-    }
-
-    public 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();
-    }
-
-}
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/ConstantLookup.java	Fri Sep 13 18:46:07 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,62 +0,0 @@
-/*
- * 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.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);
-    }
-}
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/ConstantMap.java	Fri Sep 13 18:46:07 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,191 +0,0 @@
-/*
- * 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.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;
-    }
-
-    public boolean isResolved(long id) {
-        if (objects.hasKey(id)) {
-            return objects.isSetId(id, RESOLUTION_FINISHED);
-        }
-        return false;
-    }
-}
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/Dispatcher.java	Fri Sep 13 18:46:07 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,144 +0,0 @@
-package jdk.jfr.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.ChunkParser.ParserConfiguration;
-import jdk.jfr.internal.LongMap;
-import jdk.jfr.internal.consumer.InternalEventFilter;
-
-public final class Dispatcher {
-
-    public final static class EventDispatcher {
-        final static EventDispatcher[] NO_DISPATCHERS = new EventDispatcher[0];
-        final String eventName;
-        final Consumer<RecordedEvent> action;
-
-        public EventDispatcher(Consumer<RecordedEvent> action) {
-            this(null, action);
-        }
-
-        public EventDispatcher(String eventName, Consumer<RecordedEvent> action) {
-            this.eventName = eventName;
-            this.action = action;
-        }
-
-        public void offer(RecordedEvent event) {
-            action.accept(event);
-        }
-
-        public boolean accepts(EventType eventType) {
-            return (eventName == null || eventType.getName().equals(eventName));
-        }
-    }
-
-    final Consumer<Throwable>[] errorActions;
-    final Runnable[] flushActions;
-    final Runnable[] closeActions;
-    final EventDispatcher[] dispatchers;
-    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({"unchecked","rawtypes"})
-    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;
-    }
-
-    private static InternalEventFilter buildFilter(EventDispatcher[] dispatchers) {
-        InternalEventFilter ef = new InternalEventFilter();
-        for (EventDispatcher ed : dispatchers) {
-            String name = ed.eventName;
-            if (name == null) {
-                return InternalEventFilter.ACCEPT_ALL;
-            }
-            ef.setThreshold(name, 0);
-        }
-        return ef;
-    }
-
-    protected final 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;
-        }
-        for (int i = 0; i < dispatchers.length; i++) {
-            try {
-                dispatchers[i].offer(event);
-            } catch (Exception e) {
-                handleError(e);
-            }
-        }
-    }
-
-    public 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> conusmer = (Consumer<Throwable>) consumers[i];
-            conusmer.accept(e);
-        }
-    }
-
-    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);
-            }
-        }
-    }
-
-    void defaultErrorHandler(Throwable e) {
-        e.printStackTrace();
-    }
-}
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/EventDirectoryStream.java	Fri Sep 13 18:46:07 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,181 +0,0 @@
-/*
- * 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.time.Instant;
-import java.util.Arrays;
-import java.util.Objects;
-
-import jdk.jfr.consumer.ChunkParser.ParserConfiguration;
-import jdk.jfr.internal.Utils;
-import jdk.jfr.internal.consumer.FileAccess;
-import jdk.jfr.internal.consumer.RecordingInput;
-import jdk.jfr.internal.consumer.RepositoryFiles;
-
-/**
- * Implementation of an {@code EventStream}} that operates against a directory
- * with chunk files.
- *
- */
-final class EventDirectoryStream extends AbstractEventStream {
-    private final RepositoryFiles repositoryFiles;
-    private final boolean active;
-    private final FileAccess fileAccess;
-
-    private ChunkParser chunkParser;
-    private long chunkStartNanos;
-    private RecordedEvent[] sortedList;
-
-    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 Exception {
-        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;
-        }
-        chunkStartNanos = repositoryFiles.getTimestamp(path);
-        try (RecordingInput input = new RecordingInput(path.toFile(), fileAccess)) {
-            chunkParser = new ChunkParser(input, disp.parserConfiguration);
-            long segmentStart = chunkParser.getStartNanos() + chunkParser.getChunkDuration();
-            long filterStart = validStartTime ? disp.startNanos : segmentStart;
-            long filterEnd = disp.endTime != null ? disp.endNanos: Long.MAX_VALUE;
-
-            while (!isClosed()) {
-                boolean awaitnewEvent = false;
-                while (!isClosed() && !chunkParser.isChunkFinished()) {
-                    disp = dispatcher();
-                    ParserConfiguration pc = disp.parserConfiguration;
-                    pc.filterStart = filterStart;
-                    pc.filterEnd = filterEnd;
-                    chunkParser.updateConfiguration(pc, true);
-                    chunkParser.setFlushOperation(getFlushOperation());
-                    if (pc.ordered) {
-                        awaitnewEvent = processOrdered(disp, awaitnewEvent);
-                    } else {
-                        awaitnewEvent = processUnordered(disp, awaitnewEvent);
-                    }
-                    if (chunkParser.getStartNanos() + chunkParser.getChunkDuration() > filterEnd) {
-                        close();
-                        return;
-                    }
-                }
-
-                if (isClosed()) {
-                    return;
-                }
-                long durationNanos = chunkParser.getChunkDuration();
-                path = repositoryFiles.nextPath(chunkStartNanos + durationNanos);
-                if (path == null) {
-                    return; // stream closed
-                }
-                chunkStartNanos = repositoryFiles.getTimestamp(path);
-                input.setFile(path);
-                chunkParser = chunkParser.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 (sortedList == null) {
-            sortedList = new RecordedEvent[100_000];
-        }
-        int index = 0;
-        while (true) {
-            RecordedEvent e = chunkParser.readStreamingEvent(awaitNewEvents);
-            if (e == null) {
-                // wait for new event with next call to
-                // readStreamingEvent()
-                awaitNewEvents = true;
-                break;
-            }
-            awaitNewEvents = false;
-            if (index == sortedList.length) {
-                sortedList = Arrays.copyOf(sortedList, sortedList.length * 2);
-            }
-            sortedList[index++] = e;
-        }
-
-        // no events found
-        if (index == 0 && chunkParser.isChunkFinished()) {
-            return awaitNewEvents;
-        }
-        // at least 2 events, sort them
-        if (index > 1) {
-            Arrays.sort(sortedList, 0, index, END_TIME);
-        }
-        for (int i = 0; i < index; i++) {
-            c.dispatch(sortedList[i]);
-        }
-        return awaitNewEvents;
-    }
-
-    private boolean processUnordered(Dispatcher c, boolean awaitNewEvents) throws IOException {
-        while (true) {
-            RecordedEvent e = chunkParser.readStreamingEvent(awaitNewEvents);
-            if (e == null) {
-                return true;
-            } else {
-                c.dispatch(e);
-            }
-        }
-    }
-}
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/EventFileStream.java	Fri Sep 13 18:46:07 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,141 +0,0 @@
-/*
- * 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.util.Arrays;
-import java.util.Objects;
-
-import jdk.jfr.internal.consumer.FileAccess;
-import jdk.jfr.internal.consumer.RecordingInput;
-
-/**
- * Implementation of an event stream that operates against a recording file.
- *
- */
-final class EventFileStream extends AbstractEventStream {
-    private final RecordingInput input;
-    private ChunkParser chunkParser;
-    private RecordedEvent[] sortedList;
-
-    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;
-        }
-
-        chunkParser = new ChunkParser(input, disp.parserConfiguration);
-        while (!isClosed()) {
-            if (chunkParser.getStartNanos() > end) {
-                close();
-                return;
-            }
-            disp = dispatcher();
-            disp.parserConfiguration.filterStart = start;
-            disp.parserConfiguration.filterEnd = end;
-            chunkParser.updateConfiguration(disp.parserConfiguration, true);
-            chunkParser.setFlushOperation(getFlushOperation());
-            if (disp.parserConfiguration.ordered) {
-                processOrdered(disp);
-            } else {
-                processUnordered(disp);
-            }
-            if (isClosed() || chunkParser.isLastChunk()) {
-                return;
-            }
-            chunkParser = chunkParser.nextChunkParser();
-        }
-    }
-
-    private void processOrdered(Dispatcher c) throws IOException {
-        if (sortedList == null) {
-            sortedList = new RecordedEvent[10_000];
-        }
-        RecordedEvent event;
-        int index = 0;
-        while (true) {
-            event = chunkParser.readEvent();
-            if (event == null) {
-                Arrays.sort(sortedList, 0, index, END_TIME);
-                for (int i = 0; i < index; i++) {
-                    c.dispatch(sortedList[i]);
-                }
-                return;
-            }
-            if (index == sortedList.length) {
-                RecordedEvent[] tmp = sortedList;
-                sortedList = new RecordedEvent[2 * tmp.length];
-                System.arraycopy(tmp, 0, sortedList, 0, tmp.length);
-            }
-            sortedList[index++] = event;
-        }
-    }
-
-    private void processUnordered(Dispatcher c) throws IOException {
-        while (!isClosed()) {
-            RecordedEvent event = chunkParser.readEvent();
-            if (event == null) {
-                return;
-            }
-            c.dispatch(event);
-        }
-    }
-}
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/EventFilter.java	Fri Sep 13 18:46:07 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,82 +0,0 @@
-/*
- * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.jfr.consumer;
-
-import java.time.Duration;
-import java.util.Arrays;
-import java.util.List;
-
-final class EventFilter {
-    private final String[] eventNames;
-    private final Duration threshold;
-    private final String[] fields;
-
-    private EventFilter(String[] eventNames, Duration threshold, String[] fields) {
-        this.eventNames = eventNames;
-        this.threshold = threshold;
-        this.fields = fields;
-    }
-
-    public static EventFilter eventTypes(String... eventNames) {
-        return new EventFilter(eventNames.clone(), null, new String[0]);
-    }
-
-    public EventFilter aboveThreshold(Duration threshold) {
-        return new EventFilter(eventNames, threshold, fields);
-    }
-
-    public EventFilter mustHaveFields(String... fieldNames) {
-        return new EventFilter(eventNames, threshold, fieldNames);
-    }
-
-    public EventFilter onlyThreads(Thread... t) {
-        return this;
-    }
-
-    public EventFilter onlyThreadIds(long... threadId) {
-        return this;
-    }
-
-    public EventFilter onlyThreadNames(String... threadName) {
-        return this;
-    }
-
-    public EventFilter threadFilters(String... filter) {
-        return this;
-    }
-
-    List<String> getFields() {
-        return Arrays.asList(fields);
-    }
-
-    List<String> getEventNames() {
-        return Arrays.asList(fields);
-    }
-
-    Duration getThreshold() {
-        return threshold;
-    }
-}
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/EventParser.java	Fri Sep 13 18:46:07 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,193 +0,0 @@
-/*
- * 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.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.Parser;
-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;
-    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 = new RecordedEvent(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 = new RecordedEvent(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();
-            event.startTimeTicks = startTicks;
-            event.endTimeTicks = endTicks;
-            Object[] values = event.objects;
-            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 new RecordedEvent(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;
-    }
-
-    public 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;
-    }
-}
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/EventStream.java	Fri Sep 13 18:46:07 2019 +0200
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/EventStream.java	Mon Sep 16 09:45:22 2019 +0200
@@ -36,6 +36,8 @@
 
 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;
 
 /**
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/ObjectContext.java	Fri Sep 13 18:46:07 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,63 +0,0 @@
-/*
- * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-package jdk.jfr.consumer;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import jdk.jfr.EventType;
-import jdk.jfr.ValueDescriptor;
-
-final class ObjectContext {
-    private final Map<ValueDescriptor, ObjectContext> contextLookup;
-
-    final EventType eventType;
-    final List<ValueDescriptor> fields;
-    final TimeConverter timeConverter;
-
-    public 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;
-    }
-}
\ No newline at end of file
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/ObjectFactory.java	Fri Sep 13 18:46:07 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,82 +0,0 @@
-/*
- * 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.consumer;
-
-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 ObjectContext objectContext;
-
-    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);
-}
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/ParserFactory.java	Fri Sep 13 18:46:07 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,380 +0,0 @@
-/*
- * 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.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);
-        }
-        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);
-            }
-        }
-    }
-
-    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);
-            }
-        }
-    }
-
-    public 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();
-        }
-    }
-
-    public 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();
-        }
-    }
-}
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedClass.java	Fri Sep 13 18:46:07 2019 +0200
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedClass.java	Mon Sep 16 09:45:22 2019 +0200
@@ -27,7 +27,7 @@
 
 import java.lang.reflect.Modifier;
 
-import jdk.jfr.internal.Type;
+import jdk.jfr.internal.consumer.ObjectContext;
 
 /**
  * A recorded Java type, such as a class or an interface.
@@ -35,20 +35,10 @@
  * @since 9
  */
 public final class RecordedClass extends RecordedObject {
-
-    static ObjectFactory<RecordedClass> createFactory(Type type, TimeConverter timeConverter) {
-        return new ObjectFactory<RecordedClass>(type, timeConverter) {
-            @Override
-            RecordedClass createTyped(ObjectContext objectContext, long id, Object[] values) {
-                return new RecordedClass(objectContext, id, values);
-            }
-        };
-    }
-
     private final long uniqueId;
 
     // package private
-    private RecordedClass(ObjectContext objectContext, long id, Object[] values) {
+    RecordedClass(ObjectContext objectContext, long id, Object[] values) {
         super(objectContext, values);
         this.uniqueId = id;
     }
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedClassLoader.java	Fri Sep 13 18:46:07 2019 +0200
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedClassLoader.java	Mon Sep 16 09:45:22 2019 +0200
@@ -25,7 +25,7 @@
 
 package jdk.jfr.consumer;
 
-import jdk.jfr.internal.Type;
+import jdk.jfr.internal.consumer.ObjectContext;
 
 /**
  * A recorded Java class loader.
@@ -33,20 +33,10 @@
  * @since 9
  */
 public final class RecordedClassLoader extends RecordedObject {
-
-    static ObjectFactory<RecordedClassLoader> createFactory(Type type, TimeConverter timeConverter) {
-        return new ObjectFactory<RecordedClassLoader>(type, timeConverter) {
-            @Override
-            RecordedClassLoader createTyped(ObjectContext objectContext, long id, Object[] values) {
-                return new RecordedClassLoader(objectContext, id, values);
-            }
-        };
-    }
-
     private final long uniqueId;
 
     // package private
-    private RecordedClassLoader(ObjectContext objectContext, long id, Object[] values) {
+    RecordedClassLoader(ObjectContext objectContext, long id, Object[] values) {
         super(objectContext, values);
         this.uniqueId = id;
     }
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedEvent.java	Fri Sep 13 18:46:07 2019 +0200
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedEvent.java	Mon Sep 16 09:45:22 2019 +0200
@@ -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.
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedFrame.java	Fri Sep 13 18:46:07 2019 +0200
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedFrame.java	Mon Sep 16 09:45:22 2019 +0200
@@ -27,7 +27,7 @@
 
 import java.lang.reflect.Modifier;
 
-import jdk.jfr.internal.Type;
+import jdk.jfr.internal.consumer.ObjectContext;
 
 /**
  * A recorded frame in a stack trace.
@@ -35,19 +35,9 @@
  * @since 9
  */
 public final class RecordedFrame extends RecordedObject {
-
-    static ObjectFactory<RecordedFrame> createFactory(Type type, TimeConverter timeConverter) {
-        return new ObjectFactory<RecordedFrame>(type, timeConverter) {
-            @Override
-            RecordedFrame createTyped(ObjectContext objectContext, long id, Object[] values) {
-                return new RecordedFrame(objectContext, values);
-            }
-        };
-    }
-
     // package private
-    RecordedFrame(ObjectContext objectContext, Object[] objects) {
-        super(objectContext, objects);
+    RecordedFrame(ObjectContext objectContext, Object[] values) {
+        super(objectContext, values);
     }
 
     /**
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedMethod.java	Fri Sep 13 18:46:07 2019 +0200
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedMethod.java	Mon Sep 16 09:45:22 2019 +0200
@@ -27,7 +27,7 @@
 
 import java.lang.reflect.Modifier;
 
-import jdk.jfr.internal.Type;
+import jdk.jfr.internal.consumer.ObjectContext;
 
 /**
  * A recorded method.
@@ -36,16 +36,8 @@
  */
 public final class RecordedMethod extends RecordedObject {
 
-    static ObjectFactory<RecordedMethod> createFactory(Type type, TimeConverter timeConverter) {
-        return new ObjectFactory<RecordedMethod>(type, timeConverter) {
-            @Override
-            RecordedMethod createTyped(ObjectContext objectContext, long id, Object[] values) {
-                return new RecordedMethod(objectContext, values);
-            }
-        };
-    }
-
-    private RecordedMethod(ObjectContext objectContext, Object[] values) {
+    // package private
+    RecordedMethod(ObjectContext objectContext, Object[] values) {
         super(objectContext, values);
     }
 
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedObject.java	Fri Sep 13 18:46:07 2019 +0200
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedObject.java	Mon Sep 16 09:45:22 2019 +0200
@@ -31,17 +31,18 @@
 import java.time.Duration;
 import java.time.Instant;
 import java.time.OffsetDateTime;
-import java.util.Collections;
+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.Parser;
-import jdk.jfr.internal.consumer.RecordingInternals;
+import jdk.jfr.internal.consumer.ObjectContext;
 import jdk.jfr.internal.tool.PrettyWriter;
 
 /**
@@ -57,7 +58,7 @@
 public class RecordedObject {
 
     static{
-        RecordingInternals.INSTANCE = new RecordingInternals() {
+        JdkJfrConsumer access = new JdkJfrConsumer() {
             public List<Type> readTypes(RecordingFile file) throws IOException {
                 return file.readTypes();
             }
@@ -72,15 +73,71 @@
             }
 
             @Override
-            public void sort(List<RecordedEvent> events) {
-               Collections.sort(events, (e1, e2) -> Long.compare(e1.endTimeTicks, e2.endTimeTicks));
+            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 Parser newStringParser() {
-                return new StringParser(null, false);
+            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 {
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedStackTrace.java	Fri Sep 13 18:46:07 2019 +0200
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedStackTrace.java	Mon Sep 16 09:45:22 2019 +0200
@@ -29,7 +29,7 @@
 import java.util.Collections;
 import java.util.List;
 
-import jdk.jfr.internal.Type;
+import jdk.jfr.internal.consumer.ObjectContext;
 
 /**
  * A recorded stack trace.
@@ -37,17 +37,8 @@
  * @since 9
  */
 public final class RecordedStackTrace extends RecordedObject {
-
-    static ObjectFactory<RecordedStackTrace> createFactory(Type type, TimeConverter timeConverter) {
-        return new ObjectFactory<RecordedStackTrace>(type, timeConverter) {
-            @Override
-            RecordedStackTrace createTyped(ObjectContext objectContext, long id, Object[] values) {
-                return new RecordedStackTrace(objectContext, values);
-            }
-        };
-    }
-
-    private RecordedStackTrace(ObjectContext objectContext, Object[] values) {
+    // package private
+    RecordedStackTrace(ObjectContext objectContext, Object[] values) {
         super(objectContext, values);
     }
 
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedThread.java	Fri Sep 13 18:46:07 2019 +0200
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedThread.java	Mon Sep 16 09:45:22 2019 +0200
@@ -25,7 +25,7 @@
 
 package jdk.jfr.consumer;
 
-import jdk.jfr.internal.Type;
+import jdk.jfr.internal.consumer.ObjectContext;
 
 /**
  * A recorded thread.
@@ -33,19 +33,10 @@
  * @since 9
  */
 public final class RecordedThread extends RecordedObject {
-
-    static ObjectFactory<RecordedThread> createFactory(Type type, TimeConverter timeConverter) {
-        return new ObjectFactory<RecordedThread>(type, timeConverter) {
-            @Override
-            RecordedThread createTyped(ObjectContext objectContext, long id, Object[] values) {
-                return new RecordedThread(objectContext, id, values);
-            }
-        };
-    }
-
     private final long uniqueId;
 
-    private RecordedThread(ObjectContext objectContext, long id, Object[] values) {
+    // 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	Fri Sep 13 18:46:07 2019 +0200
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordedThreadGroup.java	Mon Sep 16 09:45:22 2019 +0200
@@ -25,7 +25,7 @@
 
 package jdk.jfr.consumer;
 
-import jdk.jfr.internal.Type;
+import jdk.jfr.internal.consumer.ObjectContext;
 
 /**
  * A recorded Java thread group.
@@ -33,17 +33,8 @@
  * @since 9
  */
 public final class RecordedThreadGroup extends RecordedObject {
-
-    static ObjectFactory<RecordedThreadGroup> createFactory(Type type, TimeConverter timeConverter) {
-        return new ObjectFactory<RecordedThreadGroup>(type, timeConverter) {
-            @Override
-            RecordedThreadGroup createTyped(ObjectContext objectContext, long id, Object[] values) {
-                return new RecordedThreadGroup(objectContext, values);
-            }
-        };
-    }
-
-    private RecordedThreadGroup(ObjectContext objectContext, Object[] values) {
+    // package private
+    RecordedThreadGroup(ObjectContext objectContext, Object[] values) {
         super(objectContext, values);
     }
 
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordingFile.java	Fri Sep 13 18:46:07 2019 +0200
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordingFile.java	Mon Sep 16 09:45:22 2019 +0200
@@ -39,6 +39,7 @@
 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;
 
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordingStream.java	Fri Sep 13 18:46:07 2019 +0200
+++ b/src/jdk.jfr/share/classes/jdk/jfr/consumer/RecordingStream.java	Mon Sep 16 09:45:22 2019 +0200
@@ -42,6 +42,7 @@
 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
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/StreamConfiguration.java	Fri Sep 13 18:46:07 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,102 +0,0 @@
-package jdk.jfr.consumer;
-
-import java.time.Instant;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.Consumer;
-
-import jdk.jfr.consumer.Dispatcher.EventDispatcher;
-import jdk.jfr.internal.Utils;
-
-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;
-
-    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.action == 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;
-    }
-
-    public synchronized void clearChanged() {
-        changed = false;
-    }
-}
\ No newline at end of file
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/StringParser.java	Fri Sep 13 18:46:07 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,202 +0,0 @@
-/*
- * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.jfr.consumer;
-
-import java.io.IOException;
-import java.nio.charset.Charset;
-
-import jdk.jfr.internal.consumer.Parser;
-import jdk.jfr.internal.consumer.RecordingInput;
-import jdk.jfr.internal.consumer.StringEncoding;
-
-final class StringParser extends Parser {
-    private final static Charset UTF8 = Charset.forName("UTF-8");
-    private final static Charset LATIN1 = Charset.forName("ISO-8859-1");
-
-    final static class CharsetParser extends Parser {
-        private final Charset charset;
-        private int lastSize;
-        private byte[] buffer = new byte[16];
-        private String lastString;
-
-        CharsetParser(Charset charset) {
-            this.charset = charset;
-        }
-
-        @Override
-        public Object parse(RecordingInput input) throws IOException {
-            int size = input.readInt();
-            ensureSize(size);
-            if (lastSize == size) {
-                boolean equalsLastString = true;
-                for (int i = 0; i < size; i++) {
-                    // TODO: No need to read byte per byte
-                    byte b = input.readByte();
-                    if (buffer[i] != b) {
-                        equalsLastString = false;
-                        buffer[i] = b;
-                    }
-                }
-                if (equalsLastString) {
-                    return lastString;
-                }
-            } else {
-                for (int i = 0; i < size; i++) {
-                    buffer[i] = input.readByte();
-                }
-            }
-            lastString = new String(buffer, 0, size, charset);
-            lastSize = size;
-            return lastString;
-        }
-
-        @Override
-        public void skip(RecordingInput input) throws IOException {
-            int size = input.readInt();
-            input.skipBytes(size);
-        }
-
-        private void ensureSize(int size) {
-            if (buffer.length < size) {
-                buffer = new byte[size];
-            }
-        }
-    }
-
-    final static class CharArrayParser extends Parser {
-        private char[] buffer = new char[16];
-        private int lastSize = -1;
-        private String lastString = null;
-
-        @Override
-        public Object parse(RecordingInput input) throws IOException {
-            int size = input.readInt();
-            ensureSize(size);
-            if (lastSize == size) {
-                boolean equalsLastString = true;
-                for (int i = 0; i < size; i++) {
-                    char c = input.readChar();
-                    if (buffer[i] != c) {
-                        equalsLastString = false;
-                        buffer[i] = c;
-                    }
-                }
-                if (equalsLastString) {
-                    return lastString;
-                }
-            } else {
-                for (int i = 0; i < size; i++) {
-                    buffer[i] = input.readChar();
-                }
-            }
-            lastString = new String(buffer, 0, size);
-            lastSize = size;
-            return lastString;
-        }
-
-        @Override
-        public void skip(RecordingInput input) throws IOException {
-            int size = input.readInt();
-            for (int i = 0; i < size; i++) {
-                input.readChar();
-            }
-        }
-
-        private void ensureSize(int size) {
-            if (buffer.length < size) {
-                buffer = new char[size];
-            }
-        }
-    }
-
-    private final ConstantLookup stringLookup;
-    private final CharArrayParser charArrayParser = new CharArrayParser();
-    private final CharsetParser utf8parser = new CharsetParser(UTF8);
-    private final CharsetParser latin1parser = new CharsetParser(LATIN1);
-    private final boolean event;
-
-    public StringParser(ConstantLookup stringLookup, boolean event) {
-        this.stringLookup = stringLookup;
-        this.event = event;
-    }
-
-    @Override
-    public Object parse(RecordingInput input) throws IOException {
-        byte encoding = input.readByte();
-        if (encoding == StringEncoding.STRING_ENCODING_CONSTANT_POOL) {
-            long key = input.readLong();
-            if (event) {
-                return stringLookup.getCurrentResolved(key);
-            } else {
-                return stringLookup.getCurrent(key);
-            }
-        }
-        if (encoding == StringEncoding.STRING_ENCODING_NULL) {
-            return null;
-        }
-        if (encoding == StringEncoding.STRING_ENCODING_EMPTY_STRING) {
-            return "";
-        }
-        if (encoding == StringEncoding.STRING_ENCODING_CHAR_ARRAY) {
-            return charArrayParser.parse(input);
-        }
-        if (encoding == StringEncoding.STRING_ENCODING_UTF8_BYTE_ARRAY) {
-            return utf8parser.parse(input);
-        }
-        if (encoding == StringEncoding.STRING_ENCODING_LATIN1_BYTE_ARRAY) {
-            return latin1parser.parse(input);
-        }
-        throw new IOException("Unknown string encoding " + encoding);
-    }
-
-    @Override
-    public void skip(RecordingInput input) throws IOException {
-        byte encoding = input.readByte();
-        if (encoding == StringEncoding.STRING_ENCODING_CONSTANT_POOL) {
-            input.readLong();
-            return;
-        }
-        if (encoding == StringEncoding.STRING_ENCODING_EMPTY_STRING) {
-            return;
-        }
-        if (encoding == StringEncoding.STRING_ENCODING_NULL) {
-            return;
-        }
-        if (encoding == StringEncoding.STRING_ENCODING_CHAR_ARRAY) {
-            charArrayParser.skip(input);
-            return;
-        }
-        if (encoding == StringEncoding.STRING_ENCODING_UTF8_BYTE_ARRAY) {
-            utf8parser.skip(input);
-            return;
-        }
-        if (encoding == StringEncoding.STRING_ENCODING_LATIN1_BYTE_ARRAY) {
-            latin1parser.skip(input);
-            return;
-        }
-        throw new IOException("Unknown string encoding " + encoding);
-    }
-}
\ No newline at end of file
--- a/src/jdk.jfr/share/classes/jdk/jfr/consumer/TimeConverter.java	Fri Sep 13 18:46:07 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,72 +0,0 @@
-/*
- * 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.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/consumer/UseCasesStream.java	Fri Sep 13 18:46:07 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,193 +0,0 @@
-/*
- * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.jfr.consumer;
-
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.text.ParseException;
-import java.time.Duration;
-import java.util.ArrayDeque;
-import java.util.Deque;
-
-import jdk.jfr.Configuration;
-import jdk.jfr.EventType;
-import jdk.jfr.ValueDescriptor;
-
-final class UseCasesStream {
-
-    //
-    // Use case: Out-of-the-Box Experience
-    //
-    // - Simple things should be simple
-    // - Pique interest, i.e. a one-liner on Stack Overflow
-    // - Few lines of code as possible
-    // - Should be easier than alternative technologies, like JMX and JVM TI
-    //
-    // - Non-goals: Corner-cases, advanced configuration, releasing resources
-    //
-    public static void outOfTheBox() throws Exception {
-        try (RecordingStream rs = new RecordingStream()) {
-            rs.enable("jdk.ExceptionThrown");
-            rs.onEvent(e -> System.out.println(e.getString("message")));
-            rs.start();
-        }
-
-        try (RecordingStream rs = new RecordingStream()) {
-            rs.enable("jdk.JavaMonitorEnter").withThreshold(Duration.ofMillis(20)).withoutStackTrace();
-            rs.onEvent(System.out::println);
-            rs.start();
-        }
-
-        try (RecordingStream rs = new RecordingStream()) {
-            rs.enable("jdk.CPULoad").withPeriod(Duration.ofSeconds(1));
-            rs.onEvent(System.out::println);
-            rs.start();
-        }
-
-        try (RecordingStream rs = new RecordingStream()) {
-            rs.enable("jdk.GarbageCollection");
-            rs.onEvent(System.out::println);
-            rs.start();
-        }
-    }
-
-    // Use case: Event Forwarding
-    //
-    // - Forward arbitrary event to frameworks such as RxJava, JSON/XML and
-    // Kafka
-    // - Handle flooding
-    // - Performant
-    // - Graceful shutdown
-    // - Non-goals: Filter events
-    //
-    public static void eventForwarding() throws InterruptedException, IOException, ParseException {
-        // KafkaProducer producer = new KafkaProducer<String, String>();
-        try (RecordingStream rs = new RecordingStream(Configuration.getConfiguration("default"))) {
-            rs.setMaxAge(Duration.ofMinutes(5));
-            rs.setMaxSize(1000_000_000L);
-            rs.setOrdered(false);
-            rs.setReuse(true); // default
-            // rs.consume(e -> producer.send(new
-            // ProducerRecord<String,String>("topic", e.getString("key"),
-            // e.getString("value"))));
-            rs.start();
-        }
-        // Write primitive values to XML
-        try (RecordingStream rs = new RecordingStream(Configuration.getConfiguration("deafult"))) {
-            try (PrintWriter p = new PrintWriter(new FileWriter("recording.xml"))) {
-                p.println("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
-                p.println("<events>");
-                rs.onEvent(e -> {
-                    EventType type = e.getEventType();
-                    p.println("  <event type=\"" + type.getName() + "\" start=\"" + e.getStartTime() + "\" end=\"" + e.getEndTime() + "\">");
-                    for (ValueDescriptor field : e.getEventType().getFields()) {
-                        Object value = e.getValue(field.getName());
-                        if (value instanceof Number || field.getTypeName().equals("java.lang.String")) {
-                            p.println("    <value field=\"" + field.getName() + "\">" + value + "</value>");
-                        }
-                    }
-                });
-                rs.start();
-                p.println("</events>");
-            }
-        }
-    }
-
-    // Use case: Repository Access
-    //
-    // - Read the disk repository from another process, for example a side car
-    // in aDocker container
-    // - Be able to configure flush interval from command line or jcmd.
-    // - Graceful shutdown
-    //
-    public static void repositoryAccess() throws IOException, InterruptedException {
-        Path repository = Paths.get("c:\\repository").toAbsolutePath();
-        String command = new String();
-        command += "java -XX:StartFlightRecording:flush-interval=2s";
-        command += "-XX:FlightRecorderOption:repository=" + repository + " Application";
-        Process myProcess = Runtime.getRuntime().exec(command);
-        try (RecordingStream rs = new RecordingStream()) {
-            rs.onEvent(System.out::println);
-            rs.startAsync();
-            Thread.sleep(10_000);
-            myProcess.destroy();
-            Thread.sleep(10_000);
-        }
-    }
-
-    // Use: Tooling
-    //
-    // - Monitor a stream of data for a very long time
-    // - Predictable interval, i.e. once every second
-    // - Notification with minimal delay
-    // - Events with the same period should arrive together
-    // - Consume events in chronological order
-    // - Low overhead
-    //
-    public static void tooling() throws IOException, ParseException {
-        Deque<Double> measurements = new ArrayDeque<>();
-        try (RecordingStream rs = new RecordingStream(Configuration.getConfiguration("profile"))) {
-            rs.setFlushInterval(Duration.ofSeconds(1));
-            rs.setMaxAge(Duration.ofMinutes(1));
-            rs.setOrdered(true); // default
-            rs.setReuse(false);
-            rs.onEvent("jdk.CPULoad", e -> {
-                double d = e.getDouble("totalMachine");
-                measurements.addFirst(d);
-                if (measurements.size() > 60) {
-                    measurements.removeLast();
-                }
-                // repaint();
-            });
-            rs.start();
-        }
-    }
-
-    // Use case: Low Impact
-    //
-    // - Support event subscriptions in a low latency environment (minimal GC
-    // pauses)
-    // - Filter out relevant events to minimize disk overhead and allocation
-    // pressure
-    // - Avoid impact from other recordings
-    // - Avoid observer effect, in particular self-recursion
-    //
-    // Non-goals: one-liner
-    //
-    public static void lowImpact() throws InterruptedException, IOException, ParseException {
-        try (RecordingStream rs = new RecordingStream()) {
-            rs.setReuse(true); // default
-            rs.enable("jdk.JavaMonitorEnter").withThreshold(Duration.ofMillis(10));
-            rs.enable("jdk.ExceptionThrow");
-            rs.onEvent("jdk.JavaMonitorEnter", System.out::println);
-            rs.onEvent("jdk.ExceptionThrow", System.out::println);
-            rs.start();
-        }
-    }
-}
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataReader.java	Fri Sep 13 18:46:07 2019 +0200
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataReader.java	Mon Sep 16 09:45:22 2019 +0200
@@ -49,9 +49,8 @@
 import jdk.jfr.SettingDescriptor;
 import jdk.jfr.ValueDescriptor;
 import jdk.jfr.internal.MetadataDescriptor.Element;
-import jdk.jfr.internal.consumer.Parser;
 import jdk.jfr.internal.consumer.RecordingInput;
-import jdk.jfr.internal.consumer.RecordingInternals;
+import jdk.jfr.internal.consumer.StringParser;
 
 /**
  * Parses metadata.
@@ -68,7 +67,7 @@
         this.input = input;
         int size = input.readInt();
         this.pool = new ArrayList<>(size);
-        Parser p = RecordingInternals.instance().newStringParser();
+        StringParser p = new StringParser(null, false);
         for (int i = 0; i < size; i++) {
             this.pool.add((String) p.parse(input));
         }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/AbstractEventStream.java	Mon Sep 16 09:45:22 2019 +0200
@@ -0,0 +1,285 @@
+/*
+ * 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.JVM;
+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. In particular, it handles:
+ *
+ * - configuration storage
+ * - atomic updates to a configuration
+ * - dispatch mechanism
+ * - error handling
+ * - security
+ *
+ */
+public 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;
+
+    public 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 Exception;
+
+    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.setStartNanos(startNanos);
+            }
+            configuration.setStarted(true);
+        }
+    }
+
+    private void execute() {
+        JVM.getJVM().exclude(Thread.currentThread());
+        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
+        } catch (Exception e) {
+            // TODO: Remove before integrating
+            e.printStackTrace();
+        } 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;
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ChunkParser.java	Mon Sep 16 09:45:22 2019 +0200
@@ -0,0 +1,434 @@
+/*
+ * 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 {
+        final boolean reuse;
+        final boolean ordered;
+        final InternalEventFilter eventFilter;
+
+        long filterStart;
+        long filterEnd;
+
+        public ParserConfiguration(long filterStart, long filterEnd, boolean reuse, boolean ordered, InternalEventFilter 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, InternalEventFilter.ACCEPT_ALL);
+        }
+    }
+
+    // Checkpoint that finishes a flush segment
+    static final byte CHECKPOINT_FLUSH_MASK = 1;
+    // Checkpoint contains chunk header information in the first pool
+    static final byte CHECKPOINT_CHUNK_HEADER_MASK = 2;
+    // Checkpoint contains only statics that will not change from chunk to chunk
+    static final byte CHECKPOINT_STATICS_MASK = 4;
+    // Checkpoint contains thread related information
+    static final byte CHECKPOINT_THREADS_MASK = 8;
+
+    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 long pollInterval;
+    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());
+    }
+
+    public ChunkParser(RecordingInput input, ParserConfiguration pc) throws IOException {
+       this(new ChunkHeader(input), null, pc);
+    }
+
+    public 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.pollInterval = 1000;
+            this.constantLookups = new LongMap<>();
+            this.previousMetadata = null;
+        } else {
+            this.constantLookups = previous.constantLookups;
+            this.previousMetadata = previous.metadata;
+            this.pollInterval = previous.pollInterval;
+            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);
+    }
+
+    public 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.
+     */
+    public 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();
+
+            if (typeId != 0) { // Not metadata event
+                Parser p = parsers.get(typeId);
+                if (p instanceof EventParser) {
+                    EventParser ep = (EventParser) p;
+                    RecordedEvent event = ep.parse(input);
+                    if (event != null) {
+                        input.position(pos + size);
+                        return event;
+                    }
+                }
+                if (typeId == 1 && flushOperation != null) { // checkpoint event
+                    parseCheckpoint();
+                }
+            }
+            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 c = input.readByte();
+        if ((c & CHECKPOINT_FLUSH_MASK)== 1) {
+            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(pollInterval);
+        }
+    }
+
+    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();
+    }
+
+    public 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	Mon Sep 16 09:45:22 2019 +0200
@@ -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	Mon Sep 16 09:45:22 2019 +0200
@@ -0,0 +1,191 @@
+/*
+ * 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;
+    }
+
+    public boolean isResolved(long id) {
+        if (objects.hasKey(id)) {
+            return objects.isSetId(id, RESOLUTION_FINISHED);
+        }
+        return false;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/Dispatcher.java	Mon Sep 16 09:45:22 2019 +0200
@@ -0,0 +1,144 @@
+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 {
+
+    public final static class EventDispatcher {
+        final static EventDispatcher[] NO_DISPATCHERS = new EventDispatcher[0];
+        final String eventName;
+        public final Consumer<RecordedEvent> action;
+
+        public EventDispatcher(Consumer<RecordedEvent> action) {
+            this(null, action);
+        }
+
+        public EventDispatcher(String eventName, Consumer<RecordedEvent> action) {
+            this.eventName = eventName;
+            this.action = action;
+        }
+
+        public void offer(RecordedEvent event) {
+            action.accept(event);
+        }
+
+        public boolean accepts(EventType eventType) {
+            return (eventName == null || eventType.getName().equals(eventName));
+        }
+    }
+
+    final Consumer<Throwable>[] errorActions;
+    final Runnable[] flushActions;
+    final Runnable[] closeActions;
+    final EventDispatcher[] dispatchers;
+    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({"unchecked","rawtypes"})
+    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;
+    }
+
+    private static InternalEventFilter buildFilter(EventDispatcher[] dispatchers) {
+        InternalEventFilter ef = new InternalEventFilter();
+        for (EventDispatcher ed : dispatchers) {
+            String name = ed.eventName;
+            if (name == null) {
+                return InternalEventFilter.ACCEPT_ALL;
+            }
+            ef.setThreshold(name, 0);
+        }
+        return ef;
+    }
+
+    protected final 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;
+        }
+        for (int i = 0; i < dispatchers.length; i++) {
+            try {
+                dispatchers[i].offer(event);
+            } catch (Exception e) {
+                handleError(e);
+            }
+        }
+    }
+
+    public 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> conusmer = (Consumer<Throwable>) consumers[i];
+            conusmer.accept(e);
+        }
+    }
+
+    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);
+            }
+        }
+    }
+
+    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	Mon Sep 16 09:45:22 2019 +0200
@@ -0,0 +1,183 @@
+/*
+ * 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.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 chunkParser;
+    private long chunkStartNanos;
+    private RecordedEvent[] sortedList;
+
+    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 Exception {
+        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;
+        }
+        chunkStartNanos = repositoryFiles.getTimestamp(path);
+        try (RecordingInput input = new RecordingInput(path.toFile(), fileAccess)) {
+            chunkParser = new ChunkParser(input, disp.parserConfiguration);
+            long segmentStart = chunkParser.getStartNanos() + chunkParser.getChunkDuration();
+            long filterStart = validStartTime ? disp.startNanos : segmentStart;
+            long filterEnd = disp.endTime != null ? disp.endNanos: Long.MAX_VALUE;
+
+            while (!isClosed()) {
+                boolean awaitnewEvent = false;
+                while (!isClosed() && !chunkParser.isChunkFinished()) {
+                    disp = dispatcher();
+                    ParserConfiguration pc = disp.parserConfiguration;
+                    pc.filterStart = filterStart;
+                    pc.filterEnd = filterEnd;
+                    chunkParser.updateConfiguration(pc, true);
+                    chunkParser.setFlushOperation(getFlushOperation());
+                    if (pc.ordered) {
+                        awaitnewEvent = processOrdered(disp, awaitnewEvent);
+                    } else {
+                        awaitnewEvent = processUnordered(disp, awaitnewEvent);
+                    }
+                    if (chunkParser.getStartNanos() + chunkParser.getChunkDuration() > filterEnd) {
+                        close();
+                        return;
+                    }
+                }
+
+                if (isClosed()) {
+                    return;
+                }
+                long durationNanos = chunkParser.getChunkDuration();
+                path = repositoryFiles.nextPath(chunkStartNanos + durationNanos);
+                if (path == null) {
+                    return; // stream closed
+                }
+                chunkStartNanos = repositoryFiles.getTimestamp(path);
+                input.setFile(path);
+                chunkParser = chunkParser.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 (sortedList == null) {
+            sortedList = new RecordedEvent[100_000];
+        }
+        int index = 0;
+        while (true) {
+            RecordedEvent e = chunkParser.readStreamingEvent(awaitNewEvents);
+            if (e == null) {
+                // wait for new event with next call to
+                // readStreamingEvent()
+                awaitNewEvents = true;
+                break;
+            }
+            awaitNewEvents = false;
+            if (index == sortedList.length) {
+                sortedList = Arrays.copyOf(sortedList, sortedList.length * 2);
+            }
+            sortedList[index++] = e;
+        }
+
+        // no events found
+        if (index == 0 && chunkParser.isChunkFinished()) {
+            return awaitNewEvents;
+        }
+        // at least 2 events, sort them
+        if (index > 1) {
+            Arrays.sort(sortedList, 0, index, EVENT_COMPARATOR);
+        }
+        for (int i = 0; i < index; i++) {
+            c.dispatch(sortedList[i]);
+        }
+        return awaitNewEvents;
+    }
+
+    private boolean processUnordered(Dispatcher c, boolean awaitNewEvents) throws IOException {
+        while (true) {
+            RecordedEvent e = chunkParser.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	Mon Sep 16 09:45:22 2019 +0200
@@ -0,0 +1,146 @@
+/*
+ * 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 chunkParser;
+    private RecordedEvent[] sortedList;
+
+    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;
+        }
+
+        chunkParser = new ChunkParser(input, disp.parserConfiguration);
+        while (!isClosed()) {
+            if (chunkParser.getStartNanos() > end) {
+                close();
+                return;
+            }
+            disp = dispatcher();
+            disp.parserConfiguration.filterStart = start;
+            disp.parserConfiguration.filterEnd = end;
+            chunkParser.updateConfiguration(disp.parserConfiguration, true);
+            chunkParser.setFlushOperation(getFlushOperation());
+            if (disp.parserConfiguration.ordered) {
+                processOrdered(disp);
+            } else {
+                processUnordered(disp);
+            }
+            if (isClosed() || chunkParser.isLastChunk()) {
+                return;
+            }
+            chunkParser = chunkParser.nextChunkParser();
+        }
+    }
+
+    private void processOrdered(Dispatcher c) throws IOException {
+        if (sortedList == null) {
+            sortedList = new RecordedEvent[10_000];
+        }
+        RecordedEvent event;
+        int index = 0;
+        while (true) {
+            event = chunkParser.readEvent();
+            if (event == null) {
+                Arrays.sort(sortedList, 0, index, EVENT_COMPARATOR);
+                for (int i = 0; i < index; i++) {
+                    c.dispatch(sortedList[i]);
+                }
+                return;
+            }
+            if (index == sortedList.length) {
+                RecordedEvent[] tmp = sortedList;
+                sortedList = new RecordedEvent[2 * tmp.length];
+                System.arraycopy(tmp, 0, sortedList, 0, tmp.length);
+            }
+            sortedList[index++] = event;
+        }
+    }
+
+    private void processUnordered(Dispatcher c) throws IOException {
+        while (!isClosed()) {
+            RecordedEvent event = chunkParser.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	Mon Sep 16 09:45:22 2019 +0200
@@ -0,0 +1,203 @@
+/*
+ * 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}.
+ *
+ */
+public 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();
+            PRIVATE_ACCESS.setStartTicks(event, startTicks);
+            PRIVATE_ACCESS.setEndTicks(event, endTicks);
+            Object[] values = PRIVATE_ACCESS.eventValues(event);
+            for (int i = 0; i < values.length; i++) {
+                values[i] = parsers[startIndex + i].parse(input);
+            }
+//            event.startTimeTicks = startTicks;
+//            event.endTimeTicks = endTicks;
+//            Object[] values = event.objects;
+//            for (int i = 0; i < values.length; i++) {
+//
+//            }
+            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;
+    }
+
+    public 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/JdkJfrConsumer.java	Mon Sep 16 09:45:22 2019 +0200
@@ -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);
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ObjectContext.java	Mon Sep 16 09:45:22 2019 +0200
@@ -0,0 +1,63 @@
+/*
+ * 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.List;
+import java.util.Map;
+
+import jdk.jfr.EventType;
+import jdk.jfr.ValueDescriptor;
+
+public final class ObjectContext {
+    private final Map<ValueDescriptor, ObjectContext> contextLookup;
+
+    public final EventType eventType;
+    public final List<ValueDescriptor> fields;
+    public final TimeConverter timeConverter;
+
+    public 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;
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ObjectFactory.java	Mon Sep 16 09:45:22 2019 +0200
@@ -0,0 +1,154 @@
+/*
+ * 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();
+
+    public final static String TYPE_PREFIX_VERSION_1 = "com.oracle.jfr.types.";
+    public 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";
+
+    public 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;
+
+    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/ParserFactory.java	Mon Sep 16 09:45:22 2019 +0200
@@ -0,0 +1,380 @@
+/*
+ * 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);
+        }
+        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);
+            }
+        }
+    }
+
+    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);
+            }
+        }
+    }
+
+    public 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();
+        }
+    }
+
+    public 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();
+        }
+    }
+}
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/RecordingInternals.java	Fri Sep 13 18:46:07 2019 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-/*
- * 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.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() {
-        if (INSTANCE == null) {
-            // Force initialization
-            try {
-                Class<?> c = RecordedObject.class;
-                Class.forName(c.getName(), true, c.getClassLoader());
-            } catch (ClassNotFoundException e) {
-                throw new InternalError("Should not happen");
-            }
-        }
-        return INSTANCE;
-    }
-
-    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);
-
-    public abstract Parser newStringParser();
-}
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/StreamConfiguration.java	Fri Sep 13 18:46:07 2019 +0200
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/StreamConfiguration.java	Mon Sep 16 09:45:22 2019 +0200
@@ -1,25 +1,103 @@
-/*
- * 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.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;
+
+    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.action == 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;
+    }
+
+    public synchronized void clearChanged() {
+        changed = false;
+    }
+}
\ No newline at end of file
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/StringEncoding.java	Fri Sep 13 18:46:07 2019 +0200
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/StringEncoding.java	Mon Sep 16 09:45:22 2019 +0200
@@ -30,5 +30,4 @@
     public final static byte STRING_ENCODING_UTF8_BYTE_ARRAY = (byte) 3;
     public final static byte STRING_ENCODING_CHAR_ARRAY = (byte) 4;
     public final static byte STRING_ENCODING_LATIN1_BYTE_ARRAY = (byte) 5;
-
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/StringParser.java	Mon Sep 16 09:45:22 2019 +0200
@@ -0,0 +1,202 @@
+/*
+ * 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;
+
+import jdk.jfr.internal.consumer.Parser;
+import jdk.jfr.internal.consumer.RecordingInput;
+import jdk.jfr.internal.consumer.StringEncoding;
+
+public final class StringParser extends Parser {
+    private final static Charset UTF8 = Charset.forName("UTF-8");
+    private final static Charset LATIN1 = Charset.forName("ISO-8859-1");
+
+    final static class CharsetParser extends Parser {
+        private final Charset charset;
+        private int lastSize;
+        private byte[] buffer = new byte[16];
+        private String lastString;
+
+        CharsetParser(Charset charset) {
+            this.charset = charset;
+        }
+
+        @Override
+        public Object parse(RecordingInput input) throws IOException {
+            int size = input.readInt();
+            ensureSize(size);
+            if (lastSize == size) {
+                boolean equalsLastString = true;
+                for (int i = 0; i < size; i++) {
+                    // TODO: No need to read byte per byte
+                    byte b = input.readByte();
+                    if (buffer[i] != b) {
+                        equalsLastString = false;
+                        buffer[i] = b;
+                    }
+                }
+                if (equalsLastString) {
+                    return lastString;
+                }
+            } else {
+                for (int i = 0; i < size; i++) {
+                    buffer[i] = input.readByte();
+                }
+            }
+            lastString = new String(buffer, 0, size, charset);
+            lastSize = size;
+            return lastString;
+        }
+
+        @Override
+        public void skip(RecordingInput input) throws IOException {
+            int size = input.readInt();
+            input.skipBytes(size);
+        }
+
+        private void ensureSize(int size) {
+            if (buffer.length < size) {
+                buffer = new byte[size];
+            }
+        }
+    }
+
+    final static class CharArrayParser extends Parser {
+        private char[] buffer = new char[16];
+        private int lastSize = -1;
+        private String lastString = null;
+
+        @Override
+        public Object parse(RecordingInput input) throws IOException {
+            int size = input.readInt();
+            ensureSize(size);
+            if (lastSize == size) {
+                boolean equalsLastString = true;
+                for (int i = 0; i < size; i++) {
+                    char c = input.readChar();
+                    if (buffer[i] != c) {
+                        equalsLastString = false;
+                        buffer[i] = c;
+                    }
+                }
+                if (equalsLastString) {
+                    return lastString;
+                }
+            } else {
+                for (int i = 0; i < size; i++) {
+                    buffer[i] = input.readChar();
+                }
+            }
+            lastString = new String(buffer, 0, size);
+            lastSize = size;
+            return lastString;
+        }
+
+        @Override
+        public void skip(RecordingInput input) throws IOException {
+            int size = input.readInt();
+            for (int i = 0; i < size; i++) {
+                input.readChar();
+            }
+        }
+
+        private void ensureSize(int size) {
+            if (buffer.length < size) {
+                buffer = new char[size];
+            }
+        }
+    }
+
+    private final ConstantLookup stringLookup;
+    private final CharArrayParser charArrayParser = new CharArrayParser();
+    private final CharsetParser utf8parser = new CharsetParser(UTF8);
+    private final CharsetParser latin1parser = new CharsetParser(LATIN1);
+    private final boolean event;
+
+    public StringParser(ConstantLookup stringLookup, boolean event) {
+        this.stringLookup = stringLookup;
+        this.event = event;
+    }
+
+    @Override
+    public Object parse(RecordingInput input) throws IOException {
+        byte encoding = input.readByte();
+        if (encoding == StringEncoding.STRING_ENCODING_CONSTANT_POOL) {
+            long key = input.readLong();
+            if (event) {
+                return stringLookup.getCurrentResolved(key);
+            } else {
+                return stringLookup.getCurrent(key);
+            }
+        }
+        if (encoding == StringEncoding.STRING_ENCODING_NULL) {
+            return null;
+        }
+        if (encoding == StringEncoding.STRING_ENCODING_EMPTY_STRING) {
+            return "";
+        }
+        if (encoding == StringEncoding.STRING_ENCODING_CHAR_ARRAY) {
+            return charArrayParser.parse(input);
+        }
+        if (encoding == StringEncoding.STRING_ENCODING_UTF8_BYTE_ARRAY) {
+            return utf8parser.parse(input);
+        }
+        if (encoding == StringEncoding.STRING_ENCODING_LATIN1_BYTE_ARRAY) {
+            return latin1parser.parse(input);
+        }
+        throw new IOException("Unknown string encoding " + encoding);
+    }
+
+    @Override
+    public void skip(RecordingInput input) throws IOException {
+        byte encoding = input.readByte();
+        if (encoding == StringEncoding.STRING_ENCODING_CONSTANT_POOL) {
+            input.readLong();
+            return;
+        }
+        if (encoding == StringEncoding.STRING_ENCODING_EMPTY_STRING) {
+            return;
+        }
+        if (encoding == StringEncoding.STRING_ENCODING_NULL) {
+            return;
+        }
+        if (encoding == StringEncoding.STRING_ENCODING_CHAR_ARRAY) {
+            charArrayParser.skip(input);
+            return;
+        }
+        if (encoding == StringEncoding.STRING_ENCODING_UTF8_BYTE_ARRAY) {
+            utf8parser.skip(input);
+            return;
+        }
+        if (encoding == StringEncoding.STRING_ENCODING_LATIN1_BYTE_ARRAY) {
+            latin1parser.skip(input);
+            return;
+        }
+        throw new IOException("Unknown string encoding " + encoding);
+    }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/TimeConverter.java	Mon Sep 16 09:45:22 2019 +0200
@@ -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
+ */
+public 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/internal/tool/EventPrintWriter.java	Fri Sep 13 18:46:07 2019 +0200
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/EventPrintWriter.java	Mon Sep 16 09:45:22 2019 +0200
@@ -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	Fri Sep 13 18:46:07 2019 +0200
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/tool/Metadata.java	Mon Sep 16 09:45:22 2019 +0200
@@ -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);