diff --git a/lucene/core/src/java/org/apache/lucene/search/NRTManager.java b/lucene/core/src/java/org/apache/lucene/search/NRTManager.java
index cfe8028..88482e9 100644
--- a/lucene/core/src/java/org/apache/lucene/search/NRTManager.java
+++ b/lucene/core/src/java/org/apache/lucene/search/NRTManager.java
@@ -70,7 +70,7 @@ import org.apache.lucene.util.ThreadInterruptedException;
  * @lucene.experimental
  */
 
-public class NRTManager extends ReferenceManager<IndexSearcher> {
+public final class NRTManager extends ReferenceManager<IndexSearcher> {
   private static final long MAX_SEARCHER_GEN = Long.MAX_VALUE;
   private final TrackingIndexWriter writer;
   private final List<WaitingListener> waitingListeners = new CopyOnWriteArrayList<WaitingListener>();
diff --git a/lucene/core/src/java/org/apache/lucene/search/ReferenceManager.java b/lucene/core/src/java/org/apache/lucene/search/ReferenceManager.java
index 21356c7..e3c8c9c 100755
--- a/lucene/core/src/java/org/apache/lucene/search/ReferenceManager.java
+++ b/lucene/core/src/java/org/apache/lucene/search/ReferenceManager.java
@@ -19,6 +19,8 @@ package org.apache.lucene.search;
 
 import java.io.Closeable;
 import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 
@@ -44,7 +46,10 @@ public abstract class ReferenceManager<G> implements Closeable {
   protected volatile G current;
   
   private final Lock refreshLock = new ReentrantLock();
-  
+
+  private final List<CloseListener> closeListeners = new CopyOnWriteArrayList<CloseListener>();
+  private final List<RefreshListener> refreshListeners = new CopyOnWriteArrayList<RefreshListener>();
+
   private void ensureOpen() {
     if (current == null) {
       throw new AlreadyClosedException(REFERENCE_MANAGER_IS_CLOSED_MSG);
@@ -124,6 +129,7 @@ public abstract class ReferenceManager<G> implements Closeable {
       // if this is already closed then invoking this method has no effect.
       swapReference(null);
       afterClose();
+      notifyCloseListeners();
     }
   }
 
@@ -142,13 +148,13 @@ public abstract class ReferenceManager<G> implements Closeable {
     // Per ReentrantLock's javadoc, calling lock() by the same thread more than
     // once is ok, as long as unlock() is called a matching number of times.
     refreshLock.lock();
+    boolean success = false;
     try {
       final G reference = acquire();
       try {
         G newReference = refreshIfNeeded(reference);
         if (newReference != null) {
           assert newReference != reference : "refreshIfNeeded should return null if refresh wasn't needed";
-          boolean success = false;
           try {
             swapReference(newReference);
             success = true;
@@ -162,11 +168,13 @@ public abstract class ReferenceManager<G> implements Closeable {
         release(reference);
       }
       afterRefresh();
+      if (success)
+        notifyRefreshListeners();
     } finally {
       refreshLock.unlock();
     }
   }
-  
+
   /**
    * You must call this (or {@link #maybeRefreshBlocking()}), periodically, if
    * you want that {@link #acquire()} will return refreshed instances.
@@ -245,4 +253,67 @@ public abstract class ReferenceManager<G> implements Closeable {
     assert reference != null;
     decRef(reference);
   }
+
+  private void notifyRefreshListeners() {
+    for (RefreshListener refreshListener : refreshListeners) {
+      refreshListener.afterRefresh();
+    }
+  }
+  private void notifyCloseListeners() {
+    for (CloseListener closeListener : closeListeners) {
+      closeListener.afterClose();
+    }
+  }
+
+  /**
+   * Adds a listener, to be notified when a reference is refreshed/swapped.
+   */
+  public void addListener(RefreshListener listener) {
+    if (listener == null)
+      throw new NullPointerException("Listener cannot be null");
+    refreshListeners.add(listener);
+  }
+
+  /**
+   * Remove a listener added with {@link #addListener(RefreshListener)}.
+   */
+  public void removeListener(RefreshListener listener) {
+    if (listener == null)
+      throw new NullPointerException("Listener cannot be null");
+    refreshListeners.remove(listener);
+  }
+
+  /**
+   * Adds a listener, to be notified when the manager is closed, allows you to clean up any resources if needed.
+   */
+  public void addListener(CloseListener listener) {
+    if (listener == null)
+      throw new NullPointerException("Listener cannot be null");
+    closeListeners.add(listener);
+  }
+
+  /**
+   * Remove a listener added with {@link #addListener(CloseListener}.
+   */
+  public void removeListener(CloseListener listener) {
+    if (listener == null)
+      throw new NullPointerException("Listener cannot be null");
+    closeListeners.remove(listener);
+  }
+
+
+  public interface RefreshListener {
+
+    /**
+     * Called after a successful refresh and a new reference has been installed. When this is called {@link #acquire()} is guaranteed to return a new instance.
+     */
+    void afterRefresh();
+  }
+
+  public interface CloseListener {
+    /**
+     * Called after the manager was closed, so any resource can be freed
+     */
+    void afterClose();
+  }
 }
