8257602: Introduce JFR Event Throttling and new jdk.ObjectAllocationSample event (enabled by default)

Co-authored-by: Jaroslav Bachorik <jbachorik@openjdk.org>
Reviewed-by: egahlin, jbachorik
diff --git a/make/src/classes/build/tools/jfr/GenerateJfrFiles.java b/make/src/classes/build/tools/jfr/GenerateJfrFiles.java
index cf6a08c..dbaa8db 100644
--- a/make/src/classes/build/tools/jfr/GenerateJfrFiles.java
+++ b/make/src/classes/build/tools/jfr/GenerateJfrFiles.java
@@ -172,6 +172,7 @@
         boolean startTime;
         String period = "";
         boolean cutoff;
+        boolean throttle;
         boolean experimental;
         long id;
         boolean isEvent;
@@ -194,6 +195,7 @@
             pos.writeBoolean(startTime);
             pos.writeUTF(period);
             pos.writeBoolean(cutoff);
+            pos.writeBoolean(throttle);
             pos.writeBoolean(experimental);
             pos.writeLong(id);
             pos.writeBoolean(isEvent);
@@ -490,6 +492,7 @@
                 currentType.startTime = getBoolean(attributes, "startTime", true);
                 currentType.period = getString(attributes, "period");
                 currentType.cutoff = getBoolean(attributes, "cutoff", false);
+                currentType.throttle = getBoolean(attributes, "throttle", false);
                 currentType.commitState = getString(attributes, "commitState");
                 currentType.isEvent = "Event".equals(qName);
                 currentType.isRelation = "Relation".equals(qName);
@@ -759,6 +762,7 @@
             out.write("  void set_starttime(const Ticks&) const {}");
             out.write("  void set_endtime(const Ticks&) const {}");
             out.write("  bool should_commit() const { return false; }");
+            out.write("  bool is_started() const { return false; }");
             out.write("  static bool is_enabled() { return false; }");
             out.write("  void commit() {}");
             out.write("};");
@@ -820,6 +824,7 @@
             out.write("  static const bool hasStackTrace = " + event.stackTrace + ";");
             out.write("  static const bool isInstant = " + !event.startTime + ";");
             out.write("  static const bool hasCutoff = " + event.cutoff + ";");
+            out.write("  static const bool hasThrottle = " + event.throttle + ";");
             out.write("  static const bool isRequestable = " + !event.period.isEmpty() + ";");
             out.write("  static const JfrEventId eventId = Jfr" + event.name + "Event;");
             out.write("");
diff --git a/src/hotspot/share/gc/shared/allocTracer.cpp b/src/hotspot/share/gc/shared/allocTracer.cpp
index db89e4c..9de1d5b 100644
--- a/src/hotspot/share/gc/shared/allocTracer.cpp
+++ b/src/hotspot/share/gc/shared/allocTracer.cpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2020, 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
@@ -24,14 +24,86 @@
 
 #include "precompiled.hpp"
 #include "gc/shared/allocTracer.hpp"
+#include "gc/shared/threadLocalAllocBuffer.inline.hpp"
 #include "jfr/jfrEvents.hpp"
-#include "runtime/handles.hpp"
 #include "utilities/globalDefinitions.hpp"
 #include "utilities/macros.hpp"
 #if INCLUDE_JFR
 #include "jfr/support/jfrAllocationTracer.hpp"
 #endif
 
+static THREAD_LOCAL int64_t _last_allocated_bytes = 0;
+
+inline void send_allocation_sample(const Klass* klass, int64_t allocated_bytes) {
+  assert(allocated_bytes > 0, "invariant");
+  EventObjectAllocationSample event;
+  if (event.should_commit()) {
+    const size_t weight = allocated_bytes - _last_allocated_bytes;
+    assert(weight > 0, "invariant");
+    event.set_objectClass(klass);
+    event.set_weight(weight);
+    event.commit();
+    _last_allocated_bytes = allocated_bytes;
+  }
+}
+
+inline bool send_allocation_sample_with_result(const Klass* klass, int64_t allocated_bytes) {
+  assert(allocated_bytes > 0, "invariant");
+  EventObjectAllocationSample event;
+  if (event.should_commit()) {
+    const size_t weight = allocated_bytes - _last_allocated_bytes;
+    assert(weight > 0, "invariant");
+    event.set_objectClass(klass);
+    event.set_weight(weight);
+    event.commit();
+    _last_allocated_bytes = allocated_bytes;
+    return true;
+  }
+  return false;
+}
+
+inline intptr_t estimate_tlab_size_bytes(Thread* thread) {
+  assert(thread != NULL, "invariant");
+  const size_t desired_tlab_size_bytes = thread->tlab().desired_size() * HeapWordSize;
+  const size_t alignment_reserve_bytes = thread->tlab().alignment_reserve_in_bytes();
+  assert(desired_tlab_size_bytes > alignment_reserve_bytes, "invariant");
+  return static_cast<intptr_t>(desired_tlab_size_bytes - alignment_reserve_bytes);
+}
+
+inline int64_t load_allocated_bytes(Thread* thread) {
+  const int64_t allocated_bytes = thread->allocated_bytes();
+  if (allocated_bytes < _last_allocated_bytes) {
+    // A hw thread can detach and reattach to the VM, and when it does,
+    // it gets a new JavaThread representation. The thread local variable
+    // tracking _last_allocated_bytes is mapped to the existing hw thread,
+    // so it needs to be reset.
+    _last_allocated_bytes = 0;
+  }
+  return allocated_bytes == _last_allocated_bytes ? 0 : allocated_bytes;
+}
+
+// To avoid large objects from being undersampled compared to the regular TLAB samples,
+// the data amount is normalized as if it was a TLAB, giving a number of TLAB sampling attempts to the large object.
+static void normalize_as_tlab_and_send_allocation_samples(Klass* klass, intptr_t obj_alloc_size_bytes, Thread* thread) {
+  const int64_t allocated_bytes = load_allocated_bytes(thread);
+  assert(allocated_bytes > 0, "invariant"); // obj_alloc_size_bytes is already attributed to allocated_bytes at this point.
+  if (!UseTLAB) {
+    send_allocation_sample(klass, allocated_bytes);
+    return;
+  }
+  const intptr_t tlab_size_bytes = estimate_tlab_size_bytes(thread);
+  if (allocated_bytes - _last_allocated_bytes < tlab_size_bytes) {
+    return;
+  }
+  assert(obj_alloc_size_bytes > 0, "invariant");
+  do {
+    if (send_allocation_sample_with_result(klass, allocated_bytes)) {
+      return;
+    }
+    obj_alloc_size_bytes -= tlab_size_bytes;
+  } while (obj_alloc_size_bytes > 0);
+}
+
 void AllocTracer::send_allocation_outside_tlab(Klass* klass, HeapWord* obj, size_t alloc_size, Thread* thread) {
   JFR_ONLY(JfrAllocationTracer tracer(obj, alloc_size, thread);)
   EventObjectAllocationOutsideTLAB event;
@@ -40,6 +112,7 @@
     event.set_allocationSize(alloc_size);
     event.commit();
   }
+  normalize_as_tlab_and_send_allocation_samples(klass, static_cast<intptr_t>(alloc_size), thread);
 }
 
 void AllocTracer::send_allocation_in_new_tlab(Klass* klass, HeapWord* obj, size_t tlab_size, size_t alloc_size, Thread* thread) {
@@ -51,6 +124,11 @@
     event.set_tlabSize(tlab_size);
     event.commit();
   }
+  const int64_t allocated_bytes = load_allocated_bytes(thread);
+  if (allocated_bytes == 0) {
+    return;
+  }
+  send_allocation_sample(klass, allocated_bytes);
 }
 
 void AllocTracer::send_allocation_requiring_gc_event(size_t size, uint gcId) {
diff --git a/src/hotspot/share/jfr/jni/jfrJniMethod.cpp b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp
index eb5ac62..3e8404e 100644
--- a/src/hotspot/share/jfr/jni/jfrJniMethod.cpp
+++ b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp
@@ -35,6 +35,7 @@
 #include "jfr/recorder/repository/jfrRepository.hpp"
 #include "jfr/recorder/repository/jfrChunkRotation.hpp"
 #include "jfr/recorder/repository/jfrChunkWriter.hpp"
+#include "jfr/recorder/service/jfrEventThrottler.hpp"
 #include "jfr/recorder/service/jfrOptionSet.hpp"
 #include "jfr/recorder/stacktrace/jfrStackTraceRepository.hpp"
 #include "jfr/recorder/stringpool/jfrStringPool.hpp"
@@ -175,6 +176,11 @@
   return JfrEventSetting::set_cutoff(event_type_id, cutoff_ticks) ? JNI_TRUE : JNI_FALSE;
 NO_TRANSITION_END
 
+NO_TRANSITION(jboolean, jfr_set_throttle(JNIEnv* env, jobject jvm, jlong event_type_id, jlong event_sample_size, jlong period_ms))
+  JfrEventThrottler::configure(static_cast<JfrEventId>(event_type_id), event_sample_size, period_ms);
+  return JNI_TRUE;
+NO_TRANSITION_END
+
 NO_TRANSITION(jboolean, jfr_should_rotate_disk(JNIEnv* env, jobject jvm))
   return JfrChunkRotation::should_rotate() ? JNI_TRUE : JNI_FALSE;
 NO_TRANSITION_END
diff --git a/src/hotspot/share/jfr/jni/jfrJniMethod.hpp b/src/hotspot/share/jfr/jni/jfrJniMethod.hpp
index 975ecef..a193df5 100644
--- a/src/hotspot/share/jfr/jni/jfrJniMethod.hpp
+++ b/src/hotspot/share/jfr/jni/jfrJniMethod.hpp
@@ -132,6 +132,8 @@
 
 jboolean JNICALL jfr_set_cutoff(JNIEnv* env, jobject jvm, jlong event_type_id, jlong cutoff_ticks);
 
+jboolean JNICALL jfr_set_throttle(JNIEnv* env, jobject jvm, jlong event_type_id, jlong event_sample_size, jlong period_ms);
+
 void JNICALL jfr_emit_old_object_samples(JNIEnv* env, jobject jvm, jlong cutoff_ticks, jboolean, jboolean);
 
 jboolean JNICALL jfr_should_rotate_disk(JNIEnv* env, jobject jvm);
diff --git a/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp b/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp
index 53d55e4..2d11467 100644
--- a/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp
+++ b/src/hotspot/share/jfr/jni/jfrJniMethodRegistration.cpp
@@ -81,6 +81,7 @@
       (char*)"setForceInstrumentation", (char*)"(Z)V", (void*)jfr_set_force_instrumentation,
       (char*)"getUnloadedEventClassCount", (char*)"()J", (void*)jfr_get_unloaded_event_classes_count,
       (char*)"setCutoff", (char*)"(JJ)Z", (void*)jfr_set_cutoff,
+      (char*)"setThrottle", (char*)"(JJJ)Z", (void*)jfr_set_throttle,
       (char*)"emitOldObjectSamples", (char*)"(JZZ)V", (void*)jfr_emit_old_object_samples,
       (char*)"shouldRotateDisk", (char*)"()Z", (void*)jfr_should_rotate_disk,
       (char*)"exclude", (char*)"(Ljava/lang/Thread;)V", (void*)jfr_exclude_thread,
diff --git a/src/hotspot/share/jfr/leakprofiler/sampling/objectSampler.cpp b/src/hotspot/share/jfr/leakprofiler/sampling/objectSampler.cpp
index 797f9e2..c5ba05c 100644
--- a/src/hotspot/share/jfr/leakprofiler/sampling/objectSampler.cpp
+++ b/src/hotspot/share/jfr/leakprofiler/sampling/objectSampler.cpp
@@ -175,7 +175,7 @@
   record_stacktrace(thread);
   // try enter critical section
   JfrTryLock tryLock(&_lock);
-  if (!tryLock.has_lock()) {
+  if (!tryLock.acquired()) {
     log_trace(jfr, oldobject, sampling)("Skipping old object sample due to lock contention");
     return;
   }
diff --git a/src/hotspot/share/jfr/metadata/metadata.xml b/src/hotspot/share/jfr/metadata/metadata.xml
index d4484f7..1115060 100644
--- a/src/hotspot/share/jfr/metadata/metadata.xml
+++ b/src/hotspot/share/jfr/metadata/metadata.xml
@@ -614,6 +614,12 @@
     <Field type="ulong" contentType="bytes" name="allocationSize" label="Allocation Size" />
   </Event>
 
+  <Event name="ObjectAllocationSample" category="Java Application" label="Object Allocation Sample" thread="true" stackTrace="true" startTime="false" throttle="true">
+    <Field type="Class" name="objectClass" label="Object Class" description="Class of allocated object" />
+    <Field type="long" contentType="bytes" name="weight" label="Sample Weight"
+      description="The relative weight of the sample. Aggregating the weights for a large number of samples, for a particular class, thread or stack trace, gives a statistically accurate representation of the allocation pressure" />
+  </Event>
+
   <Event name="OldObjectSample" category="Java Virtual Machine, Profiling" label="Old Object Sample" description="A potential memory leak" stackTrace="true" thread="true"
     startTime="false" cutoff="true">
     <Field type="Ticks" name="allocationTime" label="Allocation Time" />
diff --git a/src/hotspot/share/jfr/metadata/metadata.xsd b/src/hotspot/share/jfr/metadata/metadata.xsd
index bded74c..017307e 100644
--- a/src/hotspot/share/jfr/metadata/metadata.xsd
+++ b/src/hotspot/share/jfr/metadata/metadata.xsd
@@ -70,6 +70,7 @@
               <xs:attribute name="stackTrace" type="xs:boolean" use="optional" />
               <xs:attribute name="period" type="periodType" use="optional" />
               <xs:attribute name="cutoff" type="xs:boolean" use="optional" />
+              <xs:attribute name="throttle" type="xs:boolean" use="optional" />
               <xs:attribute name="commitState" type="xs:string" use="optional" />
             </xs:complexType>
           </xs:element>
diff --git a/src/hotspot/share/jfr/recorder/jfrRecorder.cpp b/src/hotspot/share/jfr/recorder/jfrRecorder.cpp
index 254b5b5..baf7fcc 100644
--- a/src/hotspot/share/jfr/recorder/jfrRecorder.cpp
+++ b/src/hotspot/share/jfr/recorder/jfrRecorder.cpp
@@ -33,6 +33,7 @@
 #include "jfr/recorder/jfrRecorder.hpp"
 #include "jfr/recorder/checkpoint/jfrCheckpointManager.hpp"
 #include "jfr/recorder/repository/jfrRepository.hpp"
+#include "jfr/recorder/service/jfrEventThrottler.hpp"
 #include "jfr/recorder/service/jfrOptionSet.hpp"
 #include "jfr/recorder/service/jfrPostBox.hpp"
 #include "jfr/recorder/service/jfrRecorderService.hpp"
@@ -289,6 +290,9 @@
   if (!create_thread_sampling()) {
     return false;
   }
+  if (!create_event_throttler()) {
+    return false;
+  }
   return true;
 }
 
@@ -362,6 +366,10 @@
   return _thread_sampling != NULL;
 }
 
