diff --git a/core/src/main/java/com/yahoo/ycsb/Client.java b/core/src/main/java/com/yahoo/ycsb/Client.java
index d64772564d81e90e5daee589ef8fc9ba28ca8d7b..07c46034d62b64b925697ab99725cb5ea387831d 100644
--- a/core/src/main/java/com/yahoo/ycsb/Client.java
+++ b/core/src/main/java/com/yahoo/ycsb/Client.java
@@ -47,6 +47,12 @@ class StatusThread extends Thread
   /** Counts down each of the clients completing. */
   private final CountDownLatch _completeLatch;
 
+  /** Stores the measurements for the run. */
+  private final Measurements _measurements;
+  
+  /** Whether or not to track the JVM stats per run */
+  private final boolean _trackJVMStats;
+  
   /** The clients that are running. */
   private final List<ClientThread> _clients;
 
@@ -56,8 +62,17 @@ class StatusThread extends Thread
   /** The interval for reporting status. */
   private long _sleeptimeNs;
 
+  /** JVM max/mins */
+  private int _maxThreads;
+  private int _minThreads = Integer.MAX_VALUE;
+  private long _maxUsedMem;
+  private long _minUsedMem = Long.MAX_VALUE;
+  private double _maxLoadAvg;
+  private double _minLoadAvg = Double.MAX_VALUE;
+  private long lastGCCount = 0;
+
   /**
-   * Creates a new StatusThread.
+   * Creates a new StatusThread without JVM stat tracking.
    *
    * @param completeLatch The latch that each client thread will {@link CountDownLatch#countDown()} as they complete.
    * @param clients The clients to collect metrics from.
@@ -67,14 +82,33 @@ class StatusThread extends Thread
    */
   public StatusThread(CountDownLatch completeLatch, List<ClientThread> clients,
                       String label, boolean standardstatus, int statusIntervalSeconds)
+  {
+    this(completeLatch, clients, label, standardstatus, statusIntervalSeconds, false);
+  }
+  
+  /**
+   * Creates a new StatusThread.
+   *
+   * @param completeLatch The latch that each client thread will {@link CountDownLatch#countDown()} as they complete.
+   * @param clients The clients to collect metrics from.
+   * @param label The label for the status.
+   * @param standardstatus If true the status is printed to stdout in addition to stderr.
+   * @param statusIntervalSeconds The number of seconds between status updates.
+   * @param trackJVMStats Whether or not to track JVM stats.
+   */
+  public StatusThread(CountDownLatch completeLatch, List<ClientThread> clients,
+                      String label, boolean standardstatus, int statusIntervalSeconds,
+                      boolean trackJVMStats)
   {
     _completeLatch=completeLatch;
     _clients=clients;
     _label=label;
     _standardstatus=standardstatus;
     _sleeptimeNs=TimeUnit.SECONDS.toNanos(statusIntervalSeconds);
+    _measurements = Measurements.getMeasurements();
+    _trackJVMStats = trackJVMStats;
   }
-
+  
   /**
    * Run and periodically report status.
    */
@@ -94,6 +128,10 @@ class StatusThread extends Thread
       long nowMs=System.currentTimeMillis();
 
       lastTotalOps = computeStats(startTimeMs, startIntervalMs, nowMs, lastTotalOps);
+      
+      if (_trackJVMStats) {
+        measureJVM();
+      }
 
       alldone = waitForClientsUntil(deadline);
 
@@ -102,6 +140,9 @@ class StatusThread extends Thread
     }
     while (!alldone);
 
+    if (_trackJVMStats) {
+      measureJVM();
+    }
     // Print the final stats.
     computeStats(startTimeMs, startIntervalMs, System.currentTimeMillis(), lastTotalOps);
   }
@@ -187,6 +228,83 @@ class StatusThread extends Thread
 
     return alldone;
   }
