diff --git a/Control/AthenaKernel/AthenaKernel/RecyclableDataObject.h b/Control/AthenaKernel/AthenaKernel/RecyclableDataObject.h
new file mode 100644
index 0000000000000000000000000000000000000000..45f3fd46d626a8cba67144f9e7ce0f48c1b465a2
--- /dev/null
+++ b/Control/AthenaKernel/AthenaKernel/RecyclableDataObject.h
@@ -0,0 +1,141 @@
+// This file's extension implies that it's C, but it's really -*- C++ -*-.
+/*
+ * Copyright (C) 2002-2018 CERN for the benefit of the ATLAS collaboration.
+ */
+/**
+ * @file AthenaKernel/RecyclableDataObject.h
+ * @author scott snyder <snyder@bnl.gov>
+ * @date Oct, 2018
+ * @brief Helper for recycling objects from event to event.
+ */
+
+
+#ifndef ATHENAKERNEL_RECYCLABLEDATAOBJECT_H
+#define ATHENAKERNEL_RECYCLABLEDATAOBJECT_H
+
+
+// Work around a warning in tbb, found by gcc8.
+// Fixed in TBB 2018 U5.
+#if defined(__GNUC__) && __GNUC__ >= 8
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wclass-memaccess"
+#endif
+#include "tbb/concurrent_queue.h"
+#if defined(__GNUC__) && __GNUC__ >= 8
+# pragma GCC diagnostic pop
+#endif
+
+
+namespace Athena {
+
+
+/**
+ * @brief Helper for recycling objects from event to event.
+ *
+ * In some cases, one wants to reuse already-allocated objects from event to event,
+ * rather than deleting and recreating them each time.  This has typically been done
+ * for complicated objects, such as classes deriving from IdentfiableContainer.
+ *
+ * Some care is obviously needed for this to work in a MT job.  Rather than holding
+ * a pointer to a specific object from a class member, we instead maintain
+ * a concurrent_queue of pointers to objects.  When we want an object, we get
+ * one from the queue; if the queue is empty, then we allocate a new one.
+ *
+ * The object used should derive from @c DataObject.  We bump the reference count
+ * by 1 when the object is allocated so that it won't get deleted by StoreGate.
+ * We also override @c release() so that when the reference count goes back
+ * to 1 it is `recycled'.  When this happens, we call the @c recycle() method
+ * on the object (which should probably be protected) and put the object
+ * back on the queue to be allocated again.
+ *
+ * Example use:
+ *@code
+ *  class MyDataObj : public DataObject { ...
+ *  public:
+ *    MyDataObj (bool createColl); ...
+ *  protected:
+ *    void recycle();
+ *  ... };
+ *
+ *  class MyConverter { ...
+ *  private:
+ *    RecyclableDataQueue<MyDataObj> m_queue;
+ *    ... };
+ *
+ *  StatusCode MyConverter::createObj (...) { ...
+ *    MyDataObj* obj = m_queue.get (true);
+ @endcode
+ * 
+ * @c DOBJ should derive from @c DataObject and should provide a member @c recycle().
+ */
+template <class DOBJ>
+class RecyclableDataObject : public DOBJ
+{
+public:
+  /// Underlying queue type holding these objects.
+  typedef tbb::concurrent_queue<DOBJ*> queue_t;
+
+
+  /**
+   * @brief Constructor.
+   * @param queue Queue on which this object will be entered when it is released.
+   * @param args... Additional arguments to pass to the @c DOBJ constructor.
+   */
+  template <typename... ARGS>
+  RecyclableDataObject (queue_t& queue, ARGS&&... args);
+
+
+  /**
+   * @brief DataObject release method.
+   *
+   * This overrides the @c release() method of @c DataObject.
+   * Once the reference count falls back to 1 (meaning that the object is no longer
+   * referenced from StoreGate), we recycle the object back to the queue.
+   */
+  virtual unsigned long release() override;
+
+
+private:
+  /// The queue on which this object will be placed when it is recycled.
+  queue_t& m_queue;
+};
+
+
+/**
+ * @brief Holder for recyclable objects.
+ *
+ * See @c RecyclableDataObject.
+ */
+template <class DOBJ>
+class RecyclableDataQueue
+  : public RecyclableDataObject<DOBJ>::queue_t
+{
+public:
+  /// Underlying queue type holding these objects.
+  using RecyclableDataObject<DOBJ>::queue_t::queue_t;
+
+
+  /**
+   * @brief Get an object, either a new one or one recycled from a previous event.
+   * @param args... Arguments to pass to the @c DOBJ constructor if we make a new one.
+   */
+  template <typename... ARGS>
+  DOBJ* get (ARGS&&... args);
+
+
+  /**
+   * @brief Destructor.
+   *
+   * Free all objects in the queue.
+   */
+  ~RecyclableDataQueue();
+};
+
+
+} // namespace Athena
+
+
+#include "AthenaKernel/RecyclableDataObject.icc"
+
+
+#endif // not ATHENAKERNEL_RECYCLABLEDATAOBJECT_H
diff --git a/Control/AthenaKernel/AthenaKernel/RecyclableDataObject.icc b/Control/AthenaKernel/AthenaKernel/RecyclableDataObject.icc
new file mode 100644
index 0000000000000000000000000000000000000000..870f26dad722e462a340db311e42fdabe9805d98
--- /dev/null
+++ b/Control/AthenaKernel/AthenaKernel/RecyclableDataObject.icc
@@ -0,0 +1,82 @@
+// $Id$
+/*
+ * Copyright (C) 2002-2018 CERN for the benefit of the ATLAS collaboration.
+ */
+/**
+ * @file AthenaKernel/RecyclableDataObject.icc
+ * @author scott snyder <snyder@bnl.gov>
+ * @date Oct, 2018
+ * @brief Helper for recycling objects from event to event.
+ */
+
+
+namespace Athena {
+
+
+/**
+ * @brief Constructor.
+ * @param queue Queue on which this object will be entered when it is released.
+ * @param args... Additional arguments to pass to the @c DOBJ constructor.
+ */
+template <class DOBJ>
+template <typename... ARGS>
+RecyclableDataObject<DOBJ>::RecyclableDataObject (queue_t& queue, ARGS&&... args)
+  : DOBJ (std::forward<ARGS>(args)...),
+    m_queue (queue)
+{
+  // Bump reference count by one so that these objects won't be deleted by StoreGate.
+  this->addRef();
+}
+
+
+/**
+ * @brief DataObject release method.
+ *
+ * This overrides the @c release() method of @c DataObject.
+ * Once the reference count falls back to 1 (meaning that the object is no longer
+ * referenced from StoreGate), we recycle the object back to the queue.
+ */
+template <class DOBJ>
+unsigned long RecyclableDataObject<DOBJ>::release()
+{
+  unsigned long cnt = DOBJ::release();
+  if (cnt == 1) {
+    this->recycle();
+    m_queue.push (this);
+  }
+  return cnt;
+}
+
+
+/**
+ * @brief Get an object, either a new one or one recycled from a previous event.
+ * @param args... Arguments to pass to the @c DOBJ constructor if we make a new one.
+ */
+template <class DOBJ>
+template <typename... ARGS>
+DOBJ* RecyclableDataQueue<DOBJ>::get (ARGS&&... args)
+{
+  DOBJ* obj = nullptr;
+  if (this->try_pop (obj)) {
+    return obj;
+  }
+  return new RecyclableDataObject<DOBJ> (*this, std::forward<ARGS>(args)...);
+}
+
+
+/**
+ * @brief Destructor.
+ *
+ * Free all objects in the queue.
+ */
+template <class DOBJ>
+RecyclableDataQueue<DOBJ>::~RecyclableDataQueue()
+{
+  DOBJ* obj = nullptr;
+  while (this->try_pop (obj)) {
+    delete obj;
+  }
+}
+
+
+} // namespace Athena
diff --git a/Control/AthenaKernel/CMakeLists.txt b/Control/AthenaKernel/CMakeLists.txt
index 0cdda1adc8e927d8b6adadb0a909f099325ae1e5..99a1e35c6db90cba27c40d20f9bb73328d802535 100644
--- a/Control/AthenaKernel/CMakeLists.txt
+++ b/Control/AthenaKernel/CMakeLists.txt
@@ -21,6 +21,7 @@ find_package( Boost COMPONENTS program_options regex filesystem thread )
 find_package( ROOT COMPONENTS Core )
 find_package( UUID )
 find_package(CLHEP)