+bool JfrRecorder::create_event_throttler() {
+  return JfrEventThrottler::create();
+}
+
 void JfrRecorder::destroy_components() {
   JfrJvmtiAgent::destroy();
   if (_post_box != NULL) {
@@ -396,6 +404,7 @@
     JfrThreadSampling::destroy();
     _thread_sampling = NULL;
   }
+  JfrEventThrottler::destroy();
 }
 
 bool JfrRecorder::create_recorder_thread() {
diff --git a/src/hotspot/share/jfr/recorder/jfrRecorder.hpp b/src/hotspot/share/jfr/recorder/jfrRecorder.hpp
index 1fbda65..fb7f291 100644
--- a/src/hotspot/share/jfr/recorder/jfrRecorder.hpp
+++ b/src/hotspot/share/jfr/recorder/jfrRecorder.hpp
@@ -53,6 +53,7 @@
   static bool create_storage();
   static bool create_stringpool();
   static bool create_thread_sampling();
+  static bool create_event_throttler();
   static bool create_components();
   static void destroy_components();
   static void on_recorder_thread_exit();
diff --git a/src/hotspot/share/jfr/recorder/service/jfrEvent.hpp b/src/hotspot/share/jfr/recorder/service/jfrEvent.hpp
index f7ca268..7c1e953 100644
--- a/src/hotspot/share/jfr/recorder/service/jfrEvent.hpp
+++ b/src/hotspot/share/jfr/recorder/service/jfrEvent.hpp
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2020, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -26,6 +26,7 @@
 #define SHARE_JFR_RECORDER_SERVICE_JFREVENT_HPP
 
 #include "jfr/recorder/jfrEventSetting.inline.hpp"
+#include "jfr/recorder/service/jfrEventThrottler.hpp"
 #include "jfr/recorder/stacktrace/jfrStackTraceRepository.hpp"
 #include "jfr/utilities/jfrTime.hpp"
 #include "jfr/utilities/jfrTypes.hpp"
@@ -63,9 +64,14 @@
   jlong _start_time;
   jlong _end_time;
   bool _started;
+  bool _untimed;
+  bool _should_commit;
+  bool _evaluated;
 
  protected:
-  JfrEvent(EventStartTime timing=TIMED) : _start_time(0), _end_time(0), _started(false)
+  JfrEvent(EventStartTime timing=TIMED) : _start_time(0), _end_time(0),
+                                          _started(false), _untimed(timing == UNTIMED),
+                                          _should_commit(false), _evaluated(false)
 #ifdef ASSERT
   , _verifier()
 #endif
@@ -79,19 +85,12 @@
   }
 
   void commit() {
-    if (!should_commit()) {
+    assert(!_verifier.committed(), "event already committed");
+    if (!should_write()) {
       return;
     }
-    assert(!_verifier.committed(), "event already committed");
-    if (_start_time == 0) {
-      set_starttime(JfrTicks::now());
-    } else if (_end_time == 0) {
-      set_endtime(JfrTicks::now());
-    }
-    if (should_write()) {
-      write_event();
-      DEBUG_ONLY(_verifier.set_committed();)
-    }
+    write_event();
+    DEBUG_ONLY(_verifier.set_committed();)
   }
 
  public:
@@ -147,16 +146,44 @@
     return T::hasStackTrace;
   }
 