+
+  /** Executes the JVM measurements. */
+  private void measureJVM() {
+    final int threads = Utils.getActiveThreadCount();
+    if (threads < _minThreads) {
+      _minThreads = threads;
+    }
+    if (threads > _maxThreads) {
+      _maxThreads = threads;
+    }
+    _measurements.measure("THREAD_COUNT", threads);
+    
+    // TODO - once measurements allow for other number types, switch to using
+    // the raw bytes. Otherwise we can track in MB to avoid negative values 
+    // when faced with huge heaps.
+    final int usedMem = Utils.getUsedMemoryMegaBytes();
+    if (usedMem < _minUsedMem) {
+      _minUsedMem = usedMem;
+    }
+    if (usedMem > _maxUsedMem) {
+      _maxUsedMem = usedMem;
+    }
+    _measurements.measure("USED_MEM_MB", usedMem);
+    
+    // Some JVMs may not implement this feature so if the value is less than
+    // zero, just ommit it.
+    final double systemLoad = Utils.getSystemLoadAverage();
+    if (systemLoad >= 0) {
+      // TODO - store the double if measurements allows for them
+      _measurements.measure("SYS_LOAD_AVG", (int)systemLoad);
+      if (systemLoad > _maxLoadAvg) {
+        _maxLoadAvg = systemLoad;
+      }
+      if (systemLoad < _minLoadAvg) {
+        _minLoadAvg = systemLoad;
+      }
+    }
+     
+    final long gcs = Utils.getGCTotalCollectionCount();
+    _measurements.measure("GCS", (int)(gcs - lastGCCount));
+    lastGCCount = gcs;
+  }
+  
+  /** @return The maximum threads running during the test. */
+  public int getMaxThreads() {
+    return _maxThreads;
+  }
+  
+  /** @return The minimum threads running during the test. */
+  public int getMinThreads() {
+    return _minThreads;
+  }
+  
+  /** @return The maximum memory used during the test. */
+  public long getMaxUsedMem() {
+    return _maxUsedMem;
+  }
+  
+  /** @return The minimum memory used during the test. */
+  public long getMinUsedMem() {
+    return _minUsedMem;
+  }
+  
+  /** @return The maximum load average during the test. */
+  public double getMaxLoadAvg() {
+    return _maxLoadAvg;
+  }
+  
+  /** @return The minimum load average during the test. */
+  public double getMinLoadAvg() {
+    return _minLoadAvg;
+  }
+
+  /** @return Whether or not the thread is tracking JVM stats. */
+  public boolean trackJVMStats() {
+    return _trackJVMStats;
+  }
 }
 
 /**
@@ -474,6 +592,8 @@ public class Client
    */
   public static final String DO_TRANSACTIONS_PROPERTY = "dotransactions";
 
+  /** An optional thread used to track progress and measure JVM stats. */
+  private static StatusThread statusthread = null;
 
   public static void usageMessage()
   {
@@ -553,6 +673,16 @@ public class Client
       exporter.write("OVERALL", "RunTime(ms)", runtime);
       double throughput = 1000.0 * (opcount) / (runtime);
       exporter.write("OVERALL", "Throughput(ops/sec)", throughput);
+      
+      exporter.write("TOTAL_GCs", "Count", Utils.getGCTotalCollectionCount());
+      if (statusthread != null && statusthread.trackJVMStats()) {
+        exporter.write("MAX_MEM_USED", "MBs", statusthread.getMaxUsedMem());
+        exporter.write("MIN_MEM_USED", "MBs", statusthread.getMinUsedMem());
+        exporter.write("MAX_THREADS", "Count", statusthread.getMaxThreads());
+        exporter.write("MIN_THREADS", "Count", statusthread.getMinThreads());
+        exporter.write("MAX_SYS_LOAD_AVG", "Load", statusthread.getMaxLoadAvg());
+        exporter.write("MIN_SYS_LOAD_AVG", "Load", statusthread.getMinLoadAvg());
+      }
 
       Measurements.getMeasurements().exportMeasurements(exporter);
     } finally
@@ -894,8 +1024,6 @@ public class Client
       clients.add(t);
     }
 
-    StatusThread statusthread=null;
-
     if (status)
     {
       boolean standardstatus=false;
@@ -904,7 +1032,9 @@ public class Client
         standardstatus=true;
       }
       int statusIntervalSeconds = Integer.parseInt(props.getProperty("status.interval","10"));
-      statusthread=new StatusThread(completeLatch,clients,label,standardstatus,statusIntervalSeconds);
+      boolean trackJVMStats = props.getProperty(Measurements.MEASUREMENT_TRACK_JVM_PROPERTY, 
+          Measurements.MEASUREMENT_TRACK_JVM_PROPERTY_DEFAULT).equals("true");
+      statusthread=new StatusThread(completeLatch,clients,label,standardstatus,statusIntervalSeconds,trackJVMStats);
       statusthread.start();
     }
 
diff --git a/core/src/main/java/com/yahoo/ycsb/Utils.java b/core/src/main/java/com/yahoo/ycsb/Utils.java
index 5fe699afdaa311d1953b4f2b1674edfcc345750d..219906770dfdc89fc0bd54ec30dd0a86d8c3642f 100644
--- a/core/src/main/java/com/yahoo/ycsb/Utils.java
+++ b/core/src/main/java/com/yahoo/ycsb/Utils.java
@@ -17,6 +17,10 @@
 
 package com.yahoo.ycsb;
 
