diff --git a/native/jni/src/suggest/core/dicnode/dic_node.cpp b/native/jni/src/suggest/core/dicnode/dic_node.cpp
index 73855977e0d7c170938f93fdb2d8c547ccbb0710..414dc3b1e174d87fb17486e6c0837a7fe6f1415b 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node.cpp
+++ b/native/jni/src/suggest/core/dicnode/dic_node.cpp
@@ -24,8 +24,7 @@ DicNode::DicNode(const DicNode &dicNode)
           mProfiler(dicNode.mProfiler),
 #endif
           mDicNodeProperties(dicNode.mDicNodeProperties), mDicNodeState(dicNode.mDicNodeState),
-          mIsCachedForNextSuggestion(dicNode.mIsCachedForNextSuggestion), mIsUsed(dicNode.mIsUsed),
-          mReleaseListener(nullptr) {
+          mIsCachedForNextSuggestion(dicNode.mIsCachedForNextSuggestion) {
     /* empty */
 }
 
@@ -36,8 +35,6 @@ DicNode &DicNode::operator=(const DicNode &dicNode) {
     mDicNodeProperties = dicNode.mDicNodeProperties;
     mDicNodeState = dicNode.mDicNodeState;
     mIsCachedForNextSuggestion = dicNode.mIsCachedForNextSuggestion;
-    mIsUsed = dicNode.mIsUsed;
-    mReleaseListener = dicNode.mReleaseListener;
     return *this;
 }
 
diff --git a/native/jni/src/suggest/core/dicnode/dic_node.h b/native/jni/src/suggest/core/dicnode/dic_node.h
index 258aa9ce3edf2f403f5f2b7c4c17fa7417aec656..b54c39f758a16a5fe90ad58f03c0170e31497cd4 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node.h
+++ b/native/jni/src/suggest/core/dicnode/dic_node.h
@@ -19,7 +19,6 @@
 
 #include "defines.h"
 #include "suggest/core/dicnode/dic_node_profiler.h"
-#include "suggest/core/dicnode/dic_node_release_listener.h"
 #include "suggest/core/dicnode/dic_node_utils.h"
 #include "suggest/core/dicnode/internal/dic_node_state.h"
 #include "suggest/core/dicnode/internal/dic_node_properties.h"
@@ -89,8 +88,7 @@ class DicNode {
 #if DEBUG_DICT
               mProfiler(),
 #endif
-              mDicNodeProperties(), mDicNodeState(), mIsCachedForNextSuggestion(false),
-              mIsUsed(false), mReleaseListener(nullptr) {}
+              mDicNodeProperties(), mDicNodeState(), mIsCachedForNextSuggestion(false) {}
 
     DicNode(const DicNode &dicNode);
     DicNode &operator=(const DicNode &dicNode);
@@ -98,7 +96,6 @@ class DicNode {
 
     // Init for copy
     void initByCopy(const DicNode *const dicNode) {
-        mIsUsed = true;
         mIsCachedForNextSuggestion = dicNode->mIsCachedForNextSuggestion;
         mDicNodeProperties.initByCopy(&dicNode->mDicNodeProperties);
         mDicNodeState.initByCopy(&dicNode->mDicNodeState);
@@ -107,7 +104,6 @@ class DicNode {
 
     // Init for root with prevWordPtNodePos which is used for bigram
     void initAsRoot(const int rootPtNodeArrayPos, const int prevWordPtNodePos) {
-        mIsUsed = true;
         mIsCachedForNextSuggestion = false;
         mDicNodeProperties.init(rootPtNodeArrayPos, prevWordPtNodePos);
         mDicNodeState.init();
@@ -116,7 +112,6 @@ class DicNode {
 
     // Init for root with previous word
     void initAsRootWithPreviousWord(const DicNode *const dicNode, const int rootPtNodeArrayPos) {
-        mIsUsed = true;
         mIsCachedForNextSuggestion = dicNode->mIsCachedForNextSuggestion;
         mDicNodeProperties.init(rootPtNodeArrayPos, dicNode->mDicNodeProperties.getPtNodePos());
         mDicNodeState.initAsRootWithPreviousWord(&dicNode->mDicNodeState,
@@ -125,7 +120,6 @@ class DicNode {
     }
 
     void initAsPassingChild(DicNode *parentDicNode) {
-        mIsUsed = true;
         mIsCachedForNextSuggestion = parentDicNode->mIsCachedForNextSuggestion;
         const int parentCodePoint = parentDicNode->getNodeTypedCodePoint();
         mDicNodeProperties.init(&parentDicNode->mDicNodeProperties, parentCodePoint);
@@ -137,7 +131,6 @@ class DicNode {
             const int childrenPtNodeArrayPos, const int probability, const bool isTerminal,
             const bool hasChildren, const bool isBlacklistedOrNotAWord,
             const uint16_t mergedNodeCodePointCount, const int *const mergedNodeCodePoints) {
-        mIsUsed = true;
         uint16_t newDepth = static_cast<uint16_t>(dicNode->getNodeCodePointCount() + 1);
         mIsCachedForNextSuggestion = dicNode->mIsCachedForNextSuggestion;
         const uint16_t newLeavingDepth = static_cast<uint16_t>(
@@ -150,17 +143,6 @@ class DicNode {
         PROF_NODE_COPY(&dicNode->mProfiler, mProfiler);
     }
 
-    AK_FORCE_INLINE void finalize() {
-        mIsUsed = false;
-        if (mReleaseListener) {
-            mReleaseListener->onReleased(this);
-        }
-    }
-
-    bool isUsed() const {
-        return mIsUsed;
-    }
-
     bool isRoot() const {
         return getNodeCodePointCount() == 0;
     }
@@ -466,10 +448,6 @@ class DicNode {
 #endif
     }
 
-    void setReleaseListener(DicNodeReleaseListener *releaseListener) {
-        mReleaseListener = releaseListener;
-    }
-
     AK_FORCE_INLINE bool compare(const DicNode *right) const {
         // Promote exact matches to prevent them from being pruned.
         const bool leftExactMatch = ErrorTypeUtils::isExactMatch(getContainedErrorTypes());
@@ -507,8 +485,6 @@ class DicNode {
     DicNodeState mDicNodeState;
     // TODO: Remove
     bool mIsCachedForNextSuggestion;
-    bool mIsUsed;
-    DicNodeReleaseListener *mReleaseListener;
 
     AK_FORCE_INLINE int getTotalInputIndex() const {
         int index = 0;
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_pool.h b/native/jni/src/suggest/core/dicnode/dic_node_pool.h
new file mode 100644
index 0000000000000000000000000000000000000000..a660b744f3c1baf2e746945d8eb0332d3b463fb2
--- /dev/null
+++ b/native/jni/src/suggest/core/dicnode/dic_node_pool.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LATINIME_DIC_NODE_POOL_H
+#define LATINIME_DIC_NODE_POOL_H
+
+#include <deque>
+#include <unordered_set>
+#include <vector>
+
+#include "defines.h"
+#include "suggest/core/dicnode/dic_node.h"
+
+namespace latinime {
+
+class DicNodePool {
+ public:
+    explicit DicNodePool(const int capacity) : mDicNodes(), mPooledDicNodes() {
+        reset(capacity);
+    }
+
+    void reset(const int capacity) {
+        if (capacity == static_cast<int>(mDicNodes.size())
+                && capacity == static_cast<int>(mPooledDicNodes.size())) {
+            // No need to reset.
+            return;
+        }
+        mDicNodes.resize(capacity);
+        mDicNodes.shrink_to_fit();
+        mPooledDicNodes.clear();
+        for (auto &dicNode : mDicNodes) {
+            mPooledDicNodes.emplace_back(&dicNode);
+        }
+    }
+
+    // Get a DicNode instance from the pool. The instance has to be returned by returnInstance().
+    DicNode *getInstance() {
+        if (mPooledDicNodes.empty()) {
+            return nullptr;
+        }
+        DicNode *const dicNode = mPooledDicNodes.back();
+        mPooledDicNodes.pop_back();
+        return dicNode;
+    }
+
+    // Return an instance that has been removed from the pool by getInstance() to the pool. The
+    // instance must not be used after returning without getInstance().
+    void placeBackInstance(DicNode *dicNode) {
+        mPooledDicNodes.emplace_back(dicNode);
+    }
+
+    void dump() const {
+        AKLOGI("\n\n\n\n\n===========================");
+        std::unordered_set<const DicNode*> usedDicNodes;
+        for (const auto &dicNode : mDicNodes) {
+            usedDicNodes.insert(&dicNode);
+        }
+        for (const auto &dicNodePtr : mPooledDicNodes) {
+            usedDicNodes.erase(dicNodePtr);
+        }
+        for (const auto &usedDicNodePtr : usedDicNodes) {
+            usedDicNodePtr->dump("DIC_NODE_POOL: ");
+        }
+        AKLOGI("===========================\n\n\n\n\n");
+    }
+
+ private:
+    DISALLOW_IMPLICIT_CONSTRUCTORS(DicNodePool);
+
+    std::vector<DicNode> mDicNodes;
+    std::deque<DicNode*> mPooledDicNodes;
+};
+} // namespace latinime
+#endif // LATINIME_DIC_NODE_POOL_H
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_priority_queue.h b/native/jni/src/suggest/core/dicnode/dic_node_priority_queue.h
index 213b1b96867ffa5676f545f655399c52b0791c92..7b753f2e4cea40256fbc9e9fdabaee306c26fb67 100644
--- a/native/jni/src/suggest/core/dicnode/dic_node_priority_queue.h
+++ b/native/jni/src/suggest/core/dicnode/dic_node_priority_queue.h
@@ -23,38 +23,30 @@
 
 #include "defines.h"
 #include "suggest/core/dicnode/dic_node.h"
-#include "suggest/core/dicnode/dic_node_release_listener.h"
+#include "suggest/core/dicnode/dic_node_pool.h"
 
 namespace latinime {
 
-class DicNodePriorityQueue : public DicNodeReleaseListener {
+class DicNodePriorityQueue {
  public:
     AK_FORCE_INLINE explicit DicNodePriorityQueue(const int capacity)
-            : mCapacity(capacity), mMaxSize(capacity), mDicNodesBuf(),
-              mUnusedNodeIndices(), mNextUnusedNodeId(0), mDicNodesQueue() {
-        mDicNodesBuf.resize(mCapacity + 1);
-        mUnusedNodeIndices.resize(mCapacity + 1);
-        clearAndResizeToCapacity();
+            : mMaxSize(capacity), mDicNodesQueue(), mDicNodePool(capacity) {
+        clear();
     }
 
     // Non virtual inline destructor -- never inherit this class
     AK_FORCE_INLINE ~DicNodePriorityQueue() {}
 
-    int getSize() const {
+    AK_FORCE_INLINE int getSize() const {
         return static_cast<int>(mDicNodesQueue.size());
     }
 
-    int getMaxSize() const {
+    AK_FORCE_INLINE int getMaxSize() const {
         return mMaxSize;
     }
 
     AK_FORCE_INLINE void setMaxSize(const int maxSize) {
-        ASSERT(maxSize <= mCapacity);
-        mMaxSize = std::min(maxSize, mCapacity);
-    }
-
-    AK_FORCE_INLINE void clearAndResizeToCapacity() {
-        clearAndResize(mCapacity);
+        mMaxSize = maxSize;
     }
 
     AK_FORCE_INLINE void clear() {
@@ -62,25 +54,32 @@ class DicNodePriorityQueue : public DicNodeReleaseListener {
     }
 
     AK_FORCE_INLINE void clearAndResize(const int maxSize) {
-        ASSERT(maxSize <= mCapacity);
+        mMaxSize = maxSize;
         while (!mDicNodesQueue.empty()) {
             mDicNodesQueue.pop();
         }
-        setMaxSize(maxSize);
-        for (int i = 0; i < mCapacity + 1; ++i) {
-            mDicNodesBuf[i].finalize();
-            mDicNodesBuf[i].setReleaseListener(this);
-            mUnusedNodeIndices[i] = (i == mCapacity) ? NOT_A_NODE_ID : (i + 1);
-        }
-        mNextUnusedNodeId = 0;
+        mDicNodePool.reset(mMaxSize + 1);
     }
 
-    // Copy
-    AK_FORCE_INLINE DicNode *copyPush(const DicNode *const dicNode) {
-        return copyPush(dicNode, mMaxSize);
+    AK_FORCE_INLINE void copyPush(const DicNode *const dicNode) {
+        DicNode *const pooledDicNode = newDicNode(dicNode);
+        if (!pooledDicNode) {
+            return;
+        }
+        if (getSize() < mMaxSize) {
+            mDicNodesQueue.push(pooledDicNode);
+            return;
+        }
+        if (betterThanWorstDicNode(pooledDicNode)) {
+            mDicNodePool.placeBackInstance(mDicNodesQueue.top());
+            mDicNodesQueue.pop();
+            mDicNodesQueue.push(pooledDicNode);
+            return;
+        }
+        mDicNodePool.placeBackInstance(pooledDicNode);
     }
 
-    AK_FORCE_INLINE void copyPop(DicNode *dest) {
+    AK_FORCE_INLINE void copyPop(DicNode *const dest) {
         if (mDicNodesQueue.empty()) {
             ASSERT(false);
             return;
@@ -89,34 +88,16 @@ class DicNodePriorityQueue : public DicNodeReleaseListener {
         if (dest) {
             DicNodeUtils::initByCopy(node, dest);
         }
-        node->finalize();
+        mDicNodePool.placeBackInstance(node);
         mDicNodesQueue.pop();
     }
 
-    void onReleased(const DicNode *dicNode) {
-        const int index = static_cast<int>(dicNode - &mDicNodesBuf[0]);
-        if (mUnusedNodeIndices[index] != NOT_A_NODE_ID) {
-            // it's already released
-            return;
-        }
-        mUnusedNodeIndices[index] = mNextUnusedNodeId;
-        mNextUnusedNodeId = index;
-        ASSERT(index >= 0 && index < (mCapacity + 1));
-    }
-
-    AK_FORCE_INLINE void dump() const {
-        AKLOGI("\n\n\n\n\n===========================");
-        for (int i = 0; i < mCapacity + 1; ++i) {
-            if (mDicNodesBuf[i].isUsed()) {
-                mDicNodesBuf[i].dump("QUEUE: ");
-            }
-        }
-        AKLOGI("===========================\n\n\n\n\n");
+    AK_FORCE_INLINE void dump() {
+        mDicNodePool.dump();
     }
 
  private:
     DISALLOW_IMPLICIT_CONSTRUCTORS(DicNodePriorityQueue);
-    static const int NOT_A_NODE_ID = -1;
 
     AK_FORCE_INLINE static bool compareDicNode(const DicNode *const left,
             const DicNode *const right) {
@@ -124,26 +105,15 @@ class DicNodePriorityQueue : public DicNodeReleaseListener {
     }
 
     struct DicNodeComparator {
-        bool operator ()(DicNode *left, DicNode *right) {
+        bool operator ()(const DicNode *left, const DicNode *right) const {
             return compareDicNode(left, right);
         }
     };
 
     typedef std::priority_queue<DicNode *, std::vector<DicNode *>, DicNodeComparator> DicNodesQueue;
-    const int mCapacity;
     int mMaxSize;
-    std::vector<DicNode> mDicNodesBuf; // of each element of mDicNodesBuf respectively
-    std::vector<int> mUnusedNodeIndices;
-    int mNextUnusedNodeId;
     DicNodesQueue mDicNodesQueue;
-
-    inline bool isFull(const int maxSize) const {
-        return getSize() >= maxSize;
-    }
-
-    AK_FORCE_INLINE void pop() {
-        copyPop(nullptr);
-    }
+    DicNodePool mDicNodePool;
 
     AK_FORCE_INLINE bool betterThanWorstDicNode(const DicNode *const dicNode) const {
         DicNode *worstNode = mDicNodesQueue.top();
@@ -153,61 +123,13 @@ class DicNodePriorityQueue : public DicNodeReleaseListener {
         return compareDicNode(dicNode, worstNode);
     }
 
-    AK_FORCE_INLINE DicNode *searchEmptyDicNode() {
-        if (mCapacity == 0) {
-            return nullptr;
-        }
-        if (mNextUnusedNodeId == NOT_A_NODE_ID) {
-            AKLOGI("No unused node found.");
-            for (int i = 0; i < mCapacity + 1; ++i) {
-                AKLOGI("Dump node availability, %d, %d, %d",
-                        i, mDicNodesBuf[i].isUsed(), mUnusedNodeIndices[i]);
-            }
-            ASSERT(false);
-            return nullptr;
-        }
-        DicNode *dicNode = &mDicNodesBuf[mNextUnusedNodeId];
-        markNodeAsUsed(dicNode);
-        return dicNode;
-    }
-
-    AK_FORCE_INLINE void markNodeAsUsed(DicNode *dicNode) {
-        const int index = static_cast<int>(dicNode - &mDicNodesBuf[0]);
-        mNextUnusedNodeId = mUnusedNodeIndices[index];
-        mUnusedNodeIndices[index] = NOT_A_NODE_ID;
-        ASSERT(index >= 0 && index < (mCapacity + 1));
-    }
-
-    AK_FORCE_INLINE DicNode *pushPoolNodeWithMaxSize(DicNode *dicNode, const int maxSize) {
-        if (!dicNode) {
-            return nullptr;
-        }
-        if (!isFull(maxSize)) {
-            mDicNodesQueue.push(dicNode);
-            return dicNode;
-        }
-        if (betterThanWorstDicNode(dicNode)) {
-            pop();
-            mDicNodesQueue.push(dicNode);
-            return dicNode;
-        }
-        dicNode->finalize();
-        return nullptr;
-    }
-
-    // Copy
-    AK_FORCE_INLINE DicNode *copyPush(const DicNode *const dicNode, const int maxSize) {
-        return pushPoolNodeWithMaxSize(newDicNode(dicNode), maxSize);
-    }
-
     AK_FORCE_INLINE DicNode *newDicNode(const DicNode *const dicNode) {
-        DicNode *newNode = searchEmptyDicNode();
+        DicNode *newNode = mDicNodePool.getInstance();
         if (newNode) {
             DicNodeUtils::initByCopy(dicNode, newNode);
         }
         return newNode;
     }
-
 };
 } // namespace latinime
 #endif // LATINIME_DIC_NODE_PRIORITY_QUEUE_H
diff --git a/native/jni/src/suggest/core/dicnode/dic_node_release_listener.h b/native/jni/src/suggest/core/dicnode/dic_node_release_listener.h
deleted file mode 100644
index c3f432951694d7110a663e459adf8773ef98565c..0000000000000000000000000000000000000000
--- a/native/jni/src/suggest/core/dicnode/dic_node_release_listener.h
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef LATINIME_DIC_NODE_RELEASE_LISTENER_H
-#define LATINIME_DIC_NODE_RELEASE_LISTENER_H
-
-#include "defines.h"
-
-namespace latinime {
-
-class DicNode;
-
-class DicNodeReleaseListener {
- public:
-    DicNodeReleaseListener() {}
-    virtual ~DicNodeReleaseListener() {}
-    virtual void onReleased(const DicNode *dicNode) = 0;
- private:
-    DISALLOW_COPY_AND_ASSIGN(DicNodeReleaseListener);
-};
-} // namespace latinime
-#endif // LATINIME_DIC_NODE_RELEASE_LISTENER_H
diff --git a/native/jni/src/suggest/core/dicnode/dic_nodes_cache.h b/native/jni/src/suggest/core/dicnode/dic_nodes_cache.h
index 6b8dc8c967cc6832dfc228e523b61cb74720ac4e..089d4467f3549bd43b1e595c165d8ecb0ba19c23 100644
--- a/native/jni/src/suggest/core/dicnode/dic_nodes_cache.h
+++ b/native/jni/src/suggest/core/dicnode/dic_nodes_cache.h
@@ -49,15 +49,14 @@ class DicNodesCache {
     AK_FORCE_INLINE void reset(const int nextActiveSize, const int terminalSize) {
         mInputIndex = 0;
         mLastCachedInputIndex = 0;
-        // We want to use the max capacity for the current active dic node queue.
-        mActiveDicNodes->clearAndResizeToCapacity();
-        // nextActiveSize is used to limit the next iteration's active dic node size.
+        // The size of current active DicNode queue doesn't have to be changed.
+        mActiveDicNodes->clear();
+        // nextActiveSize is used to limit the next iteration's active DicNode size.
         const int nextActiveSizeFittingToTheCapacity = std::min(nextActiveSize, getCacheCapacity());
         mNextActiveDicNodes->clearAndResize(nextActiveSizeFittingToTheCapacity);
         mTerminalDicNodes->clearAndResize(terminalSize);
-        // We want to use the max capacity for the cached dic nodes that will be used for the
-        // continuous suggestion.
-        mCachedDicNodesForContinuousSuggestion->clearAndResizeToCapacity();
+        // The size of cached DicNode queue doesn't have to be changed.
+        mCachedDicNodesForContinuousSuggestion->clear();
     }
 
     AK_FORCE_INLINE void continueSearch() {
@@ -95,8 +94,8 @@ class DicNodesCache {
         mActiveDicNodes->copyPush(dicNode);
     }
 
-    AK_FORCE_INLINE bool copyPushContinue(DicNode *dicNode) {
-        return mCachedDicNodesForContinuousSuggestion->copyPush(dicNode);
+    AK_FORCE_INLINE void copyPushContinue(DicNode *dicNode) {
+        mCachedDicNodesForContinuousSuggestion->copyPush(dicNode);
     }
 
     AK_FORCE_INLINE void copyPushNextActive(DicNode *dicNode) {