-  bool should_commit() {
+  bool is_started() const {
     return _started;
   }
 
+  bool should_commit() {
+    if (!_started) {
+      return false;
+    }
+    if (_untimed) {
+      return true;
+    }
+    if (_evaluated) {
+      return _should_commit;
+    }
+    _should_commit = evaluate();
+    _evaluated = true;
+    return _should_commit;
+  }
+
  private:
   bool should_write() {
-    if (T::isInstant || T::isRequestable || T::hasCutoff) {
-      return true;
+    return _started && (_evaluated ? _should_commit : evaluate());
+  }
+
+  bool evaluate() {
+    assert(_started, "invariant");
+    if (_start_time == 0) {
+      set_starttime(JfrTicks::now());
+    } else if (_end_time == 0) {
+      set_endtime(JfrTicks::now());
     }
-    return (_end_time - _start_time) >= JfrEventSetting::threshold(T::eventId);
+    if (T::isInstant || T::isRequestable) {
+      return T::hasThrottle ? JfrEventThrottler::accept(T::eventId, _untimed ? 0 : _start_time) : true;
+    }
+    if (_end_time - _start_time < JfrEventSetting::threshold(T::eventId)) {
+      return false;
+    }
+    return T::hasThrottle ? JfrEventThrottler::accept(T::eventId, _untimed ? 0 : _end_time) : true;
   }
 
   void write_event() {
diff --git a/src/hotspot/share/jfr/recorder/service/jfrEventThrottler.cpp b/src/hotspot/share/jfr/recorder/service/jfrEventThrottler.cpp
new file mode 100644
index 0000000..0f5777c
--- /dev/null
+++ b/src/hotspot/share/jfr/recorder/service/jfrEventThrottler.cpp
@@ -0,0 +1,276 @@
+/*
+ * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, Datadog, Inc. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#include "precompiled.hpp"
+#include "jfr/recorder/service/jfrEventThrottler.hpp"
+#include "jfr/utilities/jfrSpinlockHelper.hpp"
+#include "logging/log.hpp"
+
+constexpr static const JfrSamplerParams _disabled_params = {
+                                                             0, // sample points per window
+                                                             0, // window duration ms
+                                                             0, // window lookback count
+                                                             false // reconfigure
+                                                           };
+
+static JfrEventThrottler* _throttler = NULL;
+
+JfrEventThrottler::JfrEventThrottler(JfrEventId event_id) :
+  JfrAdaptiveSampler(),
+  _last_params(),
+  _sample_size(0),
+  _period_ms(0),
+  _sample_size_ewma(0),
+  _event_id(event_id),
+  _disabled(false),
+  _update(false) {}
+
+bool JfrEventThrottler::create() {
+  assert(_throttler == NULL, "invariant");
+  _throttler = new JfrEventThrottler(JfrObjectAllocationSampleEvent);
+  return _throttler != NULL && _throttler->initialize();
+}
+
+void JfrEventThrottler::destroy() {
+  delete _throttler;
+  _throttler = NULL;
+}
+
+// There is currently only one throttler instance, for the jdk.ObjectAllocationSample event.
+// When introducing additional throttlers, also add a lookup map keyed by event id.
+JfrEventThrottler* JfrEventThrottler::for_event(JfrEventId event_id) {
+  assert(_throttler != NULL, "JfrEventThrottler has not been properly initialized");
+  assert(event_id == JfrObjectAllocationSampleEvent, "Event type has an unconfigured throttler");
+  return event_id == JfrObjectAllocationSampleEvent ? _throttler : NULL;
+}
+
+void JfrEventThrottler::configure(JfrEventId event_id, int64_t sample_size, int64_t period_ms) {
+  if (event_id != JfrObjectAllocationSampleEvent) {
+    return;
+  }
+  assert(_throttler != NULL, "JfrEventThrottler has not been properly initialized");
+  _throttler->configure(sample_size, period_ms);
+}
+
+/*
+ * The event throttler currently only supports a single configuration option, a rate, but more may be added in the future:
+ *
+ * We configure to throttle dynamically, to maintain a continuous, maximal event emission rate per time period.
+ *
+ * - sample_size size of the event sample set
+ * - period_ms   time period expressed in milliseconds
+ */
+void JfrEventThrottler::configure(int64_t sample_size, int64_t period_ms) {
+  JfrSpinlockHelper mutex(&_lock);
+  _sample_size = sample_size;
+  _period_ms = period_ms;
+  _update = true;
+  reconfigure();
+}
+
+// Predicate for event selection.
+bool JfrEventThrottler::accept(JfrEventId event_id, int64_t timestamp /* 0 */) {
+  JfrEventThrottler* const throttler = for_event(event_id);
+  if (throttler == NULL) return true;
+  return _throttler->_disabled ? true : _throttler->sample(timestamp);
+}
+
+/*
+ * The window_lookback_count defines the history in number of windows to take into account
+ * when the JfrAdaptiveSampler engine is calcualting an expected weigthed moving average (EWMA) over the population.
+ * Technically, it determines the alpha coefficient in the EMWA formula.
+ */
+constexpr static const size_t default_window_lookback_count = 25; // 25 windows == 5 seconds (for default window duration of 200 ms)
+
+/*
+ * Rates lower than or equal to the 'low rate upper bound', are considered special.
+ * They will use a single window of whatever duration, because the rates are so low they
+ * do not justify the overhead of more frequent window rotations.
+ */
+constexpr static const intptr_t low_rate_upper_bound = 9;
+constexpr static const size_t  window_divisor = 5;
+
+constexpr static const int64_t MINUTE = 60 * MILLIUNITS;
+constexpr static const int64_t TEN_PER_1000_MS_IN_MINUTES = 600;
+constexpr static const int64_t HOUR = 60 * MINUTE;
+constexpr static const int64_t TEN_PER_1000_MS_IN_HOURS = 36000;
+constexpr static const int64_t DAY = 24 * HOUR;
+constexpr static const int64_t TEN_PER_1000_MS_IN_DAYS = 864000;
+
+inline void set_window_lookback(JfrSamplerParams& params) {
+  if (params.window_duration_ms <= MILLIUNITS) {
+    params.window_lookback_count = default_window_lookback_count; // 5 seconds
+    return;
+  }
+  if (params.window_duration_ms == MINUTE) {
+    params.window_lookback_count = 5; // 5 windows == 5 minutes
+    return;
+  }
+  params.window_lookback_count = 1; // 1 window == 1 hour or 1 day
+}
+
+inline void set_low_rate(JfrSamplerParams& params, int64_t event_sample_size, int64_t period_ms) {
+  params.sample_points_per_window = event_sample_size;
+  params.window_duration_ms = period_ms;
+}
+
+// If the throttler is off, it accepts all events.
+constexpr static const int64_t event_throttler_off = -2;
+
+/*
+ * Set the number of sample points and window duration.
+ */
+inline void set_sample_points_and_window_duration(JfrSamplerParams& params, int64_t sample_size, int64_t period_ms) {
+  assert(sample_size != event_throttler_off, "invariant");
+  assert(sample_size >= 0, "invariant");
+  assert(period_ms >= 1000, "invariant");
+  if (sample_size <= low_rate_upper_bound) {
+    set_low_rate(params, sample_size, period_ms);
+    return;
+  } else if (period_ms == MINUTE && sample_size < TEN_PER_1000_MS_IN_MINUTES) {
+    set_low_rate(params, sample_size, period_ms);
+    return;
+  } else if (period_ms == HOUR && sample_size < TEN_PER_1000_MS_IN_HOURS) {
+    set_low_rate(params, sample_size, period_ms);
+    return;
+  } else if (period_ms == DAY && sample_size < TEN_PER_1000_MS_IN_DAYS) {
+    set_low_rate(params, sample_size, period_ms);
+    return;
+  }
+  assert(period_ms % window_divisor == 0, "invariant");
+  params.sample_points_per_window = sample_size / window_divisor;
+  params.window_duration_ms = period_ms / window_divisor;
+}
+
+/*
+ * If the input event sample size is large enough, normalize to per 1000 ms
+ */
+inline void normalize(int64_t* sample_size, int64_t* period_ms) {
+  assert(sample_size != NULL, "invariant");
+  assert(period_ms != NULL, "invariant");
+  if (*period_ms == MILLIUNITS) {
+    return;
+  }
+  if (*period_ms == MINUTE) {
+    if (*sample_size >= TEN_PER_1000_MS_IN_MINUTES) {
+      *sample_size /= 60;
+      *period_ms /= 60;
+    }
+    return;
+  }
+  if (*period_ms == HOUR) {
+    if (*sample_size >= TEN_PER_1000_MS_IN_HOURS) {
+      *sample_size /= 3600;
+      *period_ms /= 3600;
+    }
+    return;
+  }
+  if (*sample_size >= TEN_PER_1000_MS_IN_DAYS) {
+    *sample_size /= 86400;
+    *period_ms /= 86400;
+  }
+}
+
+inline bool is_disabled(int64_t event_sample_size) {
+  return event_sample_size == event_throttler_off;
+}
+
+const JfrSamplerParams& JfrEventThrottler::update_params(const JfrSamplerWindow* expired) {
+  _disabled = is_disabled(_sample_size);
+  if (_disabled) {
+    return _disabled_params;
+  }
+  normalize(&_sample_size, &_period_ms);
+  set_sample_points_and_window_duration(_last_params, _sample_size, _period_ms);
+  set_window_lookback(_last_params);
+  _sample_size_ewma = 0;
+  _last_params.reconfigure = true;
+  _update = false;
+  return _last_params;
+}
+
+/*
+ * Exponentially Weighted Moving Average (EWMA):
+ *
+ * Y is a datapoint (at time t)
+ * S is the current EMWA (at time t-1)
+ * alpha represents the degree of weighting decrease, a constant smoothing factor between 0 and 1.
+ *
+ * A higher alpha discounts older observations faster.
+ * Returns the new EWMA for S
+*/
+
+inline double exponentially_weighted_moving_average(double Y, double alpha, double S) {
+  return alpha * Y + (1 - alpha) * S;
+}
+
+inline double compute_ewma_alpha_coefficient(size_t lookback_count) {
+  return lookback_count <= 1 ? 1 : static_cast<double>(1) / static_cast<double>(lookback_count);
+}
+
+/*
+ * To start debugging the throttler: -Xlog:jfr+system+throttle=debug
+ * It will log details of each expired window together with an average sample size.
+ *
+ * Excerpt:
+ *
+ * "jdk.ObjectAllocationSample: avg.sample size: 19.8377, window set point: 20 ..."
+ *
+ * Monitoring the relation of average sample size to the window set point, i.e the target,
+ * is a good indicator of how the throttler is performing over time.
+ *
+ * Note: there is currently only one throttler instance, for the ObjectAllocationSample event.
+ * When introducing additional throttlers, also provide a map from the event id to the event name.
+ */
+static void log(const JfrSamplerWindow* expired, double* sample_size_ewma) {
+  assert(sample_size_ewma != NULL, "invariant");
+  if (log_is_enabled(Debug, jfr, system, throttle)) {
+    *sample_size_ewma = exponentially_weighted_moving_average(expired->sample_size(), compute_ewma_alpha_coefficient(expired->params().window_lookback_count), *sample_size_ewma);
+    log_debug(jfr, system, throttle)("jdk.ObjectAllocationSample: avg.sample size: %0.4f, window set point: %zu, sample size: %zu, population size: %zu, ratio: %.4f, window duration: %zu ms\n",
+      *sample_size_ewma, expired->params().sample_points_per_window, expired->sample_size(), expired->population_size(),
+      expired->population_size() == 0 ? 0 : (double)expired->sample_size() / (double)expired->population_size(),
+      expired->params().window_duration_ms);
+  }
+}
+
+/*
+ * This is the feedback control loop.
+ *
+ * The JfrAdaptiveSampler engine calls this when a sampler window has expired, providing
+ * us with an opportunity to perform some analysis. To reciprocate, we returns a set of
+ * parameters, possibly updated, for the engine to apply to the next window.
+ *
+ * Try to keep relatively quick, since the engine is currently inside a critical section,
+ * in the process of rotating windows.
+ */
+const JfrSamplerParams& JfrEventThrottler::next_window_params(const JfrSamplerWindow* expired) {
+  assert(expired != NULL, "invariant");
+  assert(_lock, "invariant");
+  log(expired, &_sample_size_ewma);
+  if (_update) {
+    return update_params(expired); // Updates _last_params in-place.
+  }
+  return _disabled ? _disabled_params : _last_params;
+}
diff --git a/src/hotspot/share/jfr/recorder/service/jfrEventThrottler.hpp b/src/hotspot/share/jfr/recorder/service/jfrEventThrottler.hpp
new file mode 100644
index 0000000..226a2d4
--- /dev/null
+++ b/src/hotspot/share/jfr/recorder/service/jfrEventThrottler.hpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, Datadog, Inc. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#ifndef SHARE_JFR_RECORDER_SERVICE_JFREVENTTHROTTLER_HPP
+#define SHARE_JFR_RECORDER_SERVICE_JFREVENTTHROTTLER_HPP
+
+#include "jfrfiles/jfrEventIds.hpp"
+#include "jfr/support/jfrAdaptiveSampler.hpp"
+
+class JfrEventThrottler : public JfrAdaptiveSampler {
+  friend class JfrRecorder;
+ private:
+  JfrSamplerParams _last_params;
+  int64_t _sample_size;
+  int64_t _period_ms;
+  double _sample_size_ewma;
+  JfrEventId _event_id;
+  bool _disabled;
+  bool _update;
+
+  static bool create();
+  static void destroy();
+  JfrEventThrottler(JfrEventId event_id);
+  void configure(int64_t event_sample_size, int64_t period_ms);
+
+  const JfrSamplerParams& update_params(const JfrSamplerWindow* expired);
+  const JfrSamplerParams& next_window_params(const JfrSamplerWindow* expired);
+  static JfrEventThrottler* for_event(JfrEventId event_id);
+
+ public:
+  static void configure(JfrEventId event_id, int64_t event_sample_size, int64_t period_ms);
+  static bool accept(JfrEventId event_id, int64_t timestamp = 0);
+};
+
+#endif // SHARE_JFR_RECORDER_SERVICE_JFREVENTTHROTTLER_HPP
\ No newline at end of file
diff --git a/src/hotspot/share/jfr/support/jfrAdaptiveSampler.cpp b/src/hotspot/share/jfr/support/jfrAdaptiveSampler.cpp
new file mode 100644
index 0000000..3eb53a5
--- /dev/null
+++ b/src/hotspot/share/jfr/support/jfrAdaptiveSampler.cpp
@@ -0,0 +1,385 @@
+/*
+* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
+* Copyright (c) 2020, Datadog, Inc. All rights reserved.
+* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+*
+* This code is free software; you can redistribute it and/or modify it
+* under the terms of the GNU General Public License version 2 only, as
+* published by the Free Software Foundation.
+*
+* This code is distributed in the hope that it will be useful, but WITHOUT
+* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+* FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+* version 2 for more details (a copy is included in the LICENSE file that
+* accompanied this code).
+*
+* You should have received a copy of the GNU General Public License version
+* 2 along with this work; if not, write to the Free Software Foundation,
+* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+*
+* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+* or visit www.oracle.com if you need additional information or have any
+* questions.
+*
+*/
+
+#include "precompiled.hpp"
+#include "jfr/support/jfrAdaptiveSampler.hpp"
+#include "jfr/utilities/jfrRandom.inline.hpp"
+#include "jfr/utilities/jfrSpinlockHelper.hpp"
+#include "jfr/utilities/jfrTime.hpp"
+#include "jfr/utilities/jfrTimeConverter.hpp"
+#include "jfr/utilities/jfrTryLock.hpp"
+#include "logging/log.hpp"
+#include "runtime/atomic.hpp"
+#include "utilities/globalDefinitions.hpp"
+#include <cmath>
+
+JfrSamplerWindow::JfrSamplerWindow() :
+  _params(),
+  _end_ticks(0),
+  _sampling_interval(1),
+  _projected_population_size(0),
+  _measured_population_size(0) {}
+
+JfrAdaptiveSampler::JfrAdaptiveSampler() :
+  _prng(this),
+  _window_0(NULL),
+  _window_1(NULL),
+  _active_window(NULL),
+  _avg_population_size(0),
+  _ewma_population_size_alpha(0),
+  _acc_debt_carry_limit(0),
+  _acc_debt_carry_count(0),
+  _lock(0) {}
+
+JfrAdaptiveSampler::~JfrAdaptiveSampler() {
+  delete _window_0;
+  delete _window_1;
+}
+
+bool JfrAdaptiveSampler::initialize() {
+  assert(_window_0 == NULL, "invariant");
+  _window_0 = new JfrSamplerWindow();
+  if (_window_0 == NULL) {
+    return false;
+  }
+  assert(_window_1 == NULL, "invariant");
+  _window_1 = new JfrSamplerWindow();
+  if (_window_1 == NULL) {
+    return false;
+  }
+  _active_window = _window_0;
+  return true;
+}
+
+/*
+ * The entry point to the sampler.
+ */
+bool JfrAdaptiveSampler::sample(int64_t timestamp) {
+  bool expired_window;
+  const bool result = active_window()->sample(timestamp, &expired_window);
+  if (expired_window) {
+    JfrTryLock mutex(&_lock);
+    if (mutex.acquired()) {
+      rotate_window(timestamp);
+    }
+  }
+  return result;
+}
+
+inline const JfrSamplerWindow* JfrAdaptiveSampler::active_window() const {
+  return Atomic::load_acquire(&_active_window);
+}
+
+inline int64_t now() {
+  return JfrTicks::now().value();
+}
+
+inline bool JfrSamplerWindow::is_expired(int64_t timestamp) const {
+  const int64_t end_ticks = Atomic::load(&_end_ticks);
+  return timestamp == 0 ? now() >= end_ticks : timestamp >= end_ticks;
+}
+
+bool JfrSamplerWindow::sample(int64_t timestamp, bool* expired_window) const {
+  assert(expired_window != NULL, "invariant");
+  *expired_window = is_expired(timestamp);
+  return *expired_window ? false : sample();
+}
+
+inline bool JfrSamplerWindow::sample() const {
+  const size_t ordinal = Atomic::add(&_measured_population_size, static_cast<size_t>(1));
+  return ordinal <= _projected_population_size && ordinal % _sampling_interval == 0;
+}
+
+// Called exclusively by the holder of the lock when a window is determined to have expired.
+void JfrAdaptiveSampler::rotate_window(int64_t timestamp) {
+  assert(_lock, "invariant");
+  const JfrSamplerWindow* const current = active_window();
+  assert(current != NULL, "invariant");
+  if (!current->is_expired(timestamp)) {
+    // Someone took care of it.
+    return;
+  }
+  rotate(current);
+}
+
+// Subclasses can call this to immediately trigger a reconfiguration of the sampler.
+// There is no need to await the expiration of the current active window.
+void JfrAdaptiveSampler::reconfigure() {
+  assert(_lock, "invariant");
+  rotate(active_window());
+}
+
+// Call next_window_param() to report the expired window and to retreive params for the next window.
+void JfrAdaptiveSampler::rotate(const JfrSamplerWindow* expired) {
+  assert(expired == active_window(), "invariant");
+  install(configure(next_window_params(expired), expired));
+}
+
+inline void JfrAdaptiveSampler::install(const JfrSamplerWindow* next) {
+  assert(next != active_window(), "invariant");
+  Atomic::release_store(&_active_window, next);
+}
+
+const JfrSamplerWindow* JfrAdaptiveSampler::configure(const JfrSamplerParams& params, const JfrSamplerWindow* expired) {
+  assert(_lock, "invariant");
+  if (params.reconfigure) {
+    // Store updated params once to both windows.
+    const_cast<JfrSamplerWindow*>(expired)->_params = params;
+    next_window(expired)->_params = params;
+    configure(params);
+  }
+  JfrSamplerWindow* const next = set_rate(params, expired);
+  next->initialize(params);
+  return next;
+}
+
+/*
+ * Exponentially Weighted Moving Average (EWMA):
+ *
+ * Y is a datapoint (at time t)
+ * S is the current EMWA (at time t-1)
+ * alpha represents the degree of weighting decrease, a constant smoothing factor between 0 and 1.
+ *
+ * A higher alpha discounts older observations faster.
+ * Returns the new EWMA for S
+*/
+
+inline double exponentially_weighted_moving_average(double Y, double alpha, double S) {
+  return alpha * Y + (1 - alpha) * S;
+}
+
+inline double compute_ewma_alpha_coefficient(size_t lookback_count) {
+  return lookback_count <= 1 ? 1 : static_cast<double>(1) / static_cast<double>(lookback_count);
+}
+
+inline size_t compute_accumulated_debt_carry_limit(const JfrSamplerParams& params) {
+  if (params.window_duration_ms == 0 || params.window_duration_ms >= MILLIUNITS) {
+    return 1;
+  }
+  return MILLIUNITS / params.window_duration_ms;
+}
+
+void JfrAdaptiveSampler::configure(const JfrSamplerParams& params) {
+  assert(params.reconfigure, "invariant");
+  _avg_population_size = 0;
+  _ewma_population_size_alpha = compute_ewma_alpha_coefficient(params.window_lookback_count);
+  _acc_debt_carry_limit = compute_accumulated_debt_carry_limit(params);
+  _acc_debt_carry_count = _acc_debt_carry_limit;
+  params.reconfigure = false;
+}
+
+inline int64_t millis_to_countertime(int64_t millis) {
+  return JfrTimeConverter::nanos_to_countertime(millis * NANOSECS_PER_MILLISEC);
+}
+
+void JfrSamplerWindow::initialize(const JfrSamplerParams& params) {
+  assert(_sampling_interval >= 1, "invariant");
+  if (params.window_duration_ms == 0) {
+    Atomic::store(&_end_ticks, static_cast<int64_t>(0));
+    return;
+  }
+  Atomic::store(&_measured_population_size, static_cast<size_t>(0));
+  const int64_t end_ticks = now() + millis_to_countertime(params.window_duration_ms);
+  Atomic::store(&_end_ticks, end_ticks);
+}
+
+/*
+ * Based on what it has learned from the past, the sampler creates a future 'projection',
+ * a speculation, or model, of what the situation will be like during the next window.
+ * This projection / model is used to derive values for the parameters, which are estimates for
+ * collecting a sample set that, should the model hold, is as close as possible to the target,
+ * i.e. the set point, which is a function of the number of sample_points_per_window + amortization.
+ * The model is a geometric distribution over the number of trials / selections required until success.
+ * For each window, the sampling interval is a random variable from this geometric distribution.
+ */
+JfrSamplerWindow* JfrAdaptiveSampler::set_rate(const JfrSamplerParams& params, const JfrSamplerWindow* expired) {
+  JfrSamplerWindow* const next = next_window(expired);
+  assert(next != expired, "invariant");
+  const size_t sample_size = project_sample_size(params, expired);
+  if (sample_size == 0) {
+    next->_projected_population_size = 0;
+    return next;
+  }
+  next->_sampling_interval = derive_sampling_interval(sample_size, expired);
+  assert(next->_sampling_interval >= 1, "invariant");
+  next->_projected_population_size = sample_size * next->_sampling_interval;
+  return next;
+}
+
+inline JfrSamplerWindow* JfrAdaptiveSampler::next_window(const JfrSamplerWindow* expired) const {
+  assert(expired != NULL, "invariant");
+  return expired == _window_0 ? _window_1 : _window_0;
+}
+
+size_t JfrAdaptiveSampler::project_sample_size(const JfrSamplerParams& params, const JfrSamplerWindow* expired) {
+  return params.sample_points_per_window + amortize_debt(expired);
+}
+
+/*
+ * When the sampler is configured to maintain a rate, is employs the concepts
+ * of 'debt' and 'accumulated debt'. 'Accumulated debt' can be thought of as
+ * a cumulative error term, and is indicative for how much the sampler is
+ * deviating from a set point, i.e. the ideal target rate. Debt accumulates naturally
+ * as a function of undersampled windows, caused by system fluctuations,
+ * i.e. too small populations.
+ *
+ * A specified rate is implicitly a _maximal_ rate, so the sampler must ensure
+ * to respect this 'limit'. Rates are normalized as per-second ratios, hence the
+ * limit to respect is on a per second basis. During this second, the sampler
+ * has freedom to dynamically re-adjust, and it does so by 'amortizing'
+ * accumulated debt over a certain number of windows that fall within the second.
+ *
+ * Intuitively, accumulated debt 'carry over' from the predecessor to the successor
+ * window if within the allowable time frame (determined in # of 'windows' given by
+ * _acc_debt_carry_limit). The successor window will sample more points to make amends,
+ * or 'amortize' debt accumulated by its predecessor(s).
+ */
+size_t JfrAdaptiveSampler::amortize_debt(const JfrSamplerWindow* expired) {
+  assert(expired != NULL, "invariant");
+  const intptr_t accumulated_debt = expired->accumulated_debt();
+  assert(accumulated_debt <= 0, "invariant");
+  if (_acc_debt_carry_count == _acc_debt_carry_limit) {
+    _acc_debt_carry_count = 1;
+    return 0;
+  }
+  ++_acc_debt_carry_count;
+  return -accumulated_debt; // negation
+}
+
+inline size_t JfrSamplerWindow::max_sample_size() const {
+  return _projected_population_size / _sampling_interval;
+}
+
+// The sample size is derived from the measured population size.
+size_t JfrSamplerWindow::sample_size() const {
+  const size_t size = population_size();
+  return size > _projected_population_size ? max_sample_size() : size / _sampling_interval;
+}
+
+size_t JfrSamplerWindow::population_size() const {
+  return Atomic::load(&_measured_population_size);
+}
+
+intptr_t JfrSamplerWindow::accumulated_debt() const {
+  return _projected_population_size == 0 ? 0 : static_cast<intptr_t>(_params.sample_points_per_window - max_sample_size()) + debt();
+}
+
+intptr_t JfrSamplerWindow::debt() const {
+  return _projected_population_size == 0 ? 0 : static_cast<intptr_t>(sample_size() - _params.sample_points_per_window);
+}
+
+/*
+ * Inverse transform sampling from a uniform to a geometric distribution.
+ *
+ * PMF: f(x)  = P(X=x) = ((1-p)^x-1)p
+ *
+ * CDF: F(x)  = P(X<=x) = 1 - (1-p)^x
+ *
+ * Inv
+ * CDF: F'(u) = ceil( ln(1-u) / ln(1-p) ) // u = random uniform, 0.0 < u < 1.0
+ *
+ */
+inline size_t next_geometric(double p, double u) {
+  assert(u >= 0.0, "invariant");
+  assert(u <= 1.0, "invariant");
+  if (u == 0.0) {
+    u = 0.01;
+  } else if (u == 1.0) {
+    u = 0.99;
+  }
+  // Inverse CDF for the geometric distribution.
+  return ceil(log(1.0 - u) / log(1.0 - p));
+}
+
+size_t JfrAdaptiveSampler::derive_sampling_interval(double sample_size, const JfrSamplerWindow* expired) {
+  assert(sample_size > 0, "invariant");
+  const size_t population_size = project_population_size(expired);
+  if (population_size <= sample_size) {
+    return 1;
+  }
+  assert(population_size > 0, "invariant");
+  const double projected_probability = sample_size / population_size;
+  return next_geometric(projected_probability, _prng.next_uniform());
+}
+
+// The projected population size is an exponentially weighted moving average, a function of the window_lookback_count.
+inline size_t JfrAdaptiveSampler::project_population_size(const JfrSamplerWindow* expired) {
+  assert(expired != NULL, "invariant");
+  _avg_population_size = exponentially_weighted_moving_average(expired->population_size(), _ewma_population_size_alpha, _avg_population_size);
+  return _avg_population_size;
+}
+
+/* GTEST support */
+JfrGTestFixedRateSampler::JfrGTestFixedRateSampler(size_t sample_points_per_window, size_t window_duration_ms, size_t lookback_count) : JfrAdaptiveSampler(), _params() {
+  _sample_size_ewma = 0.0;
+  _params.sample_points_per_window = sample_points_per_window;
+  _params.window_duration_ms = window_duration_ms;
+  _params.window_lookback_count = lookback_count;
+  _params.reconfigure = true;
+}
+
+bool JfrGTestFixedRateSampler::initialize() {
+  const bool result = JfrAdaptiveSampler::initialize();
+  JfrSpinlockHelper mutex(&_lock);
+  reconfigure();
+  return result;
+}
+
+/*
+ * To start debugging the sampler: -Xlog:jfr+system+throttle=debug
+ * It will log details of each expired window together with an average sample size.
+ *
+ * Excerpt:
+ *
+ * "JfrGTestFixedRateSampler: avg.sample size: 19.8377, window set point: 20 ..."
+ *
+ * Monitoring the relation of average sample size to the window set point, i.e the target,
+ * is a good indicator of how the sampler is performing over time.
+ *
+ */
+static void log(const JfrSamplerWindow* expired, double* sample_size_ewma) {
+  assert(sample_size_ewma != NULL, "invariant");
+  if (log_is_enabled(Debug, jfr, system, throttle)) {
+    *sample_size_ewma = exponentially_weighted_moving_average(expired->sample_size(), compute_ewma_alpha_coefficient(expired->params().window_lookback_count), *sample_size_ewma);
+    log_debug(jfr, system, throttle)("JfrGTestFixedRateSampler: avg.sample size: %0.4f, window set point: %zu, sample size: %zu, population size: %zu, ratio: %.4f, window duration: %zu ms\n",
+      *sample_size_ewma, expired->params().sample_points_per_window, expired->sample_size(), expired->population_size(),
+      expired->population_size() == 0 ? 0 : (double)expired->sample_size() / (double)expired->population_size(),
+      expired->params().window_duration_ms);
+  }
+}
+
+/*
+ * This is the feedback control loop.
+ *
+ * The JfrAdaptiveSampler engine calls this when a sampler window has expired, providing
+ * us with an opportunity to perform some analysis.To reciprocate, we returns a set of
+ * parameters, possibly updated, for the engine to apply to the next window.
+ */
+const JfrSamplerParams& JfrGTestFixedRateSampler::next_window_params(const JfrSamplerWindow* expired) {
+  assert(expired != NULL, "invariant");
+  assert(_lock, "invariant");
+  log(expired, &_sample_size_ewma);
+  return _params;
+}
diff --git a/src/hotspot/share/jfr/support/jfrAdaptiveSampler.hpp b/src/hotspot/share/jfr/support/jfrAdaptiveSampler.hpp
new file mode 100644
index 0000000..e518cb5
--- /dev/null
+++ b/src/hotspot/share/jfr/support/jfrAdaptiveSampler.hpp
@@ -0,0 +1,155 @@
+/*
+* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
+* Copyright (c) 2020, Datadog, Inc. All rights reserved.
+* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+*
+* This code is free software; you can redistribute it and/or modify it
+* under the terms of the GNU General Public License version 2 only, as
+* published by the Free Software Foundation.
+*
+* This code is distributed in the hope that it will be useful, but WITHOUT
+* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+* FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+* version 2 for more details (a copy is included in the LICENSE file that
+* accompanied this code).
+*
+* You should have received a copy of the GNU General Public License version
+* 2 along with this work; if not, write to the Free Software Foundation,
+* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+*
+* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+* or visit www.oracle.com if you need additional information or have any
+* questions.
+*
+*/
+
+#ifndef SHARE_JFR_SUPPORT_JFRADAPTIVESAMPLER_HPP
+#define SHARE_JFR_SUPPORT_JFRADAPTIVESAMPLER_HPP
+
+#include "jfr/utilities/jfrAllocation.hpp"
+#include "jfr/utilities/jfrRandom.hpp"
+
+/*
+ * The terminology is mostly from the domain of statistics:
+ *
+ * Population - a set of elements of interest.
+ * Sample - a subset of elements from a population selected by a defined procedure.
+ * Sample point - an element of a sample (sub)set.
+ * Sampling interval - the distance between which measurements are taken, also referred to as 'nth selection'
+ * Debt - an error term, signifying the deviation from a configured set point.
+ * Amortization - a projection or strategy to recover accumulated debt.
+ * Window - as in time window or time frame. The sampler sees the evolution of the system in time slices, i.e. in windows.
+ * Rotate - the process of retiring an expired window and installing a new window with updated parameters.
+ *
+ * The adaptive sampler will guarantee a maximum number of sample points selected from a populuation
+ * during a certain time interval. It is using fixed size time windows and adjusts the sampling interval for the next
+ * window based on what it learned in the past. Each window has a set point, which is the target number of sample points
+ * to select. The sampler keeps a cumulative error term, called 'accumulated debt', which is a measure
+ * for how much the sampler is deviating from the set point over time. The maximum number of sample points selected
+ * during an individual window is the set point + the accumulated debt.
+ * The 'accumulated debt' also works as a 'spike damper', smoothing out the extremes in a way that the overall
+ * target rate is obeyed without highly over- or under-sampled windows.
+ *
+ * Sample point selection is defined by a sampling interval, which gives instructions for selecting the 'nth' element
+ * in a population. Which 'nth' to select is a random variable from a geometric distribution, recalculated for each window.
+ *
+ * Each window is configured individually, by an instance of the JfrSamplerParams struct. On window expiration,
+ * but before switching in the next window, the sampler calls a subclass with the just expired window as an argument.
+.* A subclass can inspect the window to study the history of the system and also get an overview of how the sampler
+ * is performing to help draw inferences. Based on what it learned, it can choose to let the sampler re-apply an updated
+ * set of parameters to the next, upcoming, window. This is a basic feedback control loop to be developed further,
+ * perhaps evolving more elaborate sampling schemes in the future.
+ *
+ * Using the JfrAdaptiveSampler, we can let a user specify at a high level, for example that he/she would like a
+ * maximum rate of n sample points per second. Naturally, lower rates will be reported if the system does not produce
+ * a population to sustain the requested rate, but n per second is respected as a maximum limit, hence it will never
+ * report a rate higher than n per second.
+ *
+ * One good use of the sampler is to employ it as a throttler, or regulator, to help shape large data sets into smaller,
+ * more managable subsets while still keeping the data somewhat representative.
+ *
+ */
+
+struct JfrSamplerParams {
+  size_t sample_points_per_window; // The number of sample points to target per window.
+  size_t window_duration_ms;
+  size_t window_lookback_count; // The number of data points (windows) to include when calculating a moving average for the population size.
+  mutable bool reconfigure;     // The sampler should issue a reconfiguration because some parameter changed.
+};
+
+class JfrSamplerWindow : public JfrCHeapObj {
+  friend class JfrAdaptiveSampler;
+ private:
+  JfrSamplerParams _params;
+  volatile int64_t _end_ticks;
+  size_t _sampling_interval;
+  size_t _projected_population_size;
+  mutable volatile size_t _measured_population_size;
+
+  JfrSamplerWindow();
+  void initialize(const JfrSamplerParams& params);
+  size_t max_sample_size() const;
+  bool is_expired(int64_t timestamp) const;
+  bool sample() const;
+  bool sample(int64_t timestamp, bool* is_expired) const;
+
+ public:
+  size_t population_size() const;
+  size_t sample_size() const;
+  intptr_t debt() const;
+  intptr_t accumulated_debt() const;
+  const JfrSamplerParams& params() const {
+    return _params;
+  }
+};
+
+class JfrAdaptiveSampler : public JfrCHeapObj {
+ private:
+  JfrPRNG _prng;
+  JfrSamplerWindow* _window_0;
+  JfrSamplerWindow* _window_1;
+  const JfrSamplerWindow* _active_window;
+  double _avg_population_size;
+  double _ewma_population_size_alpha;
+  size_t _acc_debt_carry_limit;
+  size_t _acc_debt_carry_count;
+
+  void rotate_window(int64_t timestamp);
+  void rotate(const JfrSamplerWindow* expired);
+  const JfrSamplerWindow* active_window() const;
+  JfrSamplerWindow* next_window(const JfrSamplerWindow* expired) const;
+  void install(const JfrSamplerWindow* next);
+
+  size_t amortize_debt(const JfrSamplerWindow* expired);
+  size_t derive_sampling_interval(double sample_size, const JfrSamplerWindow* expired);
+  size_t project_population_size(const JfrSamplerWindow* expired);
+  size_t project_sample_size(const JfrSamplerParams& params, const JfrSamplerWindow* expired);
+  JfrSamplerWindow* set_rate(const JfrSamplerParams& params, const JfrSamplerWindow* expired);
+
+  void configure(const JfrSamplerParams& params);
+  const JfrSamplerWindow* configure(const JfrSamplerParams& params, const JfrSamplerWindow* expired);
+
+ protected:
+  volatile int _lock;
+  JfrAdaptiveSampler();
+  virtual ~JfrAdaptiveSampler();
+  virtual bool initialize();
+  virtual const JfrSamplerParams& next_window_params(const JfrSamplerWindow* expired) = 0;
+  void reconfigure();
+
+ public:
+  bool sample(int64_t timestamp = 0);
+};
+
+/* GTEST support */
+class JfrGTestFixedRateSampler : public JfrAdaptiveSampler {
+ private:
+  JfrSamplerParams _params;
+  double _sample_size_ewma;
+ public:
+  JfrGTestFixedRateSampler(size_t sample_points_per_window, size_t window_duration_ms, size_t lookback_count);
+  virtual bool initialize();
+  const JfrSamplerParams& next_window_params(const JfrSamplerWindow* expired);
+};
+
+#endif // SHARE_JFR_SUPPORT_JFRADAPTIVESAMPLER_HPP
diff --git a/src/hotspot/share/jfr/utilities/jfrLogTagSets.hpp b/src/hotspot/share/jfr/utilities/jfrLogTagSets.hpp
index 67a80da..47fdeaa 100644
--- a/src/hotspot/share/jfr/utilities/jfrLogTagSets.hpp
+++ b/src/hotspot/share/jfr/utilities/jfrLogTagSets.hpp
@@ -54,6 +54,7 @@
   JFR_LOG_TAG(jfr, system, parser) \
   JFR_LOG_TAG(jfr, system, metadata) \
   JFR_LOG_TAG(jfr, system, streaming) \
+  JFR_LOG_TAG(jfr, system, throttle) \
   JFR_LOG_TAG(jfr, metadata) \
   JFR_LOG_TAG(jfr, event) \
   JFR_LOG_TAG(jfr, setting) \
diff --git a/src/hotspot/share/jfr/utilities/jfrRandom.hpp b/src/hotspot/share/jfr/utilities/jfrRandom.hpp
new file mode 100644
index 0000000..7f3fae9
--- /dev/null
+++ b/src/hotspot/share/jfr/utilities/jfrRandom.hpp
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, Google and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#ifndef SHARE_JFR_UTILITIES_JFRRANDOM_HPP
+#define SHARE_JFR_UTILITIES_JFRRANDOM_HPP
+
+#include "jfr/utilities/jfrAllocation.hpp"
+
+// Cheap pseudorandom number generator
+
+class JfrPRNG : public JfrCHeapObj {
+ private:
+  mutable uint64_t _rnd;
+ public:
+  JfrPRNG(const void* seed);
+  double next_uniform() const;
+};
+
+#endif // SHARE_JFR_UTILITIES_JFRRANDOM_HPP
diff --git a/src/hotspot/share/jfr/utilities/jfrRandom.inline.hpp b/src/hotspot/share/jfr/utilities/jfrRandom.inline.hpp
new file mode 100644
index 0000000..2e84911
--- /dev/null
+++ b/src/hotspot/share/jfr/utilities/jfrRandom.inline.hpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, Google and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#ifndef SHARE_JFR_UTILITIES_JFRRANDOM_INLINE_HPP
+#define SHARE_JFR_UTILITIES_JFRRANDOM_INLINE_HPP
+
+#include "jfr/utilities/jfrRandom.hpp"
+
+inline JfrPRNG::JfrPRNG(const void* seed) : _rnd(reinterpret_cast<uint64_t>(seed)) {
+  assert(seed != NULL, "invariant");
+}
+
+// Returns the next prng value.
+// pRNG is: aX+b mod c with a = 0x5DEECE66D, b =  0xB, c = 1<<48
+// This is the lrand64 generator.
+inline uint64_t next(uint64_t rnd) {
+  static const uint64_t PrngMult = 0x5DEECE66DLL;
+  static const uint64_t PrngAdd = 0xB;
+  static const uint64_t PrngModPower = 48;
+  static const uint64_t PrngModMask = (static_cast<uint64_t>(1) << PrngModPower) - 1;
+  return (PrngMult * rnd + PrngAdd) & PrngModMask;
+}
+
+inline double JfrPRNG::next_uniform() const {
+  _rnd = next(_rnd);
+  // Take the top 26 bits as the random number
+  // (This plus a 1<<58 sampling bound gives a max possible step of
+  // 5194297183973780480 bytes.  In this case,
+  // for sample_parameter = 1<<19, max possible step is
+  // 9448372 bytes (24 bits).
+  static const uint64_t PrngModPower = 48;  // Number of bits in prng
+  // The uint32_t cast is to prevent a (hard-to-reproduce) NAN
+  // under piii debug for some binaries.
+  // the n_rand value is between 0 and 2**26-1 so it needs to be normalized by dividing by 2**26 (67108864)
+  return (static_cast<uint32_t>(_rnd >> (PrngModPower - 26)) / static_cast<double>(67108864));
+}
+
+#endif // SHARE_JFR_UTILITIES_JFRRANDOM_INLINE_HPP
diff --git a/src/hotspot/share/jfr/utilities/jfrTryLock.hpp b/src/hotspot/share/jfr/utilities/jfrTryLock.hpp
index 9f92b6c..10b3c93 100644
--- a/src/hotspot/share/jfr/utilities/jfrTryLock.hpp
+++ b/src/hotspot/share/jfr/utilities/jfrTryLock.hpp
@@ -33,20 +33,20 @@
 class JfrTryLock {
  private:
   volatile int* const _lock;
-  bool _has_lock;
+  bool _acquired;
 
  public:
-  JfrTryLock(volatile int* lock) : _lock(lock), _has_lock(Atomic::cmpxchg(lock, 0, 1) == 0) {}
+  JfrTryLock(volatile int* lock) : _lock(lock), _acquired(Atomic::cmpxchg(lock, 0, 1) == 0) {}
 
   ~JfrTryLock() {
-    if (_has_lock) {
+    if (_acquired) {
       OrderAccess::fence();
       *_lock = 0;
     }
   }
 
-  bool has_lock() const {
-    return _has_lock;
+  bool acquired() const {
+    return _acquired;
   }
 };
 
diff --git a/src/hotspot/share/logging/logTag.hpp b/src/hotspot/share/logging/logTag.hpp
index 56ea22b..55f9aa4 100644
--- a/src/hotspot/share/logging/logTag.hpp
+++ b/src/hotspot/share/logging/logTag.hpp
@@ -179,6 +179,7 @@
   LOG_TAG(task) \
   DEBUG_ONLY(LOG_TAG(test)) \
   LOG_TAG(thread) \
+  LOG_TAG(throttle) \
   LOG_TAG(time) \
   LOG_TAG(timer) \
   LOG_TAG(tlab) \
diff --git a/src/hotspot/share/runtime/objectMonitor.cpp b/src/hotspot/share/runtime/objectMonitor.cpp
index 242eb2f..9642bb6 100644
--- a/src/hotspot/share/runtime/objectMonitor.cpp
+++ b/src/hotspot/share/runtime/objectMonitor.cpp
@@ -378,7 +378,7 @@
 
   JFR_ONLY(JfrConditionalFlushWithStacktrace<EventJavaMonitorEnter> flush(jt);)
   EventJavaMonitorEnter event;
-  if (event.should_commit()) {
+  if (event.is_started()) {
     event.set_monitorClass(object()->klass());
     // Set an address that is 'unique enough', such that events close in
     // time and with the same address are likely (but not guaranteed) to
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java
index de93476..eba46e8 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/EventControl.java
@@ -50,6 +50,7 @@
 import jdk.jfr.internal.settings.PeriodSetting;
 import jdk.jfr.internal.settings.StackTraceSetting;
 import jdk.jfr.internal.settings.ThresholdSetting;
+import jdk.jfr.internal.settings.ThrottleSetting;
 
 // This class can't have a hard reference from PlatformEventType, since it
 // holds SettingControl instances that need to be released
@@ -69,6 +70,7 @@
     private static final Type TYPE_STACK_TRACE = TypeLibrary.createType(StackTraceSetting.class);
     private static final Type TYPE_PERIOD = TypeLibrary.createType(PeriodSetting.class);
     private static final Type TYPE_CUTOFF = TypeLibrary.createType(CutoffSetting.class);
+    private static final Type TYPE_THROTTLE = TypeLibrary.createType(ThrottleSetting.class);
 
     private final ArrayList<SettingInfo> settingInfos = new ArrayList<>();
     private final ArrayList<NamedControl> namedControls = new ArrayList<>(5);
@@ -76,7 +78,6 @@
     private final String idName;
 
     EventControl(PlatformEventType eventType) {
-        addControl(Enabled.NAME, defineEnabled(eventType));
         if (eventType.hasDuration()) {
             addControl(Threshold.NAME, defineThreshold(eventType));
         }
@@ -89,6 +90,10 @@
         if (eventType.hasCutoff()) {
             addControl(Cutoff.NAME, defineCutoff(eventType));
         }
+        if (eventType.hasThrottle()) {
+            addControl(Throttle.NAME, defineThrottle(eventType));
+        }
+        addControl(Enabled.NAME, defineEnabled(eventType));
 
         ArrayList<AnnotationElement> aes = new ArrayList<>(eventType.getAnnotationElements());
         remove(eventType, aes, Threshold.class);
@@ -96,6 +101,7 @@
         remove(eventType, aes, Enabled.class);
         remove(eventType, aes, StackTrace.class);
         remove(eventType, aes, Cutoff.class);
+        remove(eventType, aes, Throttle.class);
         aes.trimToSize();
         eventType.setAnnotations(aes);
         this.type = eventType;
@@ -252,6 +258,15 @@
         return new Control(new CutoffSetting(type), def);
     }
 
+    private static Control defineThrottle(PlatformEventType type) {
+        Throttle throttle = type.getAnnotation(Throttle.class);
+        String def = Throttle.DEFAULT;
+        if (throttle != null) {
+            def = throttle.value();
+        }
+        type.add(PrivateAccess.getInstance().newSettingDescriptor(TYPE_THROTTLE, Throttle.NAME, def, Collections.emptyList()));
+        return new Control(new ThrottleSetting(type), def);
+    }
 
     private static Control definePeriod(PlatformEventType type) {
         Period period = type.getAnnotation(Period.class);
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java
index c850fcc..ffde604 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/JVM.java
@@ -517,6 +517,18 @@
     public native boolean setCutoff(long eventTypeId, long cutoffTicks);
 
     /**
+     * Sets the event emission rate in event sample size per time unit.
+     *
+     * Determines how events are throttled.
+     *
+     * @param eventTypeId the id of the event type
+     * @param eventSampleSize event sample size
+     * @param period_ms time period in milliseconds
+     * @return true, if it could be set
+     */
+    public native boolean setThrottle(long eventTypeId, long eventSampleSize, long period_ms);
+
+    /**
      * Emit old object sample events.
      *
      * @param cutoff the cutoff in ticks
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/LogTag.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/LogTag.java
index c194175..f2cbeabf 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/LogTag.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/LogTag.java
@@ -67,21 +67,25 @@
      */
     JFR_SYSTEM_STREAMING(7),
     /**
+     *  Covers throttling (for Hotspot developers)
+     */
+    JFR_SYSTEM_THROTTLE(8),
+    /**
      *  Covers metadata for Java user (for Hotspot developers)
      */
-    JFR_METADATA(8),
+    JFR_METADATA(9),
     /**
      * Covers events (for users of the JDK)
      */
-    JFR_EVENT(9),
+    JFR_EVENT(10),
     /**
      * Covers setting (for users of the JDK)
      */
-    JFR_SETTING(10),
+    JFR_SETTING(11),
     /**
      * Covers usage of jcmd with JFR
      */
-    JFR_DCMD(11);
+    JFR_DCMD(12);
 
     /* set from native side */
     volatile int tagSetLevel = 100; // prevent logging if JVM log system has not been initialized
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataLoader.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataLoader.java
index e0e242b..20477ec 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataLoader.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataLoader.java
@@ -81,6 +81,7 @@
         private final boolean startTime;
         private final boolean stackTrace;
         private final boolean cutoff;
+        private final boolean throttle;
         private final boolean isEvent;
         private final boolean isRelation;
         private final boolean experimental;
@@ -101,6 +102,7 @@
             startTime = dis.readBoolean();
             period = dis.readUTF();
             cutoff = dis.readBoolean();
+            throttle = dis.readBoolean();
             experimental = dis.readBoolean();
             id = dis.readLong();
             isEvent = dis.readBoolean();
@@ -304,6 +306,9 @@
                 if (t.cutoff) {
                     aes.add(new AnnotationElement(Cutoff.class, Cutoff.INFINITY));
                 }
+                if (t.throttle) {
+                    aes.add(new AnnotationElement(Throttle.class, Throttle.DEFAULT));
+                }
             }
             if (t.experimental) {
                 aes.add(EXPERIMENTAL);
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataRepository.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataRepository.java
index f7b4746..469827a 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataRepository.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/MetadataRepository.java
@@ -76,6 +76,7 @@
                 pEventType.setHasDuration(eventType.getAnnotation(Threshold.class) != null);
                 pEventType.setHasStackTrace(eventType.getAnnotation(StackTrace.class) != null);
                 pEventType.setHasCutoff(eventType.getAnnotation(Cutoff.class) != null);
+                pEventType.setHasThrottle(eventType.getAnnotation(Throttle.class) != null);
                 pEventType.setHasPeriod(eventType.getAnnotation(Period.class) != null);
                 // Must add hook before EventControl is created as it removes
                 // annotations, such as Period and Threshold.
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java
index 6212ddc..8e246ac 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java
@@ -59,6 +59,7 @@
     private boolean hasDuration = true;
     private boolean hasPeriod = true;
     private boolean hasCutoff = false;
+    private boolean hasThrottle = false;
     private boolean isInstrumented;
     private boolean markForInstrumentation;
     private boolean registered = true;
@@ -142,6 +143,10 @@
        this.hasCutoff = hasCutoff;
     }
 
+    public void setHasThrottle(boolean hasThrottle) {
+        this.hasThrottle = hasThrottle;
+    }
+
     public void setCutoff(long cutoffNanos) {
         if (isJVM) {
             long cutoffTicks = Utils.nanosToTicks(cutoffNanos);
@@ -149,6 +154,12 @@
         }
     }
 
+    public void setThrottle(long eventSampleSize, long period_ms) {
+        if (isJVM) {
+            JVM.getJVM().setThrottle(getId(), eventSampleSize, period_ms);
+        }
+    }
+
     public void setHasPeriod(boolean hasPeriod) {
         this.hasPeriod = hasPeriod;
     }
@@ -169,6 +180,10 @@
         return this.hasCutoff;
     }
 
+    public boolean hasThrottle() {
+        return this.hasThrottle;
+    }
+
     public boolean isEnabled() {
         return enabled;
     }
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/Throttle.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/Throttle.java
new file mode 100644
index 0000000..39f0cc4
--- /dev/null
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/Throttle.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, Datadog, Inc. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.jfr.internal;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import jdk.jfr.MetadataDefinition;
+
+/**
+ * Event annotation, determines the event emission rate in events per time unit.
+ *
+ * This setting is only supported for JVM events.
+ *
+ * @since 16
+ */
+@MetadataDefinition
+@Target({ ElementType.TYPE })
+@Inherited
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Throttle {
+    /**
+     * Settings name {@code "throttle"} for configuring an event emission rate in events per time unit.
+     */
+    public final static String NAME = "throttle";
+    public final static String DEFAULT = "off";
+
+    /**
+     * Throttle, for example {@code "100/s"}.
+     * <p>
+     * String representation of a non-negative {@code Long} value followed by a slash ("/")
+     * and one of the following units<br>
+     * {@code "ns"} (nanoseconds)<br>
+     * {@code "us"} (microseconds)<br>
+     * {@code "ms"} (milliseconds)<br>
+     * {@code "s"} (seconds)<br>
+     * {@code "m"} (minutes)<br>
+     * {@code "h"} (hours)<br>
+     * {@code "d"} (days)<br>
+     * <p>
+     * Example values, {@code "6000/m"}, {@code "10/ms"} and {@code "200/s"}.
+     * When zero is specified, for example {@code "0/s"}, no events are emitted.
+     * When {@code "off"} is specified, all events are emitted.
+     *
+     * @return the throttle value, default {@code "off"} not {@code null}
+     *
+     */
+    String value() default DEFAULT;
+}
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/Utils.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/Utils.java
index 5c01370..49953c3 100644
--- a/src/jdk.jfr/share/classes/jdk/jfr/internal/Utils.java
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/Utils.java
@@ -49,6 +49,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.concurrent.TimeUnit;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -69,6 +70,7 @@
 
     private static final Object flushObject = new Object();
     private static final String INFINITY = "infinity";
+    private static final String OFF = "off";
     public static final String EVENTS_PACKAGE_NAME = "jdk.jfr.events";
     public static final String INSTRUMENT_PACKAGE_NAME = "jdk.jfr.internal.instrument";
     public static final String HANDLERS_PACKAGE_NAME = "jdk.jfr.internal.handlers";
@@ -78,7 +80,6 @@
 
     private static Boolean SAVE_GENERATED;
 
-
     private static final Duration MICRO_SECOND = Duration.ofNanos(1_000);
     private static final Duration SECOND = Duration.ofSeconds(1);
     private static final Duration MINUTE = Duration.ofMinutes(1);
@@ -88,6 +89,7 @@
     private static final int MILL_SIGNIFICANT_FIGURES = 3;
     private static final int DISPLAY_NANO_DIGIT = 3;
     private static final int BASE = 10;
+    private static long THROTTLE_OFF = -2;
 
 
     public static void checkAccessFlightRecorder() throws SecurityException {
@@ -204,6 +206,94 @@
         }
     }
 
+    enum ThrottleUnit {
+        NANOSECONDS("ns", TimeUnit.NANOSECONDS, TimeUnit.SECONDS.toNanos(1), TimeUnit.SECONDS.toMillis(1)),
+        MICROSECONDS("us", TimeUnit.MICROSECONDS, TimeUnit.SECONDS.toNanos(1) / 1000, TimeUnit.SECONDS.toMillis(1)),
+        MILLISECONDS("ms", TimeUnit.MILLISECONDS, TimeUnit.SECONDS.toMillis(1), TimeUnit.SECONDS.toMillis(1)),
+        SECONDS("s", TimeUnit.SECONDS, 1, TimeUnit.SECONDS.toMillis(1)),
+        MINUTES("m", TimeUnit.MINUTES, 1, TimeUnit.MINUTES.toMillis(1)),
+        HOUR("h", TimeUnit.HOURS, 1, TimeUnit.HOURS.toMillis(1)),
+        DAY("d", TimeUnit.DAYS, 1, TimeUnit.DAYS.toMillis(1));
+
+        private final String text;
+        private final TimeUnit timeUnit;
+        private final long factor;
+        private final long millis;
+
+        ThrottleUnit(String t, TimeUnit u, long factor, long millis) {
+            this.text = t;
+            this.timeUnit = u;
+            this.factor = factor;
+            this.millis = millis;
+        }
+
+        private static ThrottleUnit parse(String s) {
+            if (s.equals(OFF)) {
+                return MILLISECONDS;
+            }
+            return unit(parseThrottleString(s, false));
+        }
+
+        private static ThrottleUnit unit(String s) {
+            if (s.endsWith("ns") || s.endsWith("us") || s.endsWith("ms")) {
+                return value(s.substring(s.length() - 2));
+            }
+            if (s.endsWith("s") || s.endsWith("m") || s.endsWith("h") || s.endsWith("d")) {
+                return value(s.substring(s.length() - 1));
+            }
+            throw new NumberFormatException("'" + s + "' is not a valid time unit.");
+        }
+
+        private static ThrottleUnit value(String s) {
+            for (ThrottleUnit t : values()) {
+                if (t.text.equals(s)) {
+                    return t;
+                }
+            }
+            throw new NumberFormatException("'" + s + "' is not a valid time unit.");
+        }
+
+        static long asMillis(String s) {
+            return parse(s).millis;
+        }
+
+        static long normalizeValueAsMillis(long value, String s) {
+            return value * parse(s).factor;
+        }
+    }
+
+    private static void throwThrottleNumberFormatException(String s) {
+        throw new NumberFormatException("'" + s + "' is not valid. Should be a non-negative numeric value followed by a delimiter. i.e. '/', and then followed by a unit e.g. 100/s.");
+    }
+
+    // Expected input format is "x/y" where x is a non-negative long
+    // and y is a time unit. Split the string at the delimiter.
+    private static String parseThrottleString(String s, boolean value) {
+        String[] split = s.split("/");
+        if (split.length != 2) {
+            throwThrottleNumberFormatException(s);
+        }
+        return value ? split[0].trim() : split[1].trim();
+    }
+
+    public static long parseThrottleValue(String s) {
+        if (s.equals(OFF)) {
+            return THROTTLE_OFF;
+        }
+        String parsedValue = parseThrottleString(s, true);
+        long normalizedValue = 0;
+        try {
+            normalizedValue = ThrottleUnit.normalizeValueAsMillis(Long.parseLong(parsedValue), s);
+        } catch (NumberFormatException nfe) {
+            throwThrottleNumberFormatException(s);
+        }
+        return normalizedValue;
+    }
+
+    public static long parseThrottleTimeUnit(String s) {
+        return ThrottleUnit.asMillis(s);
+    }
+
     public static long parseTimespanWithInfinity(String s) {
         if (INFINITY.equals(s)) {
             return Long.MAX_VALUE;
diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/ThrottleSetting.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/ThrottleSetting.java
new file mode 100644
index 0000000..550e450
--- /dev/null
+++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/settings/ThrottleSetting.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, Datadog, Inc. 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.settings;
+
+import static java.util.concurrent.TimeUnit.MICROSECONDS;
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import java.util.concurrent.TimeUnit;
+import java.util.Objects;
+import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+import jdk.jfr.Description;
+import jdk.jfr.Label;
+import jdk.jfr.MetadataDefinition;
+import jdk.jfr.Name;
+import jdk.jfr.Timespan;
+import jdk.jfr.internal.PlatformEventType;
+import jdk.jfr.internal.Type;
+import jdk.jfr.internal.Utils;
+
+@MetadataDefinition
+@Label("Event Emission Throttle")
+@Description("Throttles the emission rate for an event")
+@Name(Type.SETTINGS_PREFIX + "Throttle")
+public final class ThrottleSetting extends JDKSettingControl {
+    private final static long typeId = Type.getTypeId(ThrottleSetting.class);
+    private final static long OFF = -2;
+    private String value = "0/s";
+    private final PlatformEventType eventType;
+
+    public ThrottleSetting(PlatformEventType eventType) {
+       this.eventType = Objects.requireNonNull(eventType);
+    }
+
+    @Override
+    public String combine(Set<String> values) {
+        long max = OFF;
+        String text = "off";
+        for (String value : values) {
+            long l = parseValueSafe(value);
+            if (l > max) {
+                text = value;
+                max = l;
+            }
+        }
+        return text;
+    }
+
+    private static long parseValueSafe(String s) {
+        long value = 0L;
+        try {
+            value = Utils.parseThrottleValue(s);
+        } catch (NumberFormatException nfe) {
+        }
+        return value;
+    }
+
+    @Override
+    public void setValue(String s) {
+        long size = 0;
+        long millis = 1000;
+        try {
+            size = Utils.parseThrottleValue(s);
+            millis = Utils.parseThrottleTimeUnit(s);
+            this.value = s;
+        } catch (NumberFormatException nfe) {
+        }
+        eventType.setThrottle(size, millis);
+    }
+
+    @Override
+    public String getValue() {
+        return value;
+    }
+
+    public static boolean isType(long typeId) {
+        return ThrottleSetting.typeId == typeId;
+    }
+}
+
diff --git a/src/jdk.jfr/share/conf/jfr/default.jfc b/src/jdk.jfr/share/conf/jfr/default.jfc
index 53151be..5c102f7 100644
--- a/src/jdk.jfr/share/conf/jfr/default.jfc
+++ b/src/jdk.jfr/share/conf/jfr/default.jfc
@@ -274,7 +274,7 @@
     </event>
 
     <event name="jdk.ObjectCount">
-      <setting name="enabled" control="memory-profiling-enabled-all">false</setting>
+      <setting name="enabled" control="heap-statistics-enabled">false</setting>
       <setting name="period">everyChunk</setting>
     </event>
 
@@ -443,11 +443,11 @@
     </event>
 
     <event name="jdk.PromoteObjectInNewPLAB">
-      <setting name="enabled" control="memory-profiling-enabled-medium">false</setting>
+      <setting name="enabled" control="promotion-enabled">false</setting>
     </event>
 
     <event name="jdk.PromoteObjectOutsidePLAB">
-      <setting name="enabled" control="memory-profiling-enabled-medium">false</setting>
+      <setting name="enabled" control="promotion-enabled">false</setting>
     </event>
 
     <event name="jdk.ConcurrentModeFailure">
@@ -605,12 +605,18 @@
     </event>
 
     <event name="jdk.ObjectAllocationInNewTLAB">
-      <setting name="enabled" control="memory-profiling-enabled-medium">false</setting>
+      <setting name="enabled">false</setting>
       <setting name="stackTrace">true</setting>
     </event>
 
     <event name="jdk.ObjectAllocationOutsideTLAB">
-      <setting name="enabled" control="memory-profiling-enabled-medium">false</setting>
+      <setting name="enabled">false</setting>
+      <setting name="stackTrace">true</setting>
+    </event>
+
+    <event name="jdk.ObjectAllocationSample">
+      <setting name="enabled" control="enable-object-allocation">true</setting>
+      <setting name="throttle" control="object-allocation-rate">150/s</setting>
       <setting name="stackTrace">true</setting>
     </event>
 
@@ -826,21 +832,41 @@
         <test name="gc-level" operator="equal" value="all"/>
       </condition>
 
-      <selection name="memory-profiling" default="off" label="Memory Profiling">
+      <selection name="memory-profiling" default="low" label="Memory Profiling">
         <option label="Off" name="off">off</option>
+        <option label="Object Allocation" name="low">low</option>
         <option label="Object Allocation and Promotion" name="medium">medium</option>
         <option label="All, including Heap Statistics (May cause long full GCs)" name="all">all</option>
       </selection>
 
-      <condition name="memory-profiling-enabled-medium" true="true" false="false">
+     <condition name="memory-profiling-enabled-low" true="true" false="false">
+        <test name="memory-profiling" operator="equal" value="low"/>
+      </condition>
+
+      <condition name="object-allocation-enabled" true="true" false="false">
+        <or>
+          <test name="memory-profiling" operator="equal" value="low"/>
+          <test name="memory-profiling" operator="equal" value="medium"/>
+          <test name="memory-profiling" operator="equal" value="all"/>
+        </or>
+      </condition>
+
+      <condition name="object-allocation-rate" true="300/s" false="150/s">
+         <or>
+           <test name="memory-profiling" operator="equal" value="medium"/>
+           <test name="memory-profiling" operator="equal" value="all"/>
+         </or>
+       </condition>
+
+      <condition name="promotion-enabled" true="true" false="false">
         <or>
           <test name="memory-profiling" operator="equal" value="medium"/>
           <test name="memory-profiling" operator="equal" value="all"/>
         </or>
       </condition>
 
-      <condition name="memory-profiling-enabled-all" true="true" false="false">
-        <test name="memory-profiling" operator="equal" value="all"/>
+      <condition name="heap-statistics-enabled" true="true" false="false">
+          <test name="memory-profiling" operator="equal" value="all"/>
       </condition>
 
       <selection name="compiler-level" default="normal" label="Compiler">
diff --git a/src/jdk.jfr/share/conf/jfr/profile.jfc b/src/jdk.jfr/share/conf/jfr/profile.jfc
index 7958f6a..52cbd2d 100644
--- a/src/jdk.jfr/share/conf/jfr/profile.jfc
+++ b/src/jdk.jfr/share/conf/jfr/profile.jfc
@@ -274,7 +274,7 @@
     </event>
 
     <event name="jdk.ObjectCount">
-      <setting name="enabled" control="memory-profiling-enabled-all">false</setting>
+      <setting name="enabled" control="heap-statistics-enabled">false</setting>
       <setting name="period">everyChunk</setting>
     </event>
 
@@ -443,11 +443,11 @@
     </event>
 
     <event name="jdk.PromoteObjectInNewPLAB">
-      <setting name="enabled" control="memory-profiling-enabled-medium">true</setting>
+      <setting name="enabled" control="promotion-enabled">true</setting>
     </event>
 
     <event name="jdk.PromoteObjectOutsidePLAB">
-      <setting name="enabled" control="memory-profiling-enabled-medium">true</setting>
+      <setting name="enabled" control="promotion-enabled">true</setting>
     </event>
 
     <event name="jdk.ConcurrentModeFailure">
@@ -605,12 +605,18 @@
     </event>
 
     <event name="jdk.ObjectAllocationInNewTLAB">
-      <setting name="enabled" control="memory-profiling-enabled-medium">true</setting>
+      <setting name="enabled">false</setting>
       <setting name="stackTrace">true</setting>
     </event>
 
     <event name="jdk.ObjectAllocationOutsideTLAB">
-      <setting name="enabled" control="memory-profiling-enabled-medium">true</setting>
+      <setting name="enabled">false</setting>
+      <setting name="stackTrace">true</setting>
+    </event>
+
+    <event name="jdk.ObjectAllocationSample">
+      <setting name="enabled" control="enable-object-allocation">true</setting>
+      <setting name="throttle" control="object-allocation-rate">300/s</setting>
       <setting name="stackTrace">true</setting>
     </event>
 
@@ -826,23 +832,42 @@
         <test name="gc-level" operator="equal" value="all"/>
       </condition>
 
-      <selection name="memory-profiling" default="medium" label="Memory Profiling">
+      <selection name="memory-profiling" default="low" label="Memory Profiling">
         <option label="Off" name="off">off</option>
+        <option label="Object Allocation" name="low">low</option>
         <option label="Object Allocation and Promotion" name="medium">medium</option>
         <option label="All, including Heap Statistics (May cause long full GCs)" name="all">all</option>
       </selection>
 
-      <condition name="memory-profiling-enabled-medium" true="true" false="false">
+     <condition name="memory-profiling-enabled-low" true="true" false="false">
+        <test name="memory-profiling" operator="equal" value="low"/>
+      </condition>
+
+      <condition name="object-allocation-enabled" true="true" false="false">
+        <or>
+          <test name="memory-profiling" operator="equal" value="low"/>
+          <test name="memory-profiling" operator="equal" value="medium"/>
+          <test name="memory-profiling" operator="equal" value="all"/>
+        </or>
+      </condition>
+
+      <condition name="object-allocation-rate" true="300/s" false="150/s">
+         <or>
+           <test name="memory-profiling" operator="equal" value="medium"/>
+           <test name="memory-profiling" operator="equal" value="all"/>
+         </or>
+       </condition>
+
+      <condition name="promotion-enabled" true="true" false="false">
         <or>
           <test name="memory-profiling" operator="equal" value="medium"/>
           <test name="memory-profiling" operator="equal" value="all"/>
         </or>
       </condition>
 
-      <condition name="memory-profiling-enabled-all" true="true" false="false">
-        <test name="memory-profiling" operator="equal" value="all"/>
+      <condition name="heap-statistics-enabled" true="true" false="false">
+          <test name="memory-profiling" operator="equal" value="all"/>
       </condition>
-
       <selection name="compiler-level" default="detailed" label="Compiler">
         <option label="Off" name="off">off</option>
         <option label="Normal" name="normal">normal</option>
diff --git a/test/hotspot/gtest/jfr/test_adaptiveSampler.cpp b/test/hotspot/gtest/jfr/test_adaptiveSampler.cpp
new file mode 100644
index 0000000..920c874
--- /dev/null
+++ b/test/hotspot/gtest/jfr/test_adaptiveSampler.cpp
@@ -0,0 +1,281 @@
+/*
+ * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, Datadog, Inc. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ *
+ */
+
+#include "precompiled.hpp"
+
+// This test performs mocking of certain JVM functionality. This works by
+// including the source file under test inside an anonymous namespace (which
+// prevents linking conflicts) with the mocked symbols redefined.
+
+// The include list should mirror the one found in the included source file -
+// with the ones that should pick up the mocks removed. Those should be included
+// later after the mocks have been defined.
+
+#include <cmath>
+
+#include "jfr/utilities/jfrAllocation.hpp"
+#include "jfr/utilities/jfrRandom.inline.hpp"
+#include "jfr/utilities/jfrSpinlockHelper.hpp"
+#include "jfr/utilities/jfrTime.hpp"
+#include "jfr/utilities/jfrTimeConverter.hpp"
+#include "jfr/utilities/jfrTryLock.hpp"
+#include "logging/log.hpp"
+#include "runtime/atomic.hpp"
+#include "utilities/globalDefinitions.hpp"
+
+#include "unittest.hpp"
+
+// #undef SHARE_JFR_SUPPORT_JFRADAPTIVESAMPLER_HPP
+
+namespace {
+  class MockJfrTimeConverter : public ::JfrTimeConverter {
+  public:
+    static double nano_to_counter_multiplier(bool is_os_time = false) {
+      return 1.0;
+    }
+    static jlong counter_to_nanos(jlong c, bool is_os_time = false) {
+      return c;
+    }
+    static jlong counter_to_millis(jlong c, bool is_os_time = false) {
+      return c * NANOS_PER_MILLISEC;
+    }
+    static jlong nanos_to_countertime(jlong c, bool as_os_time = false) {
+      return c;
+    }
+  };
+
+  class MockJfrTickValue {
+  private:
+    jlong _ticks;
+  public:
+    MockJfrTickValue(jlong ticks) : _ticks(ticks) {};
+    jlong value() {
+      return _ticks;
+    }
+  };
+  class MockJfrTicks {
+  public:
+    static jlong tick;
+    static MockJfrTickValue now() {
+      return MockJfrTickValue(tick);
+    }
+  };
+
+  jlong MockJfrTicks::tick = 0;
+
+  // Reincluding source files in the anonymous namespace unfortunately seems to
+  // behave strangely with precompiled headers (only when using gcc though)
+#ifndef DONT_USE_PRECOMPILED_HEADER
+#define DONT_USE_PRECOMPILED_HEADER
+#endif
+
+#define JfrTicks MockJfrTicks
+#define JfrTimeConverter MockJfrTimeConverter
+
+#include "jfr/support/jfrAdaptiveSampler.hpp"
+#include "jfr/support/jfrAdaptiveSampler.cpp"
+
+#undef JfrTimeConverter
+#undef JfrTicks
+} // anonymous namespace
+
+class JfrGTestAdaptiveSampling : public ::testing::Test {
+ protected:
+  const int max_population_per_window = 2000;
+  const int min_population_per_window = 2;
+  const int window_count = 10000;
+  const clock_t window_duration_ms = 100;
+  const size_t expected_sample_points_per_window = 50;
+  const size_t expected_sample_points = expected_sample_points_per_window * (size_t)window_count;
+  const size_t window_lookback_count = 50; // 50 windows == 5 seconds (for a window duration of 100 ms)
+  const double max_sample_bias = 0.11;
+
+  void SetUp() {
+    // Ensure that tests are separated in time by spreading them by 24hrs apart
+    MockJfrTicks::tick += (24 * 60 * 60) * NANOSECS_PER_SEC;
+  }
+
+  void TearDown() {
+    // nothing
+  }
+
+  void assertDistributionProperties(int distr_slots, jlong* population, jlong* sample, size_t population_size, size_t sample_size, const char* msg) {
+    size_t population_sum = 0;
+    size_t sample_sum = 0;
+    for (int i = 0; i < distr_slots; i++) {
+      population_sum += i * population[i];
+      sample_sum += i * sample[i];
+    }
+
+    double population_mean = population_sum / (double)population_size;
+    double sample_mean = sample_sum / (double)sample_size;
+
+    double population_variance = 0;
+    double sample_variance = 0;
+    for (int i = 0; i < distr_slots; i++) {
+      double population_diff = i - population_mean;
+      population_variance = population[i] * population_diff * population_diff;
+
+      double sample_diff = i - sample_mean;
+      sample_variance = sample[i] * sample_diff * sample_diff;
+    }
+    population_variance = population_variance / (population_size - 1);
+    sample_variance = sample_variance / (sample_size - 1);
+    double population_stdev = sqrt(population_variance);
+    double sample_stdev = sqrt(sample_variance);
+
+    // make sure the standard deviation is ok
+    EXPECT_NEAR(population_stdev, sample_stdev, 0.5) << msg;
+    // make sure that the subsampled set mean is within 2-sigma of the original set mean
+    EXPECT_NEAR(population_mean, sample_mean, population_stdev) << msg;
+    // make sure that the original set mean is within 2-sigma of the subsampled set mean
+    EXPECT_NEAR(sample_mean, population_mean, sample_stdev) << msg;
+  }
+
+  typedef size_t(JfrGTestAdaptiveSampling::* incoming)() const;
+  void test(incoming inc, size_t events_per_window, double expectation, const char* description);
+
+ public:
+  size_t incoming_uniform() const {
+    return os::random() % max_population_per_window + min_population_per_window;
+  }
+
+  size_t incoming_bursty_10_percent() const {
+    bool is_burst = (os::random() % 100) < 10; // 10% burst chance
+    return is_burst ? max_population_per_window : min_population_per_window;
+  }
+
+  size_t incoming_bursty_90_percent() const {
+    bool is_burst = (os::random() % 100) < 90; // 90% burst chance
+    return is_burst ? max_population_per_window : min_population_per_window;
+  }
+
+  size_t incoming_low_rate() const {
+    return min_population_per_window;
+  }
+
+  size_t incoming_high_rate() const {
+    return max_population_per_window;
+  }
+
+  size_t incoming_burst_eval(size_t& count, size_t mod_value) const {
+    return count++ % 10 == mod_value ? max_population_per_window : 0;
+  }
+
+  size_t incoming_early_burst() const {
+    static size_t count = 1;
+    return incoming_burst_eval(count, 1);
+  }
+
+  size_t incoming_mid_burst() const {
+    static size_t count = 1;
+    return incoming_burst_eval(count, 5);
+  }
+
+  size_t incoming_late_burst() const {
+    static size_t count = 1;
+    return incoming_burst_eval(count, 0);
+  }
+};
+
+void JfrGTestAdaptiveSampling::test(JfrGTestAdaptiveSampling::incoming inc, size_t sample_points_per_window, double error_factor, const char* const description) {
+  assert(description != NULL, "invariant");
+  char output[1024] = "Adaptive sampling: ";
+  strcat(output, description);
+  fprintf(stdout, "=== %s\n", output);
+  jlong population[100] = { 0 };
+  jlong sample[100] = { 0 };
+  ::JfrGTestFixedRateSampler sampler = ::JfrGTestFixedRateSampler(expected_sample_points_per_window, window_duration_ms, window_lookback_count);
+  EXPECT_TRUE(sampler.initialize());
+
+  size_t population_size = 0;
+  size_t sample_size = 0;
+  for (int t = 0; t < window_count; t++) {
+    const size_t incoming_events = (this->*inc)();
+    for (size_t i = 0; i < incoming_events; i++) {
+      ++population_size;
+      size_t index = os::random() % 100;
+      population[index] += 1;
+      if (sampler.sample()) {
+        ++sample_size;
+        sample[index] += 1;
+      }
+    }
+    MockJfrTicks::tick += window_duration_ms * NANOSECS_PER_MILLISEC + 1;
+    sampler.sample(); // window rotation
+  }
+
+  const size_t target_sample_size = sample_points_per_window * window_count;
+  EXPECT_NEAR(target_sample_size, sample_size, expected_sample_points * error_factor) << output;
+  strcat(output, ", hit distribution");
+  assertDistributionProperties(100, population, sample, population_size, sample_size, output);
+}
+
+TEST_VM_F(JfrGTestAdaptiveSampling, uniform_rate) {
+  test(&JfrGTestAdaptiveSampling::incoming_uniform, expected_sample_points_per_window, 0.05, "random uniform, all samples");
+}
+
+TEST_VM_F(JfrGTestAdaptiveSampling, low_rate) {
+  test(&JfrGTestAdaptiveSampling::incoming_low_rate, min_population_per_window, 0.05, "low rate");
+}
+
+TEST_VM_F(JfrGTestAdaptiveSampling, high_rate) {
+  test(&JfrGTestAdaptiveSampling::incoming_high_rate, expected_sample_points_per_window, 0.02, "high rate");
+}
+
+// We can think of the windows as splitting up a time period, for example a second (window_duration_ms = 100)
+// The burst tests for early, mid and late apply a burst rate at a selected window, with other windows having no incoming input.
+//
+// - early during the first window of a new time period
+// - mid   during the middle window of a new time period
+// - late  during the last window of a new time period
+//
+// The tests verify the total sample size correspond to the selected bursts:
+//
+// - early start of a second -> each second will have sampled the window set point for a single window only since no debt has accumulated into the new time period.
+// - mid   middle of the second -> each second will have sampled the window set point + accumulated debt for the first 4 windows.
+// - late end of the second -> each second will have sampled the window set point + accumulated debt for the first 9 windows (i.e. it will have sampled all)
+//
+
+TEST_VM_F(JfrGTestAdaptiveSampling, early_burst) {
+  test(&JfrGTestAdaptiveSampling::incoming_early_burst, expected_sample_points_per_window, 0.9, "early burst");
+}
+
+TEST_VM_F(JfrGTestAdaptiveSampling, mid_burst) {
+  test(&JfrGTestAdaptiveSampling::incoming_mid_burst, expected_sample_points_per_window, 0.5, "mid burst");
+}
+
+TEST_VM_F(JfrGTestAdaptiveSampling, late_burst) {
+  test(&JfrGTestAdaptiveSampling::incoming_late_burst, expected_sample_points_per_window, 0.0, "late burst");
+}
+
+// These are randomized burst tests
+TEST_VM_F(JfrGTestAdaptiveSampling, bursty_rate_10_percent) {
+  test(&JfrGTestAdaptiveSampling::incoming_bursty_10_percent, expected_sample_points_per_window, 0.96, "bursty 10%");
+}
+
+TEST_VM_F(JfrGTestAdaptiveSampling, bursty_rate_90_percent) {
+  test(&JfrGTestAdaptiveSampling::incoming_bursty_10_percent, expected_sample_points_per_window, 0.96, "bursty 90%");
+}
diff --git a/test/jdk/TEST.groups b/test/jdk/TEST.groups
index 62160ae..c5bed11 100644
--- a/test/jdk/TEST.groups
+++ b/test/jdk/TEST.groups
@@ -464,7 +464,7 @@
     jdk/jfr/api/recording/event/TestLoadEventAfterStart.java \
     jdk/jfr/api/recording/state/TestState.java \
     jdk/jfr/event/os/TestCPULoad.java \
-    jdk/jfr/event/compiler/TestAllocInNewTLAB.java \
+    jdk/jfr/event/allocation/TestObjectAllocationSampleEvent.java \
     jdk/jfr/jcmd/TestJcmdStartStopDefault.java \
     jdk/jfr/event/io/TestFileStreamEvents.java \
     jdk/jfr/event/compiler/TestCompilerCompile.java \
diff --git a/test/jdk/jdk/jfr/event/compiler/TestAllocInNewTLAB.java b/test/jdk/jdk/jfr/event/allocation/TestObjectAllocationInNewTLABEvent.java
similarity index 65%
rename from test/jdk/jdk/jfr/event/compiler/TestAllocInNewTLAB.java
rename to test/jdk/jdk/jfr/event/allocation/TestObjectAllocationInNewTLABEvent.java
index 61c2366..51d4f27 100644
--- a/test/jdk/jdk/jfr/event/compiler/TestAllocInNewTLAB.java
+++ b/test/jdk/jdk/jfr/event/allocation/TestObjectAllocationInNewTLABEvent.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2020, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -23,18 +23,15 @@
  * questions.
  */
 
-package jdk.jfr.event.compiler;
+package jdk.jfr.event.allocation;
 
 import static java.lang.Math.floor;
-import static jdk.test.lib.Asserts.assertGreaterThanOrEqual;
-import static jdk.test.lib.Asserts.assertLessThanOrEqual;
-
-import java.time.Duration;
 
 import jdk.jfr.Recording;
 import jdk.jfr.consumer.RecordedEvent;
 import jdk.test.lib.jfr.EventNames;
 import jdk.test.lib.jfr.Events;
+import jdk.test.lib.Asserts;
 
 /**
  * @test
@@ -42,8 +39,8 @@
  * @key jfr
  * @requires vm.hasJFR
  * @library /test/lib
- * @run main/othervm -XX:+UseTLAB -XX:TLABSize=100k -XX:-ResizeTLAB -XX:TLABRefillWasteFraction=1 jdk.jfr.event.compiler.TestAllocInNewTLAB
- * @run main/othervm -XX:+UseTLAB -XX:TLABSize=100k -XX:-ResizeTLAB -XX:TLABRefillWasteFraction=1 -Xint jdk.jfr.event.compiler.TestAllocInNewTLAB
+ * @run main/othervm -XX:+UseTLAB -XX:TLABSize=100k -XX:-ResizeTLAB -XX:TLABRefillWasteFraction=1 jdk.jfr.event.allocation.TestObjectAllocationInNewTLABEvent
+ * @run main/othervm -XX:+UseTLAB -XX:TLABSize=100k -XX:-ResizeTLAB -XX:TLABRefillWasteFraction=1 -Xint jdk.jfr.event.allocation.TestObjectAllocationInNewTLABEvent
  */
 
 /**
@@ -56,56 +53,32 @@
  *      max TLAB waste at refill is set to minimum (-XX:TLABRefillWasteFraction=1),
  *          to provoke a new TLAB creation.
  */
-public class TestAllocInNewTLAB {
+public class TestObjectAllocationInNewTLABEvent {
     private final static String EVENT_NAME = EventNames.ObjectAllocationInNewTLAB;
 
     private static final int BYTE_ARRAY_OVERHEAD = 16; // Extra bytes used by a byte array.
     private static final int OBJECT_SIZE  = 100 * 1024;
-    private static final int OBJECT_SIZE_ALT = OBJECT_SIZE + 8; // Object size in case of disabled CompressedOops
+    private static final int OBJECT_SIZE_ALT = OBJECT_SIZE + 8; // Object size in case of disabled CompressedOops.
     private static final int OBJECTS_TO_ALLOCATE = 100;
     private static final String BYTE_ARRAY_CLASS_NAME = new byte[0].getClass().getName();
     private static final int INITIAL_TLAB_SIZE = 100 * 1024;
+    private static int countAllTlabs; // Count all matching tlab allocations.
+    private static int countFullTlabs; // Count matching tlab allocations with full tlab size.
 
-    // make sure allocation isn't dead code eliminated
+    // Make sure allocation isn't dead code eliminated.
     public static byte[] tmp;
 
     public static void main(String[] args) throws Exception {
         Recording recording = new Recording();
-        recording.enable(EVENT_NAME).withThreshold(Duration.ofMillis(0));
-
+        recording.enable(EVENT_NAME);
         recording.start();
         System.gc();
-        for (int i = 0; i < OBJECTS_TO_ALLOCATE; ++i) {
-            tmp = new byte[OBJECT_SIZE - BYTE_ARRAY_OVERHEAD];
-        }
+        allocate();
         recording.stop();
-
-        int countAllTlabs = 0;  // Count all matching tlab allocations.
-        int countFullTlabs = 0; // Count matching tlab allocations with full tlab size.
-        for (RecordedEvent event : Events.fromRecording(recording)) {
-            if (!EVENT_NAME.equals(event.getEventType().getName())) {
-                continue;
-            }
-            System.out.println("Event:" + event);
-
-            long allocationSize = Events.assertField(event, "allocationSize").atLeast(1L).getValue();
-            long tlabSize = Events.assertField(event, "tlabSize").atLeast(allocationSize).getValue();
-            String className = Events.assertField(event, "objectClass.name").notEmpty().getValue();
-
-            boolean isMyEvent = Thread.currentThread().getId() == event.getThread().getJavaThreadId()
-                 && className.equals(BYTE_ARRAY_CLASS_NAME)
-                 && (allocationSize == OBJECT_SIZE || allocationSize == OBJECT_SIZE_ALT);
-            if (isMyEvent) {
-                countAllTlabs++;
-                if (tlabSize == INITIAL_TLAB_SIZE + OBJECT_SIZE || tlabSize == INITIAL_TLAB_SIZE + OBJECT_SIZE_ALT) {
-                    countFullTlabs++;
-                }
-            }
-        }
-
+        verifyRecording(recording);
         int minCount = (int) floor(OBJECTS_TO_ALLOCATE * 0.80);
-        assertGreaterThanOrEqual(countAllTlabs, minCount, "Too few tlab objects allocated");
-        assertLessThanOrEqual(countAllTlabs, OBJECTS_TO_ALLOCATE, "Too many tlab objects allocated");
+        Asserts.assertGreaterThanOrEqual(countAllTlabs, minCount, "Too few tlab objects allocated");
+        Asserts.assertLessThanOrEqual(countAllTlabs, OBJECTS_TO_ALLOCATE, "Too many tlab objects allocated");
 
         // For most GCs we expect the size of each tlab to be
         // INITIAL_TLAB_SIZE + ALLOCATION_SIZE, but that is not always true for G1.
@@ -119,7 +92,33 @@
         // It is only the last tlab in each region that has a smaller size.
         // This means that at least 50% of the allocated tlabs should
         // have the expected size (1 full tlab, and 1 fractional tlab).
-        assertGreaterThanOrEqual(2*countFullTlabs, countAllTlabs, "Too many fractional tlabs.");
+        Asserts.assertGreaterThanOrEqual(2*countFullTlabs, countAllTlabs, "Too many fractional tlabs.");
     }
 
+    private static void allocate() {
+        for (int i = 0; i < OBJECTS_TO_ALLOCATE; ++i) {
+            tmp = new byte[OBJECT_SIZE - BYTE_ARRAY_OVERHEAD];
+        }
+    }
+
+    private static void verifyRecording(Recording recording) throws Exception {
+        for (RecordedEvent event : Events.fromRecording(recording)) {
+            verify(event);
+        }
+    }
+
+    private static void verify(RecordedEvent event) {
+        if (Thread.currentThread().getId() != event.getThread().getJavaThreadId()) {
+            return;
+        }
+        long allocationSize = Events.assertField(event, "allocationSize").atLeast(1L).getValue();
+        long tlabSize = Events.assertField(event, "tlabSize").atLeast(allocationSize).getValue();
+        String className = Events.assertField(event, "objectClass.name").notEmpty().getValue();
+        if (className.equals(BYTE_ARRAY_CLASS_NAME) && (allocationSize == OBJECT_SIZE || allocationSize == OBJECT_SIZE_ALT)) {
+            countAllTlabs++;
+            if (tlabSize == INITIAL_TLAB_SIZE + OBJECT_SIZE || tlabSize == INITIAL_TLAB_SIZE + OBJECT_SIZE_ALT) {
+                countFullTlabs++;
+            }
+        }
+    }
 }
diff --git a/test/jdk/jdk/jfr/event/compiler/TestAllocOutsideTLAB.java b/test/jdk/jdk/jfr/event/allocation/TestObjectAllocationOutsideTLABEvent.java
similarity index 66%
rename from test/jdk/jdk/jfr/event/compiler/TestAllocOutsideTLAB.java
rename to test/jdk/jdk/jfr/event/allocation/TestObjectAllocationOutsideTLABEvent.java
index ec90d3d..9efbf4b 100644
--- a/test/jdk/jdk/jfr/event/compiler/TestAllocOutsideTLAB.java
+++ b/test/jdk/jdk/jfr/event/allocation/TestObjectAllocationOutsideTLABEvent.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2020, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -23,18 +23,15 @@
  * questions.
  */
 
-package jdk.jfr.event.compiler;
+package jdk.jfr.event.allocation;
 
 import static java.lang.Math.floor;
-import static jdk.test.lib.Asserts.assertGreaterThanOrEqual;
-import static jdk.test.lib.Asserts.assertLessThanOrEqual;
-
-import java.time.Duration;
 
 import jdk.jfr.Recording;
 import jdk.jfr.consumer.RecordedEvent;
 import jdk.test.lib.jfr.EventNames;
 import jdk.test.lib.jfr.Events;
+import jdk.test.lib.Asserts;
 
 /**
  * @test
@@ -42,8 +39,8 @@
  * @key jfr
  * @requires vm.hasJFR
  * @library /test/lib
- * @run main/othervm -XX:+UseTLAB -XX:TLABSize=90k -XX:-ResizeTLAB -XX:TLABRefillWasteFraction=256 jdk.jfr.event.compiler.TestAllocOutsideTLAB
- * @run main/othervm -XX:+UseTLAB -XX:TLABSize=90k -XX:-ResizeTLAB -XX:TLABRefillWasteFraction=256 -Xint jdk.jfr.event.compiler.TestAllocOutsideTLAB
+ * @run main/othervm -XX:+UseTLAB -XX:TLABSize=90k -XX:-ResizeTLAB -XX:TLABRefillWasteFraction=256 jdk.jfr.event.allocation.TestObjectAllocationOutsideTLABEvent
+ * @run main/othervm -XX:+UseTLAB -XX:TLABSize=90k -XX:-ResizeTLAB -XX:TLABRefillWasteFraction=256 -Xint jdk.jfr.event.allocation.TestObjectAllocationOutsideTLABEvent
  */
 
 /**
@@ -56,7 +53,7 @@
  *      max TLAB waste at refill is set to 256 (-XX:TLABRefillWasteFraction=256),
  *          to prevent a new TLAB creation.
 */
-public class TestAllocOutsideTLAB {
+public class TestObjectAllocationOutsideTLABEvent {
     private static final String EVENT_NAME = EventNames.ObjectAllocationOutsideTLAB;
 
     private static final int BYTE_ARRAY_OVERHEAD = 16; // Extra bytes used by a byte array
@@ -64,39 +61,43 @@
     private static final int OBJECT_SIZE_ALT = OBJECT_SIZE + 8; // Object size in case of disabled CompressedOops
     private static final int OBJECTS_TO_ALLOCATE = 100;
     private static final String BYTE_ARRAY_CLASS_NAME = new byte[0].getClass().getName();
+    private static int eventCount;
 
-    public static byte[] tmp; // Used to prevent optimizer from removing code.
+    // Make sure allocation isn't dead code eliminated.
+    public static byte[] tmp;
 
     public static void main(String[] args) throws Exception {
         Recording recording = new Recording();
-        recording.enable(EVENT_NAME).withThreshold(Duration.ofMillis(0));
+        recording.enable(EVENT_NAME);
         recording.start();
+        allocate();
+        recording.stop();
+        verifyRecording(recording);
+        int minCount = (int) floor(OBJECTS_TO_ALLOCATE * 0.80);
+        Asserts.assertGreaterThanOrEqual(eventCount, minCount, "Too few objects allocated");
+        Asserts.assertLessThanOrEqual(eventCount, OBJECTS_TO_ALLOCATE, "Too many objects allocated");
+    }
+
+    private static void allocate() {
         for (int i = 0; i < OBJECTS_TO_ALLOCATE; ++i) {
             tmp = new byte[OBJECT_SIZE - BYTE_ARRAY_OVERHEAD];
         }
-        recording.stop();
-
-        int countEvents = 0;
-        for (RecordedEvent event : Events.fromRecording(recording)) {
-            if (!EVENT_NAME.equals(event.getEventType().getName())) {
-                continue;
-            }
-            System.out.println("Event:" + event);
-
-            long allocationSize = Events.assertField(event, "allocationSize").atLeast(1L).getValue();
-            String className = Events.assertField(event, "objectClass.name").notEmpty().getValue();
-
-            boolean isMyEvent = Thread.currentThread().getId() == event.getThread().getJavaThreadId()
-                && className.equals(BYTE_ARRAY_CLASS_NAME)
-                 && (allocationSize == OBJECT_SIZE || allocationSize == OBJECT_SIZE_ALT);
-            if (isMyEvent) {
-                ++countEvents;
-            }
-        }
-
-        int minCount = (int) floor(OBJECTS_TO_ALLOCATE * 0.80);
-        assertGreaterThanOrEqual(countEvents, minCount, "Too few tlab objects allocated");
-        assertLessThanOrEqual(countEvents, OBJECTS_TO_ALLOCATE, "Too many tlab objects allocated");
     }
 
+    private static void verifyRecording(Recording recording) throws Exception {
+        for (RecordedEvent event : Events.fromRecording(recording)) {
+            verify(event);
+        }
+    }
+
+    private static void verify(RecordedEvent event) {
+        if (Thread.currentThread().getId() != event.getThread().getJavaThreadId()) {
+            return;
+        }
+        long allocationSize = Events.assertField(event, "allocationSize").atLeast(1L).getValue();
+        String className = Events.assertField(event, "objectClass.name").notEmpty().getValue();
+        if (className.equals(BYTE_ARRAY_CLASS_NAME) && (allocationSize == OBJECT_SIZE || allocationSize == OBJECT_SIZE_ALT)) {
+            ++eventCount;
+        }
+    }
 }
diff --git a/test/jdk/jdk/jfr/event/allocation/TestObjectAllocationSampleEvent.java b/test/jdk/jdk/jfr/event/allocation/TestObjectAllocationSampleEvent.java
new file mode 100644
index 0000000..f16d9ee
--- /dev/null
+++ b/test/jdk/jdk/jfr/event/allocation/TestObjectAllocationSampleEvent.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.jfr.event.allocation;
+
+import java.util.concurrent.CountDownLatch;
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.jfr.consumer.RecordingStream;
+import jdk.test.lib.Asserts;
+import jdk.test.lib.jfr.EventNames;
+import jdk.test.lib.jfr.Events;
+
+/**
+ * @test
+ * @summary Tests ObjectAllocationSampleEvent
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+ * @run main/othervm -XX:+UseTLAB -XX:TLABSize=2k -XX:-ResizeTLAB jdk.jfr.event.allocation.TestObjectAllocationSampleEvent
+ */
+public class TestObjectAllocationSampleEvent {
+    private static final String EVENT_NAME = EventNames.ObjectAllocationSample;
+    private static final int OBJECT_SIZE = 4 * 1024;
+    private static final int OBJECTS_TO_ALLOCATE = 16;
+    private static final String BYTE_ARRAY_CLASS_NAME = new byte[0].getClass().getName();
+
+    // Make sure allocation isn't dead code eliminated.
+    public static byte[] tmp;
+
+    public static void main(String... args) throws Exception {
+        CountDownLatch delivered = new CountDownLatch(1);
+        Thread current = Thread.currentThread();
+        try (RecordingStream rs = new RecordingStream()) {
+            rs.enable(EVENT_NAME);
+            rs.onEvent(EVENT_NAME, e -> {
+                if (verify(e, current)) {
+                    delivered.countDown();
+                }
+            });
+            rs.startAsync();
+            for (int i = 0; i < OBJECTS_TO_ALLOCATE; ++i) {
+                tmp = new byte[OBJECT_SIZE];
+            }
+            delivered.await();
+        }
+    }
+
+    private static boolean verify(RecordedEvent event, Thread thread) {
+        if (thread.getId() != event.getThread().getJavaThreadId()) {
+            return false;
+        }
+        if (Events.assertField(event, "objectClass.name").notEmpty().getValue().equals(BYTE_ARRAY_CLASS_NAME)) {
+            Events.assertField(event, "weight").atLeast(1L);
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/test/jdk/jdk/jfr/event/allocation/TestObjectAllocationSampleEventThrottling.java b/test/jdk/jdk/jfr/event/allocation/TestObjectAllocationSampleEventThrottling.java
new file mode 100644
index 0000000..1e09441
--- /dev/null
+++ b/test/jdk/jdk/jfr/event/allocation/TestObjectAllocationSampleEventThrottling.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.jfr.event.allocation;
+
+import static java.lang.Math.floor;
+
+import java.util.List;
+
+import jdk.jfr.Recording;
+import jdk.jfr.consumer.RecordedEvent;
+import jdk.test.lib.jfr.EventNames;
+import jdk.test.lib.jfr.Events;
+import jdk.test.lib.Asserts;
+
+/**
+ * @test
+ * @summary Test that when an object is allocated outside a TLAB an event will be triggered.
+ * @key jfr
+ * @requires vm.hasJFR
+ * @library /test/lib
+*  @run main/othervm -XX:+UseTLAB -XX:TLABSize=2k -XX:-ResizeTLAB jdk.jfr.event.allocation.TestObjectAllocationSampleEventThrottling
+ */
+
+public class TestObjectAllocationSampleEventThrottling {
+    private static final String EVENT_NAME = EventNames.ObjectAllocationSample;
+
+    private static final int BYTE_ARRAY_OVERHEAD = 16; // Extra bytes used by a byte array
+    private static final int OBJECT_SIZE = 100 * 1024;
+    private static final int OBJECT_SIZE_ALT = OBJECT_SIZE + 8; // Object size in case of disabled CompressedOops
+    private static final int OBJECTS_TO_ALLOCATE = 100;
+    private static final String BYTE_ARRAY_CLASS_NAME = new byte[0].getClass().getName();
+    private static int eventCount;
+
+    // Make sure allocation isn't dead code eliminated.
+    public static byte[] tmp;
+
+    public static void main(String[] args) throws Exception {
+        testZeroPerSecond();
+        testThrottleSettings();
+    }
+
+    private static void testZeroPerSecond() throws Exception {
+        Recording r1 = new Recording();
+        setThrottle(r1, "0/s");
+        r1.start();
+        allocate();
+        r1.stop();
+        List<RecordedEvent> events = Events.fromRecording(r1);
+        Asserts.assertTrue(events.isEmpty(), "throttle rate 0/s should not emit any events");
+    }
+
+    private static void testThrottleSettings() throws Exception {
+        Recording r1 = new Recording();
+        // 0/s will not emit any events
+        setThrottle(r1, "0/s");
+        r1.start();
+        Recording r2 = new Recording();
+        // 1/ns is a *very* high emit rate, it should trump the previous 0/s value
+        // to allow the allocation sample events to be recorded.
+        setThrottle(r2, "1/ns");
+        r2.start();
+        allocate();
+        r2.stop();
+        r1.stop();
+        verifyRecording(r2);
+        int minCount = (int) floor(OBJECTS_TO_ALLOCATE * 0.80);
+        Asserts.assertGreaterThanOrEqual(eventCount, minCount, "Too few object samples allocated");
+        List<RecordedEvent> events = Events.fromRecording(r1);
+        Asserts.assertFalse(events.isEmpty(), "r1 should also have events");
+    }
+
+    private static void setThrottle(Recording recording, String rate) {
+        recording.enable(EVENT_NAME).with("throttle", rate);
+    }
+
+    private static void allocate() {
+        for (int i = 0; i < OBJECTS_TO_ALLOCATE; ++i) {
+            tmp = new byte[OBJECT_SIZE - BYTE_ARRAY_OVERHEAD];
+        }
+    }
+
+    private static void verifyRecording(Recording recording) throws Exception {
+        for (RecordedEvent event : Events.fromRecording(recording)) {
+            verify(event);
+        }
+    }
+
+    private static void verify(RecordedEvent event) {
+        if (Thread.currentThread().getId() != event.getThread().getJavaThreadId()) {
+            return;
+        }
+        if (Events.assertField(event, "objectClass.name").notEmpty().getValue().equals(BYTE_ARRAY_CLASS_NAME)) {
+            Events.assertField(event, "weight").atLeast(1L);
+            ++eventCount;
+        }
+    }
+}
diff --git a/test/lib/jdk/test/lib/jfr/EventNames.java b/test/lib/jdk/test/lib/jfr/EventNames.java
index 482de84..3d383a9 100644
--- a/test/lib/jdk/test/lib/jfr/EventNames.java
+++ b/test/lib/jdk/test/lib/jfr/EventNames.java
@@ -165,6 +165,7 @@
     public final static String CodeCacheFull = PREFIX + "CodeCacheFull";
     public final static String ObjectAllocationInNewTLAB = PREFIX + "ObjectAllocationInNewTLAB";
     public final static String ObjectAllocationOutsideTLAB = PREFIX + "ObjectAllocationOutsideTLAB";
+    public final static String ObjectAllocationSample = PREFIX + "ObjectAllocationSample";
     public final static String Deoptimization = PREFIX + "Deoptimization";
 
     // OS