+import java.lang.management.GarbageCollectorMXBean;
+import java.lang.management.ManagementFactory;
+import java.lang.management.OperatingSystemMXBean;
+import java.util.List;
 import java.util.Random;
 
 /**
@@ -174,4 +178,49 @@ public class Utils
       public static byte[] doubleToBytes(final double val) {
         return longToBytes(Double.doubleToRawLongBits(val));
       }
+      
+      /**
+       * Measure the estimated active thread count in the current thread group.
+       * Since this calls {@link Thread.activeCount} it should be called from the
+       * main thread or one started by the main thread. Threads included in the
+       * count can be in any state. 
+       * For a more accurate count we could use {@link Thread.getAllStackTraces().size()} 
+       * but that freezes the JVM and incurs a high overhead.
+       * @return An estimated thread count, good for showing the thread count
+       * over time.
+       */
+      public static int getActiveThreadCount() {
+        return Thread.activeCount();
+      }
+      
+      /** @return The currently used memory in bytes */
+      public static long getUsedMemoryBytes() {
+        final Runtime runtime = Runtime.getRuntime();
+        return runtime.totalMemory() - runtime.freeMemory();
+      }
+      
+      /** @return The currently used memory in megabytes. */
+      public static int getUsedMemoryMegaBytes() {
+        return (int)(getUsedMemoryBytes() / 1024 / 1024);
+      }
+      
+      /** @return The current system load average if supported by the JDK. 
+       * If it's not supported, the value will be negative. */
+      public static double getSystemLoadAverage() {
+        final OperatingSystemMXBean osBean = 
+            ManagementFactory.getOperatingSystemMXBean();
+        return osBean.getSystemLoadAverage();
+      }
+      
+      /** @return The total number of garbage collections executed for all 
+       * memory pools. */ 
+      public static long getGCTotalCollectionCount() {
+        final List<GarbageCollectorMXBean> gcBeans = 
+            ManagementFactory.getGarbageCollectorMXBeans();
+        long count = 0;
+        for (final GarbageCollectorMXBean bean : gcBeans) {
+          count += bean.getCollectionCount();
+        }
+        return count;
+      }
 }
diff --git a/core/src/main/java/com/yahoo/ycsb/measurements/Measurements.java b/core/src/main/java/com/yahoo/ycsb/measurements/Measurements.java
index fe1e9cc16707ee8b3dce8855f2a0d4a95995c85c..26d340cc4ce8241a5d276a28e95aeb3a241753a8 100644
--- a/core/src/main/java/com/yahoo/ycsb/measurements/Measurements.java
+++ b/core/src/main/java/com/yahoo/ycsb/measurements/Measurements.java
@@ -49,6 +49,9 @@ public class Measurements {
   
   public static final String MEASUREMENT_INTERVAL = "measurement.interval";
   private static final String MEASUREMENT_INTERVAL_DEFAULT = "op";
+  
+  public static final String MEASUREMENT_TRACK_JVM_PROPERTY = "measurement.trackjvm";
+  public static final String MEASUREMENT_TRACK_JVM_PROPERTY_DEFAULT = "false";
 
   static Measurements singleton=null;
   static Properties measurementproperties=null;
diff --git a/core/src/test/java/com/yahoo/ycsb/TestUtils.java b/core/src/test/java/com/yahoo/ycsb/TestUtils.java
index cde5177656b728fc7d2d636b78e65ec808cf733f..7121313781c85c681b72a738a87f874947c7e422 100644
--- a/core/src/test/java/com/yahoo/ycsb/TestUtils.java
+++ b/core/src/test/java/com/yahoo/ycsb/TestUtils.java
@@ -99,6 +99,19 @@ public class TestUtils {
     Utils.bytesToDouble(new byte[] { 0, 0, 0, 0, 0, 0, 0 });
   }
   
+  @Test
+  public void jvmUtils() throws Exception {
+    // This should ALWAYS return at least one thread.
+    assertTrue(Utils.getActiveThreadCount() > 0);
+    // This should always be greater than 0 or something is goofed up in the JVM.
+    assertTrue(Utils.getUsedMemoryBytes() > 0);
+    // Some operating systems may not implement this so we don't have a good
+    // test. Just make sure it doesn't throw an exception.
+    Utils.getSystemLoadAverage();
+    // This will probably be zero but should never be negative.
+    assertTrue(Utils.getGCTotalCollectionCount() >= 0);
+  }
+  
   /**
    * Since this version of TestNG doesn't appear to have an assertArrayEquals,
    * this will compare the two to make sure they're the same. 
diff --git a/workloads/workload_template b/workloads/workload_template
index f5e80c8899e0e66ab41ea7a2add5c4a511dcb5e5..aff1e7c8e13cba9fd014b7a56b7878a15519e8cf 100644
--- a/workloads/workload_template
+++ b/workloads/workload_template
@@ -1,4 +1,4 @@
-# Copyright (c) 2012 YCSB contributors. All rights reserved.
+# Copyright (c) 2012-2016 YCSB contributors. All rights reserved.
 #
 # Licensed under the Apache License, Version 2.0 (the "License"); you
 # may not use this file except in compliance with the License. You
@@ -133,6 +133,16 @@ measurementtype=histogram
 # a new output file will be created.
 #measurement.raw.output_file = /tmp/your_output_file_for_this_run
 
+# JVM Reporting.
+#
+# Measure JVM information over time including GC counts, max and min memory
+# used, max and min thread counts, max and min system load and others. This
+# setting must be enabled in conjunction with the "-s" flag to run the status
+# thread. Every "status.interval", the status thread will capture JVM 
+# statistics and record the results. At the end of the run, max and mins will
+# be recorded.
+# measurement.trackjvm = false
+
 # The range of latencies to track in the histogram (milliseconds)
 histogram.buckets=1000
 
@@ -168,4 +178,4 @@ timeseries.granularity=1000
 # core_workload_insertion_retry_limit = 0
 #
 # the following number controls the interval between retries (in seconds):
-# core_workload_insertion_retry_interval = 3
+# core_workload_insertion_retry_interval = 3
\ No newline at end of file