+find_package( TBB )
 # Only link agains the RT library on Linux:
 set( rt_library )
 if( UNIX AND NOT APPLE )
@@ -134,3 +135,8 @@ atlas_add_test( MetaContDataBucket_test
 atlas_add_test( TopBase_test
    SOURCES test/TopBase_test.cxx
    LINK_LIBRARIES AthenaKernel TestTools )
+
+atlas_add_test( RecyclableDataObject_test
+  SOURCES test/RecyclableDataObject_test.cxx
+  INCLUDE_DIRS ${TBB_INCLUDE_DIRS}
+  LINK_LIBRARIES AthenaKernel TestTools ${TBB_LIBRARIES} )
diff --git a/Control/AthenaKernel/share/RecyclableDataObject_test.ref b/Control/AthenaKernel/share/RecyclableDataObject_test.ref
new file mode 100644
index 0000000000000000000000000000000000000000..7250f763119a9b04b294338d16ad45c33b427443
--- /dev/null
+++ b/Control/AthenaKernel/share/RecyclableDataObject_test.ref
@@ -0,0 +1,2 @@
+RecyclableDataObject_test
+test1
diff --git a/Control/AthenaKernel/test/RecyclableDataObject_test.cxx b/Control/AthenaKernel/test/RecyclableDataObject_test.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..dfa48c25972365c3c0f76c5714a604fbaaaaa02a
--- /dev/null
+++ b/Control/AthenaKernel/test/RecyclableDataObject_test.cxx
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2002-2018 CERN for the benefit of the ATLAS collaboration.
+ */
+// $Id$
+/**
+ * @file AthenaKernel/test/RecyclableDataObject_test.cxx
+ * @author scott snyder <snyder@bnl.gov>
+ * @date Oct, 2018
+ * @brief Tests for RecyclableDataObject.
+ */
+
+
+#undef NDEBUG
+#include "AthenaKernel/RecyclableDataObject.h"
+#include "GaudiKernel/DataObject.h"
+#include <atomic>
+#include <iostream>
+#include <cassert>
+
+
+class MyDataObj
+  : public DataObject
+{
+public:
+  MyDataObj (int x) : m_x (x)
+  { ++s_count; }
+  ~MyDataObj() { --s_count; }
+
+  void recycle();
+
+  static std::atomic<int> s_count;
+  int m_x = 0;
+  bool m_recycled = false;
+};
+
+
+std::atomic<int> MyDataObj::s_count (0);
+
+
+void MyDataObj::recycle()
+{
+  m_recycled = true;
+}
+
+
+void test1()
+{
+  std::cout << "test1\n"; 
+  {
+    Athena::RecyclableDataQueue<MyDataObj> queue;
+
+    MyDataObj* obj1 = queue.get (1);
+    obj1->addRef();
+    assert (MyDataObj::s_count == 1);
+    assert (obj1->m_x == 1);
+    assert (!obj1->m_recycled);
+
+    MyDataObj* obj2 = queue.get (2);
+    obj2->addRef();
+    assert (MyDataObj::s_count == 2);
+    assert (obj2->m_x == 2);
+    assert (!obj2->m_recycled);
+
+    obj1->release();
+
+    MyDataObj* obj3 = queue.get (3);
+    obj3->addRef();
+    assert (MyDataObj::s_count == 2);
+    assert (obj3->m_x == 1);
+    assert (obj3->m_recycled);
+
+    obj2->release();
+    obj1->release();
+  }
+  assert (MyDataObj::s_count == 0);
+}
+
+
+int main()
+{
+  std::cout << "RecyclableDataObject_test\n";
+  test1();
+  return 0;
+}