You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by am...@apache.org on 2019/06/05 11:08:32 UTC

[ignite] 29/31: GG-19221 [GG-18667]-[IGNITE-11750] Implement locked pages info dump for long-running B+Tree operations

This is an automated email from the ASF dual-hosted git repository.

amashenkov pushed a commit to branch gg-19225
in repository https://gitbox.apache.org/repos/asf/ignite.git

commit 76289ae047f2ab53c7f3f8ec5a4702d0f948eab2
Author: Dmitriy Govorukhin <dm...@gmail.com>
AuthorDate: Wed Jun 5 13:27:00 2019 +0300

    GG-19221 [GG-18667]-[IGNITE-11750] Implement locked pages info dump for long-running B+Tree operations
---
 .../JmhPageLockTrackerBenchmark.java               | 143 ++++
 .../stack/LockTrackerNoBarrier.java                |  65 ++
 .../benchmarks/jmh/tree/BPlusTreeBenchmark.java    |  15 +-
 .../org/apache/ignite/IgniteSystemProperties.java  |  33 +
 .../ignite/internal/commandline/CommandList.java   |  10 +-
 .../commandline/diagnostic/DiagnosticCommand.java  | 114 ++++
 .../diagnostic/DiagnosticSubCommand.java           |  73 ++
 .../commandline/diagnostic/PageLocksCommand.java   | 210 ++++++
 .../processors/cache/CacheDiagnosticManager.java   | 134 ++++
 .../processors/cache/GridCacheProcessor.java       |  11 +-
 .../processors/cache/GridCacheSharedContext.java   |  25 +-
 .../cache/IgniteCacheOffheapManager.java           |   5 -
 .../cache/IgniteCacheOffheapManagerImpl.java       |  37 +-
 .../processors/cache/mvcc/txlog/TxLog.java         |  37 +-
 .../processors/cache/mvcc/txlog/TxLogTree.java     |  30 +-
 .../cache/persistence/DataStructure.java           | 113 ++--
 .../cache/persistence/GridCacheOffheapManager.java |  93 ++-
 .../IgniteCacheDatabaseSharedManager.java          |  50 +-
 .../cache/persistence/IndexStorageImpl.java        |  38 +-
 .../diagnostic/pagelocktracker/DumpProcessor.java  |  40 ++
 .../diagnostic/pagelocktracker/DumpSupported.java  |  42 ++
 .../diagnostic/pagelocktracker/InvalidContext.java |  38 ++
 .../pagelocktracker/LockTrackerFactory.java        | 114 ++++
 .../diagnostic/pagelocktracker/PageLockDump.java   |  32 +
 .../PageLockListenerIndexAdapter.java              |  69 ++
 .../pagelocktracker/PageLockTracker.java           | 365 ++++++++++
 .../pagelocktracker/PageLockTrackerMXBean.java     |  58 ++
 .../pagelocktracker/PageLockTrackerMXBeanImpl.java |  59 ++
 .../pagelocktracker/PageLockTrackerManager.java    | 270 ++++++++
 .../pagelocktracker/PageMetaInfoStore.java         |  89 +++
 .../pagelocktracker/SharedPageLockTracker.java     | 444 ++++++++++++
 .../pagelocktracker/ThreadPageLocksDumpLock.java   |  91 +++
 .../dumpprocessors/ToFileDumpProcessor.java        |  83 +++
 .../dumpprocessors/ToStringDumpProcessor.java      | 335 +++++++++
 .../diagnostic/pagelocktracker/log/LockLog.java    | 137 ++++
 .../pagelocktracker/log/PageLockLogSnapshot.java   | 138 ++++
 .../pagelocktracker/stack/LockStack.java           | 188 ++++++
 .../stack/PageLockStackSnapshot.java               |  79 +++
 .../store/HeapPageMetaInfoStore.java               | 162 +++++
 .../store/OffHeapPageMetaInfoStore.java            | 200 ++++++
 .../persistence/freelist/AbstractFreeList.java     |  13 +-
 .../{CacheFreeListImpl.java => CacheFreeList.java} |  33 +-
 .../cache/persistence/freelist/PagesList.java      |  12 +-
 .../cache/persistence/metastorage/MetaStorage.java |  51 +-
 .../persistence/metastorage/MetastorageTree.java   |  24 +-
 .../UpgradePendingTreeToPerPartitionTask.java      |   3 +-
 .../cache/persistence/tree/BPlusTree.java          |  22 +-
 .../persistence/tree/reuse/ReuseListImpl.java      |  24 +-
 .../wal/reader/IgniteWalIteratorFactory.java       |   2 +-
 .../processors/cache/tree/CacheDataTree.java       |  11 +-
 .../processors/cache/tree/PendingEntriesTree.java  |  13 +-
 .../processors/diagnostic/DiagnosticProcessor.java |   8 +
 .../visor/diagnostic/VisorPageLocksResult.java     |  82 +++
 .../visor/diagnostic/VisorPageLocksTask.java       | 141 ++++
 .../diagnostic/VisorPageLocksTrackerArgs.java      | 135 ++++
 .../main/resources/META-INF/classnames.properties  |   4 +
 .../db/wal/IgniteWalIteratorSwitchSegmentTest.java |   2 +
 .../db/wal/WalRecoveryTxLogicalRecordsTest.java    |   4 +-
 .../pagelocktracker/AbstractPageLockTest.java      |  56 ++
 .../PageLockTrackerMXBeanImplTest.java             |  80 +++
 .../PageLockTrackerManagerTest.java                | 218 ++++++
 .../pagelocktracker/PageLockTrackerTestSuit.java   |  43 ++
 .../pagelocktracker/SharedPageLockTrackerTest.java | 541 +++++++++++++++
 .../dumpprocessors/ToFileDumpProcessorTest.java    | 108 +++
 .../pagelocktracker/log/HeapArrayLockLogTest.java  |  29 +
 .../pagelocktracker/log/OffHeapLockLogTest.java    |  29 +
 .../pagelocktracker/log/PageLockLogTest.java       | 641 ++++++++++++++++++
 .../stack/HeapArrayLockStackTest.java              |  29 +
 .../stack/OffHeapLockStackTest.java                |  29 +
 .../pagelocktracker/stack/PageLockStackTest.java   | 751 +++++++++++++++++++++
 .../pagemem/BPlusTreePageMemoryImplTest.java       |   4 +-
 .../BPlusTreeReuseListPageMemoryImplTest.java      |   1 +
 .../pagemem/IndexStoragePageMemoryImplTest.java    |   4 +-
 .../pagemem/PageMemoryImplNoLoadTest.java          |   1 +
 .../persistence/pagemem/PageMemoryImplTest.java    |   1 +
 .../database/BPlusTreeReuseSelfTest.java           |  62 +-
 .../processors/database/BPlusTreeSelfTest.java     | 277 ++++----
 ...mplSelfTest.java => CacheFreeListSelfTest.java} |  22 +-
 .../processors/database/IndexStorageSelfTest.java  |   1 +
 .../loadtests/hashmap/GridCacheTestContext.java    |  10 +-
 .../ignite/testsuites/IgniteBasicTestSuite.java    |   4 +-
 .../ignite/testsuites/IgnitePdsTestSuite4.java     |  16 +
 .../apache/ignite/util/GridCommandHandlerTest.java |  52 +-
 .../processors/query/h2/IgniteH2Indexing.java      |   9 +-
 .../processors/query/h2/database/H2Tree.java       |  12 +-
 85 files changed, 7522 insertions(+), 361 deletions(-)

diff --git a/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/diagnostic/pagelocktracker/JmhPageLockTrackerBenchmark.java b/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/diagnostic/pagelocktracker/JmhPageLockTrackerBenchmark.java
new file mode 100644
index 0000000..41dd51d
--- /dev/null
+++ b/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/diagnostic/pagelocktracker/JmhPageLockTrackerBenchmark.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.benchmarks.jmh.diagnostic.pagelocktracker;
+
+import org.apache.ignite.internal.benchmarks.jmh.diagnostic.pagelocktracker.stack.LockTrackerNoBarrier;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory.HEAP_LOG;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory.HEAP_STACK;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory.OFF_HEAP_LOG;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory.OFF_HEAP_STACK;
+
+/**
+ * Benchmark PageLockTracker (factory LockTrackerFactory)
+ */
+public class JmhPageLockTrackerBenchmark {
+    /**
+     * @param args Params.
+     */
+    public static void main(String[] args) throws Exception {
+        Options opt = new OptionsBuilder()
+            .include(JmhPageLockTrackerBenchmark.class.getSimpleName())
+            .build();
+
+        new Runner(opt).run();
+    }
+
+    /** */
+    @State(Scope.Thread)
+    public static class ThreadLocalState {
+        PageLockListener pl;
+
+        @Param({"2", "4", "8", "16"})
+        int stackSize;
+
+        @Param({
+            "HeapArrayLockStack",
+            "HeapArrayLockLog",
+            "OffHeapLockStack",
+            "OffHeapLockLog"
+        })
+        String type;
+
+        @Param({"true", "false"})
+        boolean barrier;
+
+        int StructureId = 123;
+
+        @Setup
+        public void doSetup() {
+            pl = create(Thread.currentThread().getName(), type, barrier);
+        }
+    }
+
+    /**
+     *  Mesure cost for (beforelock -> lock -> unlock) operation.
+     */
+    @Benchmark
+    @BenchmarkMode(Mode.Throughput)
+    @Fork(1)
+    @Warmup(iterations = 10)
+    @Measurement(iterations = 10)
+    //@OutputTimeUnit(TimeUnit.MICROSECONDS)
+    public void lockUnlock(ThreadLocalState localState) {
+        PageLockListener pl = localState.pl;
+
+        for (int i = 0; i < localState.stackSize; i++) {
+            int pageId = i + 1;
+
+            pl.onBeforeReadLock(localState.StructureId, pageId, pageId);
+
+            pl.onReadLock(localState.StructureId, pageId, pageId, pageId);
+        }
+
+        for (int i = localState.stackSize; i > 0; i--) {
+            int pageId = i;
+
+            pl.onReadUnlock(localState.StructureId, pageId, pageId, pageId);
+        }
+    }
+
+    /**
+     * Factory method.
+     *
+     * @param name Lock tracer name.
+     * @param type Lock tracer type.
+     * @param barrier If {@code True} use real implementation,
+     * if {@code False} use implementation with safety dump barrier.
+     * @return Page lock tracker as PageLockListener.
+     */
+    private static PageLockListener create(String name, String type, boolean barrier) {
+        PageLockTracker tracker;
+
+        switch (type) {
+            case "HeapArrayLockStack":
+                tracker = LockTrackerFactory.create(HEAP_STACK, name);
+                break;
+            case "HeapArrayLockLog":
+                tracker = LockTrackerFactory.create(HEAP_LOG, name);
+                break;
+            case "OffHeapLockStack":
+                tracker = LockTrackerFactory.create(OFF_HEAP_STACK, name);
+                break;
+
+            case "OffHeapLockLog":
+                tracker = LockTrackerFactory.create(OFF_HEAP_LOG, name);
+                break;
+            default:
+                throw new IllegalArgumentException("type:" + type);
+        }
+
+        return barrier ? tracker : new LockTrackerNoBarrier(tracker);
+    }
+}
diff --git a/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/diagnostic/pagelocktracker/stack/LockTrackerNoBarrier.java b/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/diagnostic/pagelocktracker/stack/LockTrackerNoBarrier.java
new file mode 100644
index 0000000..dc3b3ff
--- /dev/null
+++ b/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/diagnostic/pagelocktracker/stack/LockTrackerNoBarrier.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.benchmarks.jmh.diagnostic.pagelocktracker.stack;
+
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
+
+/**
+ * Local without barrier syncronization on operation.
+ */
+public class LockTrackerNoBarrier implements PageLockListener {
+    /** */
+    private final PageLockTracker delegate;
+
+    /** */
+    public LockTrackerNoBarrier(
+        PageLockTracker delegate
+    ) {
+        this.delegate = delegate;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onBeforeWriteLock(int cacheId, long pageId, long page) {
+        delegate.onBeforeWriteLock0(cacheId, pageId, page);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onWriteLock(int cacheId, long pageId, long page, long pageAddr) {
+        delegate.onWriteLock0(cacheId, pageId, page, pageAddr);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onWriteUnlock(int cacheId, long pageId, long page, long pageAddr) {
+        delegate.onWriteUnlock0(cacheId, pageId, page, pageAddr);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onBeforeReadLock(int cacheId, long pageId, long page) {
+        delegate.onBeforeReadLock0(cacheId, pageId, page);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onReadLock(int cacheId, long pageId, long page, long pageAddr) {
+        delegate.onReadLock0(cacheId, pageId, page, pageAddr);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onReadUnlock(int cacheId, long pageId, long page, long pageAddr) {
+        delegate.onReadUnlock(cacheId, pageId, page, pageAddr);
+    }
+}
\ No newline at end of file
diff --git a/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/tree/BPlusTreeBenchmark.java b/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/tree/BPlusTreeBenchmark.java
index 6513c13..586b85c 100644
--- a/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/tree/BPlusTreeBenchmark.java
+++ b/modules/benchmarks/src/main/java/org/apache/ignite/internal/benchmarks/jmh/tree/BPlusTreeBenchmark.java
@@ -174,8 +174,19 @@ public class BPlusTreeBenchmark extends JmhAbstractBenchmark {
          */
         TestTree(ReuseList reuseList, int cacheId, PageMemory pageMem, long metaPageId)
             throws IgniteCheckedException {
-            super("test", cacheId, pageMem, null, new AtomicLong(), metaPageId, reuseList,
-                new IOVersions<>(new LongInnerIO()), new IOVersions<>(new LongLeafIO()), null);
+            super(
+                "test",
+                cacheId,
+                pageMem,
+                null,
+                new AtomicLong(),
+                metaPageId,
+                reuseList,
+                new IOVersions<>(new LongInnerIO()),
+                new IOVersions<>(new LongLeafIO()),
+                null,
+                null
+            );
 
             PageIO.registerTest(latestInnerIO(), latestLeafIO());
 
diff --git a/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java b/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java
index 4e7b312..ee9eac9 100644
--- a/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java
+++ b/modules/core/src/main/java/org/apache/ignite/IgniteSystemProperties.java
@@ -1120,6 +1120,39 @@ public final class IgniteSystemProperties {
     public static final String IGNITE_TX_OWNER_DUMP_REQUESTS_ALLOWED = "IGNITE_TX_OWNER_DUMP_REQUESTS_ALLOWED";
 
     /**
+     * Page lock tracker type.
+     * -1 - Disable lock tracking.
+     *  1 - HEAP_STACK
+     *  2 - HEAP_LOG
+     *  3 - OFF_HEAP_STACK
+     *  4 - OFF_HEAP_LOG
+     *
+     * Default is 2 - HEAP_LOG.
+     */
+    public static final String IGNITE_PAGE_LOCK_TRACKER_TYPE = "IGNITE_PAGE_LOCK_TRACKER_TYPE";
+
+    /**
+     * Capacity in pages for storing in page lock tracker strucuture.
+     *
+     * Default is 512 pages.
+     */
+    public static final String IGNITE_PAGE_LOCK_TRACKER_CAPACITY = "IGNITE_PAGE_LOCK_TRACKER_CAPACITY";
+
+    /**
+     * Page lock tracker thread for checking hangs threads interval.
+     *
+     * Default is 60_000 ms.
+     */
+    public static final String IGNITE_PAGE_LOCK_TRACKER_CHECK_INTERVAL = "IGNITE_PAGE_LOCK_TRACKER_CHECK_INTERVAL";
+
+    /**
+     * Enables threads locks dumping on critical node failure.
+     *
+     * Default is {@code true}.
+     */
+    public static final String IGNITE_DUMP_PAGE_LOCK_ON_FAILURE = "IGNITE_DUMP_PAGE_LOCK_ON_FAILURE";
+
+    /**
      * Enforces singleton.
      */
     private IgniteSystemProperties() {
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/commandline/CommandList.java b/modules/core/src/main/java/org/apache/ignite/internal/commandline/CommandList.java
index d595e05..232b0f5 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/commandline/CommandList.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/commandline/CommandList.java
@@ -17,6 +17,7 @@
 package org.apache.ignite.internal.commandline;
 
 import org.apache.ignite.internal.commandline.cache.CacheCommands;
+import org.apache.ignite.internal.commandline.diagnostic.DiagnosticCommand;
 
 /**
  * High-level commands.
@@ -41,7 +42,10 @@ public enum CommandList {
     CACHE("--cache", new CacheCommands()),
 
     /** */
-    WAL("--wal", new WalCommands());
+    WAL("--wal", new WalCommands()),
+
+    /** */
+    DIAGNOSTIC("--diagnostic", new DiagnosticCommand());
 
     /** Private values copy so there's no need in cloning it every time. */
     private static final CommandList[] VALUES = CommandList.values();
@@ -89,8 +93,8 @@ public enum CommandList {
     }
 
     /** {@inheritDoc} */
-    @Override public String toString() { 
-        return text; 
+    @Override public String toString() {
+        return text;
     }
 
     /**
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/commandline/diagnostic/DiagnosticCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/commandline/diagnostic/DiagnosticCommand.java
new file mode 100644
index 0000000..97e0218
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/commandline/diagnostic/DiagnosticCommand.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.commandline.diagnostic;
+
+import org.apache.ignite.internal.client.GridClientConfiguration;
+import org.apache.ignite.internal.commandline.Command;
+import org.apache.ignite.internal.commandline.CommandArgIterator;
+import org.apache.ignite.internal.commandline.CommandLogger;
+
+import static org.apache.ignite.internal.commandline.CommandHandler.UTILITY_NAME;
+import static org.apache.ignite.internal.commandline.CommandList.DIAGNOSTIC;
+import static org.apache.ignite.internal.commandline.CommandLogger.join;
+import static org.apache.ignite.internal.commandline.diagnostic.DiagnosticSubCommand.HELP;
+import static org.apache.ignite.internal.commandline.diagnostic.DiagnosticSubCommand.PAGE_LOCKS;
+
+/**
+ *
+ */
+public class DiagnosticCommand implements Command<DiagnosticSubCommand> {
+    /**
+     *
+     */
+    private DiagnosticSubCommand subcommand;
+
+    /**
+     *
+     */
+    private CommandLogger logger;
+
+    /** {@inheritDoc} */
+    @Override public Object execute(GridClientConfiguration clientCfg, CommandLogger logger) throws Exception {
+        this.logger = logger;
+
+        if (subcommand == HELP) {
+            printDiagnosticHelp();
+
+            return null;
+        }
+
+        Command command = subcommand.subcommand();
+
+        if (command == null)
+            throw new IllegalStateException("Unknown command " + subcommand);
+
+        return command.execute(clientCfg, logger);
+    }
+
+    /** {@inheritDoc} */
+    @Override public DiagnosticSubCommand arg() {
+        return subcommand;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void parseArguments(CommandArgIterator argIter) {
+        if (!argIter.hasNextSubArg()) {
+            subcommand = HELP;
+
+            return;
+        }
+
+        String str = argIter.nextArg("").toLowerCase();
+
+        DiagnosticSubCommand cmd = DiagnosticSubCommand.of(str);
+
+        if (cmd == null)
+            cmd = HELP;
+
+        switch (cmd) {
+            case HELP:
+                break;
+
+            case PAGE_LOCKS:
+                cmd.subcommand().parseArguments(argIter);
+
+                break;
+
+            default:
+                throw new IllegalArgumentException("Unknown diagnostic subcommand " + cmd);
+        }
+
+        if (argIter.hasNextSubArg())
+            throw new IllegalArgumentException("Unexpected argument of diagnostic subcommand: " + argIter.peekNextArg());
+
+        subcommand = cmd;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void printUsage(CommandLogger logger) {
+        logger.logWithIndent("View diagnostic information in a cluster. For more details type:");
+        logger.logWithIndent(join(" ", UTILITY_NAME, DIAGNOSTIC, HELP), 2);
+        logger.nl();
+    }
+
+    /**
+     *
+     */
+    private void printDiagnosticHelp() {
+        logger.log(join(" ", UTILITY_NAME, DIAGNOSTIC, PAGE_LOCKS + " - dump page locks info."));
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/commandline/diagnostic/DiagnosticSubCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/commandline/diagnostic/DiagnosticSubCommand.java
new file mode 100644
index 0000000..3fd1c03
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/commandline/diagnostic/DiagnosticSubCommand.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.commandline.diagnostic;
+
+import org.apache.ignite.internal.commandline.Command;
+
+/**
+ *
+ */
+public enum DiagnosticSubCommand {
+    /** */
+    HELP("help", null),
+
+    /** */
+    PAGE_LOCKS("pageLocks", new PageLocksCommand());
+
+    /** Diagnostic command name. */
+    private final String name;
+
+    /** Command instance for certain type. */
+    private final Command command;
+
+    /**
+     * @param name Command name.
+     * @param command Command handler.
+     */
+    DiagnosticSubCommand(
+        String name,
+        Command command
+    ) {
+        this.name = name;
+        this.command = command;
+    }
+
+    /**
+     * @return Subcommand realization.
+     */
+    public Command subcommand() {
+        return command;
+    }
+
+    /**
+     * @param text Command text.
+     * @return Command for the text.
+     */
+    public static DiagnosticSubCommand of(String text) {
+        for (DiagnosticSubCommand cmd : DiagnosticSubCommand.values()) {
+            if (cmd.name.equalsIgnoreCase(text))
+                return cmd;
+        }
+
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return name;
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/commandline/diagnostic/PageLocksCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/commandline/diagnostic/PageLocksCommand.java
new file mode 100644
index 0000000..c110c98
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/commandline/diagnostic/PageLocksCommand.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.commandline.diagnostic;
+
+import java.io.File;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import org.apache.ignite.cluster.ClusterNode;
+import org.apache.ignite.internal.client.GridClient;
+import org.apache.ignite.internal.client.GridClientConfiguration;
+import org.apache.ignite.internal.commandline.Command;
+import org.apache.ignite.internal.commandline.CommandArgIterator;
+import org.apache.ignite.internal.commandline.CommandLogger;
+import org.apache.ignite.internal.commandline.TaskExecutor;
+import org.apache.ignite.internal.visor.diagnostic.VisorPageLocksResult;
+import org.apache.ignite.internal.visor.diagnostic.VisorPageLocksTask;
+import org.apache.ignite.internal.visor.diagnostic.VisorPageLocksTrackerArgs;
+
+import static org.apache.ignite.internal.commandline.CommandHandler.UTILITY_NAME;
+import static org.apache.ignite.internal.commandline.CommandList.DIAGNOSTIC;
+import static org.apache.ignite.internal.commandline.diagnostic.DiagnosticSubCommand.PAGE_LOCKS;
+
+/**
+ *
+ */
+public class PageLocksCommand implements Command<PageLocksCommand.Args> {
+    /**
+     *
+     */
+    public static final String DUMP = "dump";
+    /**
+     *
+     */
+    public static final String DUMP_LOG = "dump_log";
+
+    /**
+     *
+     */
+    private Args args;
+
+    /**
+     *
+     */
+    private CommandLogger logger;
+
+    /**
+     *
+     */
+    private boolean help;
+
+    /** {@inheritDoc} */
+    @Override public Object execute(GridClientConfiguration clientCfg, CommandLogger logger) throws Exception {
+        this.logger = logger;
+
+        if (help) {
+            help = false;
+
+            printUsage(logger);
+
+            return null;
+        }
+
+        Set<String> nodeIds = args.nodeIds;
+
+        try (GridClient client = Command.startClient(clientCfg)) {
+            if (args.allNodes) {
+                client.compute().nodes().forEach(n -> {
+                    nodeIds.add(String.valueOf(n.consistentId()));
+                    nodeIds.add(n.nodeId().toString());
+                });
+            }
+        }
+
+        VisorPageLocksTrackerArgs taskArg = new VisorPageLocksTrackerArgs(args.op, args.type, args.filePath, nodeIds);
+
+        Map<ClusterNode, VisorPageLocksResult> res;
+
+        try (GridClient client = Command.startClient(clientCfg)) {
+            res = TaskExecutor.executeTask(
+                client,
+                VisorPageLocksTask.class,
+                taskArg,
+                clientCfg
+            );
+        }
+
+        printResult(res);
+
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Args arg() {
+        return args;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void parseArguments(CommandArgIterator argIter) {
+        if (argIter.hasNextSubArg()) {
+            String cmd = argIter.nextArg("").toLowerCase();
+
+            if (DUMP.equals(cmd) || DUMP_LOG.equals(cmd)) {
+                boolean allNodes = false;
+                String filePath = null;
+
+                Set<String> nodeIds = new TreeSet<>();
+
+                while (argIter.hasNextArg()){
+                    String nextArg = argIter.nextArg("").toLowerCase();
+
+                    if ("--all".equals(nextArg))
+                        allNodes = true;
+                    else if ("--nodes".equals(nextArg)) {
+                        while (argIter.hasNextArg()){
+                            nextArg = argIter.nextArg("").toLowerCase();
+
+                            nodeIds.add(nextArg);
+                        }
+                    }
+                    else {
+                        if (new File(nextArg).isDirectory())
+                            filePath = nextArg;
+                    }
+                }
+
+                args = new Args(DUMP, cmd, filePath, allNodes, nodeIds);
+            }
+            else
+                help = true;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public void printUsage(CommandLogger logger) {
+        logger.log("View pages locks state information on the node or nodes.");
+        logger.log(CommandLogger.join(" ",
+            UTILITY_NAME, DIAGNOSTIC, PAGE_LOCKS, DUMP,
+            "[--path path_to_directory] [--all|--nodes nodeId1,nodeId2,..|--nodes consistentId1,consistentId2,..]",
+            "// Save page locks dump to file generated in IGNITE_HOME/work directory."));
+        logger.log(CommandLogger.join(" ",
+            UTILITY_NAME, DIAGNOSTIC, PAGE_LOCKS, DUMP_LOG,
+            "[--all|--nodes nodeId1,nodeId2,..|--nodes consistentId1,consistentId2,..]",
+            "// Pring page locks dump to console on the node or nodes."));
+        logger.nl();
+    }
+
+    /**
+     * @param res Result.
+     */
+    private void printResult(Map<ClusterNode, VisorPageLocksResult> res) {
+        res.forEach((n, res0) -> {
+            logger.log(n.id() + " (" + n.consistentId() + ") " + res0.result());
+        });
+    }
+
+    /**
+     *
+     */
+    public static class Args {
+        /**
+         *
+         */
+        private final String op;
+        /**
+         *
+         */
+        private final String type;
+        /**
+         *
+         */
+        private final String filePath;
+        /**
+         *
+         */
+        private final boolean allNodes;
+        /**
+         *
+         */
+        private final Set<String> nodeIds;
+
+        /**
+         * @param op Operation.
+         * @param type Type.
+         * @param filePath File path.
+         * @param nodeIds Node ids.
+         */
+        public Args(String op, String type, String filePath, boolean allNodes, Set<String> nodeIds) {
+            this.op = op;
+            this.type = type;
+            this.filePath = filePath;
+            this.allNodes = allNodes;
+            this.nodeIds = nodeIds;
+        }
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheDiagnosticManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheDiagnosticManager.java
new file mode 100644
index 0000000..ec271fb
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheDiagnosticManager.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.cache;
+
+import javax.management.InstanceNotFoundException;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTrackerMXBean;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTrackerManager;
+import org.apache.ignite.internal.util.typedef.internal.U;
+
+/**
+ * Component for manage all cache diagnostic functionality.
+ */
+public class CacheDiagnosticManager extends GridCacheSharedManagerAdapter {
+    /** Diagnostic mxbeans group name. */
+    public static final String MBEAN_GROUP = "Diagnostic";
+
+    /** Page lock tracker manager */
+    private  PageLockTrackerManager pageLockTrackerManager;
+
+    /** {@inheritDoc} */
+    @Override protected void start0() throws IgniteCheckedException {
+        super.start0();
+
+        String name = cctx.kernalContext().pdsFolderResolver().resolveFolders().consistentId().toString();
+
+        pageLockTrackerManager = new PageLockTrackerManager(log, name);
+
+        pageLockTrackerManager.start();
+
+        registerMetricsMBean(
+            cctx.gridConfig(),
+            MBEAN_GROUP,
+            PageLockTrackerMXBean.MBEAN_NAME,
+            pageLockTrackerManager.mxBean(),
+            PageLockTrackerMXBean.class
+        );
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void stop0(boolean cancel) {
+        super.stop0(cancel);
+
+        unregisterMetricsMBean(cctx.gridConfig(), MBEAN_GROUP, PageLockTrackerMXBean.MBEAN_NAME);
+
+        pageLockTrackerManager.stop();
+    }
+
+    /**
+     * Getter.
+     *
+     * @return Page lock tracker mananger.
+     */
+    public PageLockTrackerManager pageLockTracker() {
+        return pageLockTrackerManager;
+    }
+
+    /**
+     * @param cfg Ignite configuration.
+     * @param groupName Name of group.
+     * @param mbeanName Metrics MBean name.
+     * @param impl Metrics implementation.
+     * @param clazz Metrics class type.
+     */
+    protected <T> void registerMetricsMBean(
+        IgniteConfiguration cfg,
+        String groupName,
+        String mbeanName,
+        T impl,
+        Class<T> clazz
+    ) {
+        if (U.IGNITE_MBEANS_DISABLED)
+            return;
+
+        try {
+            U.registerMBean(
+                cfg.getMBeanServer(),
+                cfg.getIgniteInstanceName(),
+                groupName,
+                mbeanName,
+                impl,
+                clazz);
+        }
+        catch (Throwable e) {
+            U.error(log, "Failed to register MBean with name: " + mbeanName, e);
+        }
+    }
+
+    /**
+     * @param cfg Ignite configuration.
+     * @param groupName Name of group.
+     * @param name Name of MBean.
+     */
+    protected void unregisterMetricsMBean(
+        IgniteConfiguration cfg,
+        String groupName,
+        String name
+    ) {
+        if (U.IGNITE_MBEANS_DISABLED)
+            return;
+
+        assert cfg != null;
+
+        try {
+            cfg.getMBeanServer().unregisterMBean(
+                U.makeMBeanName(
+                    cfg.getIgniteInstanceName(),
+                    groupName,
+                    name
+                ));
+        }
+        catch (InstanceNotFoundException ignored) {
+            // We tried to unregister a non-existing MBean, not a big deal.
+        }
+        catch (Throwable e) {
+            U.error(log, "Failed to unregister MBean for memory metrics: " + name, e);
+        }
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheProcessor.java
index 167cdcc..8c3c871 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheProcessor.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheProcessor.java
@@ -3340,8 +3340,10 @@ public class GridCacheProcessor extends GridProcessorAdapter {
      * @throws IgniteCheckedException If failed.
      */
     @SuppressWarnings("unchecked")
-    private GridCacheSharedContext createSharedContext(GridKernalContext kernalCtx,
-        Collection<CacheStoreSessionListener> storeSesLsnrs) throws IgniteCheckedException {
+    private GridCacheSharedContext createSharedContext(
+        GridKernalContext kernalCtx,
+        Collection<CacheStoreSessionListener> storeSesLsnrs
+    ) throws IgniteCheckedException {
         IgniteTxManager tm = new IgniteTxManager();
         GridCacheMvccManager mvccMgr = new GridCacheMvccManager();
         GridCacheVersionManager verMgr = new GridCacheVersionManager();
@@ -3392,6 +3394,8 @@ public class GridCacheProcessor extends GridProcessorAdapter {
 
         DeadlockDetectionManager deadlockDetectionMgr = new DeadlockDetectionManager();
 
+        CacheDiagnosticManager diagnosticMgr = new CacheDiagnosticManager();
+
         return new GridCacheSharedContext(
             kernalCtx,
             tm,
@@ -3411,7 +3415,8 @@ public class GridCacheProcessor extends GridProcessorAdapter {
             jta,
             storeSesLsnrs,
             mvccCachingMgr,
-            deadlockDetectionMgr
+            deadlockDetectionMgr,
+            diagnosticMgr
         );
     }
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheSharedContext.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheSharedContext.java
index d130fad..ba9dc9a 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheSharedContext.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheSharedContext.java
@@ -142,6 +142,9 @@ public class GridCacheSharedContext<K, V> {
     /** Tx metrics. */
     private final TransactionMetricsAdapter txMetrics;
 
+    /** Cache diagnostic manager. */
+    private CacheDiagnosticManager diagnosticMgr;
+
     /** Store session listeners. */
     private Collection<CacheStoreSessionListener> storeSesLsnrs;
 
@@ -221,7 +224,8 @@ public class GridCacheSharedContext<K, V> {
         CacheJtaManagerAdapter jtaMgr,
         Collection<CacheStoreSessionListener> storeSesLsnrs,
         MvccCachingManager mvccCachingMgr,
-        DeadlockDetectionManager deadlockDetectionMgr
+        DeadlockDetectionManager deadlockDetectionMgr,
+        CacheDiagnosticManager diagnosticMgr
     ) {
         this.kernalCtx = kernalCtx;
 
@@ -243,7 +247,8 @@ public class GridCacheSharedContext<K, V> {
             ttlMgr,
             evictMgr,
             mvccCachingMgr,
-            deadlockDetectionMgr
+            deadlockDetectionMgr,
+            diagnosticMgr
         );
 
         this.storeSesLsnrs = storeSesLsnrs;
@@ -411,7 +416,9 @@ public class GridCacheSharedContext<K, V> {
             ttlMgr,
             evictMgr,
             mvccCachingMgr,
-            deadlockDetectionMgr);
+            deadlockDetectionMgr,
+            diagnosticMgr
+        );
 
         this.mgrs = mgrs;
 
@@ -458,7 +465,10 @@ public class GridCacheSharedContext<K, V> {
         GridCacheSharedTtlCleanupManager ttlMgr,
         PartitionsEvictManager evictMgr,
         MvccCachingManager mvccCachingMgr,
-        DeadlockDetectionManager deadlockDetectionMgr) {
+        DeadlockDetectionManager deadlockDetectionMgr,
+        CacheDiagnosticManager diagnosticMgr
+    ) {
+        this.diagnosticMgr = add(mgrs, diagnosticMgr);
         this.mvccMgr = add(mgrs, mvccMgr);
         this.verMgr = add(mgrs, verMgr);
         this.txMgr = add(mgrs, txMgr);
@@ -829,6 +839,13 @@ public class GridCacheSharedContext<K, V> {
     }
 
     /**
+     * @return Diagnostic manager.
+     */
+    public CacheDiagnosticManager diagnostic(){
+        return diagnosticMgr;
+    }
+
+    /**
      * @return Deadlock detection manager.
      */
     public DeadlockDetectionManager deadlockDetectionMgr() {
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheOffheapManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheOffheapManager.java
index f8fe509..90a9370 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheOffheapManager.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheOffheapManager.java
@@ -593,11 +593,6 @@ public interface IgniteCacheOffheapManager {
         int partId();
 
         /**
-         * @return Store name.
-         */
-        String name();
-
-        /**
          * @param size Size to init.
          * @param updCntr Update counter to init.
          * @param cacheSizes Cache sizes if store belongs to group containing multiple caches.
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheOffheapManagerImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheOffheapManagerImpl.java
index 9592c1e..201fdc4 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheOffheapManagerImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheOffheapManagerImpl.java
@@ -67,6 +67,7 @@ import org.apache.ignite.internal.processors.cache.persistence.tree.io.DataPageI
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
 import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
 import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageHandler;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
 import org.apache.ignite.internal.processors.cache.query.GridCacheQueryManager;
 import org.apache.ignite.internal.processors.cache.tree.CacheDataRowStore;
 import org.apache.ignite.internal.processors.cache.tree.CacheDataTree;
@@ -205,17 +206,21 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
         assert !cctx.group().persistenceEnabled();
 
         if (cctx.affinityNode() && cctx.ttl().eagerTtlEnabled() && pendingEntries == null) {
-            String name = "PendingEntries";
+            String pendingEntriesTreeName = cctx.name() + "##PendingEntries";
 
             long rootPage = allocateForTree();
 
+            PageLockListener lsnr = ctx.diagnostic().pageLockTracker().createPageLockTracker(pendingEntriesTreeName);
+
             pendingEntries = new PendingEntriesTree(
                 grp,
-                name,
+                pendingEntriesTreeName,
                 grp.dataRegion().pageMemory(),
                 rootPage,
                 grp.reuseList(),
-                true);
+                true,
+                lsnr
+            );
         }
     }
 
@@ -1210,23 +1215,26 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
      * @return Cache data store.
      * @throws IgniteCheckedException If failed.
      */
-    protected CacheDataStore createCacheDataStore0(int p)
-        throws IgniteCheckedException {
+    protected CacheDataStore createCacheDataStore0(int p) throws IgniteCheckedException {
         final long rootPage = allocateForTree();
 
         CacheDataRowStore rowStore = new CacheDataRowStore(grp, grp.freeList(), p);
 
-        String idxName = treeName(p);
+        String dataTreeName = grp.cacheOrGroupName() + "-" + treeName(p);
+
+        PageLockListener lsnr = ctx.diagnostic().pageLockTracker().createPageLockTracker(dataTreeName);
 
         CacheDataTree dataTree = new CacheDataTree(
             grp,
-            idxName,
+            dataTreeName,
             grp.reuseList(),
             rowStore,
             rootPage,
-            true);
+            true,
+            lsnr
+        );
 
-        return new CacheDataStoreImpl(p, idxName, rowStore, dataTree);
+        return new CacheDataStoreImpl(p, rowStore, dataTree);
     }
 
     /** {@inheritDoc} */
@@ -1369,9 +1377,6 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
         /** */
         private final int partId;
 
-        /** Tree name. */
-        private String name;
-
         /** */
         private final CacheDataRowStore rowStore;
 
@@ -1398,18 +1403,15 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
 
         /**
          * @param partId Partition number.
-         * @param name Name.
          * @param rowStore Row store.
          * @param dataTree Data tree.
          */
         public CacheDataStoreImpl(
             int partId,
-            String name,
             CacheDataRowStore rowStore,
             CacheDataTree dataTree
         ) {
             this.partId = partId;
-            this.name = name;
             this.rowStore = rowStore;
             this.dataTree = dataTree;
         }
@@ -1538,11 +1540,6 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
             return pCntr.finalizeUpdateCounters();
         }
 
-        /** {@inheritDoc} */
-        @Override public String name() {
-            return name;
-        }
-
         /**
          * @param cctx Cache context.
          * @param oldRow Old row.
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/mvcc/txlog/TxLog.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/mvcc/txlog/TxLog.java
index 238f7d0..041edfe 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/mvcc/txlog/TxLog.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/mvcc/txlog/TxLog.java
@@ -29,6 +29,7 @@ import org.apache.ignite.internal.pagemem.PageIdUtils;
 import org.apache.ignite.internal.pagemem.PageMemory;
 import org.apache.ignite.internal.pagemem.wal.IgniteWriteAheadLogManager;
 import org.apache.ignite.internal.pagemem.wal.record.delta.MetaPageInitRecord;
+import org.apache.ignite.internal.processors.cache.CacheDiagnosticManager;
 import org.apache.ignite.internal.processors.cache.mvcc.MvccUtils;
 import org.apache.ignite.internal.processors.cache.persistence.DbCheckpointListener;
 import org.apache.ignite.internal.processors.cache.persistence.GridCacheDatabaseSharedManager;
@@ -41,6 +42,7 @@ import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageMetaI
 import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
 import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseListImpl;
 import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageHandler;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
 import org.apache.ignite.internal.util.IgniteTree;
 import org.apache.ignite.internal.util.typedef.internal.CU;
 import org.apache.ignite.internal.util.typedef.internal.U;
@@ -91,7 +93,16 @@ public class TxLog implements DbCheckpointListener {
      * @throws IgniteCheckedException If failed.
      */
     private void init(GridKernalContext ctx) throws IgniteCheckedException {
+        String txLogName = TX_LOG_CACHE_NAME + "##Tree";
+
+        CacheDiagnosticManager diagnosticMgr = ctx.cache().context().diagnostic();
+
+        PageLockListener txLogLockLsnr = diagnosticMgr.pageLockTracker().createPageLockTracker(txLogName);
+
         if (CU.isPersistenceEnabled(ctx.config())) {
+            String txLogReuseListName = TX_LOG_CACHE_NAME + "##ReuseList";
+            PageLockListener txLogReuseListLockLsnr = diagnosticMgr.pageLockTracker().createPageLockTracker(txLogReuseListName);
+
             mgr.checkpointReadLock();
 
             try {
@@ -164,9 +175,20 @@ public class TxLog implements DbCheckpointListener {
                     pageMemory,
                     wal,
                     reuseListRoot,
-                    isNew);
+                    isNew,
+                    txLogReuseListLockLsnr
+                );
 
-                tree = new TxLogTree(pageMemory, wal, treeRoot, reuseList, ctx.failure(), isNew);
+                tree = new TxLogTree(
+                    TX_LOG_CACHE_NAME,
+                    pageMemory,
+                    wal,
+                    treeRoot,
+                    reuseList,
+                    ctx.failure(),
+                    isNew,
+                    txLogLockLsnr
+                );
 
                 ((GridCacheDatabaseSharedManager)mgr).addCheckpointListener(this);
             }
@@ -183,7 +205,16 @@ public class TxLog implements DbCheckpointListener {
             if ((treeRoot = reuseList1.takeRecycledPage()) == 0L)
                 treeRoot = pageMemory.allocatePage(TX_LOG_CACHE_ID, INDEX_PARTITION, FLAG_IDX);
 
-            tree = new TxLogTree(pageMemory, null, treeRoot, reuseList1, ctx.failure(), true);
+            tree = new TxLogTree(
+                txLogName,
+                pageMemory,
+                null,
+                treeRoot,
+                reuseList1,
+                ctx.failure(),
+                true,
+                txLogLockLsnr
+            );
         }
     }
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/mvcc/txlog/TxLogTree.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/mvcc/txlog/TxLogTree.java
index 6c939d3..c3025fd 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/mvcc/txlog/TxLogTree.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/mvcc/txlog/TxLogTree.java
@@ -23,6 +23,7 @@ import org.apache.ignite.internal.pagemem.wal.IgniteWriteAheadLogManager;
 import org.apache.ignite.internal.processors.cache.persistence.tree.BPlusTree;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.BPlusIO;
 import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
 import org.apache.ignite.internal.processors.failure.FailureProcessor;
 
 /**
@@ -38,12 +39,29 @@ public class TxLogTree extends BPlusTree<TxKey, TxRow> {
      * @param initNew {@code True} if new tree should be created.
      * @throws IgniteCheckedException If fails.
      */
-    public TxLogTree(PageMemory pageMem,
-        IgniteWriteAheadLogManager wal, long metaPageId,
-        ReuseList reuseList, FailureProcessor failureProcessor,
-        boolean initNew) throws IgniteCheckedException {
-        super(TxLog.TX_LOG_CACHE_NAME, TxLog.TX_LOG_CACHE_ID, pageMem, wal, new AtomicLong(), metaPageId,
-            reuseList, TxLogInnerIO.VERSIONS, TxLogLeafIO.VERSIONS, failureProcessor);
+    public TxLogTree(
+        String name,
+        PageMemory pageMem,
+        IgniteWriteAheadLogManager wal,
+        long metaPageId,
+        ReuseList reuseList,
+        FailureProcessor failureProcessor,
+        boolean initNew,
+        PageLockListener lockLsnr
+    ) throws IgniteCheckedException {
+        super(
+            TxLog.TX_LOG_CACHE_NAME,
+            TxLog.TX_LOG_CACHE_ID,
+            pageMem,
+            wal,
+            new AtomicLong(),
+            metaPageId,
+            reuseList,
+            TxLogInnerIO.VERSIONS,
+            TxLogLeafIO.VERSIONS,
+            failureProcessor,
+            lockLsnr
+        );
 
         initTree(initNew);
     }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/DataStructure.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/DataStructure.java
index 56842b3..944c27f 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/DataStructure.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/DataStructure.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2019 GridGain Systems, Inc. and Contributors.
- * 
+ *
  * Licensed under the GridGain Community Edition License (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
- * 
+ *
  *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
- * 
+ *
  * 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.
@@ -41,7 +41,7 @@ import static org.apache.ignite.internal.pagemem.PageIdUtils.MAX_ITEMID_NUM;
 /**
  * Base class for all the data structures based on {@link PageMemory}.
  */
-public abstract class DataStructure implements PageLockListener {
+public abstract class DataStructure {
     /** For tests. */
     public static Random rnd;
 
@@ -55,6 +55,9 @@ public abstract class DataStructure implements PageLockListener {
     protected final IgniteWriteAheadLogManager wal;
 
     /** */
+    protected final PageLockListener lockLsnr;
+
+    /** */
     protected ReuseList reuseList;
 
     /**
@@ -65,13 +68,15 @@ public abstract class DataStructure implements PageLockListener {
     public DataStructure(
         int cacheId,
         PageMemory pageMem,
-        IgniteWriteAheadLogManager wal
+        IgniteWriteAheadLogManager wal,
+        PageLockListener lockLsnr
     ) {
         assert pageMem != null;
 
         this.grpId = cacheId;
         this.pageMem = pageMem;
         this.wal = wal;
+        this.lockLsnr = lockLsnr == null ? NOOP_LSNR : lockLsnr;
     }
 
     /**
@@ -145,7 +150,7 @@ public abstract class DataStructure implements PageLockListener {
 
     /**
      * @param pageId Page ID.
-     * @param page  Page pointer.
+     * @param page Page pointer.
      */
     protected final void releasePage(long pageId, long page) {
         pageMem.releasePage(grpId, pageId, page);
@@ -157,7 +162,7 @@ public abstract class DataStructure implements PageLockListener {
      * @return Page address or {@code 0} if failed to lock due to recycling.
      */
     protected final long tryWriteLock(long pageId, long page) {
-        return PageHandler.writeLock(pageMem, grpId, pageId, page, this, true);
+        return PageHandler.writeLock(pageMem, grpId, pageId, page, lockLsnr, true);
     }
 
     /**
@@ -166,13 +171,12 @@ public abstract class DataStructure implements PageLockListener {
      * @return Page address.
      */
     protected final long writeLock(long pageId, long page) {
-        return PageHandler.writeLock(pageMem, grpId, pageId, page, this, false);
+        return PageHandler.writeLock(pageMem, grpId, pageId, page, lockLsnr, false);
     }
 
     /**
-     * <p>
-     * Note: Default WAL record policy will be used.
-     * </p>
+     * <p> Note: Default WAL record policy will be used. </p>
+     *
      * @param pageId Page ID
      * @param page Page pointer.
      * @param pageAddr Page address.
@@ -188,27 +192,27 @@ public abstract class DataStructure implements PageLockListener {
      * @return Page address.
      */
     protected final long readLock(long pageId, long page) {
-        return PageHandler.readLock(pageMem, grpId, pageId, page, this);
+        return PageHandler.readLock(pageMem, grpId, pageId, page, lockLsnr);
     }
 
     /**
      * @param pageId Page ID
      * @param page Page pointer.
-     * @param pageAddr  Page address.
+     * @param pageAddr Page address.
      */
     protected final void readUnlock(long pageId, long page, long pageAddr) {
-        PageHandler.readUnlock(pageMem, grpId, pageId, page, pageAddr, this);
+        PageHandler.readUnlock(pageMem, grpId, pageId, page, pageAddr, lockLsnr);
     }
 
     /**
      * @param pageId Page ID
      * @param page Page pointer.
-     * @param pageAddr  Page address.
+     * @param pageAddr Page address.
      * @param walPlc Full page WAL record policy.
      * @param dirty Dirty flag.
      */
     protected final void writeUnlock(long pageId, long page, long pageAddr, Boolean walPlc, boolean dirty) {
-        PageHandler.writeUnlock(pageMem, grpId, pageId, page, pageAddr, this, walPlc, dirty);
+        PageHandler.writeUnlock(pageMem, grpId, pageId, page, pageAddr, lockLsnr, walPlc, dirty);
     }
 
     /**
@@ -233,8 +237,10 @@ public abstract class DataStructure implements PageLockListener {
         long pageId,
         PageHandler<?, R> h,
         int intArg,
-        R lockFailed) throws IgniteCheckedException {
-        return PageHandler.writePage(pageMem, grpId, pageId, this, h, null, null, null, null, intArg, lockFailed);
+        R lockFailed
+    ) throws IgniteCheckedException {
+        return PageHandler.writePage(pageMem, grpId, pageId, lockLsnr, h,
+            null, null, null, null, intArg, lockFailed);
     }
 
     /**
@@ -251,8 +257,10 @@ public abstract class DataStructure implements PageLockListener {
         PageHandler<X, R> h,
         X arg,
         int intArg,
-        R lockFailed) throws IgniteCheckedException {
-        return PageHandler.writePage(pageMem, grpId, pageId, this, h, null, null, null, arg, intArg, lockFailed);
+        R lockFailed
+    ) throws IgniteCheckedException {
+        return PageHandler.writePage(pageMem, grpId, pageId, lockLsnr, h,
+            null, null, null, arg, intArg, lockFailed);
     }
 
     /**
@@ -271,8 +279,10 @@ public abstract class DataStructure implements PageLockListener {
         PageHandler<X, R> h,
         X arg,
         int intArg,
-        R lockFailed) throws IgniteCheckedException {
-        return PageHandler.writePage(pageMem, grpId, pageId, page, this, h, null, null, null, arg, intArg, lockFailed);
+        R lockFailed
+    ) throws IgniteCheckedException {
+        return PageHandler.writePage(pageMem, grpId, pageId, page, lockLsnr, h,
+            null, null, null, arg, intArg, lockFailed);
     }
 
     /**
@@ -291,8 +301,10 @@ public abstract class DataStructure implements PageLockListener {
         PageIO init,
         X arg,
         int intArg,
-        R lockFailed) throws IgniteCheckedException {
-        return PageHandler.writePage(pageMem, grpId, pageId, this, h, init, wal, null, arg, intArg, lockFailed);
+        R lockFailed
+    ) throws IgniteCheckedException {
+        return PageHandler.writePage(pageMem, grpId, pageId, lockLsnr, h,
+            init, wal, null, arg, intArg, lockFailed);
     }
 
     /**
@@ -309,8 +321,10 @@ public abstract class DataStructure implements PageLockListener {
         PageHandler<X, R> h,
         X arg,
         int intArg,
-        R lockFailed) throws IgniteCheckedException {
-        return PageHandler.readPage(pageMem, grpId, pageId, this, h, arg, intArg, lockFailed);
+        R lockFailed
+    ) throws IgniteCheckedException {
+        return PageHandler.readPage(pageMem, grpId, pageId, lockLsnr,
+            h, arg, intArg, lockFailed);
     }
 
     /**
@@ -329,8 +343,10 @@ public abstract class DataStructure implements PageLockListener {
         PageHandler<X, R> h,
         X arg,
         int intArg,
-        R lockFailed) throws IgniteCheckedException {
-        return PageHandler.readPage(pageMem, grpId, pageId, page, this, h, arg, intArg, lockFailed);
+        R lockFailed
+    ) throws IgniteCheckedException {
+        return PageHandler.readPage(pageMem, grpId, pageId, page, lockLsnr, h,
+            arg, intArg, lockFailed);
     }
 
     /**
@@ -339,7 +355,7 @@ public abstract class DataStructure implements PageLockListener {
      * @throws IgniteCheckedException if failed.
      */
     protected final void init(long pageId, PageIO init) throws IgniteCheckedException {
-        PageHandler.initPage(pageMem, grpId, pageId, init, wal, this);
+        PageHandler.initPage(pageMem, grpId, pageId, init, wal, lockLsnr);
     }
 
     /**
@@ -392,27 +408,30 @@ public abstract class DataStructure implements PageLockListener {
         return pageMem.realPageSize(grpId);
     }
 
-    @Override public void onBeforeWriteLock(int cacheId, long pageId, long page) {
-        // No-op.
-    }
+    /** No-op page lock listener. */
+    public static final PageLockListener NOOP_LSNR = new PageLockListener() {
+        @Override public void onBeforeWriteLock(int cacheId, long pageId, long page) {
+            // No-op.
+        }
 
-    @Override public void onWriteLock(int cacheId, long pageId, long page, long pageAddr) {
-        // No-op.
-    }
+        @Override public void onWriteLock(int cacheId, long pageId, long page, long pageAddr) {
+            // No-op.
+        }
 
-    @Override public void onWriteUnlock(int cacheId, long pageId, long page, long pageAddr) {
-        // No-op.
-    }
+        @Override public void onWriteUnlock(int cacheId, long pageId, long page, long pageAddr) {
+            // No-op.
+        }
 
-    @Override public void onBeforeReadLock(int cacheId, long pageId, long page) {
-        // No-op.
-    }
+        @Override public void onBeforeReadLock(int cacheId, long pageId, long page) {
+            // No-op.
+        }
 
-    @Override public void onReadLock(int cacheId, long pageId, long page, long pageAddr) {
-        // No-op.
-    }
+        @Override public void onReadLock(int cacheId, long pageId, long page, long pageAddr) {
+            // No-op.
+        }
 
-    @Override public void onReadUnlock(int cacheId, long pageId, long page, long pageAddr) {
-        // No-op.
-    }
+        @Override public void onReadUnlock(int cacheId, long pageId, long page, long pageAddr) {
+            // No-op.
+        }
+    };
 }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/GridCacheOffheapManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/GridCacheOffheapManager.java
index 23650ef..4dd24ba 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/GridCacheOffheapManager.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/GridCacheOffheapManager.java
@@ -49,6 +49,7 @@ import org.apache.ignite.internal.pagemem.wal.record.delta.MetaPageInitRecord;
 import org.apache.ignite.internal.pagemem.wal.record.delta.MetaPageUpdatePartitionDataRecord;
 import org.apache.ignite.internal.pagemem.wal.record.delta.PartitionDestroyRecord;
 import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
+import org.apache.ignite.internal.processors.cache.CacheDiagnosticManager;
 import org.apache.ignite.internal.processors.cache.CacheEntryPredicate;
 import org.apache.ignite.internal.processors.cache.CacheGroupContext;
 import org.apache.ignite.internal.processors.cache.CacheObject;
@@ -64,7 +65,7 @@ import org.apache.ignite.internal.processors.cache.distributed.dht.topology.Grid
 import org.apache.ignite.internal.processors.cache.distributed.dht.topology.GridDhtPartitionState;
 import org.apache.ignite.internal.processors.cache.mvcc.MvccSnapshot;
 import org.apache.ignite.internal.processors.cache.mvcc.MvccVersion;
-import org.apache.ignite.internal.processors.cache.persistence.freelist.CacheFreeListImpl;
+import org.apache.ignite.internal.processors.cache.persistence.freelist.CacheFreeList;
 import org.apache.ignite.internal.processors.cache.persistence.migration.UpgradePendingTreeToPerPartitionTask;
 import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMemoryEx;
 import org.apache.ignite.internal.processors.cache.persistence.partstate.GroupPartitionId;
@@ -126,18 +127,27 @@ public class GridCacheOffheapManager extends IgniteCacheOffheapManagerImpl imple
 
         Metas metas = getOrAllocateCacheMetas();
 
+        CacheDiagnosticManager diagnosticMgr = ctx.diagnostic();
+
+        String reuseListName = grp.cacheOrGroupName() + "##ReuseList";
+        String indexStorageTreeName = grp.cacheOrGroupName() + "##IndexStorageTree";
+
         RootPage reuseListRoot = metas.reuseListRoot;
 
-        reuseList = new ReuseListImpl(grp.groupId(),
+        reuseList = new ReuseListImpl(
+            grp.groupId(),
             grp.cacheOrGroupName(),
             grp.dataRegion().pageMemory(),
             ctx.wal(),
             reuseListRoot.pageId().pageId(),
-            reuseListRoot.isAllocated());
+            reuseListRoot.isAllocated(),
+            diagnosticMgr.pageLockTracker().createPageLockTracker(reuseListName)
+        );
 
         RootPage metastoreRoot = metas.treeRoot;
 
-        indexStorage = new IndexStorageImpl(grp.dataRegion().pageMemory(),
+        indexStorage = new IndexStorageImpl(
+            grp.dataRegion().pageMemory(),
             ctx.wal(),
             globalRemoveId(),
             grp.groupId(),
@@ -147,7 +157,9 @@ public class GridCacheOffheapManager extends IgniteCacheOffheapManagerImpl imple
             reuseList,
             metastoreRoot.pageId().pageId(),
             metastoreRoot.isAllocated(),
-            ctx.kernalContext().failure());
+            ctx.kernalContext().failure(),
+            diagnosticMgr.pageLockTracker().createPageLockTracker(indexStorageTreeName)
+        );
 
         ((GridCacheDatabaseSharedManager)ctx.database()).addCheckpointListener(this);
     }
@@ -264,7 +276,7 @@ public class GridCacheOffheapManager extends IgniteCacheOffheapManagerImpl imple
         RowStore rowStore0 = store.rowStore();
 
         if (rowStore0 != null) {
-            ((CacheFreeListImpl)rowStore0.freeList()).saveMetadata();
+            ((CacheFreeList)rowStore0.freeList()).saveMetadata();
 
             long updCntr = store.updateCounter();
             long size = store.fullSize();
@@ -1023,7 +1035,7 @@ public class GridCacheOffheapManager extends IgniteCacheOffheapManagerImpl imple
         for (CacheDataStore store : partDataStores.values()) {
             assert store instanceof GridCacheDataStore;
 
-            CacheFreeListImpl freeList = ((GridCacheDataStore)store).freeList;
+            CacheFreeList freeList = ((GridCacheDataStore)store).freeList;
 
             if (freeList == null)
                 continue;
@@ -1045,7 +1057,7 @@ public class GridCacheOffheapManager extends IgniteCacheOffheapManagerImpl imple
         for (CacheDataStore store : partDataStores.values()) {
             assert store instanceof GridCacheDataStore;
 
-            CacheFreeListImpl freeList = ((GridCacheDataStore)store).freeList;
+            CacheFreeList freeList = ((GridCacheDataStore)store).freeList;
 
             if (freeList == null)
                 continue;
@@ -1463,10 +1475,7 @@ public class GridCacheOffheapManager extends IgniteCacheOffheapManagerImpl imple
         private final int partId;
 
         /** */
-        private String name;
-
-        /** */
-        private volatile CacheFreeListImpl freeList;
+        private volatile CacheFreeList freeList;
 
         /** */
         private PendingEntriesTree pendingTree;
@@ -1502,8 +1511,34 @@ public class GridCacheOffheapManager extends IgniteCacheOffheapManagerImpl imple
         private GridCacheDataStore(int partId, boolean exists) {
             this.partId = partId;
             this.exists = exists;
+        }
 
-            name = treeName(partId);
+        /**
+         * @return Name of free pages list.
+         */
+        private String freeListName() {
+            return grp.cacheOrGroupName() + "-" + partId;
+        }
+
+        /**
+         * @return Name of partition meta store.
+         */
+        private String partitionMetaStoreName() {
+            return grp.cacheOrGroupName() + "-partstore-" + partId;
+        }
+
+        /**
+         * @return Name of data tree.
+         */
+        private String dataTreeName() {
+            return grp.cacheOrGroupName() + "-" + treeName(partId);
+        }
+
+        /**
+         * @return Name of pending entires tree.
+         */
+        private String pendingEntriesTreeName() {
+            return grp.cacheOrGroupName() + "-" +"PendingEntries-" + partId;
         }
 
         /**
@@ -1538,17 +1573,21 @@ public class GridCacheOffheapManager extends IgniteCacheOffheapManagerImpl imple
                             ", metas=" + metas + ']');
                     }
 
+                    String freeListName = freeListName();
+
                     RootPage reuseRoot = metas.reuseListRoot;
 
-                    freeList = new CacheFreeListImpl(
+                    freeList = new CacheFreeList(
                         grp.groupId(),
-                        grp.cacheOrGroupName() + "-" + partId,
+                        freeListName,
                         grp.dataRegion().memoryMetrics(),
                         grp.dataRegion(),
                         null,
                         ctx.wal(),
                         reuseRoot.pageId().pageId(),
-                        reuseRoot.isAllocated()) {
+                        reuseRoot.isAllocated(),
+                        ctx.diagnostic().pageLockTracker().createPageLockTracker(freeListName)
+                    ) {
                         /** {@inheritDoc} */
                         @Override protected long allocatePageNoReuse() throws IgniteCheckedException {
                             assert grp.shared().database().checkpointLockIsHeldByThread();
@@ -1556,6 +1595,7 @@ public class GridCacheOffheapManager extends IgniteCacheOffheapManagerImpl imple
                             return pageMem.allocatePage(grpId, partId, PageIdAllocator.FLAG_DATA);
                         }
                     };
+                    String dataTreeName = dataTreeName();
 
                     CacheDataRowStore rowStore = new CacheDataRowStore(grp, freeList, partId);
 
@@ -1563,11 +1603,13 @@ public class GridCacheOffheapManager extends IgniteCacheOffheapManagerImpl imple
 
                     CacheDataTree dataTree = new CacheDataTree(
                         grp,
-                        name,
+                        dataTreeName,
                         freeList,
                         rowStore,
                         treeRoot.pageId().pageId(),
-                        treeRoot.isAllocated()) {
+                        treeRoot.isAllocated(),
+                        ctx.diagnostic().pageLockTracker().createPageLockTracker(dataTreeName)
+                    ) {
                         /** {@inheritDoc} */
                         @Override protected long allocatePageNoReuse() throws IgniteCheckedException {
                             assert grp.shared().database().checkpointLockIsHeldByThread();
@@ -1576,15 +1618,19 @@ public class GridCacheOffheapManager extends IgniteCacheOffheapManagerImpl imple
                         }
                     };
 
+                    String pendingEntriesTreeName = pendingEntriesTreeName();
+
                     RootPage pendingTreeRoot = metas.pendingTreeRoot;
 
                     final PendingEntriesTree pendingTree0 = new PendingEntriesTree(
                         grp,
-                        "PendingEntries-" + partId,
+                        pendingEntriesTreeName,
                         grp.dataRegion().pageMemory(),
                         pendingTreeRoot.pageId().pageId(),
                         freeList,
-                        pendingTreeRoot.isAllocated()) {
+                        pendingTreeRoot.isAllocated(),
+                        ctx.diagnostic().pageLockTracker().createPageLockTracker(pendingEntriesTreeName)
+                    ) {
                         /** {@inheritDoc} */
                         @Override protected long allocatePageNoReuse() throws IgniteCheckedException {
                             assert grp.shared().database().checkpointLockIsHeldByThread();
@@ -1595,7 +1641,7 @@ public class GridCacheOffheapManager extends IgniteCacheOffheapManagerImpl imple
 
                     PageMemoryEx pageMem = (PageMemoryEx)grp.dataRegion().pageMemory();
 
-                    delegate0 = new CacheDataStoreImpl(partId, name, rowStore, dataTree) {
+                    delegate0 = new CacheDataStoreImpl(partId, rowStore, dataTree) {
                         /** {@inheritDoc} */
                         @Override public PendingEntriesTree pendingTree() {
                             return pendingTree0;
@@ -1802,11 +1848,6 @@ public class GridCacheOffheapManager extends IgniteCacheOffheapManagerImpl imple
         }
 
         /** {@inheritDoc} */
-        @Override public String name() {
-            return name;
-        }
-
-        /** {@inheritDoc} */
         @Override public RowStore rowStore() {
             CacheDataStore delegate0 = delegate;
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/IgniteCacheDatabaseSharedManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/IgniteCacheDatabaseSharedManager.java
index 704255e..1efe385 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/IgniteCacheDatabaseSharedManager.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/IgniteCacheDatabaseSharedManager.java
@@ -58,11 +58,12 @@ import org.apache.ignite.internal.processors.cache.persistence.evict.PageEvictio
 import org.apache.ignite.internal.processors.cache.persistence.evict.Random2LruPageEvictionTracker;
 import org.apache.ignite.internal.processors.cache.persistence.evict.RandomLruPageEvictionTracker;
 import org.apache.ignite.internal.processors.cache.persistence.filename.PdsFolderSettings;
-import org.apache.ignite.internal.processors.cache.persistence.freelist.CacheFreeListImpl;
+import org.apache.ignite.internal.processors.cache.persistence.freelist.CacheFreeList;
 import org.apache.ignite.internal.processors.cache.persistence.freelist.FreeList;
 import org.apache.ignite.internal.processors.cache.persistence.metastorage.MetaStorage;
 import org.apache.ignite.internal.processors.cache.persistence.metastorage.MetastorageLifecycleListener;
 import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
 import org.apache.ignite.internal.processors.cluster.IgniteChangeGlobalStateSupport;
 import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.internal.util.typedef.internal.CU;
@@ -118,10 +119,10 @@ public class IgniteCacheDatabaseSharedManager extends GridCacheSharedManagerAdap
     protected DataRegion dfltDataRegion;
 
     /** */
-    protected Map<String, CacheFreeListImpl> freeListMap;
+    protected Map<String, CacheFreeList> freeListMap;
 
     /** */
-    private CacheFreeListImpl dfltFreeList;
+    private CacheFreeList dfltFreeList;
 
     /** Page size from memory configuration, may be set only for fake(standalone) IgniteCacheDataBaseSharedManager */
     private int pageSize;
@@ -244,18 +245,25 @@ public class IgniteCacheDatabaseSharedManager extends GridCacheSharedManagerAdap
         for (DataRegion memPlc : dataRegionMap.values()) {
             DataRegionConfiguration memPlcCfg = memPlc.config();
 
-            DataRegionMetricsImpl memMetrics = (DataRegionMetricsImpl) memMetricsMap.get(memPlcCfg.getName());
+            DataRegionMetricsImpl memMetrics = (DataRegionMetricsImpl)memMetricsMap.get(memPlcCfg.getName());
 
             boolean persistenceEnabled = memPlcCfg.isPersistenceEnabled();
 
-            CacheFreeListImpl freeList = new CacheFreeListImpl(0,
-                    cctx.igniteInstanceName(),
-                    memMetrics,
-                    memPlc,
-                    null,
-                    persistenceEnabled ? cctx.wal() : null,
-                    0L,
-                    true);
+            String freeListName = memPlcCfg.getName() + "##FreeList";
+
+            PageLockListener lsnr = cctx.diagnostic().pageLockTracker().createPageLockTracker(freeListName);
+
+            CacheFreeList freeList = new CacheFreeList(
+                0,
+                freeListName,
+                memMetrics,
+                memPlc,
+                null,
+                persistenceEnabled ? cctx.wal() : null,
+                0L,
+                true,
+                lsnr
+            );
 
             freeListMap.put(memPlcCfg.getName(), freeList);
         }
@@ -382,11 +390,11 @@ public class IgniteCacheDatabaseSharedManager extends GridCacheSharedManagerAdap
         final String dataRegName = dataRegCfg.getName();
 
         return new IgniteOutClosure<Long>() {
-            private CacheFreeListImpl freeList;
+            private CacheFreeList freeList;
 
             @Override public Long apply() {
                 if (freeList == null) {
-                    CacheFreeListImpl freeList0 = freeListMap.get(dataRegName);
+                    CacheFreeList freeList0 = freeListMap.get(dataRegName);
 
                     if (freeList0 == null)
                         return 0L;
@@ -409,9 +417,9 @@ public class IgniteCacheDatabaseSharedManager extends GridCacheSharedManagerAdap
         final String dataRegName = dataRegCfg.getName();
 
         return new DataRegionMetricsProvider() {
-            private CacheFreeListImpl freeList;
+            private CacheFreeList freeList;
 
-            private CacheFreeListImpl getFreeList() {
+            private CacheFreeList getFreeList() {
                 if (freeList == null)
                     freeList = freeListMap.get(dataRegName);
 
@@ -419,13 +427,13 @@ public class IgniteCacheDatabaseSharedManager extends GridCacheSharedManagerAdap
             }
 
             @Override public long partiallyFilledPagesFreeSpace() {
-                CacheFreeListImpl freeList0 = getFreeList();
+                CacheFreeList freeList0 = getFreeList();
 
                 return freeList0 == null ? 0L : freeList0.freeSpace();
             }
 
             @Override public long emptyDataPages() {
-                CacheFreeListImpl freeList0 = getFreeList();
+                CacheFreeList freeList0 = getFreeList();
 
                 return freeList0 == null ? 0L : freeList0.emptyDataPages();
             }
@@ -698,7 +706,7 @@ public class IgniteCacheDatabaseSharedManager extends GridCacheSharedManagerAdap
      */
     public void dumpStatistics(IgniteLogger log) {
         if (freeListMap != null) {
-            for (CacheFreeListImpl freeList : freeListMap.values())
+            for (CacheFreeList freeList : freeListMap.values())
                 freeList.dumpStatistics(log);
         }
     }
@@ -991,12 +999,12 @@ public class IgniteCacheDatabaseSharedManager extends GridCacheSharedManagerAdap
 
         int sysPageSize = pageMem.systemPageSize();
 
-        CacheFreeListImpl freeListImpl = freeListMap.get(plcCfg.getName());
+        CacheFreeList freeList = freeListMap.get(plcCfg.getName());
 
         for (;;) {
             long allocatedPagesCnt = pageMem.loadedPages();
 
-            int emptyDataPagesCnt = freeListImpl.emptyDataPages();
+            int emptyDataPagesCnt = freeList.emptyDataPages();
 
             boolean shouldEvict = allocatedPagesCnt > (memorySize / sysPageSize * plcCfg.getEvictionThreshold()) &&
                 emptyDataPagesCnt < plcCfg.getEmptyPagesPoolSize();
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/IndexStorageImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/IndexStorageImpl.java
index 4a27140..9415a01 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/IndexStorageImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/IndexStorageImpl.java
@@ -33,6 +33,7 @@ import org.apache.ignite.internal.processors.cache.persistence.tree.io.BPlusLeaf
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.IOVersions;
 import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
 import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageHandler;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
 import org.apache.ignite.internal.processors.failure.FailureProcessor;
 import org.apache.ignite.internal.util.lang.GridCursor;
 import org.apache.ignite.internal.util.typedef.internal.U;
@@ -87,7 +88,8 @@ public class IndexStorageImpl implements IndexStorage {
         final ReuseList reuseList,
         final long rootPageId,
         final boolean initNew,
-        final FailureProcessor failureProcessor
+        final FailureProcessor failureProcessor,
+        final PageLockListener lockLsnr
     ) {
         try {
             this.pageMem = pageMem;
@@ -97,8 +99,21 @@ public class IndexStorageImpl implements IndexStorage {
             this.allocSpace = allocSpace;
             this.reuseList = reuseList;
 
-            metaTree = new MetaTree(grpId, allocPartId, allocSpace, pageMem, wal, globalRmvId, rootPageId,
-                reuseList, MetaStoreInnerIO.VERSIONS, MetaStoreLeafIO.VERSIONS, initNew, failureProcessor);
+            metaTree = new MetaTree(
+                grpId,
+                allocPartId,
+                allocSpace,
+                pageMem,
+                wal,
+                globalRmvId,
+                rootPageId,
+                reuseList,
+                MetaStoreInnerIO.VERSIONS,
+                MetaStoreLeafIO.VERSIONS,
+                initNew,
+                failureProcessor,
+                lockLsnr
+            );
         }
         catch (IgniteCheckedException e) {
             throw new IgniteException(e);
@@ -237,9 +252,22 @@ public class IndexStorageImpl implements IndexStorage {
             final IOVersions<? extends BPlusInnerIO<IndexItem>> innerIos,
             final IOVersions<? extends BPlusLeafIO<IndexItem>> leafIos,
             final boolean initNew,
-            @Nullable FailureProcessor failureProcessor
+            @Nullable FailureProcessor failureProcessor,
+            @Nullable PageLockListener lockLsnr
         ) throws IgniteCheckedException {
-            super(treeName("meta", "Meta"), cacheId, pageMem, wal, globalRmvId, metaPageId, reuseList, innerIos, leafIos, failureProcessor);
+            super(
+                treeName("meta", "Meta"),
+                cacheId,
+                pageMem,
+                wal,
+                globalRmvId,
+                metaPageId,
+                reuseList,
+                innerIos,
+                leafIos,
+                failureProcessor,
+                lockLsnr
+            );
 
             this.allocPartId = allocPartId;
             this.allocSpace = allocSpace;
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/DumpProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/DumpProcessor.java
new file mode 100644
index 0000000..8600d34
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/DumpProcessor.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker;
+
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.log.PageLockLogSnapshot;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.stack.PageLockStackSnapshot;
+
+/**
+ * Dump processor.
+ */
+public interface DumpProcessor {
+    /**
+     * @param snapshot Process lock log snapshot.
+     */
+    void processDump(PageLockLogSnapshot snapshot);
+
+    /**
+     * @param snapshot Process lock stack snapshot.
+     */
+    void processDump(PageLockStackSnapshot snapshot);
+
+    /**
+     * @param snapshot Process lock thread dump snapshot.
+     */
+    void processDump(ThreadPageLocksDumpLock snapshot);
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/DumpSupported.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/DumpSupported.java
new file mode 100644
index 0000000..814c2c3
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/DumpSupported.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker;
+
+import org.apache.ignite.lang.IgniteFuture;
+
+/**
+ * Interface for all page lock tracker entries which support dumping.
+ */
+public interface DumpSupported<T extends PageLockDump> {
+    /** */
+    boolean acquireSafePoint();
+
+    /** */
+    boolean releaseSafePoint();
+
+    /**
+     * Create dump.
+     */
+    T dump();
+
+    /**
+     * Create dump async.
+     *
+     * @return Ignite future.
+     */
+    IgniteFuture<T> dumpSync();
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/InvalidContext.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/InvalidContext.java
new file mode 100644
index 0000000..b27ca40
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/InvalidContext.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker;
+
+/**
+ * Failure context if any invariant is not satisfied.
+ */
+public class InvalidContext<T extends PageLockDump> {
+    /** */
+    public final String msg;
+    /** */
+    public final T dump;
+
+    /** */
+    public InvalidContext(String msg, T dump) {
+        this.msg = msg;
+        this.dump = dump;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return "Error: " + msg + "\n" + dump.toString();
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/LockTrackerFactory.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/LockTrackerFactory.java
new file mode 100644
index 0000000..ffcc5a4
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/LockTrackerFactory.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker;
+
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTrackerManager.MemoryCalculator;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.log.LockLog;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.stack.LockStack;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.store.HeapPageMetaInfoStore;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.store.OffHeapPageMetaInfoStore;
+
+import static java.lang.String.valueOf;
+import static org.apache.ignite.IgniteSystemProperties.IGNITE_PAGE_LOCK_TRACKER_CAPACITY;
+import static org.apache.ignite.IgniteSystemProperties.IGNITE_PAGE_LOCK_TRACKER_TYPE;
+import static org.apache.ignite.IgniteSystemProperties.getInteger;
+
+/**
+ * Page lock tracker factory.
+ *
+ * 1 - HEAP_STACK
+ * 2 - HEAP_LOG
+ * 3 - OFF_HEAP_STACK
+ * 4 - OFF_HEAP_LOG
+ */
+public final class LockTrackerFactory {
+    /**
+     *
+     */
+    public static final int HEAP_STACK = 1;
+    /**
+     *
+     */
+    public static final int HEAP_LOG = 2;
+    /**
+     *
+     */
+    public static final int OFF_HEAP_STACK = 3;
+    /**
+     *
+     */
+    public static final int OFF_HEAP_LOG = 4;
+
+    /**
+     *
+     */
+    public static volatile int DEFAULT_CAPACITY = getInteger(IGNITE_PAGE_LOCK_TRACKER_CAPACITY, 512);
+    /**
+     *
+     */
+    public static volatile int DEFAULT_TYPE = getInteger(IGNITE_PAGE_LOCK_TRACKER_TYPE, HEAP_LOG);
+
+    /**
+     * @param name Page lock tracker name.
+     */
+    public static PageLockTracker<? extends PageLockDump> create(String name) {
+        return create(DEFAULT_TYPE, name);
+    }
+
+    /**
+     * @param name Page lock tracker name.
+     * @param type Page lock tracker type.
+     */
+    public static PageLockTracker<? extends PageLockDump> create(int type, String name) {
+        return create(type, name, DEFAULT_CAPACITY);
+    }
+
+    /**
+     * @param name Page lock tracker name.
+     * @param type Page lock tracker type.
+     * @param size Page lock tracker size (capacity).
+     */
+    public static PageLockTracker<? extends PageLockDump> create(int type, String name, int size) {
+        return create(type, size, name, new MemoryCalculator());
+    }
+
+    /**
+     * @param name Page lock tracker name.
+     * @param type Page lock tracker type.
+     * @param size Page lock tracker size (capacity).
+     */
+    public static PageLockTracker<? extends PageLockDump> create(
+        int type,
+        int size,
+        String name,
+        MemoryCalculator memCalc
+    ) {
+        switch (type) {
+            case HEAP_STACK:
+                return new LockStack(name, new HeapPageMetaInfoStore(size, memCalc), memCalc);
+            case HEAP_LOG:
+                return new LockLog(name, new HeapPageMetaInfoStore(size, memCalc), memCalc);
+            case OFF_HEAP_STACK:
+                return new LockStack(name, new OffHeapPageMetaInfoStore(size, memCalc), memCalc);
+            case OFF_HEAP_LOG:
+                return new LockLog(name, new OffHeapPageMetaInfoStore(size, memCalc), memCalc);
+
+            default:
+                throw new IllegalArgumentException(valueOf(type));
+        }
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockDump.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockDump.java
new file mode 100644
index 0000000..e5f2856
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockDump.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker;
+
+/**
+ * Interface for page lock tracker structures dump.
+ */
+public interface PageLockDump {
+    /**
+     * @param dumpProcessort Apply dump processor.
+     */
+    void apply(DumpProcessor dumpProcessort);
+
+    /**
+     * @return Dump creation time.
+     */
+    long time();
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockListenerIndexAdapter.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockListenerIndexAdapter.java
new file mode 100644
index 0000000..5f0d4d0
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockListenerIndexAdapter.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker;
+
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
+
+/**
+ * Page lock listener adapter with Id.
+ */
+public class PageLockListenerIndexAdapter implements PageLockListener {
+    /** Adapter id. */
+    private final int id;
+
+    /** Real listener. */
+    private final PageLockListener delegate;
+
+    /**
+     * @param id Adapter id.
+     * @param delegate Real listener.
+     */
+    public PageLockListenerIndexAdapter(int id, PageLockListener delegate) {
+        this.id = id;
+        this.delegate = delegate;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onBeforeWriteLock(int cacheId, long pageId, long page) {
+        delegate.onBeforeWriteLock(id, pageId, page);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onWriteLock(int cacheId, long pageId, long page, long pageAddr) {
+        delegate.onWriteLock(id, pageId, page, pageAddr);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onWriteUnlock(int cacheId, long pageId, long page, long pageAddr) {
+        delegate.onWriteUnlock(id, pageId, page, pageAddr);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onBeforeReadLock(int cacheId, long pageId, long page) {
+        delegate.onBeforeReadLock(id, pageId, page);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onReadLock(int cacheId, long pageId, long page, long pageAddr) {
+        delegate.onReadLock(id, pageId, page, pageAddr);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onReadUnlock(int cacheId, long pageId, long page, long pageAddr) {
+        delegate.onReadUnlock(id, pageId, page, pageAddr);
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTracker.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTracker.java
new file mode 100644
index 0000000..8517340
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTracker.java
@@ -0,0 +1,365 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker;
+
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTrackerManager.MemoryCalculator;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
+import org.apache.ignite.lang.IgniteFuture;
+
+import static org.apache.ignite.internal.pagemem.PageIdUtils.flag;
+import static org.apache.ignite.internal.pagemem.PageIdUtils.pageIndex;
+import static org.apache.ignite.internal.pagemem.PageIdUtils.partId;
+import static org.apache.ignite.internal.util.IgniteUtils.hexInt;
+import static org.apache.ignite.internal.util.IgniteUtils.hexLong;
+
+/**
+ * Abstract page lock tracker.
+ */
+public abstract class PageLockTracker<T extends PageLockDump> implements PageLockListener, DumpSupported<T> {
+    /** */
+    private static final long OVERHEAD_SIZE = 16 + 8 + 8 + 4 + 4 + 4 + 8 + 8 + 4 + 4 + 8;
+    /** */
+    public static final int OP_OFFSET = 16;
+    /** */
+    public static final int LOCK_IDX_MASK = 0xFFFF0000;
+    /** */
+    public static final int LOCK_OP_MASK = 0x000000000000FF;
+
+    /** Page read lock operation id. */
+    public static final int READ_LOCK = 1;
+    /** Page read unlock operation id. */
+    public static final int READ_UNLOCK = 2;
+    /** Page write lock operation id. */
+    public static final int WRITE_LOCK = 3;
+    /** Page write unlock operation id. */
+    public static final int WRITE_UNLOCK = 4;
+    /** Page read before lock operation id. */
+    public static final int BEFORE_READ_LOCK = 5;
+    /** Page write before lock operation id. */
+    public static final int BEFORE_WRITE_LOCK = 6;
+
+    /** */
+    protected final String name;
+    /** */
+    protected final PageMetaInfoStore pages;
+    /** Counter for track lock/unlock operations. */
+    protected int heldLockCnt;
+    /** */
+    protected int nextOp;
+    /** */
+    protected int nextOpStructureId;
+    /** */
+    protected long nextOpPageId;
+    /** */
+    private long opCntr;
+    /** */
+    private volatile boolean dump;
+    /** */
+    private volatile boolean locked;
+    /** */
+    private volatile InvalidContext<T> invalidCtx;
+
+    /**
+     *
+     */
+    protected PageLockTracker(String name, PageMetaInfoStore pages, MemoryCalculator memCalc) {
+        this.name = name;
+        this.pages = pages;
+
+        memCalc.onHeapAllocated(OVERHEAD_SIZE);
+    }
+
+    /** */
+    public void onBeforeWriteLock0(int structureId, long pageId, long page) {
+        this.nextOp = BEFORE_WRITE_LOCK;
+        this.nextOpStructureId = structureId;
+        this.nextOpPageId = pageId;
+    }
+
+    /** */
+    public void onBeforeReadLock0(int structureId, long pageId, long page) {
+        this.nextOp = BEFORE_READ_LOCK;
+        this.nextOpStructureId = structureId;
+        this.nextOpPageId = pageId;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onBeforeWriteLock(int structureId, long pageId, long page) {
+        if (isInvalid())
+            return;
+
+        lock();
+
+        try {
+            onBeforeWriteLock0(structureId, pageId, page);
+        }
+        finally {
+            unLock();
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onWriteLock(int structureId, long pageId, long page, long pageAddr) {
+        if (checkFailedLock(pageAddr) || isInvalid())
+            return;
+
+        lock();
+
+        try {
+            onWriteLock0(structureId, pageId, page, pageAddr);
+        }
+        finally {
+            unLock();
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onWriteUnlock(int structureId, long pageId, long page, long pageAddr) {
+        if (isInvalid())
+            return;
+
+        lock();
+
+        try {
+            onWriteUnlock0(structureId, pageId, page, pageAddr);
+        }
+        finally {
+            unLock();
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onBeforeReadLock(int structureId, long pageId, long page) {
+        if (isInvalid())
+            return;
+
+        lock();
+
+        try {
+            onBeforeReadLock0(structureId, pageId, page);
+        }
+        finally {
+            unLock();
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onReadLock(int structureId, long pageId, long page, long pageAddr) {
+        if (checkFailedLock(pageAddr) ||isInvalid())
+            return;
+
+        lock();
+
+        try {
+            onReadLock0(structureId, pageId, page, pageAddr);
+        }
+        finally {
+            unLock();
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onReadUnlock(int structureId, long pageId, long page, long pageAddr) {
+        if (isInvalid())
+            return;
+
+        lock();
+
+        try {
+            onReadUnlock0(structureId, pageId, page, pageAddr);
+        }
+        finally {
+            unLock();
+        }
+    }
+
+    /** */
+    public abstract void onWriteLock0(int structureId, long pageId, long page, long pageAddr);
+
+    /** */
+    public abstract void onWriteUnlock0(int structureId, long pageId, long page, long pageAddr);
+
+    /** */
+    public abstract void onReadLock0(int structureId, long pageId, long page, long pageAddr);
+
+    /** */
+    public abstract void onReadUnlock0(int structureId, long pageId, long page, long pageAddr);
+
+    /** */
+    public boolean isInvalid() {
+        return invalidCtx != null;
+    }
+
+    /** */
+    private boolean checkFailedLock(long pageAddr){
+        if (pageAddr == 0) {
+            this.nextOp = 0;
+            this.nextOpStructureId = 0;
+            this.nextOpPageId = 0;
+
+            return true;
+        }
+
+        return false;
+    }
+
+    /** */
+    public InvalidContext<T> invalidContext() {
+        return invalidCtx;
+    }
+
+    /** */
+    protected void free(){
+        pages.free();
+    }
+
+    /** */
+    protected void invalid(String msg) {
+        T dump = snapshot();
+
+        invalidCtx = new InvalidContext<>(msg, dump);
+    }
+
+    /** */
+    private void lock() {
+        while (!lock0()) {
+            // Busy wait.
+        }
+    }
+
+    /** */
+    private boolean lock0() {
+        awaitDump();
+
+        locked = true;
+        if (dump) {
+            locked = false;
+
+            return false;
+        }
+
+        return true;
+    }
+
+    /** */
+    private void unLock() {
+        opCntr++;
+
+        locked = false;
+    }
+
+    /** */
+    private void awaitDump() {
+        while (dump) {
+            // Busy wait.
+        }
+    }
+
+    /** */
+    private void awaitLocks() {
+        while (locked) {
+            // Busy wait.
+        }
+    }
+
+    /**
+     * @return Number of locks operations.
+     */
+    public long operationsCounter(){
+        // Read  volatile for thread safety.
+        boolean locked = this.locked;
+
+        return opCntr;
+    }
+
+    /**
+     *
+     */
+    public int heldLocksNumber() {
+        // Read  volatile for thread safety.
+        boolean locked = this.locked;
+
+        return heldLockCnt;
+    }
+
+    /** */
+    protected boolean validateOperation(int structureId, long pageId, int op) {
+        if (nextOpStructureId == 0 || nextOp == 0 || nextOpPageId == 0)
+            return true;
+
+        if ((op == READ_LOCK && nextOp != BEFORE_READ_LOCK) ||
+            (op == WRITE_LOCK && nextOp != BEFORE_WRITE_LOCK) ||
+            (structureId != nextOpStructureId) ||
+            (pageId != nextOpPageId)) {
+
+            invalid("Unepected operation: " +
+                "exp=" + argsToString(nextOpStructureId, nextOpPageId, nextOp) + "," +
+                "actl=" + argsToString(structureId, pageId, op)
+            );
+
+            return false;
+        }
+
+        return true;
+    }
+
+    /** */
+    protected abstract T snapshot();
+
+    /** {@inheritDoc} */
+    @Override public synchronized boolean acquireSafePoint() {
+        return dump ? false : (dump = true);
+
+    }
+
+    /** {@inheritDoc} */
+    @Override public synchronized boolean releaseSafePoint() {
+        return !dump ? false : !(dump = false);
+    }
+
+    /** {@inheritDoc} */
+    @Override public synchronized T dump() {
+        boolean needRelease = acquireSafePoint();
+
+        awaitLocks();
+
+        T dump0 = snapshot();
+
+        if (needRelease)
+            releaseSafePoint();
+
+        return dump0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public IgniteFuture<T> dumpSync() {
+        throw new UnsupportedOperationException();
+    }
+
+    /** */
+    public static String argsToString(int structureId, long pageId, int flags) {
+        return "[structureId=" + structureId + ", pageId" + pageIdToString(pageId) + "]";
+    }
+
+    /** */
+    public static String pageIdToString(long pageId) {
+        return "pageId=" + pageId
+            + " [pageIdHex=" + hexLong(pageId)
+            + ", partId=" + partId(pageId) + ", pageIdx=" + pageIndex(pageId)
+            + ", flags=" + hexInt(flag(pageId)) + "]";
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTrackerMXBean.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTrackerMXBean.java
new file mode 100644
index 0000000..d9ed7d6
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTrackerMXBean.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker;
+
+import org.apache.ignite.mxbean.MXBeanDescription;
+
+/**
+ * This interface defines JMX managment interface for page lock tracking.
+ */
+@MXBeanDescription("MBean that provides access to page lock tracking.")
+public interface PageLockTrackerMXBean {
+    /** */
+    public static final String MBEAN_NAME = "PageLockTracker";
+    /**
+     * Take page locks dump.
+     *
+     * @return String representation of page locks dump.
+     */
+    @MXBeanDescription("Take page locks dump.")
+    String dumpLocks();
+
+    /**
+     * Take page locks dump and print it to console.
+     */
+    @MXBeanDescription("Take page locks dump and print it to console.")
+    void dumpLocksToLog();
+
+    /**
+     * Take page locks dump and save to file.
+     *
+     * @return Absolute file path.
+     */
+    @MXBeanDescription("Take page locks dump and save to file.")
+    String dumpLocksToFile();
+
+    /**
+     * Take page locks dump and save to file for specific path.
+     *
+     * @param path Path to save file.
+     * @return Absolute file path.
+     */
+    @MXBeanDescription("Take page locks dump and save to file for specific path.")
+    String dumpLocksToFile(String path);
+}
\ No newline at end of file
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTrackerMXBeanImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTrackerMXBeanImpl.java
new file mode 100644
index 0000000..db91457
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTrackerMXBeanImpl.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker;
+
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTrackerManager.MemoryCalculator;
+
+/**
+ * Implementation of {@link PageLockTrackerMXBean}.
+ */
+public class PageLockTrackerMXBeanImpl implements PageLockTrackerMXBean {
+    /** */
+    private static final long OVERHEAD_SIZE = 16 + 8;
+
+    /** Page lock tracker manager */
+    private final PageLockTrackerManager mgr;
+
+    /**
+     * @param mgr Page lock tracker manager.
+     */
+    public PageLockTrackerMXBeanImpl(PageLockTrackerManager mgr, MemoryCalculator memoryCalculator) {
+        this.mgr = mgr;
+
+        memoryCalculator.onHeapAllocated(OVERHEAD_SIZE);
+    }
+
+    /** {@inheritDoc} */
+    @Override public String dumpLocks() {
+        return mgr.dumpLocks();
+    }
+
+    /** {@inheritDoc} */
+    @Override public void dumpLocksToLog() {
+        mgr.dumpLocksToLog();
+    }
+
+    /** {@inheritDoc} */
+    @Override public String dumpLocksToFile() {
+        return mgr.dumpLocksToFile();
+    }
+
+    /** {@inheritDoc} */
+    @Override public String dumpLocksToFile(String path) {
+        return mgr.dumpLocksToFile(path);
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTrackerManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTrackerManager.java
new file mode 100644
index 0000000..c48d38c
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTrackerManager.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker;
+
+import java.io.File;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicLong;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.internal.processors.cache.persistence.DataStructure;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.SharedPageLockTracker.State;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.dumpprocessors.ToFileDumpProcessor;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.dumpprocessors.ToStringDumpProcessor;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.lifecycle.LifecycleAware;
+import org.jetbrains.annotations.NotNull;
+
+import static org.apache.ignite.IgniteSystemProperties.IGNITE_PAGE_LOCK_TRACKER_TYPE;
+import static org.apache.ignite.IgniteSystemProperties.getInteger;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory.HEAP_LOG;
+
+/**
+ * Page lock manager.
+ */
+public class PageLockTrackerManager implements LifecycleAware {
+    /** */
+    private static final long OVERHEAD_SIZE = 16 + 8  + 8 + 8 + 8;
+
+    /** */
+    private final MemoryCalculator memoryCalculator = new MemoryCalculator();
+
+    /** MXbean */
+    private final PageLockTrackerMXBean mxBean;
+
+    /** */
+    private final SharedPageLockTracker sharedPageLockTracker;
+
+    /** */
+    private final IgniteLogger log;
+
+    /** */
+    private Set<State> threads;
+
+    /** */
+    private final String managerNameId;
+
+    /** */
+    private final boolean trackingEnable;
+
+
+    /**
+     * Default constructor.
+     */
+    public PageLockTrackerManager(IgniteLogger log) {
+        this(log, "mgr_" + UUID.randomUUID().toString());
+    }
+
+    /**
+     * Default constructor.
+     */
+    public PageLockTrackerManager(IgniteLogger log, String managerNameId) {
+        this.trackingEnable = !(getInteger(IGNITE_PAGE_LOCK_TRACKER_TYPE, HEAP_LOG) == -1);
+        this.managerNameId = managerNameId;
+        this.mxBean = new PageLockTrackerMXBeanImpl(this, memoryCalculator);
+        this.sharedPageLockTracker = new SharedPageLockTracker(this::onHangThreads, memoryCalculator);
+        this.log = log;
+
+        memoryCalculator.onHeapAllocated(OVERHEAD_SIZE);
+    }
+
+    /**
+     * @param threads Hang threads.
+     */
+    private void onHangThreads(@NotNull Set<State> threads) {
+        assert threads != null;
+
+        // Processe only one for same list thread state.
+        // Protection of spam.
+        if (!threads.equals(this.threads)) {
+            this.threads = threads;
+
+            ThreadPageLocksDumpLock dump = sharedPageLockTracker.dump();
+
+            StringBuilder sb = new StringBuilder();
+
+            threads.forEach(s -> {
+                Thread th = s.thread;
+                sb.append("(")
+                    .append(th.getName())
+                    .append("-")
+                    .append(th.getId())
+                    .append(", ")
+                    .append(th.getState())
+                    .append(")");
+
+            });
+
+            log.warning("Threads hanged: [" + sb + "]");
+            // If some thread is hangs
+            // Print to log.
+            log.warning(ToStringDumpProcessor.toStringDump(dump));
+
+            try {
+                // Write dump to file.
+                ToFileDumpProcessor.toFileDump(dump, new File(U.defaultWorkDirectory()), managerNameId);
+            }
+            catch (IgniteCheckedException e) {
+                log.warning("Faile to save locks dump file.", e);
+            }
+        }
+    }
+
+    /**
+     * @param name Lock tracker name.
+     * @return Instance of {@link PageLockListener} for tracking lock/unlock operations.
+     */
+    public PageLockListener createPageLockTracker(String name) {
+        if (!trackingEnable)
+            return DataStructure.NOOP_LSNR;
+
+        return sharedPageLockTracker.registrateStructure(name);
+    }
+
+    /**
+     * Take page locks dump.
+     *
+     * @return String representation of page locks dump.
+     */
+    public String dumpLocks() {
+        ThreadPageLocksDumpLock dump = sharedPageLockTracker.dump();
+
+        return ToStringDumpProcessor.toStringDump(dump);
+    }
+
+    /**
+     * Take page locks dump and print it to console.
+     */
+    public void dumpLocksToLog() {
+        log.warning(dumpLocks());
+    }
+
+    /**
+     * Take page locks dump and save to file.
+     *
+     * @return Absolute file path.
+     */
+    public String dumpLocksToFile() {
+        ThreadPageLocksDumpLock dump = sharedPageLockTracker.dump();
+
+        try {
+            return ToFileDumpProcessor.toFileDump(dump, new File(U.defaultWorkDirectory()), managerNameId);
+        }
+        catch (IgniteCheckedException e) {
+            throw U.convertException(e);
+        }
+    }
+
+    /**
+     * Take page locks dump and save to file for specific path.
+     *
+     * @param path Path to save file.
+     * @return Absolute file path.
+     */
+    public String dumpLocksToFile(String path) {
+        ThreadPageLocksDumpLock dump = sharedPageLockTracker.dump();
+
+        try {
+            return ToFileDumpProcessor.toFileDump(dump, new File(path), managerNameId);
+        }
+        catch (IgniteCheckedException e) {
+            throw U.convertException(e);
+        }
+    }
+
+    /**
+     * Getter.
+     *
+     * @return PageLockTrackerMXBean object.
+     */
+    public PageLockTrackerMXBean mxBean() {
+        return mxBean;
+    }
+
+    /**
+     * @return Total heap overhead in bytes.
+     */
+    public long getHeapOverhead() {
+        return memoryCalculator.heapUsed.get();
+    }
+
+    /**
+     * @return Total offheap overhead in bytes.
+     */
+    public long getOffHeapOverhead() {
+        return memoryCalculator.offHeapUsed.get();
+    }
+
+    /**
+     * @return Total overhead in bytes.
+     */
+    public long getTotalOverhead() {
+        return getHeapOverhead() + getOffHeapOverhead();
+    }
+
+    /** {@inheritDoc} */
+    @Override public void start() throws IgniteException {
+        sharedPageLockTracker.start();
+    }
+
+    /** {@inheritDoc} */
+    @Override public void stop() throws IgniteException {
+        sharedPageLockTracker.stop();
+    }
+
+    /**
+     *
+     */
+   public static class MemoryCalculator {
+        /** */
+        private final AtomicLong heapUsed = new AtomicLong();
+        /** */
+        private final AtomicLong offHeapUsed = new AtomicLong();
+
+        /** */
+        MemoryCalculator(){
+            onHeapAllocated(16 + (8 + 16) * 2);
+        }
+
+        /** */
+        public void onHeapAllocated(long bytes) {
+            assert bytes >= 0;
+
+            heapUsed.getAndAdd(bytes);
+        }
+
+        /** */
+        public void onOffHeapAllocated(long bytes) {
+            assert bytes >= 0;
+
+            offHeapUsed.getAndAdd(bytes);
+        }
+
+        /** */
+        public void onHeapFree(long bytes) {
+            heapUsed.getAndAdd(-bytes);
+        }
+
+        /** */
+        public void onOffHeapFree(long bytes) {
+            offHeapUsed.getAndAdd(-bytes);
+        }
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageMetaInfoStore.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageMetaInfoStore.java
new file mode 100644
index 0000000..4272f94
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageMetaInfoStore.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker;
+
+/**
+ *
+ */
+public interface PageMetaInfoStore {
+    /**
+     * @return Capacity.
+     */
+    int capacity();
+
+    /**
+     * @return True if empty.
+     */
+    boolean isEmpty();
+
+    /**
+     * Add page to store.
+     *
+     * @param itemIdx Index of page in store.
+     * @param op Page operation.
+     * @param structureId Data structure id.
+     * @param pageId Page id.
+     * @param pageAddrHeader Page header addres.
+     * @param pageAddr Page addres.
+     */
+    void add(int itemIdx, int op, int structureId, long pageId, long pageAddrHeader, long pageAddr);
+
+    /**
+     * Remove page from store by index.
+     */
+    void remove(int itemIdx);
+
+    /**
+     * @param itemIdx Index of page in store.
+     * @return Page operation.
+     */
+    int getOperation(int itemIdx);
+
+    /**
+     * @param itemIdx Index of page in store.
+     * @return Data structure id.
+     */
+    int getStructureId(int itemIdx);
+
+    /**
+     * @param itemIdx Index of page in store.
+     * @return Page id.
+     */
+    long getPageId(int itemIdx);
+
+    /**
+     * @param itemIdx Index of page in store.
+     * @return Page header address.
+     */
+    long getPageAddrHeader(int itemIdx);
+
+    /**
+     * @param itemIdx Index of page in store.
+     * @return Page address.
+     */
+    long getPageAddr(int itemIdx);
+
+    /**
+     * @return Copy of current store state.
+     */
+    PageMetaInfoStore copy();
+
+    /**
+     * Free resource.
+     */
+    void free();
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/SharedPageLockTracker.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/SharedPageLockTracker.java
new file mode 100644
index 0000000..dfda39ee
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/SharedPageLockTracker.java
@@ -0,0 +1,444 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.IgniteInterruptedException;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTrackerManager.MemoryCalculator;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.lang.IgniteFuture;
+import org.apache.ignite.lifecycle.LifecycleAware;
+
+import static org.apache.ignite.IgniteSystemProperties.IGNITE_PAGE_LOCK_TRACKER_CHECK_INTERVAL;
+import static org.apache.ignite.IgniteSystemProperties.getInteger;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory.DEFAULT_CAPACITY;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory.DEFAULT_TYPE;
+
+//TODO Fast local get thread local.
+//TODO Dynamic enable/disable tracing.
+//TODO Collect page content to dump. AG
+/**
+ *
+ */
+public class SharedPageLockTracker implements LifecycleAware, PageLockListener, DumpSupported<ThreadPageLocksDumpLock> {
+    /**
+     *
+     */
+    private static final long OVERHEAD_SIZE = 16 + (8 * 8) + (4 * 3);
+
+    /**
+     *
+     */
+    private final MemoryCalculator memCalc;
+
+    /**
+     *
+     */
+    public final int threadLimits;
+    /**
+     *
+     */
+    public final int timeOutWorkerInterval;
+    /**
+     *
+     */
+    private final Map<Long, PageLockTracker<? extends PageLockDump>> threadStacks = new HashMap<>();
+    /**
+     *
+     */
+    private final Map<Long, Thread> threadIdToThreadRef = new HashMap<>();
+    /**
+     *
+     */
+    private final Map<String, Integer> structureNameToId = new HashMap<>();
+    /** Thread for clean terminated threads from map. */
+    private final TimeOutWorker timeOutWorker = new TimeOutWorker();
+    /**
+     *
+     */
+    private Map<Long, SharedPageLockTracker.State> prevThreadsState = new HashMap<>();
+    /**
+     *
+     */
+    private int idGen;
+    /**
+     *
+     */
+    private final Consumer<Set<SharedPageLockTracker.State>> hangThreadsCallBack;
+    /**
+     *
+     */
+    private final ThreadLocal<PageLockTracker> lockTracker = ThreadLocal.withInitial(this::createTracker);
+
+    /**
+     *
+     */
+    public SharedPageLockTracker() {
+        this((ids) -> {
+        }, new MemoryCalculator());
+    }
+
+    /**
+     *
+     */
+    public SharedPageLockTracker(Consumer<Set<State>> hangThreadsCallBack, MemoryCalculator memCalc) {
+        this(
+            1000,
+            getInteger(IGNITE_PAGE_LOCK_TRACKER_CHECK_INTERVAL, 60_000),
+            hangThreadsCallBack,
+            memCalc
+        );
+    }
+
+    /**
+     *
+     */
+    public SharedPageLockTracker(
+        int threadLimits,
+        int timeOutWorkerInterval,
+        Consumer<Set<SharedPageLockTracker.State>> hangThreadsCallBack,
+        MemoryCalculator memCalc
+    ) {
+        this.threadLimits = threadLimits;
+        this.timeOutWorkerInterval = timeOutWorkerInterval;
+        this.hangThreadsCallBack = hangThreadsCallBack;
+        this.memCalc = memCalc;
+
+        this.memCalc.onHeapAllocated(OVERHEAD_SIZE);
+    }
+
+    /**
+     * Factory method for creating thread local {@link PageLockTracker}.
+     *
+     * @return PageLockTracer instance.
+     */
+    private PageLockTracker createTracker() {
+        Thread thread = Thread.currentThread();
+
+        String name = "name=" + thread.getName();
+        long threadId = thread.getId();
+
+        PageLockTracker<? extends PageLockDump> tracker = LockTrackerFactory.create(
+            DEFAULT_TYPE, DEFAULT_CAPACITY, name, memCalc
+        );
+
+        synchronized (this) {
+            threadStacks.put(threadId, tracker);
+
+            threadIdToThreadRef.put(threadId, thread);
+
+            memCalc.onHeapAllocated(((8 + 16 + 8) + 8) * 2);
+
+            if (threadIdToThreadRef.size() > threadLimits)
+                cleanTerminatedThreads();
+        }
+
+        return tracker;
+    }
+
+    /**
+     *
+     */
+    public synchronized PageLockListener registrateStructure(String structureName) {
+        Integer id = structureNameToId.get(structureName);
+
+        if (id == null) {
+            structureNameToId.put(structureName, id = (++idGen));
+
+            // Size for new (K,V) pair.
+            memCalc.onHeapAllocated((structureName.getBytes().length + 16) + (8 + 16 + 4));
+        }
+
+        // Size for PageLockListenerIndexAdapter object.
+        memCalc.onHeapAllocated(16 + 4 + 8);
+
+        return new PageLockListenerIndexAdapter(id, this);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onBeforeWriteLock(int structureId, long pageId, long page) {
+        lockTracker.get().onBeforeWriteLock(structureId, pageId, page);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onWriteLock(int structureId, long pageId, long page, long pageAddr) {
+        lockTracker.get().onWriteLock(structureId, pageId, page, pageAddr);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onWriteUnlock(int structureId, long pageId, long page, long pageAddr) {
+        lockTracker.get().onWriteUnlock(structureId, pageId, page, pageAddr);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onBeforeReadLock(int structureId, long pageId, long page) {
+        lockTracker.get().onBeforeReadLock(structureId, pageId, page);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onReadLock(int structureId, long pageId, long page, long pageAddr) {
+        lockTracker.get().onReadLock(structureId, pageId, page, pageAddr);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onReadUnlock(int structureId, long pageId, long page, long pageAddr) {
+        lockTracker.get().onReadUnlock(structureId, pageId, page, pageAddr);
+    }
+
+    /** {@inheritDoc} */
+    @Override public synchronized ThreadPageLocksDumpLock dump() {
+        Collection<PageLockTracker<? extends PageLockDump>> trackers = threadStacks.values();
+        List<ThreadPageLocksDumpLock.ThreadState> threadStates = new ArrayList<>(threadStacks.size());
+
+        for (PageLockTracker tracker : trackers) {
+            boolean acquired = tracker.acquireSafePoint();
+
+            //TODO
+            assert acquired;
+        }
+
+        for (Map.Entry<Long, PageLockTracker<? extends PageLockDump>> entry : threadStacks.entrySet()) {
+            Long threadId = entry.getKey();
+            Thread thread = threadIdToThreadRef.get(threadId);
+
+            PageLockTracker<? extends PageLockDump> tracker = entry.getValue();
+
+            try {
+                PageLockDump pageLockDump = tracker.dump();
+
+                threadStates.add(
+                    new ThreadPageLocksDumpLock.ThreadState(
+                        threadId,
+                        thread.getName(),
+                        thread.getState(),
+                        pageLockDump,
+                        tracker.isInvalid() ? tracker.invalidContext() : null
+                    )
+                );
+            }
+            finally {
+                tracker.releaseSafePoint();
+            }
+        }
+
+        Map<Integer, String> idToStructureName0 =
+            Collections.unmodifiableMap(
+                structureNameToId.entrySet().stream()
+                    .collect(Collectors.toMap(
+                        Map.Entry::getValue,
+                        Map.Entry::getKey
+                    ))
+            );
+
+        List<ThreadPageLocksDumpLock.ThreadState> threadStates0 =
+            Collections.unmodifiableList(threadStates);
+
+        // Get first thread dump time or current time is threadStates is empty.
+        long time = !threadStates.isEmpty() ? threadStates.get(0).pageLockDump.time() : System.currentTimeMillis();
+
+        return new ThreadPageLocksDumpLock(time, idToStructureName0, threadStates0);
+    }
+
+    /**
+     *
+     */
+    private synchronized void cleanTerminatedThreads() {
+        Iterator<Map.Entry<Long, Thread>> it = threadIdToThreadRef.entrySet().iterator();
+
+        while (it.hasNext()) {
+            Map.Entry<Long, Thread> entry = it.next();
+
+            long threadId = entry.getKey();
+            Thread thread = entry.getValue();
+
+            if (thread.getState() == Thread.State.TERMINATED) {
+                PageLockTracker tracker = threadStacks.remove(threadId);
+
+                if (tracker != null) {
+                    memCalc.onHeapFree((8 + 16 + 8) + 8);
+
+                    tracker.free();
+                }
+
+                it.remove();
+
+                memCalc.onHeapFree((8 + 16 + 8) + 8);
+            }
+        }
+    }
+
+    /**
+     *
+     */
+    private synchronized Map<Long, State> getThreadOperationState() {
+        return threadStacks.entrySet().stream().collect(Collectors.toMap(
+            Map.Entry::getKey,
+            e -> {
+                PageLockTracker<? extends PageLockDump> lt = e.getValue();
+
+                return new State(lt.operationsCounter(), lt.heldLocksNumber(), threadIdToThreadRef.get(e.getKey()));
+            }
+        ));
+    }
+
+    /**
+     *
+     */
+    private synchronized Set<State> hangThreads() {
+        Set<State> hangsThreads = new HashSet<>();
+
+        Map<Long, SharedPageLockTracker.State> currentThreadsOperationState = getThreadOperationState();
+
+        prevThreadsState.forEach((threadId, prevState) -> {
+            State state = currentThreadsOperationState.get(threadId);
+
+            if (state == null)
+                return;
+
+            boolean threadHoldedLocks = state.heldLockCnt != 0;
+
+            // If thread holds a lock and does not change state it may be hanged.
+            if (prevState.equals(state) && threadHoldedLocks)
+                hangsThreads.add(state);
+        });
+
+        prevThreadsState = currentThreadsOperationState;
+
+        return hangsThreads;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void start() throws IgniteException {
+        timeOutWorker.setDaemon(true);
+
+        timeOutWorker.start();
+    }
+
+    /** {@inheritDoc} */
+    @Override public void stop() throws IgniteException {
+        timeOutWorker.interrupt();
+
+        try {
+            timeOutWorker.join();
+        }
+        catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+
+            throw new IgniteInterruptedException(e);
+        }
+    }
+
+    /**
+     *
+     */
+    private class TimeOutWorker extends Thread {
+        /**
+         *
+         */
+        @Override public void run() {
+            try {
+                while (!Thread.currentThread().isInterrupted()) {
+                    sleep(timeOutWorkerInterval);
+
+                    cleanTerminatedThreads();
+
+                    if (hangThreadsCallBack != null) {
+                        Set<SharedPageLockTracker.State> threadIds = hangThreads();
+
+                        if (!F.isEmpty(threadIds))
+                            hangThreadsCallBack.accept(threadIds);
+                    }
+                }
+            }
+            catch (InterruptedException e) {
+                // No-op.
+            }
+        }
+    }
+
+    /**
+     *
+     */
+    public static class State {
+        /**
+         *
+         */
+        final long threadOpCnt;
+        /**
+         *
+         */
+        final long heldLockCnt;
+        /**
+         *
+         */
+        final Thread thread;
+
+        /**
+         *
+         */
+        private State(long threadOpCnt, long heldLockCnt, Thread thread) {
+            this.threadOpCnt = threadOpCnt;
+            this.heldLockCnt = heldLockCnt;
+            this.thread = thread;
+        }
+
+        /** {@inheritDoc} */
+        @Override public boolean equals(Object o) {
+            if (this == o)
+                return true;
+            if (o == null || getClass() != o.getClass())
+                return false;
+            State state = (State)o;
+            return threadOpCnt == state.threadOpCnt &&
+                heldLockCnt == state.heldLockCnt &&
+                Objects.equals(thread, state.thread);
+        }
+
+        /** {@inheritDoc} */
+        @Override public int hashCode() {
+            return Objects.hash(threadOpCnt, heldLockCnt, thread);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public IgniteFuture<ThreadPageLocksDumpLock> dumpSync() {
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean acquireSafePoint() {
+        throw new UnsupportedOperationException();
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean releaseSafePoint() {
+        throw new UnsupportedOperationException();
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/ThreadPageLocksDumpLock.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/ThreadPageLocksDumpLock.java
new file mode 100644
index 0000000..9228c9e
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/ThreadPageLocksDumpLock.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.dumpprocessors.ToStringDumpProcessor.toStringDump;
+
+/**
+ *
+ */
+public class ThreadPageLocksDumpLock implements PageLockDump {
+    /** */
+    public final long time;
+
+    /** */
+    public final Map<Integer, String> structureIdToStrcutureName;
+
+    /** */
+    public final List<ThreadState> threadStates;
+
+    /** */
+    public ThreadPageLocksDumpLock(
+        long time,
+        Map<Integer, String> structureIdToStrcutureName,
+        List<ThreadState> threadStates
+    ) {
+        this.time = time;
+        this.structureIdToStrcutureName = structureIdToStrcutureName;
+        this.threadStates = threadStates;
+    }
+
+    /** */
+    public static class ThreadState {
+        /** */
+        public final long threadId;
+        /** */
+        public final String threadName;
+        /** */
+        public final Thread.State state;
+        /** */
+        public final PageLockDump pageLockDump;
+        /** */
+        public final InvalidContext<? extends PageLockDump> invalidContext;
+
+        /** */
+        public ThreadState(
+            long threadId,
+            String threadName,
+            Thread.State state,
+            PageLockDump pageLockDump,
+            InvalidContext<? extends PageLockDump> invalidContext
+        ) {
+            this.threadId = threadId;
+            this.threadName = threadName;
+            this.state = state;
+            this.pageLockDump = pageLockDump;
+            this.invalidContext = invalidContext;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public void apply(DumpProcessor dumpProcessor) {
+        dumpProcessor.processDump(this);
+    }
+
+    /** {@inheritDoc} */
+    @Override public long time() {
+        return time;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return toStringDump(this);
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/dumpprocessors/ToFileDumpProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/dumpprocessors/ToFileDumpProcessor.java
new file mode 100644
index 0000000..60bb590
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/dumpprocessors/ToFileDumpProcessor.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.dumpprocessors;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockDump;
+
+import static java.nio.channels.FileChannel.open;
+import static java.nio.file.StandardOpenOption.CREATE_NEW;
+import static java.nio.file.StandardOpenOption.WRITE;
+
+/**
+ *
+ */
+public class ToFileDumpProcessor {
+    /** Date format. */
+    public static final SimpleDateFormat DATE_FMT = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss_SSS");
+
+    /** File name prefix. */
+    public static final String PREFIX_NAME = "page_lock_dump_";
+
+    /**
+     * @param pageLockDump Dump.
+     * @param dir Directory to save.
+     */
+    public static String toFileDump(PageLockDump pageLockDump, File dir, String name) throws IgniteCheckedException {
+        try {
+           if (!dir.exists())
+               dir.mkdirs();
+
+            File file = new File(dir, PREFIX_NAME + name + "_" + DATE_FMT.format(new Date(pageLockDump.time())));
+
+            return saveToFile(ToStringDumpProcessor.toStringDump(pageLockDump), file);
+        }
+        catch (IOException e) {
+            throw new IgniteCheckedException(e);
+        }
+    }
+
+    /**
+     * @param dump Dump.
+     * @param file File to save.
+     */
+    private static String saveToFile(String dump, File file) throws IOException {
+        assert dump != null;
+        assert file != null;
+        assert !dump.isEmpty();
+
+        try (FileChannel ch = open(file.toPath(), CREATE_NEW, WRITE)) {
+            ByteBuffer buf = ByteBuffer.wrap(dump.getBytes());
+
+            assert buf.position() == 0;
+            assert buf.limit() > 0;
+
+            while (buf.position() != buf.limit())
+                ch.write(buf);
+
+            ch.force(true);
+        }
+
+        return file.getAbsolutePath();
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/dumpprocessors/ToStringDumpProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/dumpprocessors/ToStringDumpProcessor.java
new file mode 100644
index 0000000..51a26dc
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/dumpprocessors/ToStringDumpProcessor.java
@@ -0,0 +1,335 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.dumpprocessors;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.DumpProcessor;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockDump;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageMetaInfoStore;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.ThreadPageLocksDumpLock;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.ThreadPageLocksDumpLock.ThreadState;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.log.PageLockLogSnapshot;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.stack.PageLockStackSnapshot;
+import org.apache.ignite.internal.util.typedef.internal.SB;
+import org.apache.ignite.internal.util.typedef.internal.U;
+
+import static org.apache.ignite.internal.pagemem.PageIdUtils.flag;
+import static org.apache.ignite.internal.pagemem.PageIdUtils.pageIndex;
+import static org.apache.ignite.internal.pagemem.PageIdUtils.partId;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker.BEFORE_READ_LOCK;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker.BEFORE_WRITE_LOCK;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker.LOCK_OP_MASK;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker.READ_LOCK;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker.READ_UNLOCK;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker.WRITE_LOCK;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker.WRITE_UNLOCK;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker.pageIdToString;
+import static org.apache.ignite.internal.util.IgniteUtils.hexInt;
+import static org.apache.ignite.internal.util.IgniteUtils.hexLong;
+
+/**
+ * Proccessor for buils string from {@link PageLockDump}.
+ */
+public class ToStringDumpProcessor {
+    /** Date format. */
+    public static final SimpleDateFormat DATE_FMT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+
+    /**
+     * @param pageLockDump Dump.
+     * @return String representation of dump.
+     */
+    public static String toStringDump(PageLockDump pageLockDump) {
+        StringBuilder sb = new StringBuilder();
+
+        ToStringHelper proc = new ToStringHelper(sb, strucutreIdMapFunc(pageLockDump));
+
+        pageLockDump.apply(proc);
+
+        return sb.toString();
+    }
+
+    /** */
+    private static Function<Integer, String> strucutreIdMapFunc(PageLockDump pageLockDump) {
+        if (pageLockDump instanceof ThreadPageLocksDumpLock) {
+            ThreadPageLocksDumpLock dump = (ThreadPageLocksDumpLock)pageLockDump;
+
+            return dump.structureIdToStrcutureName::get;
+        }
+        else
+            return String::valueOf;
+    }
+
+    /** */
+    private static class ToStringHelper implements DumpProcessor {
+        /** */
+        private final Function<Integer, String> strucutreIdMapFunc;
+        /** */
+        private final StringBuilder sb;
+
+        /** */
+        private ToStringHelper(StringBuilder sb, Function<Integer, String> strucutreIdMapFunc) {
+            this.sb = sb;
+            this.strucutreIdMapFunc = strucutreIdMapFunc;
+        }
+
+        /** Helper class for track lock/unlock count. */
+        class LockState {
+            int readlock;
+            int writelock;
+        }
+
+        /** */
+        private String operationToString(int op) {
+            switch (op) {
+                case BEFORE_READ_LOCK:
+                    return "Try Read lock";
+                case BEFORE_WRITE_LOCK:
+                    return "Try Write lock";
+                case READ_LOCK:
+                    return "Read lock";
+                case READ_UNLOCK:
+                    return "Read unlock";
+                case WRITE_LOCK:
+                    return "Write lock";
+                case WRITE_UNLOCK:
+                    return "Write unlock";
+            }
+
+            return "N/A";
+        }
+
+        /**
+         * @param entry Log entry.
+         * @return String line.
+         */
+        private String buildPageInfo(PageLockLogSnapshot.LogEntry entry) {
+            int op = entry.operation;
+            long pageId = entry.pageId;
+            int structureId = entry.structureId;
+
+            return operationToString(op) + " pageId=" + pageId
+                + ", structureId=" + strucutreIdMapFunc.apply(structureId)
+                + " [pageIdHex=" + hexLong(pageId)
+                + ", partId=" + partId(pageId) + ", pageIdx=" + pageIndex(pageId)
+                + ", flags=" + hexInt(flag(pageId)) + "]";
+        }
+
+        /**
+         * @param holdedLocks Holded locks map.
+         * @return String line.
+         */
+        private String lockedPagesInfo(Map<Long, LockState> holdedLocks) {
+            SB sb = new SB();
+
+            sb.a("Locked pages = [");
+
+            boolean first = true;
+
+            for (Map.Entry<Long, LockState> entry : holdedLocks.entrySet()) {
+                Long pageId = entry.getKey();
+                LockState lockState = entry.getValue();
+
+                if (!first)
+                    sb.a(",");
+                else
+                    first = false;
+
+                sb.a(pageId)
+                    .a("[" + hexLong(pageId) + "]")
+                    .a("(r=" + lockState.readlock + "|w=" + lockState.writelock + ")");
+            }
+
+            sb.a("]");
+
+            return sb.toString();
+        }
+
+        /** {@inheritDoc} */
+        @Override public void processDump(PageLockLogSnapshot snapshot) {
+            Map<Long, LockState> holdetLocks = new LinkedHashMap<>();
+
+            SB logLocksStr = new SB();
+
+            List<PageLockLogSnapshot.LogEntry> locklog = snapshot.locklog;
+            int nextOp = snapshot.nextOp;
+            long nextOpPageId = snapshot.nextOpPageId;
+            int nextOpStructureId = snapshot.nextOpStructureId;
+
+            for (PageLockLogSnapshot.LogEntry entry : locklog) {
+                int op = entry.operation;
+                long pageId = entry.pageId;
+                int locksHolded = entry.holdedLocks;
+
+                if (op == READ_LOCK || op == WRITE_LOCK || op == BEFORE_READ_LOCK || op == BEFORE_WRITE_LOCK) {
+                    LockState state = holdetLocks.get(pageId);
+
+                    if (state == null)
+                        holdetLocks.put(pageId, state = new LockState());
+
+                    if (op == READ_LOCK)
+                        state.readlock++;
+
+                    if (op == WRITE_LOCK)
+                        state.writelock++;
+
+                    logLocksStr.a("L=" + locksHolded + " -> " + buildPageInfo(entry) + U.nl());
+                }
+
+                if (op == READ_UNLOCK || op == WRITE_UNLOCK) {
+                    LockState state = holdetLocks.get(pageId);
+
+                    if (op == READ_UNLOCK)
+                        state.readlock--;
+
+                    if (op == WRITE_UNLOCK)
+                        state.writelock--;
+
+                    if (state.readlock == 0 && state.writelock == 0)
+                        holdetLocks.remove(pageId);
+
+                    logLocksStr.a("L=" + locksHolded + " <- " + buildPageInfo(entry) + U.nl());
+                }
+            }
+
+            if (nextOpPageId != 0) {
+                logLocksStr.a("-> " + operationToString(nextOp) + " nextOpPageId=" + nextOpPageId +
+                    ", nextOpStructureId=" + strucutreIdMapFunc.apply(nextOpStructureId)
+                    + " [pageIdHex=" + hexLong(nextOpPageId)
+                    + ", partId=" + partId(nextOpPageId) + ", pageIdx=" + pageIndex(nextOpPageId)
+                    + ", flags=" + hexInt(flag(nextOpPageId)) + "]" + U.nl());
+            }
+
+            sb.append(lockedPagesInfo(holdetLocks)).append(U.nl());
+
+            sb.append("Locked pages log: ").append(snapshot.name)
+                .append(" time=(").append(snapshot.time).append(", ")
+                .append(DATE_FMT.format(new java.util.Date(snapshot.time)))
+                .append(")")
+                .append(U.nl());
+
+            sb.append(logLocksStr).append(U.nl());
+            ;
+        }
+
+        /** {@inheritDoc} */
+        @Override public void processDump(PageLockStackSnapshot snapshot) {
+            int headIdx = snapshot.headIdx;
+            PageMetaInfoStore pageIdLocksStack = snapshot.pageIdLocksStack;
+            long nextOpPageId = snapshot.nextOpPageId;
+
+            Map<Long, LockState> holdedLocks = new LinkedHashMap<>();
+
+            SB stackStr = new SB();
+
+            if (nextOpPageId != 0)
+                stackStr.a("\t-> " + operationToString(snapshot.nextOp) +
+                    " structureId=" + strucutreIdMapFunc.apply(snapshot.nextOpStructureId) +
+                    " " + pageIdToString(nextOpPageId) + U.nl());
+
+            for (int itemIdx = headIdx - 1; itemIdx >= 0; itemIdx--) {
+                long pageId = pageIdLocksStack.getPageId(itemIdx);
+
+                if (pageId == 0 && itemIdx == 0)
+                    break;
+
+                int op;
+
+                if (pageId == 0) {
+                    stackStr.a("\t -\n");
+
+                    continue;
+                }
+                else {
+                    op = pageIdLocksStack.getOperation(itemIdx) & LOCK_OP_MASK;
+
+                    int structureId = pageIdLocksStack.getStructureId(itemIdx);
+
+                    stackStr.a("\t" + operationToString(op) +
+                        " structureId=" + strucutreIdMapFunc.apply(structureId) +
+                        " " + pageIdToString(pageId) + U.nl());
+                }
+
+                if (op == READ_LOCK || op == WRITE_LOCK || op == BEFORE_READ_LOCK) {
+                    LockState state = holdedLocks.get(pageId);
+
+                    if (state == null)
+                        holdedLocks.put(pageId, state = new LockState());
+
+                    if (op == READ_LOCK)
+                        state.readlock++;
+
+                    if (op == WRITE_LOCK)
+                        state.writelock++;
+
+                }
+            }
+
+            sb.append(lockedPagesInfo(holdedLocks)).append(U.nl());
+
+            sb.append("Locked pages stack: ").append(snapshot.name)
+                .append(" time=(").append(snapshot.time).append(", ")
+                .append(DATE_FMT.format(new java.util.Date(snapshot.time)))
+                .append(")")
+                .append(U.nl());
+
+            sb.append(stackStr).append(U.nl());
+        }
+
+        /** {@inheritDoc} */
+        @Override public void processDump(ThreadPageLocksDumpLock snapshot) {
+            sb.append("Page locks dump:").append(U.nl()).append(U.nl());
+
+            List<ThreadState> threadStates = new ArrayList<>(snapshot.threadStates);
+
+            // Sort thread dump by thread names.
+            threadStates.sort(new Comparator<ThreadState>() {
+                /** {@inheritDoc} */
+                @Override public int compare(ThreadState thState1, ThreadState thState2) {
+                    return thState1.threadName.compareTo(thState2.threadName);
+                }
+            });
+
+            for (ThreadState ths : threadStates) {
+                sb.append("Thread=[name=").append(ths.threadName)
+                    .append(", id=").append(ths.threadId)
+                    .append("], state=").append(ths.state)
+                    .append(U.nl());
+
+                PageLockDump pageLockDump0;
+
+                if (ths.invalidContext == null)
+                    pageLockDump0 = ths.pageLockDump;
+                else {
+                    sb.append(ths.invalidContext.msg).append(U.nl());
+
+                    pageLockDump0 = ths.invalidContext.dump;
+                }
+
+                pageLockDump0.apply(this);
+
+                sb.append(U.nl());
+            }
+        }
+    }
+}
+
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/log/LockLog.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/log/LockLog.java
new file mode 100644
index 0000000..a1b92ba
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/log/LockLog.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.log;
+
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTrackerManager.MemoryCalculator;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageMetaInfoStore;
+
+/**
+ * Abstract page lock log class.
+ **/
+public class LockLog extends PageLockTracker<PageLockLogSnapshot> {
+    /** */
+    protected int headIdx;
+
+    /**
+     * Constructor.
+     *
+     * @param name Page lock log name.
+     * @param pageMetaInfoStore Object storing page meta info.
+     */
+    public LockLog(String name, PageMetaInfoStore pageMetaInfoStore, MemoryCalculator memCalc) {
+        super(name, pageMetaInfoStore, memCalc);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onWriteLock0(int structureId, long pageId, long page, long pageAddr) {
+        log(WRITE_LOCK, structureId, pageId, page, pageAddr);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onWriteUnlock0(int structureId, long pageId, long page, long pageAddr) {
+        log(WRITE_UNLOCK, structureId, pageId, page, pageAddr);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onReadLock0(int structureId, long pageId, long page, long pageAddr) {
+        log(READ_LOCK, structureId, pageId, page, pageAddr);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onReadUnlock0(int structureId, long pageId, long page, long pageAddr) {
+        log(READ_UNLOCK, structureId, pageId, page, pageAddr);
+    }
+
+    /**
+     * Log lock operation.
+     *
+     * @param structureId Structure id.
+     * @param pageId Page id.
+     * @param op Operation.
+     */
+    private void log(int op, int structureId, long pageId, long pageAddrHeader, long pageAddr) {
+        if (!validateOperation(structureId, pageId, op))
+            return;
+
+        if (headIdx + 1 > pages.capacity()) {
+            invalid("Log overflow, size:" + pages.capacity() +
+                ", headIdx=" + headIdx + " " + argsToString(structureId, pageId, op));
+
+            return;
+        }
+
+        long pageId0 = pages.getPageId(headIdx);
+
+        if (pageId0 != 0L && pageId0 != pageId) {
+            invalid("Head should be empty, headIdx=" + headIdx + " " +
+                argsToString(structureId, pageId, op));
+
+            return;
+        }
+
+        if (READ_LOCK == op || WRITE_LOCK == op)
+            heldLockCnt++;
+
+        if (READ_UNLOCK == op || WRITE_UNLOCK == op)
+            heldLockCnt--;
+
+        int curIdx = heldLockCnt << OP_OFFSET & LOCK_IDX_MASK;
+
+        pages.add(headIdx, curIdx | op, structureId, pageId, pageAddrHeader, pageAddr);
+
+        if (BEFORE_READ_LOCK == op || BEFORE_WRITE_LOCK == op)
+            return;
+
+        headIdx++;
+
+        if (heldLockCnt == 0)
+            reset();
+
+        if (op != BEFORE_READ_LOCK && op != BEFORE_WRITE_LOCK &&
+            nextOpPageId == pageId && nextOpStructureId == structureId) {
+            nextOpStructureId = 0;
+            nextOpPageId = 0;
+            nextOp = 0;
+        }
+    }
+
+    /**
+     * Reset log state.
+     */
+    private void reset() {
+        for (int itemIdx = 0; itemIdx < headIdx; itemIdx++)
+            pages.remove(itemIdx);
+
+        headIdx = 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public PageLockLogSnapshot snapshot() {
+        PageMetaInfoStore log = pages.copy();
+
+        return new PageLockLogSnapshot(
+            name,
+            System.currentTimeMillis(),
+            headIdx,
+            log,
+            nextOp,
+            nextOpStructureId,
+            nextOpPageId
+        );
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/log/PageLockLogSnapshot.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/log/PageLockLogSnapshot.java
new file mode 100644
index 0000000..1514df0
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/log/PageLockLogSnapshot.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.log;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockDump;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.DumpProcessor;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageMetaInfoStore;
+
+import static org.apache.ignite.internal.pagemem.PageIdUtils.pageId;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker.LOCK_IDX_MASK;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker.LOCK_OP_MASK;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker.OP_OFFSET;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.dumpprocessors.ToStringDumpProcessor.toStringDump;
+
+/**
+ * Page lock log snapshot.
+ */
+public class PageLockLogSnapshot implements PageLockDump {
+    /** Page lock log name. */
+    public final String name;
+    /** Creation time. */
+    public final long time;
+    /** Head position. */
+    public final int headIdx;
+    /** List of log entries. */
+    public final List<LogEntry> locklog;
+    /** */
+    public final PageMetaInfoStore log;
+    /** Next operation. */
+    public final int nextOp;
+    /** Next data structure. */
+    public final int nextOpStructureId;
+    /** Next page id. */
+    public final long nextOpPageId;
+
+    /**
+     *
+     */
+    public PageLockLogSnapshot(
+        String name,
+        long time,
+        int headIdx,
+        PageMetaInfoStore log,
+        int nextOp,
+        int nextOpStructureId,
+        long nextOpPageId
+    ) {
+        this.name = name;
+        this.time = time;
+        this.headIdx = headIdx;
+        this.log = log;
+        this.locklog = toList(log);
+        this.nextOp = nextOp;
+        this.nextOpStructureId = nextOpStructureId;
+        this.nextOpPageId = nextOpPageId;
+    }
+
+    /**
+     * Convert log to list {@link PageLockLogSnapshot.LogEntry}.
+     *
+     * @return List of {@link PageLockLogSnapshot.LogEntry}.
+     */
+    private List<PageLockLogSnapshot.LogEntry> toList(PageMetaInfoStore pages) {
+        List<PageLockLogSnapshot.LogEntry> lockLog = new ArrayList<>(pages.capacity());
+
+        for (int itemIdx = 0; itemIdx < headIdx; itemIdx ++) {
+            int metaOnLock = pages.getOperation(itemIdx);
+
+            assert metaOnLock != 0;
+
+            int heldLocks = (int)(metaOnLock & LOCK_IDX_MASK) >> OP_OFFSET;
+
+            assert heldLocks >= 0;
+
+            int op = metaOnLock & LOCK_OP_MASK;
+
+            int structureId = pages.getStructureId(itemIdx);
+
+            long pageId = pages.getPageId(itemIdx);
+
+            lockLog.add(new PageLockLogSnapshot.LogEntry(pageId, structureId, op, heldLocks));
+        }
+
+        return lockLog;
+    }
+
+    /**
+     * Log entry.
+     */
+    public static class LogEntry {
+        /** */
+        public final long pageId;
+        /** */
+        public final int structureId;
+        /** */
+        public final int operation;
+        /** */
+        public final int holdedLocks;
+        /** */
+        public LogEntry(long pageId, int structureId, int operation, int holdedLock) {
+            this.pageId = pageId;
+            this.structureId = structureId;
+            this.operation = operation;
+            this.holdedLocks = holdedLock;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public void apply(DumpProcessor dumpProcessor) {
+        dumpProcessor.processDump(this);
+    }
+
+    /** {@inheritDoc} */
+    @Override public long time() {
+        return time;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return toStringDump(this);
+    }
+}
\ No newline at end of file
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/stack/LockStack.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/stack/LockStack.java
new file mode 100644
index 0000000..1606c72
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/stack/LockStack.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.stack;
+
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageMetaInfoStore;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTrackerManager.MemoryCalculator;
+
+/**
+ * Abstract page lock stack.
+ */
+public class LockStack extends PageLockTracker<PageLockStackSnapshot> {
+    /**
+     *
+     */
+    protected int headIdx;
+
+    /**
+     * @param name Page lock stack name.
+     * @param pageMetaInfoStore Capacity.
+     */
+    public LockStack(String name, PageMetaInfoStore pageMetaInfoStore, MemoryCalculator memCalc) {
+        super(name, pageMetaInfoStore, memCalc);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onWriteLock0(int structureId, long pageId, long page, long pageAddr) {
+        push(WRITE_LOCK, structureId, pageId, page, pageAddr);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onWriteUnlock0(int structureId, long pageId, long page, long pageAddr) {
+        pop(WRITE_UNLOCK, structureId, pageId, page, pageAddr);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onReadLock0(int structureId, long pageId, long page, long pageAddr) {
+        push(READ_LOCK, structureId, pageId, page, pageAddr);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void onReadUnlock0(int structureId, long pageId, long page, long pageAddr) {
+        pop(READ_UNLOCK, structureId, pageId, page, pageAddr);
+    }
+
+    /**
+     * Push operation on top of stack.
+     *
+     * @param structureId Strcuture id.
+     * @param pageId Page id.
+     * @param op Operation type.
+     */
+    private void push(int op, int structureId, long pageId, long pageAddrHeader, long pageAddr) {
+        if (!validateOperation(structureId, pageId, op))
+            return;
+
+        reset();
+
+        if (headIdx + 1 > pages.capacity()) {
+            invalid("Stack overflow, size=" + pages.capacity() +
+                ", headIdx=" + headIdx + " " + argsToString(structureId, pageId, op));
+
+            return;
+        }
+
+        long pageId0 = pages.getPageId(headIdx);
+
+        if (pageId0 != 0L) {
+            invalid("Head element should be empty, headIdx=" + headIdx +
+                ", pageIdOnHead=" + pageId0 + " " + argsToString(structureId, pageId, op));
+
+            return;
+        }
+
+        int curIdx = heldLockCnt << OP_OFFSET & LOCK_IDX_MASK;
+
+        pages.add(headIdx, curIdx | op, structureId, pageId, pageAddrHeader, pageAddr);
+
+        headIdx++;
+        heldLockCnt++;
+    }
+
+    /**
+     * Pop operation from top of stack.
+     *
+     * @param structureId Structure id.
+     * @param pageId Page id.
+     * @param op Operation type.
+     */
+    private void pop(int op, int structureId, long pageId, long pageAddrHeader, long pageAddr) {
+        if (!validateOperation(structureId, pageId, op))
+            return;
+
+        reset();
+
+        if (headIdx > 1) {
+            int lastItemIdx = headIdx - 1;
+
+            long lastPageId = pages.getPageId(lastItemIdx);
+
+            if (lastPageId == pageId) {
+                pages.remove(lastItemIdx);
+
+                //Reset head to the first not empty element.
+                do {
+                    headIdx--;
+                    heldLockCnt--;
+                }
+                while (headIdx > 0 && pages.getPageId(headIdx - 1) == 0);
+            }
+            else {
+                for (int itemIdx = lastItemIdx - 1; itemIdx >= 0; itemIdx--) {
+                    if (pages.getPageId(itemIdx) == pageId) {
+                        pages.remove(itemIdx);
+                        return;
+                    }
+                }
+
+                invalid("Can not find pageId in stack, headIdx=" + headIdx + " "
+                    + argsToString(structureId, pageId, op));
+            }
+        }
+        else {
+            if (headIdx < 0) {
+                invalid("HeadIdx can not be less, headIdx="
+                    + headIdx + ", " + argsToString(structureId, pageId, op));
+
+                return;
+            }
+
+            long pageId0 = pages.getPageId(0);
+
+            if (pageId0 == 0) {
+                invalid("Stack is empty, can not pop elemnt" + argsToString(structureId, pageId, op));
+
+                return;
+            }
+
+            if (pageId0 == pageId) {
+                pages.remove(0);
+
+                headIdx = 0;
+                heldLockCnt = 0;
+            }
+            else
+                invalid("Can not find pageId in stack, headIdx=" + headIdx + " "
+                    + argsToString(structureId, pageId, op));
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override protected PageLockStackSnapshot snapshot() {
+        PageMetaInfoStore stack = pages.copy();
+
+        return new PageLockStackSnapshot(
+            name,
+            System.currentTimeMillis(),
+            headIdx,
+            stack,
+            nextOp,
+            nextOpStructureId,
+            nextOpPageId
+        );
+    }
+
+    /**
+     * Reset next opeation info.
+     */
+    private void reset() {
+        nextOpPageId = 0;
+        nextOp = 0;
+        nextOpStructureId = 0;
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/stack/PageLockStackSnapshot.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/stack/PageLockStackSnapshot.java
new file mode 100644
index 0000000..76e985d
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/stack/PageLockStackSnapshot.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.stack;
+
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.DumpProcessor;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockDump;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageMetaInfoStore;
+
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.dumpprocessors.ToStringDumpProcessor.toStringDump;
+
+/**
+ * Page lock stack snapshot.
+ */
+public class PageLockStackSnapshot implements PageLockDump {
+    /** */
+    public final String name;
+    /** */
+    public final long time;
+    /** */
+    public final int headIdx;
+    /** */
+    public final PageMetaInfoStore pageIdLocksStack;
+    /** */
+    public final int nextOp;
+    /** */
+    public final int nextOpStructureId;
+    /** */
+    public final long nextOpPageId;
+
+    /**
+     *
+     */
+    public PageLockStackSnapshot(
+        String name,
+        long time,
+        int headIdx,
+        PageMetaInfoStore pageIdLocksStack,
+        int nextOp,
+        int nextOpStructureId,
+        long nextOpPageId
+    ) {
+        this.name = name;
+        this.time = time;
+        this.headIdx = headIdx;
+        this.pageIdLocksStack = pageIdLocksStack;
+        this.nextOp = nextOp;
+        this.nextOpStructureId = nextOpStructureId;
+        this.nextOpPageId = nextOpPageId;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void apply(DumpProcessor dumpProcessor) {
+        dumpProcessor.processDump(this);
+    }
+
+    /** {@inheritDoc} */
+    @Override public long time() {
+        return time;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return toStringDump(this);
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/store/HeapPageMetaInfoStore.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/store/HeapPageMetaInfoStore.java
new file mode 100644
index 0000000..051f1e1
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/store/HeapPageMetaInfoStore.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.store;
+
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageMetaInfoStore;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTrackerManager.MemoryCalculator;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ *
+ */
+public class HeapPageMetaInfoStore implements PageMetaInfoStore {
+    /**
+     *
+     */
+    private static final int OVERHEAD_SIZE = 8 + 16 + 8 + 8;
+    /**
+     *
+     */
+    private static final int PAGE_ID_OFFSET = 0;
+    /**
+     *
+     */
+    private static final int PAGE_HEADER_ADDRESS_OFFSET = 1;
+    /**
+     *
+     */
+    private static final int PAGE_ADDRESS_OFFSET = 2;
+    /**
+     *
+     */
+    private static final int PAGE_META_OFFSET = 3;
+    /**
+     *
+     */
+    private static final int ITEM_SIZE = 4;
+    /**
+     *
+     */
+    private long[] arr;
+    /**
+     *
+     */
+    private final MemoryCalculator memoryCalc;
+
+    /**
+     *
+     */
+    public HeapPageMetaInfoStore(int capacity, @Nullable MemoryCalculator memoryCalc) {
+        this.arr = new long[capacity * ITEM_SIZE];
+        this.memoryCalc = memoryCalc;
+
+        if (memoryCalc != null)
+            memoryCalc.onHeapAllocated(arr.length * 8 + OVERHEAD_SIZE);
+    }
+
+    HeapPageMetaInfoStore(long[] arr) {
+        this.arr = arr;
+        memoryCalc = null;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int capacity() {
+        return arr.length / ITEM_SIZE;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isEmpty() {
+        for (int i = 0; i < arr.length; i++) {
+            if (arr[i] != 0)
+                return false;
+        }
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void add(int itemIdx, int op, int structureId, long pageId, long pageAddrHeader, long pageAddr) {
+        arr[ITEM_SIZE * itemIdx + PAGE_ID_OFFSET] = pageId;
+        arr[ITEM_SIZE * itemIdx + PAGE_HEADER_ADDRESS_OFFSET] = pageAddrHeader;
+        arr[ITEM_SIZE * itemIdx + PAGE_ADDRESS_OFFSET] = pageAddr;
+        arr[ITEM_SIZE * itemIdx + PAGE_META_OFFSET] = meta(structureId, op);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void remove(int itemIdx) {
+        arr[ITEM_SIZE * itemIdx + PAGE_ID_OFFSET] = 0;
+        arr[ITEM_SIZE * itemIdx + PAGE_HEADER_ADDRESS_OFFSET] = 0;
+        arr[ITEM_SIZE * itemIdx + PAGE_ADDRESS_OFFSET] = 0;
+        arr[ITEM_SIZE * itemIdx + PAGE_META_OFFSET] = 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getOperation(int itemIdx) {
+        long structureIdAndOp = arr[ITEM_SIZE * itemIdx + PAGE_META_OFFSET];
+
+        return (int)((structureIdAndOp >> 32));
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getStructureId(int itemIdx) {
+        long structureIdAndOp = arr[ITEM_SIZE * itemIdx + PAGE_META_OFFSET];
+
+        return (int)(structureIdAndOp);
+    }
+
+    /** {@inheritDoc} */
+    @Override public long getPageId(int itemIdx) {
+        return arr[ITEM_SIZE * itemIdx];
+    }
+
+    /** {@inheritDoc} */
+    @Override public long getPageAddrHeader(int itemIdx) {
+        return arr[ITEM_SIZE * itemIdx + PAGE_HEADER_ADDRESS_OFFSET];
+    }
+
+    /** {@inheritDoc} */
+    @Override public long getPageAddr(int itemIdx) {
+        return arr[ITEM_SIZE * itemIdx + PAGE_ADDRESS_OFFSET];
+    }
+
+    /** {@inheritDoc} */
+    @Override public PageMetaInfoStore copy() {
+        return new HeapPageMetaInfoStore(arr.clone());
+    }
+
+    /** {@inheritDoc} */
+    @Override public void free() {
+        if (memoryCalc != null)
+            memoryCalc.onHeapFree(arr.length * 8 + OVERHEAD_SIZE);
+
+        arr = null;
+    }
+
+    /**
+     * Build long from two int.
+     *
+     * @param structureId Structure id.
+     * @param op Operation.
+     */
+    private long meta(int structureId, int op) {
+        long major = ((long)op) << 32;
+
+        long minor = structureId & 0xFFFFFFFFL;
+
+        return major | minor;
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/store/OffHeapPageMetaInfoStore.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/store/OffHeapPageMetaInfoStore.java
new file mode 100644
index 0000000..8cdd858
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/store/OffHeapPageMetaInfoStore.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.store;
+
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageMetaInfoStore;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTrackerManager.MemoryCalculator;
+import org.apache.ignite.internal.util.GridUnsafe;
+import org.jetbrains.annotations.Nullable;
+
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker.LOCK_OP_MASK;
+
+/**
+ *
+ */
+public class OffHeapPageMetaInfoStore implements PageMetaInfoStore {
+    /**
+     *
+     */
+    private static final long OVERHEAD_SIZE = 16 + 4 + 4 + 8 + 8;
+    /**
+     *
+     */
+    private static final int PAGE_ID_OFFSET = 0;
+    /**
+     *
+     */
+    private static final int PAGE_HEADER_ADDRESS_OFFSET = 8;
+    /**
+     *
+     */
+    private static final int PAGE_ADDRESS_OFFSET = 16;
+    /**
+     *
+     */
+    private static final int PAGE_META_OFFSET = 24;
+    /**
+     *
+     */
+    private static final int ITEM_SIZE = 4;
+    /**
+     *
+     */
+    private final int size;
+    /**
+     *
+     */
+    private final int capacity;
+    /**
+     *
+     */
+    private final long ptr;
+    /**
+     *
+     */
+    private final MemoryCalculator memCalc;
+
+    /**
+     *
+     */
+    public OffHeapPageMetaInfoStore(int capacity, @Nullable MemoryCalculator memCalc) {
+        this.capacity = capacity;
+        this.size = this.capacity * (8 * ITEM_SIZE);
+        this.ptr = allocate(size);
+        this.memCalc = memCalc;
+
+        if (memCalc != null) {
+            memCalc.onHeapAllocated(OVERHEAD_SIZE);
+            memCalc.onOffHeapAllocated(size);
+        }
+    }
+
+    /**
+     *
+     */
+    private long allocate(int size) {
+        long ptr = GridUnsafe.allocateMemory(size);
+
+        GridUnsafe.setMemory(ptr, size, (byte)0);
+
+        return ptr;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int capacity() {
+        return capacity;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean isEmpty() {
+        for (int i = 0; i < size; i++) {
+            if (GridUnsafe.getByte(ptr + i) != 0)
+                return false;
+        }
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void add(int itemIdx, int op, int structureId, long pageId, long pageAddrHeader, long pageAddr) {
+        GridUnsafe.putLong(offset(itemIdx) + PAGE_ID_OFFSET, pageId);
+        GridUnsafe.putLong(offset(itemIdx) + PAGE_HEADER_ADDRESS_OFFSET, pageAddrHeader);
+        GridUnsafe.putLong(offset(itemIdx) + PAGE_ADDRESS_OFFSET, pageAddr);
+        GridUnsafe.putLong(offset(itemIdx) + PAGE_META_OFFSET, join(structureId, op));
+    }
+
+    /** {@inheritDoc} */
+    @Override public void remove(int itemIdx) {
+        GridUnsafe.putLong(offset(itemIdx) + PAGE_ID_OFFSET, 0);
+        GridUnsafe.putLong(offset(itemIdx) + PAGE_HEADER_ADDRESS_OFFSET, 0);
+        GridUnsafe.putLong(offset(itemIdx) + PAGE_ADDRESS_OFFSET, 0);
+        GridUnsafe.putLong(offset(itemIdx) + PAGE_META_OFFSET, 0);
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getOperation(int itemIdx) {
+        long structureIdAndOp = GridUnsafe.getLong(offset(itemIdx) + PAGE_META_OFFSET);
+
+        return (int)((structureIdAndOp >> 32) & LOCK_OP_MASK);
+    }
+
+    /** {@inheritDoc} */
+    @Override public int getStructureId(int itemIdx) {
+        long structureIdAndOp = GridUnsafe.getLong(offset(itemIdx) + PAGE_META_OFFSET);
+
+        return (int)(structureIdAndOp);
+    }
+
+    /** {@inheritDoc} */
+    @Override public long getPageId(int itemIdx) {
+        return GridUnsafe.getLong(offset(itemIdx) + PAGE_ID_OFFSET);
+    }
+
+    /** {@inheritDoc} */
+    @Override public long getPageAddrHeader(int itemIdx) {
+        return GridUnsafe.getLong(offset(itemIdx) + PAGE_HEADER_ADDRESS_OFFSET);
+    }
+
+    /** {@inheritDoc} */
+    @Override public long getPageAddr(int itemIdx) {
+        return GridUnsafe.getLong(offset(itemIdx) + PAGE_ADDRESS_OFFSET);
+    }
+
+    /**
+     *
+     */
+    private long offset(long itemIdx) {
+        long offset = ptr + itemIdx * 8 * ITEM_SIZE;
+
+        assert offset >= ptr && offset <= ((ptr + size) - 8 * ITEM_SIZE) :"offset=" + (offset - ptr) + ", size=" + size;
+
+        return offset;
+    }
+
+    /** {@inheritDoc} */
+    @Override public PageMetaInfoStore copy() {
+        long[] arr = new long[capacity * 4];
+
+        GridUnsafe.copyMemory(null, ptr, arr, GridUnsafe.LONG_ARR_OFF, size);
+
+        return new HeapPageMetaInfoStore(arr);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void free() {
+        GridUnsafe.freeMemory(ptr);
+
+        if (memCalc != null) {
+            memCalc.onHeapFree(OVERHEAD_SIZE);
+            memCalc.onOffHeapFree(size);
+        }
+    }
+
+    /**
+     * Build long from two int.
+     *
+     * @param structureId Structure id.
+     * @param op Operation.
+     */
+    private long join(int structureId, int op) {
+        long major = ((long)op) << 32;
+
+        long minor = structureId & 0xFFFFFFFFL;
+
+        return major | minor;
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/AbstractFreeList.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/AbstractFreeList.java
index 63e25ac..0d120d7 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/AbstractFreeList.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/AbstractFreeList.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2019 GridGain Systems, Inc. and Contributors.
- * 
+ *
  * Licensed under the GridGain Community Edition License (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
- * 
+ *
  *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
- * 
+ *
  * 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.
@@ -39,6 +39,7 @@ import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.LongLi
 import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseBag;
 import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
 import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageHandler;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
 import org.apache.ignite.internal.util.typedef.internal.U;
 
 /**
@@ -339,8 +340,10 @@ public abstract class AbstractFreeList<T extends Storable> extends PagesList imp
         ReuseList reuseList,
         IgniteWriteAheadLogManager wal,
         long metaPageId,
-        boolean initNew) throws IgniteCheckedException {
-        super(cacheId, name, memPlc.pageMemory(), BUCKETS, wal, metaPageId);
+        boolean initNew,
+        PageLockListener lockLsnr
+    ) throws IgniteCheckedException {
+        super(cacheId, name, memPlc.pageMemory(), BUCKETS, wal, metaPageId, lockLsnr);
 
         rmvRow = new RemoveRowHandler(cacheId == 0);
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/CacheFreeListImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/CacheFreeList.java
similarity index 78%
rename from modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/CacheFreeListImpl.java
rename to modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/CacheFreeList.java
index 2fd3edb..454a600 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/CacheFreeListImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/CacheFreeList.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2019 GridGain Systems, Inc. and Contributors.
- * 
+ *
  * Licensed under the GridGain Community Edition License (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
- * 
+ *
  *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
- * 
+ *
  * 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.
@@ -26,12 +26,13 @@ import org.apache.ignite.internal.processors.cache.persistence.tree.io.AbstractD
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.DataPageIO;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.IOVersions;
 import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
 import org.apache.ignite.internal.util.typedef.internal.U;
 
 /**
  * FreeList implementation for cache.
  */
-public class CacheFreeListImpl extends AbstractFreeList<CacheDataRow> {
+public class CacheFreeList extends AbstractFreeList<CacheDataRow> {
     /**
      * @param cacheId Cache id.
      * @param name Name.
@@ -42,10 +43,28 @@ public class CacheFreeListImpl extends AbstractFreeList<CacheDataRow> {
      * @param metaPageId Meta page id.
      * @param initNew Initialize new.
      */
-    public CacheFreeListImpl(int cacheId, String name, DataRegionMetricsImpl regionMetrics, DataRegion dataRegion,
+    public CacheFreeList(
+        int cacheId,
+        String name,
+        DataRegionMetricsImpl regionMetrics,
+        DataRegion dataRegion,
         ReuseList reuseList,
-        IgniteWriteAheadLogManager wal, long metaPageId, boolean initNew) throws IgniteCheckedException {
-        super(cacheId, name, regionMetrics, dataRegion, reuseList, wal, metaPageId, initNew);
+        IgniteWriteAheadLogManager wal,
+        long metaPageId,
+        boolean initNew,
+        PageLockListener lockLsnr
+    ) throws IgniteCheckedException {
+        super(
+            cacheId,
+            name,
+            regionMetrics,
+            dataRegion,
+            reuseList,
+            wal,
+            metaPageId,
+            initNew,
+            lockLsnr
+        );
     }
 
     /** {@inheritDoc} */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/PagesList.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/PagesList.java
index b1ea92bf9..1fa9062 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/PagesList.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/PagesList.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2019 GridGain Systems, Inc. and Contributors.
- * 
+ *
  * Licensed under the GridGain Community Edition License (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
- * 
+ *
  *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
- * 
+ *
  * 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.
@@ -44,6 +44,7 @@ import org.apache.ignite.internal.processors.cache.persistence.tree.io.IOVersion
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
 import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseBag;
 import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageHandler;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
 import org.apache.ignite.internal.util.GridArrays;
 import org.apache.ignite.internal.util.GridLongList;
 import org.apache.ignite.internal.util.typedef.F;
@@ -136,9 +137,10 @@ public abstract class PagesList extends DataStructure {
         PageMemory pageMem,
         int buckets,
         IgniteWriteAheadLogManager wal,
-        long metaPageId
+        long metaPageId,
+        PageLockListener lockLsnr
     ) {
-        super(cacheId, pageMem, wal);
+        super(cacheId, pageMem, wal, lockLsnr);
 
         this.name = name;
         this.buckets = buckets;
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/metastorage/MetaStorage.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/metastorage/MetaStorage.java
index ea7a997..e36ad17 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/metastorage/MetaStorage.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/metastorage/MetaStorage.java
@@ -45,6 +45,7 @@ import org.apache.ignite.internal.pagemem.wal.IgniteWriteAheadLogManager;
 import org.apache.ignite.internal.pagemem.wal.WALPointer;
 import org.apache.ignite.internal.pagemem.wal.record.MetastoreDataRecord;
 import org.apache.ignite.internal.pagemem.wal.record.delta.MetaPageInitRecord;
+import org.apache.ignite.internal.processors.cache.CacheDiagnosticManager;
 import org.apache.ignite.internal.processors.cache.CacheGroupContext;
 import org.apache.ignite.internal.processors.cache.GridCacheProcessor;
 import org.apache.ignite.internal.processors.cache.GridCacheSharedContext;
@@ -66,6 +67,7 @@ import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageParti
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.SimpleDataPageIO;
 import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
 import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageHandler;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
 import org.apache.ignite.internal.processors.failure.FailureProcessor;
 import org.apache.ignite.internal.util.lang.GridCursor;
 import org.apache.ignite.internal.util.typedef.internal.CU;
@@ -245,14 +247,39 @@ public class MetaStorage implements DbCheckpointListener, ReadOnlyMetastorage, R
             getOrAllocateMetas(partId = PageIdAllocator.METASTORE_PARTITION);
 
         if (!empty) {
-            freeList = new FreeListImpl(METASTORAGE_CACHE_ID, "metastorage",
-                regionMetrics, dataRegion, null, wal, reuseListRoot.pageId().pageId(),
-                reuseListRoot.isAllocated());
+            CacheDiagnosticManager diagnosticMgr = cctx.diagnostic();
+
+            String freeListName = METASTORAGE_CACHE_NAME + "##FreeList";
+            String treeName = METASTORAGE_CACHE_NAME + "##Tree";
+
+            freeList = new FreeListImpl(
+                METASTORAGE_CACHE_ID,
+                freeListName,
+                regionMetrics,
+                dataRegion,
+                null,
+                wal,
+                reuseListRoot.pageId().pageId(),
+                reuseListRoot.isAllocated(),
+                diagnosticMgr.pageLockTracker().createPageLockTracker(freeListName)
+            );
 
             MetastorageRowStore rowStore = new MetastorageRowStore(freeList, db);
 
-            tree = new MetastorageTree(METASTORAGE_CACHE_ID, dataRegion.pageMemory(), wal, rmvId,
-                freeList, rowStore, treeRoot.pageId().pageId(), treeRoot.isAllocated(), failureProcessor, partId);
+            tree = new MetastorageTree(
+                METASTORAGE_CACHE_ID,
+                treeName,
+                dataRegion.pageMemory(),
+                wal,
+                rmvId,
+                freeList,
+                rowStore,
+                treeRoot.pageId().pageId(),
+                treeRoot.isAllocated(),
+                failureProcessor,
+                partId,
+                diagnosticMgr.pageLockTracker().createPageLockTracker(treeName)
+            );
 
             if (!readOnly)
                 ((GridCacheDatabaseSharedManager)db).addCheckpointListener(this);
@@ -636,10 +663,18 @@ public class MetaStorage implements DbCheckpointListener, ReadOnlyMetastorage, R
     /** */
     public class FreeListImpl extends AbstractFreeList<MetastorageDataRow> {
         /** {@inheritDoc} */
-        FreeListImpl(int cacheId, String name, DataRegionMetricsImpl regionMetrics, DataRegion dataRegion,
+        FreeListImpl(
+            int cacheId,
+            String name,
+            DataRegionMetricsImpl regionMetrics,
+            DataRegion dataRegion,
             ReuseList reuseList,
-            IgniteWriteAheadLogManager wal, long metaPageId, boolean initNew) throws IgniteCheckedException {
-            super(cacheId, name, regionMetrics, dataRegion, reuseList, wal, metaPageId, initNew);
+            IgniteWriteAheadLogManager wal,
+            long metaPageId,
+            boolean initNew,
+            PageLockListener lsnr
+        ) throws IgniteCheckedException {
+            super(cacheId, name, regionMetrics, dataRegion, reuseList, wal, metaPageId, initNew, lsnr);
         }
 
         /** {@inheritDoc} */
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/metastorage/MetastorageTree.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/metastorage/MetastorageTree.java
index 936c5af..8c3d282 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/metastorage/MetastorageTree.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/metastorage/MetastorageTree.java
@@ -27,6 +27,7 @@ import org.apache.ignite.internal.processors.cache.persistence.tree.io.BPlusInne
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.BPlusLeafIO;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.IOVersions;
 import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
 import org.apache.ignite.internal.processors.failure.FailureProcessor;
 import org.jetbrains.annotations.Nullable;
 
@@ -57,7 +58,9 @@ public class MetastorageTree extends BPlusTree<MetastorageSearchRow, Metastorage
      * @param failureProcessor To call if the tree is corrupted.
      * @throws IgniteCheckedException If failed to initialize.
      */
-    public MetastorageTree(int cacheId,
+    public MetastorageTree(
+        int cacheId,
+        String name,
         PageMemory pageMem,
         IgniteWriteAheadLogManager wal,
         AtomicLong globalRmvId,
@@ -66,9 +69,22 @@ public class MetastorageTree extends BPlusTree<MetastorageSearchRow, Metastorage
         long metaPageId,
         boolean initNew,
         @Nullable FailureProcessor failureProcessor,
-        int partId) throws IgniteCheckedException {
-        super("Metastorage", cacheId, pageMem, wal,
-            globalRmvId, metaPageId, reuseList, MetastorageInnerIO.VERSIONS, MetastoreLeafIO.VERSIONS, failureProcessor);
+        int partId,
+        @Nullable PageLockListener lockLsnr
+    ) throws IgniteCheckedException {
+        super(
+            name,
+            cacheId,
+            pageMem,
+            wal,
+            globalRmvId,
+            metaPageId,
+            reuseList,
+            MetastorageInnerIO.VERSIONS,
+            MetastoreLeafIO.VERSIONS,
+            failureProcessor,
+            lockLsnr
+        );
 
         this.rowStore = rowStore;
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/migration/UpgradePendingTreeToPerPartitionTask.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/migration/UpgradePendingTreeToPerPartitionTask.java
index 059ced4..dca08ce 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/migration/UpgradePendingTreeToPerPartitionTask.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/migration/UpgradePendingTreeToPerPartitionTask.java
@@ -148,7 +148,8 @@ public class UpgradePendingTreeToPerPartitionTask implements IgniteCallable<Bool
                 grp.dataRegion().pageMemory(),
                 pendingRootPage.pageId().pageId(),
                 ((GridCacheOffheapManager)grp.offheap()).reuseListForIndex(null),
-                false
+                false,
+                null
             );
         }
         finally {
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/BPlusTree.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/BPlusTree.java
index 03524e8..422d082 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/BPlusTree.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/BPlusTree.java
@@ -58,6 +58,7 @@ import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseB
 import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
 import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageHandler;
 import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageHandlerWrapper;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
 import org.apache.ignite.internal.processors.failure.FailureProcessor;
 import org.apache.ignite.internal.util.GridArrays;
 import org.apache.ignite.internal.util.GridLongList;
@@ -746,9 +747,21 @@ public abstract class BPlusTree<L, T extends L> extends DataStructure implements
         ReuseList reuseList,
         IOVersions<? extends BPlusInnerIO<L>> innerIos,
         IOVersions<? extends BPlusLeafIO<L>> leafIos,
-        @Nullable FailureProcessor failureProcessor
+        @Nullable FailureProcessor failureProcessor,
+        @Nullable PageLockListener lockLsnr
     ) throws IgniteCheckedException {
-        this(name, cacheId, pageMem, wal, globalRmvId, metaPageId, reuseList, failureProcessor);
+        this(
+            name,
+            cacheId,
+            pageMem,
+            wal,
+            globalRmvId,
+            metaPageId,
+            reuseList,
+            failureProcessor,
+            lockLsnr
+        );
+
         setIos(innerIos, leafIos);
     }
 
@@ -771,9 +784,10 @@ public abstract class BPlusTree<L, T extends L> extends DataStructure implements
         AtomicLong globalRmvId,
         long metaPageId,
         ReuseList reuseList,
-        @Nullable FailureProcessor failureProcessor
+        @Nullable FailureProcessor failureProcessor,
+        @Nullable PageLockListener lsnr
     ) throws IgniteCheckedException {
-        super(cacheId, pageMem, wal);
+        super(cacheId, pageMem, wal, lsnr);
 
         assert !F.isEmpty(name);
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/reuse/ReuseListImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/reuse/ReuseListImpl.java
index aaf07d9..027db9b 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/reuse/ReuseListImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/reuse/ReuseListImpl.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2019 GridGain Systems, Inc. and Contributors.
- * 
+ *
  * Licensed under the GridGain Community Edition License (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
- * 
+ *
  *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
- * 
+ *
  * 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.
@@ -21,6 +21,7 @@ import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.internal.pagemem.PageMemory;
 import org.apache.ignite.internal.pagemem.wal.IgniteWriteAheadLogManager;
 import org.apache.ignite.internal.processors.cache.persistence.freelist.PagesList;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
 
 /**
  * Reuse list.
@@ -42,13 +43,24 @@ public class ReuseListImpl extends PagesList implements ReuseList {
      * @param initNew {@code True} if new metadata should be initialized.
      * @throws IgniteCheckedException If failed.
      */
-    public ReuseListImpl(int cacheId,
+    public ReuseListImpl(
+        int cacheId,
         String name,
         PageMemory pageMem,
         IgniteWriteAheadLogManager wal,
         long metaPageId,
-        boolean initNew) throws IgniteCheckedException {
-        super(cacheId, name, pageMem, 1, wal, metaPageId);
+        boolean initNew,
+        PageLockListener lockLsnr
+    ) throws IgniteCheckedException {
+        super(
+            cacheId,
+            name,
+            pageMem,
+            1,
+            wal,
+            metaPageId,
+            lockLsnr
+        );
 
         reuseList = this;
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/reader/IgniteWalIteratorFactory.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/reader/IgniteWalIteratorFactory.java
index 6dd565e..3c3d388 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/reader/IgniteWalIteratorFactory.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/reader/IgniteWalIteratorFactory.java
@@ -378,7 +378,7 @@ public class IgniteWalIteratorFactory {
             kernalCtx, null, null, null,
             null, null, null, dbMgr, null,
             null, null, null, null, null,
-            null,null, null, null, null
+            null, null, null, null, null, null
         );
     }
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/tree/CacheDataTree.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/tree/CacheDataTree.java
index 0ab46e6..320fd01 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/tree/CacheDataTree.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/tree/CacheDataTree.java
@@ -30,6 +30,7 @@ import org.apache.ignite.internal.processors.cache.persistence.tree.io.DataPageI
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.DataPagePayload;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.IOVersions;
 import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
 import org.apache.ignite.internal.processors.cache.tree.mvcc.data.MvccCacheIdAwareDataInnerIO;
 import org.apache.ignite.internal.processors.cache.tree.mvcc.data.MvccCacheIdAwareDataLeafIO;
 import org.apache.ignite.internal.processors.cache.tree.mvcc.data.MvccDataInnerIO;
@@ -67,9 +68,11 @@ public class CacheDataTree extends BPlusTree<CacheSearchRow, CacheDataRow> {
         ReuseList reuseList,
         CacheDataRowStore rowStore,
         long metaPageId,
-        boolean initNew
+        boolean initNew,
+        PageLockListener lockLsnr
     ) throws IgniteCheckedException {
-        super(name,
+        super(
+            name,
             grp.groupId(),
             grp.dataRegion().pageMemory(),
             grp.dataRegion().config().isPersistenceEnabled() ? grp.shared().wal() : null,
@@ -78,7 +81,9 @@ public class CacheDataTree extends BPlusTree<CacheSearchRow, CacheDataRow> {
             reuseList,
             innerIO(grp),
             leafIO(grp),
-            grp.shared().kernalContext().failure());
+            grp.shared().kernalContext().failure(),
+            lockLsnr
+        );
 
         assert rowStore != null;
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/tree/PendingEntriesTree.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/tree/PendingEntriesTree.java
index 9e5d65d..407e630 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/tree/PendingEntriesTree.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/tree/PendingEntriesTree.java
@@ -22,6 +22,7 @@ import org.apache.ignite.internal.processors.cache.CacheGroupContext;
 import org.apache.ignite.internal.processors.cache.persistence.tree.BPlusTree;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.BPlusIO;
 import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
 import org.apache.ignite.internal.util.typedef.internal.CU;
 
 /**
@@ -49,9 +50,11 @@ public class PendingEntriesTree extends BPlusTree<PendingRow, PendingRow> {
         PageMemory pageMem,
         long metaPageId,
         ReuseList reuseList,
-        boolean initNew)
-        throws IgniteCheckedException {
-        super(name,
+        boolean initNew,
+        PageLockListener lockLsnr
+    ) throws IgniteCheckedException {
+        super(
+            name,
             grp.groupId(),
             pageMem,
             grp.dataRegion().config().isPersistenceEnabled() ? grp.shared().wal() : null,
@@ -60,7 +63,9 @@ public class PendingEntriesTree extends BPlusTree<PendingRow, PendingRow> {
             reuseList,
             grp.sharedGroup() ? CacheIdAwarePendingEntryInnerIO.VERSIONS : PendingEntryInnerIO.VERSIONS,
             grp.sharedGroup() ? CacheIdAwarePendingEntryLeafIO.VERSIONS : PendingEntryLeafIO.VERSIONS,
-            grp.shared().kernalContext().failure());
+            grp.shared().kernalContext().failure(),
+            lockLsnr
+        );
 
         this.grp = grp;
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/diagnostic/DiagnosticProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/diagnostic/DiagnosticProcessor.java
index 36cb248..a063dc6 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/diagnostic/DiagnosticProcessor.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/diagnostic/DiagnosticProcessor.java
@@ -22,6 +22,7 @@ import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 import org.apache.ignite.Ignite;
 import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteSystemProperties;
 import org.apache.ignite.failure.FailureContext;
 import org.apache.ignite.internal.GridKernalContext;
 import org.apache.ignite.internal.processors.GridProcessorAdapter;
@@ -41,6 +42,9 @@ import static org.apache.ignite.internal.util.IgniteStopwatch.logTime;
  * Processor which contained helper methods for different diagnostic cases.
  */
 public class DiagnosticProcessor extends GridProcessorAdapter {
+    /** Value of the system property that enables page locks dumping on failure. */
+    private static final boolean IGNITE_DUMP_PAGE_LOCK_ON_FAILURE =
+        IgniteSystemProperties.getBoolean(IgniteSystemProperties.IGNITE_DUMP_PAGE_LOCK_ON_FAILURE, true);
     /** Time formatter for dump file name. */
     private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH-mm-ss_SSS");
     /** Folder name for store diagnostic info. **/
@@ -93,6 +97,10 @@ public class DiagnosticProcessor extends GridProcessorAdapter {
      * @param failureCtx Failure context.
      */
     public void onFailure(Ignite ignite, FailureContext failureCtx) {
+        // Dump data structures page locks.
+        if (IGNITE_DUMP_PAGE_LOCK_ON_FAILURE)
+            ctx.cache().context().diagnostic().pageLockTracker().dumpLocksToLog();
+
         // If we have some corruption in data structure,
         // we should scan WAL and print to log and save to file all pages related to corruption for
         // future investigation.
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/diagnostic/VisorPageLocksResult.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/diagnostic/VisorPageLocksResult.java
new file mode 100644
index 0000000..6c6cdde
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/diagnostic/VisorPageLocksResult.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.visor.diagnostic;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import org.apache.ignite.internal.dto.IgniteDataTransferObject;
+
+/**
+ *
+ */
+public class VisorPageLocksResult extends IgniteDataTransferObject {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** */
+    private String payload;
+
+    /**
+     *
+     */
+    public VisorPageLocksResult() {
+        //No-op.
+    }
+
+    /**
+     * @param payload Result payload as string.
+     */
+    public VisorPageLocksResult(String payload) {
+        this.payload = payload;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void writeExternalData(ObjectOutput out) throws IOException {
+        if (payload == null) {
+            out.writeInt(0);
+        }
+        else {
+            byte[] bytes = payload.getBytes();
+            int length = bytes.length;
+
+            out.writeInt(length);
+            out.write(bytes);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void readExternalData(byte protoVer, ObjectInput in)
+        throws IOException, ClassNotFoundException {
+        int length = in.readInt();
+
+        if (length != 0) {
+            byte[] bytes = new byte[length];
+
+            in.read(bytes);
+
+            payload = new String(bytes);
+        }
+    }
+
+    /**
+     * @return String result represetnation.
+     */
+    public String result() {
+        return payload;
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/diagnostic/VisorPageLocksTask.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/diagnostic/VisorPageLocksTask.java
new file mode 100644
index 0000000..cbe6c47
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/diagnostic/VisorPageLocksTask.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.visor.diagnostic;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.UUID;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.cluster.ClusterNode;
+import org.apache.ignite.compute.ComputeJobResult;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTrackerManager;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.dumpprocessors.ToStringDumpProcessor;
+import org.apache.ignite.internal.processors.task.GridInternal;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.util.typedef.internal.S;
+import org.apache.ignite.internal.visor.VisorJob;
+import org.apache.ignite.internal.visor.VisorMultiNodeTask;
+import org.apache.ignite.internal.visor.VisorTaskArgument;
+import org.jetbrains.annotations.Nullable;
+
+@GridInternal
+public class VisorPageLocksTask
+    extends VisorMultiNodeTask<VisorPageLocksTrackerArgs, Map<ClusterNode, VisorPageLocksResult>, VisorPageLocksResult> {
+    /**
+     *
+     */
+    private static final long serialVersionUID = 0L;
+
+    /** {@inheritDoc} */
+    @Override protected VisorJob<VisorPageLocksTrackerArgs, VisorPageLocksResult> job(VisorPageLocksTrackerArgs arg) {
+        return new VisorPageLocksTrackerJob(arg, debug);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected Collection<UUID> jobNodes(VisorTaskArgument<VisorPageLocksTrackerArgs> arg) {
+        Set<UUID> nodeIds = new HashSet<>();
+
+        Set<String> nodeIds0 = arg.getArgument().nodeIds();
+
+        for (ClusterNode node : ignite.cluster().nodes()) {
+            if (nodeIds0.contains(String.valueOf(node.consistentId())) || nodeIds0.contains(node.id().toString()))
+                nodeIds.add(node.id());
+        }
+
+        if (F.isEmpty(nodeIds))
+            nodeIds.add(ignite.localNode().id());
+
+        return nodeIds;
+    }
+
+    /** {@inheritDoc} */
+    @Nullable @Override protected Map<ClusterNode, VisorPageLocksResult> reduce0(
+        List<ComputeJobResult> results
+    ) throws IgniteException {
+        Map<ClusterNode, VisorPageLocksResult> mapRes = new TreeMap<>();
+
+        results.forEach(j -> {
+            if (j.getException() == null)
+                mapRes.put(j.getNode(), j.getData());
+            else if (j.getException() != null) {
+                StringWriter sw = new StringWriter();
+                PrintWriter pw = new PrintWriter(sw);
+
+                j.getException().printStackTrace(pw);
+
+                mapRes.put(j.getNode(), new VisorPageLocksResult(sw.toString()));
+            }
+        });
+
+        return mapRes;
+    }
+
+    /**
+     *
+     */
+    private static class VisorPageLocksTrackerJob extends VisorJob<VisorPageLocksTrackerArgs, VisorPageLocksResult> {
+        /** */
+        private static final long serialVersionUID = 0L;
+
+        /**
+         * @param arg Formal job argument.
+         * @param debug Debug flag.
+         */
+        private VisorPageLocksTrackerJob(VisorPageLocksTrackerArgs arg, boolean debug) {
+            super(arg, debug);
+        }
+
+        /** {@inheritDoc} */
+        @Override protected VisorPageLocksResult run(VisorPageLocksTrackerArgs arg) {
+            PageLockTrackerManager lockTrackerMgr = ignite.context().cache().context().diagnostic().pageLockTracker();
+
+            String op = arg.type();
+
+            String result;
+
+            if ("dump".equals(op)) {
+                String filePath = arg.filePath() != null ?
+                    lockTrackerMgr.dumpLocksToFile(arg.filePath()) :
+                    lockTrackerMgr.dumpLocksToFile();
+
+                result = "Page locks dump was writtern to file " + filePath;
+            }
+            else if ("dump_log".equals(op)) {
+                lockTrackerMgr.dumpLocksToLog();
+
+                result = "Page locks dump was printed to console " +
+                    ToStringDumpProcessor.DATE_FMT.format(new Date(System.currentTimeMillis()));
+            }
+            else
+                result = "Unsupported operation: " + op;
+
+            return new VisorPageLocksResult(result);
+        }
+
+        /** {@inheritDoc} */
+        @Override public String toString() {
+            return S.toString(VisorPageLocksTrackerJob.class, this);
+        }
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/diagnostic/VisorPageLocksTrackerArgs.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/diagnostic/VisorPageLocksTrackerArgs.java
new file mode 100644
index 0000000..2911ee5
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/diagnostic/VisorPageLocksTrackerArgs.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.visor.diagnostic;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.Collections;
+import java.util.Set;
+import org.apache.ignite.internal.dto.IgniteDataTransferObject;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ *
+ */
+public class VisorPageLocksTrackerArgs extends IgniteDataTransferObject {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** */
+    private String op;
+    /** */
+    private String type;
+    /** */
+    private String filePath;
+    /** */
+    @Nullable private Set<String> consistentIds;
+
+    public VisorPageLocksTrackerArgs() {
+
+    }
+
+    public VisorPageLocksTrackerArgs(String op, String type, String filePath, Set<String> consistentIds) {
+        this.op = op;
+        this.type = type;
+        this.filePath = filePath;
+        this.consistentIds = consistentIds;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void writeExternalData(ObjectOutput out) throws IOException {
+        if (op == null)
+            out.writeInt(0);
+        else {
+            byte[] bytes = op.getBytes();
+            out.writeInt(bytes.length);
+            out.write(bytes);
+        }
+
+        if (type == null)
+            out.writeInt(0);
+        else {
+            byte[] bytes = type.getBytes();
+            out.writeInt(bytes.length);
+            out.write(bytes);
+        }
+
+        if (filePath == null)
+            out.writeInt(0);
+        else {
+            byte[] bytes = filePath.getBytes();
+            out.writeInt(bytes.length);
+            out.write(bytes);
+        }
+
+        if (consistentIds != null)
+            U.writeCollection(out, consistentIds);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void readExternalData(byte protoVer, ObjectInput in) throws IOException, ClassNotFoundException {
+        int opLenght = in.readInt();
+
+        if (opLenght != 0) {
+            byte[] bytes = new byte[opLenght];
+
+            in.read(bytes);
+
+            op = new String(bytes);
+        }
+
+        int typeLenght = in.readInt();
+
+        if (typeLenght != 0) {
+            byte[] bytes = new byte[typeLenght];
+
+            in.read(bytes);
+
+            type = new String(bytes);
+        }
+
+        int filePathLenght = in.readInt();
+
+        if (filePathLenght != 0) {
+            byte[] bytes = new byte[filePathLenght];
+
+            in.read(bytes);
+
+            filePath = new String(bytes);
+        }
+
+        consistentIds = U.readSet(in);
+    }
+
+    public String operation() {
+        return op;
+    }
+
+    public String type() {
+        return type;
+    }
+
+    public String filePath() {
+        return filePath;
+    }
+
+    public Set<String> nodeIds(){
+        return Collections.unmodifiableSet(consistentIds);
+    }
+}
diff --git a/modules/core/src/main/resources/META-INF/classnames.properties b/modules/core/src/main/resources/META-INF/classnames.properties
index e33dcb3..36529a0 100644
--- a/modules/core/src/main/resources/META-INF/classnames.properties
+++ b/modules/core/src/main/resources/META-INF/classnames.properties
@@ -2345,6 +2345,10 @@ org.apache.ignite.internal.visor.tx.VisorTxTask$TxKillClosure
 org.apache.ignite.internal.visor.tx.VisorTxTask$VisorTxJob
 org.apache.ignite.internal.visor.tx.VisorTxTaskArg
 org.apache.ignite.internal.visor.tx.VisorTxTaskResult
+org.apache.ignite.internal.visor.diagnostic.VisorPageLocksTrackerArgs
+org.apache.ignite.internal.visor.diagnostic.VisorPageLocksResult
+org.apache.ignite.internal.visor.diagnostic.VisorPageLocksTask
+org.apache.ignite.internal.visor.diagnostic.VisorPageLocksTask$VisorPageLocksTrackerJob
 org.apache.ignite.internal.visor.util.VisorClusterGroupEmptyException
 org.apache.ignite.internal.visor.util.VisorEventMapper
 org.apache.ignite.internal.visor.util.VisorExceptionWrapper
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/db/wal/IgniteWalIteratorSwitchSegmentTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/db/wal/IgniteWalIteratorSwitchSegmentTest.java
index 00515d2..47cd341 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/db/wal/IgniteWalIteratorSwitchSegmentTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/db/wal/IgniteWalIteratorSwitchSegmentTest.java
@@ -152,6 +152,7 @@ public class IgniteWalIteratorSwitchSegmentTest extends GridCommonAbstractTest {
                 null,
                 null,
                 null,
+                null,
                 null)
         ).createSerializer(serVer);
 
@@ -478,6 +479,7 @@ public class IgniteWalIteratorSwitchSegmentTest extends GridCommonAbstractTest {
             null,
             null,
             null,
+            null,
             null
         );
 
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/db/wal/WalRecoveryTxLogicalRecordsTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/db/wal/WalRecoveryTxLogicalRecordsTest.java
index e172e01..e2caf16 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/db/wal/WalRecoveryTxLogicalRecordsTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/db/wal/WalRecoveryTxLogicalRecordsTest.java
@@ -54,7 +54,7 @@ import org.apache.ignite.internal.processors.cache.persistence.CacheDataRow;
 import org.apache.ignite.internal.processors.cache.persistence.GridCacheDatabaseSharedManager;
 import org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager;
 import org.apache.ignite.internal.processors.cache.persistence.freelist.AbstractFreeList;
-import org.apache.ignite.internal.processors.cache.persistence.freelist.CacheFreeListImpl;
+import org.apache.ignite.internal.processors.cache.persistence.freelist.CacheFreeList;
 import org.apache.ignite.internal.processors.cache.persistence.freelist.PagesList;
 import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseListImpl;
 import org.apache.ignite.internal.util.typedef.F;
@@ -885,7 +885,7 @@ public class WalRecoveryTxLogicalRecordsTest extends GridCommonAbstractTest {
         boolean foundTails = false;
 
         for (GridDhtLocalPartition part : parts) {
-            CacheFreeListImpl freeList = GridTestUtils.getFieldValue(part.dataStore(), "freeList");
+            CacheFreeList freeList = GridTestUtils.getFieldValue(part.dataStore(), "freeList");
 
             if (freeList == null)
                 // Lazy store.
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/AbstractPageLockTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/AbstractPageLockTest.java
new file mode 100644
index 0000000..f5172c5
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/AbstractPageLockTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker;
+
+import java.util.concurrent.ThreadLocalRandom;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.internal.IgniteInterruptedCheckedException;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.log.PageLockLogSnapshot;
+import org.apache.ignite.internal.util.typedef.internal.U;
+
+import static org.junit.Assert.assertEquals;
+
+/** */
+public abstract class AbstractPageLockTest {
+    /** */
+    protected void doRunnable(int deep, Runnable r) {
+        for (int i = 0; i < deep; i++)
+            r.run();
+    }
+
+    /** */
+    protected void awaitRandom(int bound) {
+        try {
+            U.sleep(nextRandomWaitTimeout(bound));
+        }
+        catch (IgniteInterruptedCheckedException e) {
+            throw new IgniteException(e);
+        }
+    }
+
+    protected int nextRandomWaitTimeout(int bound) {
+        ThreadLocalRandom rnd = ThreadLocalRandom.current();
+
+        return rnd.nextInt(bound);
+    }
+
+    protected void checkNextOp(PageLockLogSnapshot lockLog, long nextOpPageId, long nextOp, int nextOpStructureId) {
+        assertEquals(nextOpStructureId, lockLog.nextOpStructureId);
+        assertEquals(nextOp, lockLog.nextOp);
+        assertEquals(nextOpPageId, lockLog.nextOpPageId);
+    }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTrackerMXBeanImplTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTrackerMXBeanImplTest.java
new file mode 100644
index 0000000..6772eb3
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTrackerMXBeanImplTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker;
+
+import java.lang.management.ManagementFactory;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.internal.processors.cache.CacheDiagnosticManager;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.junit.Assert;
+import org.junit.Test;
+
+import static javax.management.MBeanServerInvocationHandler.newProxyInstance;
+
+/**
+ * {@link PageLockTrackerMXBean} test.
+ */
+public class PageLockTrackerMXBeanImplTest extends GridCommonAbstractTest {
+    /** {@inheritDoc} */
+    @Override protected void beforeTest() throws Exception {
+        super.beforeTest();
+
+        stopAllGrids();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTest() throws Exception {
+        super.afterTest();
+
+        stopAllGrids();
+    }
+
+    /**
+     * Simple chekc that mxbean is registrated on node start.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testSimple() throws Exception {
+        Ignite ig = startGrid();
+
+        PageLockTrackerMXBean pageLockTrackerMXBean = getPageLockTrackerMXBean(ig);
+
+        Assert.assertNotNull(pageLockTrackerMXBean);
+    }
+
+    /**
+     * @param ignite Ignite.
+     */
+    private PageLockTrackerMXBean getPageLockTrackerMXBean(Ignite ignite) throws Exception {
+        ObjectName mBeanName = U.makeMBeanName(
+            ignite.name(),
+            CacheDiagnosticManager.MBEAN_GROUP,
+            PageLockTrackerMXBean.MBEAN_NAME
+        );
+
+        MBeanServer mBeanSrv = ManagementFactory.getPlatformMBeanServer();
+
+        if (!mBeanSrv.isRegistered(mBeanName))
+            fail("MBean is not registered: " + mBeanName.getCanonicalName());
+
+        return newProxyInstance(mBeanSrv, mBeanName, PageLockTrackerMXBean.class, true);
+    }
+}
\ No newline at end of file
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTrackerManagerTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTrackerManagerTest.java
new file mode 100644
index 0000000..fdc8379
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTrackerManagerTest.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import org.apache.ignite.internal.processors.cache.persistence.DataStructure;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.testframework.ListeningTestLogger;
+import org.junit.Assert;
+import org.junit.Test;
+
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory.HEAP_LOG;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory.HEAP_STACK;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory.OFF_HEAP_LOG;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory.OFF_HEAP_STACK;
+
+/**
+ *
+ */
+public class PageLockTrackerManagerTest {
+    /**
+     *
+     */
+    @Test
+    public void testDisableTracking(){
+        System.setProperty("IGNITE_PAGE_LOCK_TRACKER_TYPE", String.valueOf(-1));
+
+        try {
+            PageLockTrackerManager mgr = new PageLockTrackerManager(new ListeningTestLogger());
+
+            PageLockListener pll = mgr.createPageLockTracker("test");
+
+            Assert.assertNotNull(pll);
+            Assert.assertSame(pll, DataStructure.NOOP_LSNR);
+
+        }finally {
+            System.clearProperty("IGNITE_PAGE_LOCK_TRACKER_TYPE");
+        }
+
+        System.setProperty("IGNITE_PAGE_LOCK_TRACKER_TYPE", String.valueOf(HEAP_LOG));
+
+        try {
+            PageLockTrackerManager mgr = new PageLockTrackerManager(new ListeningTestLogger());
+
+            PageLockListener pll = mgr.createPageLockTracker("test");
+
+            Assert.assertNotNull(pll);
+            Assert.assertNotSame(pll, DataStructure.NOOP_LSNR);
+
+        }finally {
+            System.clearProperty("IGNITE_PAGE_LOCK_TRACKER_TYPE");
+        }
+    }
+
+    /**
+     *
+     */
+    @Test
+    public void testMemoryCalculation() throws Exception {
+        int[] trackerTypes = new int[] {HEAP_STACK, HEAP_LOG, OFF_HEAP_STACK, OFF_HEAP_LOG};
+
+        for (int type : trackerTypes)
+            testMemoryCalculation0(type);
+    }
+
+    /**
+     * @param type Tracker type.
+     */
+    public void testMemoryCalculation0(int type) throws Exception {
+        System.out.println(">>> Calculation mempory tracker type:" + type);
+
+        int timeOutWorkerInterval = 10_000;
+
+        System.setProperty("IGNITE_PAGE_LOCK_TRACKER_TYPE", String.valueOf(type));
+        System.setProperty("IGNITE_PAGE_LOCK_TRACKER_CHECK_INTERVAL", String.valueOf(timeOutWorkerInterval));
+
+        try {
+            PageLockTrackerManager mgr = new PageLockTrackerManager(new ListeningTestLogger());
+
+            mgr.start();
+
+            printOverhead(mgr);
+
+            long heapOverhead0 = mgr.getHeapOverhead();
+            long offHeapOverhead0 = mgr.getOffHeapOverhead();
+            long totalOverhead0 = mgr.getTotalOverhead();
+
+            Assert.assertTrue(heapOverhead0 > 0);
+            Assert.assertTrue(offHeapOverhead0 >= 0);
+            Assert.assertEquals(heapOverhead0 + offHeapOverhead0, totalOverhead0);
+
+            PageLockListener pls = mgr.createPageLockTracker("test");
+
+            printOverhead(mgr);
+
+            long heapOverhead1 = mgr.getHeapOverhead();
+            long offHeapOverhead1 = mgr.getOffHeapOverhead();
+            long totalOverhead1 = mgr.getTotalOverhead();
+
+            Assert.assertTrue(heapOverhead1 > 0);
+            Assert.assertTrue(offHeapOverhead1 >= 0);
+            Assert.assertTrue(heapOverhead1 > heapOverhead0);
+            Assert.assertTrue(offHeapOverhead1 >= offHeapOverhead0);
+            Assert.assertEquals(heapOverhead1 + offHeapOverhead1, totalOverhead1);
+
+            int threads = 2_000;
+
+            int cacheId = 1;
+            long pageId = 2;
+            long page = 3;
+            long pageAdder = 4;
+
+            List<Thread> threadsList = new ArrayList<>(threads);
+
+            String threadNamePreffix = "my-thread-";
+
+            CountDownLatch startThreadsLatch = new CountDownLatch(threads);
+            CountDownLatch finishThreadsLatch = new CountDownLatch(1);
+
+            for (int i = 0; i < threads; i++) {
+                Thread th = new Thread(() -> {
+                    startThreadsLatch.countDown();
+
+                    pls.onBeforeReadLock(cacheId, pageId, page);
+
+                    pls.onReadLock(cacheId, pageId, page, pageAdder);
+
+                    pls.onReadUnlock(cacheId, pageId, page, pageAdder);
+
+                    try {
+                        finishThreadsLatch.await();
+                    }
+                    catch (InterruptedException ignored) {
+                        // No-op.
+                    }
+                });
+
+                th.setName(threadNamePreffix + i);
+
+                threadsList.add(th);
+
+                th.start();
+
+                System.out.println(">>> start thread:" + th.getName());
+            }
+
+            startThreadsLatch.await();
+
+            printOverhead(mgr);
+
+            long heapOverhead2 = mgr.getHeapOverhead();
+            long offHeapOverhead2 = mgr.getOffHeapOverhead();
+            long totalOverhead2 = mgr.getTotalOverhead();
+
+            Assert.assertTrue(heapOverhead2 > heapOverhead1);
+            Assert.assertTrue(offHeapOverhead2 >= offHeapOverhead1);
+            Assert.assertEquals(heapOverhead2 + offHeapOverhead2, totalOverhead2);
+
+            finishThreadsLatch.countDown();
+
+            threadsList.forEach(th -> {
+                try {
+                    System.out.println(">>> await thread:" + th.getName());
+
+                    th.join();
+                }
+                catch (InterruptedException ignored) {
+                    // No-op.
+                }
+            });
+
+            // Await cleanup worker interval.
+            U.sleep(2 * timeOutWorkerInterval);
+
+            printOverhead(mgr);
+
+            long heapOverhead3 = mgr.getHeapOverhead();
+            long offHeapOverhead3 = mgr.getOffHeapOverhead();
+            long totalOverhead3 = mgr.getTotalOverhead();
+
+            Assert.assertTrue(heapOverhead3 > 0);
+            Assert.assertTrue(offHeapOverhead3 >= 0);
+            Assert.assertTrue(heapOverhead3 < heapOverhead2);
+            Assert.assertTrue(offHeapOverhead3 <= offHeapOverhead2);
+            Assert.assertEquals(heapOverhead3 + offHeapOverhead3, totalOverhead3);
+
+            mgr.stop();
+        }
+        finally {
+            System.clearProperty("IGNITE_PAGE_LOCK_TRACKER_TYPE");
+            System.clearProperty("IGNITE_PAGE_LOCK_TRACKER_CHECK_INTERVAL");
+        }
+    }
+
+    private void printOverhead(PageLockTrackerManager mgr) {
+        System.out.println(
+            "Head:" + mgr.getHeapOverhead()
+                + ", OffHeap:" + mgr.getOffHeapOverhead()
+                + ", Total:" + mgr.getTotalOverhead());
+    }
+}
\ No newline at end of file
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTrackerTestSuit.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTrackerTestSuit.java
new file mode 100644
index 0000000..78afb50
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/PageLockTrackerTestSuit.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker;
+
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.dumpprocessors.ToFileDumpProcessorTest;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.log.HeapArrayLockLogTest;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.log.OffHeapLockLogTest;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.stack.HeapArrayLockStackTest;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.stack.OffHeapLockStackTest;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+/**
+ * Test suite for all tests ralated to {@link PageLockTracker}.
+ */
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+    PageLockTrackerManagerTest.class,
+    SharedPageLockTrackerTest.class,
+    ToFileDumpProcessorTest.class,
+    HeapArrayLockLogTest.class,
+    OffHeapLockLogTest.class,
+    HeapArrayLockStackTest.class,
+    OffHeapLockStackTest.class,
+})
+
+public class PageLockTrackerTestSuit {
+
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/SharedPageLockTrackerTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/SharedPageLockTrackerTest.java
new file mode 100644
index 0000000..8611422
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/SharedPageLockTrackerTest.java
@@ -0,0 +1,541 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.IgniteInternalFuture;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTrackerManager.MemoryCalculator;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.testframework.GridTestUtils;
+import org.junit.Assert;
+import org.junit.Test;
+
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory.HEAP_LOG;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory.HEAP_STACK;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory.OFF_HEAP_LOG;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory.OFF_HEAP_STACK;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.dumpprocessors.ToStringDumpProcessor.toStringDump;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+/**
+ *
+ */
+public class SharedPageLockTrackerTest extends AbstractPageLockTest {
+    /**
+     *
+     */
+    @Test
+    public void testTakeDumpByCount() throws Exception {
+        int[] trackerTypes = new int[] {HEAP_STACK, HEAP_LOG, OFF_HEAP_STACK, OFF_HEAP_LOG};
+
+        LockTrackerFactory.DEFAULT_CAPACITY = 512;
+
+        for (int i = 0; i < trackerTypes.length; i++) {
+            LockTrackerFactory.DEFAULT_TYPE = trackerTypes[i];
+
+            doTestTakeDumpByCount(5, 1, 10, 1);
+
+            doTestTakeDumpByCount(5, 2, 10, 2);
+
+            doTestTakeDumpByCount(10, 3, 20, 4);
+
+            doTestTakeDumpByCount(20, 6, 40, 8);
+        }
+    }
+
+    /**
+     *
+     */
+    @Test
+    public void testTakeDumpByTime() throws Exception {
+        int[] trackerTypes = new int[] {HEAP_STACK, HEAP_LOG, OFF_HEAP_STACK, OFF_HEAP_LOG};
+
+        LockTrackerFactory.DEFAULT_CAPACITY = 512;
+
+        for (int i = 0; i < trackerTypes.length; i++) {
+            LockTrackerFactory.DEFAULT_TYPE = trackerTypes[i];
+
+            doTestTakeDumpByTime(5, 1, 40_000, 1);
+
+            doTestTakeDumpByTime(5, 2, 20_000, 2);
+
+            doTestTakeDumpByTime(10, 3, 10_000, 4);
+
+            doTestTakeDumpByTime(20, 6, 10_000, 8);
+        }
+    }
+
+    /**
+     *
+     */
+    private void doTestTakeDumpByCount(
+        int pagesCnt,
+        int structuresCnt,
+        int dumpCnt,
+        int threads
+    ) throws IgniteCheckedException, InterruptedException {
+        SharedPageLockTracker sharedPageLockTracker = new SharedPageLockTracker();
+
+        List<PageMeta> pageMetas = new CopyOnWriteArrayList<>();
+
+        int id = 1;
+
+        for (int i = 0; i < pagesCnt; i++)
+            pageMetas.add(new PageMeta((id++) % structuresCnt, id++, id++, id++));
+
+        List<PageLockListener> pageLsnrs = new ArrayList<>();
+
+        for (int i = 0; i < structuresCnt; i++)
+            pageLsnrs.add(sharedPageLockTracker.registrateStructure("my-structure-" + i));
+
+        AtomicBoolean stop = new AtomicBoolean();
+
+        CountDownLatch awaitThreadStartLatch = new CountDownLatch(threads);
+
+        IgniteInternalFuture f = GridTestUtils.runMultiThreadedAsync(() -> {
+            List<PageLockListener> locks = new ArrayList<>(pageLsnrs);
+            List<PageMeta> pages = new ArrayList<>();
+
+            pages.addAll(pageMetas);
+
+            while (!stop.get()) {
+                Collections.shuffle(locks);
+                Collections.shuffle(pages);
+
+                for (PageLockListener lsnr : locks) {
+                    for (PageMeta pageMeta : pages) {
+                        awaitRandom(50);
+
+                        lsnr.onBeforeReadLock(pageMeta.structureId, pageMeta.pageId, pageMeta.page);
+
+                        awaitRandom(50);
+
+                        lsnr.onReadLock(pageMeta.structureId, pageMeta.pageId, pageMeta.page, pageMeta.pageAddr);
+                    }
+                }
+
+                awaitRandom(10);
+
+                Collections.reverse(locks);
+                Collections.reverse(pages);
+
+                for (PageLockListener lsnr : locks) {
+                    for (PageMeta pageMeta : pages) {
+                        awaitRandom(50);
+
+                        lsnr.onReadUnlock(pageMeta.structureId, pageMeta.pageId, pageMeta.page, pageMeta.pageAddr);
+                    }
+                }
+
+                if (awaitThreadStartLatch.getCount() > 0)
+                    awaitThreadStartLatch.countDown();
+            }
+        }, threads, "PageLocker");
+
+        awaitThreadStartLatch.await();
+
+        for (int i = 0; i < dumpCnt; i++) {
+            awaitRandom(1000);
+
+            ThreadPageLocksDumpLock dump = sharedPageLockTracker.dump();
+
+            System.out.println(toStringDump(dump));
+
+            assertEquals(threads, dump.threadStates.size());
+            assertEquals(0, dump.threadStates.stream().filter(e -> e.invalidContext != null).count());
+        }
+
+        stop.set(true);
+
+        f.get();
+    }
+
+    /**
+     *
+     */
+    private void doTestTakeDumpByTime(
+        int pagesCnt,
+        int structuresCnt,
+        int dumpTime,
+        int threads
+    ) throws IgniteCheckedException, InterruptedException {
+        SharedPageLockTracker sharedPageLockTracker = new SharedPageLockTracker();
+
+        List<PageMeta> pageMetas = new CopyOnWriteArrayList<>();
+
+        int id = 1;
+
+        for (int i = 0; i < pagesCnt; i++)
+            pageMetas.add(new PageMeta((id++) % structuresCnt, id++, id++, id++));
+
+        List<PageLockListener> pageLsnrs = new ArrayList<>();
+
+        for (int i = 0; i < structuresCnt; i++)
+            pageLsnrs.add(sharedPageLockTracker.registrateStructure("my-structure-" + i));
+
+        AtomicBoolean stop = new AtomicBoolean();
+
+        CountDownLatch awaitThreadStartLatch = new CountDownLatch(threads);
+
+        IgniteInternalFuture f = GridTestUtils.runMultiThreadedAsync(() -> {
+            List<PageLockListener> locks = new ArrayList<>(pageLsnrs);
+            List<PageMeta> pages = new ArrayList<>();
+
+            pages.addAll(pageMetas);
+
+            while (!stop.get()) {
+                Collections.shuffle(locks);
+                Collections.shuffle(pages);
+
+                for (PageLockListener lsnr : locks) {
+                    for (PageMeta pageMeta : pages) {
+                        //awaitRandom(5);
+
+                        lsnr.onBeforeReadLock(pageMeta.structureId, pageMeta.pageId, pageMeta.page);
+
+                        //awaitRandom(5);
+
+                        lsnr.onReadLock(pageMeta.structureId, pageMeta.pageId, pageMeta.page, pageMeta.pageAddr);
+                    }
+                }
+
+                Collections.reverse(locks);
+                Collections.reverse(pages);
+
+                for (PageLockListener lsnr : locks) {
+                    for (PageMeta pageMeta : pages) {
+                        // awaitRandom(5);
+
+                        lsnr.onReadUnlock(pageMeta.structureId, pageMeta.pageId, pageMeta.page, pageMeta.pageAddr);
+                    }
+                }
+
+                if (awaitThreadStartLatch.getCount() > 0)
+                    awaitThreadStartLatch.countDown();
+            }
+        }, threads, "PageLocker");
+
+        IgniteInternalFuture dumpFut = GridTestUtils.runAsync(() -> {
+            try {
+                awaitThreadStartLatch.await();
+            }
+            catch (InterruptedException e) {
+                // Ignore.
+                return;
+            }
+
+            while (!stop.get()) {
+                awaitRandom(20);
+
+                ThreadPageLocksDumpLock dump = sharedPageLockTracker.dump();
+
+                System.out.println(toStringDump(dump));
+
+                assertEquals(threads, dump.threadStates.size());
+                assertEquals(0, dump.threadStates.stream().filter(e -> e.invalidContext != null).count());
+            }
+        });
+
+        Thread.sleep(dumpTime);
+
+        stop.set(true);
+
+        f.get();
+
+        dumpFut.get();
+    }
+
+    /**
+     * Test for checking that internal maps is not leaked after threads stopped.
+     */
+    @Test
+    public void testMemoryLeakOnThreadTerminates() throws Exception {
+        int threadLimits = 1000;
+        int timeOutWorkerInterval = 10_000;
+        Consumer<Set<SharedPageLockTracker.State>> handler = (threads) -> {
+        };
+
+        SharedPageLockTracker sharedPageLockTracker = new SharedPageLockTracker(
+            threadLimits, timeOutWorkerInterval, handler, new MemoryCalculator());
+
+        int threads = 10_000;
+
+        int cacheId = 1;
+        long pageId = 2;
+        long page = 3;
+        long pageAdder = 4;
+
+        PageLockListener lt = sharedPageLockTracker.registrateStructure("test");
+
+        List<Thread> threadsList = new ArrayList<>(threads);
+
+        String threadNamePreffix = "my-thread-";
+
+        for (int i = 0; i < threads; i++) {
+            Thread th = new Thread(() -> {
+                lt.onBeforeReadLock(cacheId, pageId, page);
+
+                lt.onReadLock(cacheId, pageId, page, pageAdder);
+
+                lt.onReadUnlock(cacheId, pageId, page, pageAdder);
+            });
+
+            th.setName(threadNamePreffix + i);
+
+            threadsList.add(th);
+
+            th.start();
+
+            System.out.println(">>> start thread:" + th.getName());
+        }
+
+        threadsList.forEach(th -> {
+            try {
+                System.out.println(">>> await thread:" + th.getName());
+
+                th.join();
+            }
+            catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        });
+
+        sharedPageLockTracker.start();
+
+        ThreadPageLocksDumpLock dump = sharedPageLockTracker.dump();
+
+        assertTrue(dump.time > 0);
+        assertTrue(!dump.threadStates.isEmpty());
+
+        for (ThreadPageLocksDumpLock.ThreadState threadState : dump.threadStates) {
+            assertNull(threadState.invalidContext);
+            assertTrue(threadState.threadName.startsWith(threadNamePreffix));
+            assertSame(Thread.State.TERMINATED, threadState.state);
+
+        }
+
+        Assert.assertEquals(1, dump.structureIdToStrcutureName.size());
+
+        synchronized (sharedPageLockTracker) {
+            Map<Long, Thread> threadMap0 = U.field(sharedPageLockTracker, "threadIdToThreadRef");
+            Map<Long, ?> threadStacksMap0 = U.field(sharedPageLockTracker, "threadStacks");
+
+            // Stopped threads should remove from map after map limit reached.
+            assertTrue(threadMap0.size() <= threadLimits);
+            assertTrue(threadStacksMap0.size() <= threadLimits);
+        }
+
+        // Await cleanup worker interval.
+        U.sleep(timeOutWorkerInterval + 1000);
+
+        synchronized (sharedPageLockTracker) {
+            Map<Long, Thread> threadMap1 = U.field(sharedPageLockTracker, "threadIdToThreadRef");
+            Map<Long, ?> threadStacksMap1 = U.field(sharedPageLockTracker, "threadStacks");
+
+            // Cleanup worker should remove all stopped threads.
+            assertTrue(threadMap1.isEmpty());
+            assertTrue(threadStacksMap1.isEmpty());
+        }
+
+        ThreadPageLocksDumpLock dump1 = sharedPageLockTracker.dump();
+
+        assertTrue(dump1.time > 0);
+        assertTrue(dump1.threadStates.isEmpty());
+    }
+
+    /**
+     *
+     */
+    @Test
+    public void testAutoDetectHangThreads() throws Exception {
+        String thInWaitName = "threadInWait";
+        String thInRunnableName = "threadInRunnable";
+        String thInAwaitWithoutLocksName = "threadInAwaitWithoutLocks";
+
+        AtomicReference<Exception> error = new AtomicReference<>();
+
+        CountDownLatch awaitLatch = new CountDownLatch(1);
+
+        SharedPageLockTracker sharedPageLockTracker = new SharedPageLockTracker(
+            1000,
+            10_000,
+            hangsThreads -> {
+                if (hangsThreads.isEmpty()) {
+                    error.set(new Exception("No one thread is hangs."));
+
+                    return;
+                }
+
+                // Checking threads.
+                for (SharedPageLockTracker.State state : hangsThreads) {
+                    String name = state.thread.getName();
+
+                    if (name.equals(thInAwaitWithoutLocksName)) {
+                        error.set(new Exception("Thread without locks should not be here." + state));
+                        continue;
+                    }
+
+                    if (name.equals(thInWaitName)) {
+                        if (state.heldLockCnt == 0)
+                            error.set(new Exception("Thread should hold lock." + state));
+
+                        if (state.thread.getState() != Thread.State.WAITING)
+                            error.set(new Exception("Thread should in WAITING state." + state));
+
+                        continue;
+                    }
+
+                    if (name.equals(thInRunnableName)) {
+                        if (state.heldLockCnt == 0)
+                            error.set(new Exception("Thread should hold lock." + state));
+
+                        if (state.thread.getState() != Thread.State.RUNNABLE)
+                            error.set(new Exception("Thread should in RUNNABLE state." + state));
+
+                        continue;
+                    }
+                }
+
+                awaitLatch.countDown();
+            }, new MemoryCalculator()
+        );
+
+        int cacheId = 1;
+        long pageId = 2;
+        long page = 3;
+        long pageAdder = 4;
+
+        PageLockListener lt = sharedPageLockTracker.registrateStructure("test");
+
+        Thread thInWait = new Thread(() -> {
+            lt.onBeforeReadLock(cacheId, pageId, page);
+
+            lt.onReadLock(cacheId, pageId, page, pageAdder);
+
+            try {
+                awaitLatch.await();
+            }
+            catch (InterruptedException ignored) {
+                // No-op.
+            }
+        });
+
+        thInWait.setName(thInWaitName);
+
+        Thread thInRunnable = new Thread(() -> {
+            lt.onBeforeReadLock(cacheId, pageId, page);
+
+            lt.onReadLock(cacheId, pageId, page, pageAdder);
+
+            while (awaitLatch.getCount() > 0) {
+                // Busy wait. Can not park this thread, we should check running hangs too.
+            }
+        });
+
+        thInRunnable.setName(thInRunnableName);
+
+        Thread thInAwaitWithoutLocks = new Thread(() -> {
+            lt.onBeforeReadLock(cacheId, pageId, page);
+
+            lt.onReadLock(cacheId, pageId, page, pageAdder);
+
+            lt.onReadUnlock(cacheId, pageId, page, pageAdder);
+
+            try {
+                awaitLatch.await();
+            }
+            catch (InterruptedException ignored) {
+                // No-op.
+            }
+        });
+
+        thInAwaitWithoutLocks.setName(thInAwaitWithoutLocksName);
+
+        sharedPageLockTracker.start();
+
+        thInWait.start();
+        thInRunnable.start();
+        thInAwaitWithoutLocks.start();
+
+        thInWait.join();
+        thInRunnable.join();
+        thInAwaitWithoutLocks.join();
+
+        if (error.get() != null)
+            throw error.get();
+    }
+
+    /**
+     *
+     */
+    private static class PageMeta {
+        /**
+         *
+         */
+        final int structureId;
+        /**
+         *
+         */
+        final long pageId;
+        /**
+         *
+         */
+        final long page;
+        /**
+         *
+         */
+        final long pageAddr;
+
+        /**
+         *
+         */
+        private PageMeta(
+            int structureId,
+            long pageId,
+            long page,
+            long pageAddr
+        ) {
+            this.structureId = structureId;
+            this.pageId = pageId;
+            this.page = page;
+            this.pageAddr = pageAddr;
+        }
+
+        @Override public String toString() {
+            return "PageMeta{" +
+                "structureId=" + structureId +
+                ", pageId=" + pageId +
+                ", page=" + page +
+                ", pageAddr=" + pageAddr +
+                '}';
+        }
+    }
+}
\ No newline at end of file
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/dumpprocessors/ToFileDumpProcessorTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/dumpprocessors/ToFileDumpProcessorTest.java
new file mode 100644
index 0000000..696ab3b
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/dumpprocessors/ToFileDumpProcessorTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.dumpprocessors;
+
+import java.io.File;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.file.StandardOpenOption;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockDump;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import static java.nio.file.Paths.get;
+
+/**
+ *
+ */
+public class ToFileDumpProcessorTest {
+    /** */
+    private File file;
+
+    @Before
+    public void beforeTest() {
+        cleanFile();
+    }
+
+    @After
+    public void afterTest() {
+        //  cleanFile();
+    }
+
+    /**
+     * Clean files.
+     */
+    public void cleanFile() {
+        if (file != null && file.exists())
+            file.delete();
+    }
+
+    /** */
+    @Test
+    public void toFileDump() throws Exception {
+        String igHome = U.defaultWorkDirectory();
+
+        System.out.println("IGNITE_HOME:" + igHome);
+
+        PageLockTracker pageLockTracker = LockTrackerFactory.create("test");
+
+        pageLockTracker.onBeforeReadLock(1, 2, 3);
+        pageLockTracker.onReadLock(1, 2, 3, 4);
+
+        PageLockDump pageLockDump = pageLockTracker.dump();
+
+        Assert.assertNotNull(pageLockDump);
+
+        String expDumpStr = ToStringDumpProcessor.toStringDump(pageLockDump);
+
+        String filePath = ToFileDumpProcessor.toFileDump(pageLockDump, file = new File(igHome), "test");
+
+        System.out.println("Dump saved:" + filePath);
+
+        boolean found = false;
+
+        for (File file : file.listFiles()) {
+            if (file.getAbsolutePath().equals(filePath)) {
+                found = true;
+
+                break;
+            }
+        }
+
+        Assert.assertTrue(found);
+
+        String actDumpStr;
+
+        try (FileChannel ch = FileChannel.open(get(filePath), StandardOpenOption.READ)) {
+            long size = ch.size();
+
+            ByteBuffer buf = ByteBuffer.allocate((int)size);
+
+            while (buf.position() != buf.capacity())
+                ch.read(buf);
+
+            actDumpStr = new String(buf.array());
+        }
+
+        Assert.assertEquals(expDumpStr, actDumpStr);
+    }
+}
\ No newline at end of file
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/log/HeapArrayLockLogTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/log/HeapArrayLockLogTest.java
new file mode 100644
index 0000000..41fc802
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/log/HeapArrayLockLogTest.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.log;
+
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory;
+
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory.HEAP_LOG;
+
+/** */
+public class HeapArrayLockLogTest extends PageLockLogTest {
+    /** {@inheritDoc} */
+    @Override protected LockLog createLogStackTracer(String name) {
+        return (LockLog)LockTrackerFactory.create(HEAP_LOG, name);
+    }
+}
\ No newline at end of file
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/log/OffHeapLockLogTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/log/OffHeapLockLogTest.java
new file mode 100644
index 0000000..ef1aed4
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/log/OffHeapLockLogTest.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.log;
+
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory;
+
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory.OFF_HEAP_LOG;
+
+/** */
+public class OffHeapLockLogTest extends PageLockLogTest {
+    /** {@inheritDoc} */
+    @Override protected LockLog createLogStackTracer(String name) {
+        return (LockLog)LockTrackerFactory.create(OFF_HEAP_LOG, name);
+    }
+}
\ No newline at end of file
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/log/PageLockLogTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/log/PageLockLogTest.java
new file mode 100644
index 0000000..5075d5a
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/log/PageLockLogTest.java
@@ -0,0 +1,641 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.log;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.IgniteInternalFuture;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.AbstractPageLockTest;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.log.PageLockLogSnapshot.LogEntry;
+import org.apache.ignite.testframework.GridTestUtils;
+import org.junit.Assert;
+import org.junit.Test;
+
+import static java.lang.System.out;
+import static java.time.Duration.ofMinutes;
+import static java.util.stream.IntStream.range;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory.DEFAULT_CAPACITY;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker.BEFORE_READ_LOCK;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker.READ_LOCK;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker.READ_UNLOCK;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/** */
+public abstract class PageLockLogTest extends AbstractPageLockTest {
+    /** */
+    protected static final int STRUCTURE_ID = 123;
+
+    /** */
+    protected abstract LockLog createLogStackTracer(String name);
+
+    /** */
+    private void checkLogEntry(LogEntry logEntry, long pageId, int operation, int structureId, int holdedLocks) {
+        assertEquals(pageId, logEntry.pageId);
+        assertEquals(operation, logEntry.operation);
+        assertEquals(structureId, logEntry.structureId);
+        assertEquals(holdedLocks, logEntry.holdedLocks);
+    }
+
+    /** */
+    @Test
+    public void testOneReadPageLock() {
+        LockLog lockLog = createLogStackTracer(Thread.currentThread().getName());
+
+        long pageId = 1;
+        long page = 2;
+        long pageAddr = 3;
+
+        PageLockLogSnapshot logDump;
+
+        lockLog.onBeforeReadLock(STRUCTURE_ID, pageId, page);
+
+        logDump = lockLog.dump();
+
+        out.println(logDump);
+
+        assertEquals(0, logDump.headIdx);
+        assertTrue(logDump.locklog.isEmpty());
+        checkNextOp(logDump, pageId, BEFORE_READ_LOCK, STRUCTURE_ID);
+
+        lockLog.onReadLock(STRUCTURE_ID, pageId, page, pageAddr);
+
+        logDump = lockLog.dump();
+
+        out.println(logDump);
+
+        assertEquals(1, logDump.headIdx);
+        checkLogEntry(logDump.locklog.get(0), pageId, READ_LOCK, STRUCTURE_ID, 1);
+        checkNextOp(logDump, 0, 0, 0);
+
+        lockLog.onReadUnlock(STRUCTURE_ID, pageId, page, pageAddr);
+
+        logDump = lockLog.dump();
+
+        out.println(logDump);
+
+        assertTrue(logDump.locklog.isEmpty());
+        checkNextOp(logDump, 0, 0, 0);
+    }
+
+    /** */
+    @Test
+    public void testTwoReadPageLock() {
+        LockLog lockLog = createLogStackTracer(Thread.currentThread().getName());
+
+        long pageId1 = 1;
+        long pageId2 = 11;
+        long page1 = 2;
+        long page2 = 12;
+        long pageAddr1 = 3;
+        long pageAddr2 = 13;
+
+        PageLockLogSnapshot logDump;
+
+        lockLog.onBeforeReadLock(STRUCTURE_ID, pageId1, page1);
+
+        logDump = lockLog.dump();
+
+        out.println(logDump);
+
+        assertEquals(0, logDump.headIdx);
+        assertTrue(logDump.locklog.isEmpty());
+        checkNextOp(logDump, pageId1, BEFORE_READ_LOCK, STRUCTURE_ID);
+
+        lockLog.onReadLock(STRUCTURE_ID, pageId1, page1, pageAddr1);
+
+        logDump = lockLog.dump();
+
+        out.println(logDump);
+
+        assertEquals(1, logDump.headIdx);
+        checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+        checkNextOp(logDump, 0, 0, 0);
+
+        lockLog.onBeforeReadLock(STRUCTURE_ID, pageId2, page2);
+
+        logDump = lockLog.dump();
+
+        out.println(logDump);
+
+        assertEquals(1, logDump.headIdx);
+        checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+        checkNextOp(logDump, pageId2, BEFORE_READ_LOCK, STRUCTURE_ID);
+
+        lockLog.onReadLock(STRUCTURE_ID, pageId2, page2, pageAddr2);
+
+        logDump = lockLog.dump();
+
+        out.println(logDump);
+
+        assertEquals(2, logDump.locklog.size());
+
+        checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+        checkLogEntry(logDump.locklog.get(1), pageId2, READ_LOCK, STRUCTURE_ID, 2);
+        checkNextOp(logDump, 0, 0, 0);
+
+        lockLog.onReadUnlock(STRUCTURE_ID, pageId2, page2, pageAddr2);
+
+        logDump = lockLog.dump();
+
+        out.println(logDump);
+
+        assertEquals(3, logDump.locklog.size());
+
+        checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+        checkLogEntry(logDump.locklog.get(1), pageId2, READ_LOCK, STRUCTURE_ID, 2);
+        checkLogEntry(logDump.locklog.get(2), pageId2, READ_UNLOCK, STRUCTURE_ID, 1);
+        checkNextOp(logDump, 0, 0, 0);
+
+        lockLog.onReadUnlock(STRUCTURE_ID, pageId1, page1, pageAddr1);
+
+        logDump = lockLog.dump();
+
+        out.println(logDump);
+
+        assertTrue(logDump.locklog.isEmpty());
+        checkNextOp(logDump, 0, 0, 0);
+    }
+
+    /** */
+    @Test
+    public void testThreeReadPageLock_1() {
+        LockLog lockLog = createLogStackTracer(Thread.currentThread().getName());
+
+        long pageId1 = 1;
+        long pageId2 = 11;
+        long pageId3 = 111;
+        long page1 = 2;
+        long page2 = 12;
+        long page3 = 122;
+        long pageAddr1 = 3;
+        long pageAddr2 = 13;
+        long pageAddr3 = 133;
+
+        PageLockLogSnapshot logDump;
+
+        lockLog.onBeforeReadLock(STRUCTURE_ID, pageId1, page1);
+
+        logDump = lockLog.dump();
+
+        out.println(logDump);
+
+        assertEquals(0, logDump.headIdx);
+        assertTrue(logDump.locklog.isEmpty());
+        checkNextOp(logDump, pageId1, BEFORE_READ_LOCK, STRUCTURE_ID);
+
+        lockLog.onReadLock(STRUCTURE_ID, pageId1, page1, pageAddr1);
+
+        logDump = lockLog.dump();
+
+        out.println(logDump);
+
+        assertEquals(1, logDump.headIdx);
+        checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+        checkNextOp(logDump, 0, 0, 0);
+
+        lockLog.onBeforeReadLock(STRUCTURE_ID, pageId2, page2);
+
+        logDump = lockLog.dump();
+
+        out.println(logDump);
+
+        assertEquals(1, logDump.headIdx);
+        checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+        checkNextOp(logDump, pageId2, BEFORE_READ_LOCK, STRUCTURE_ID);
+
+        lockLog.onReadLock(STRUCTURE_ID, pageId2, page2, pageAddr2);
+
+        logDump = lockLog.dump();
+
+        out.println(logDump);
+
+        assertEquals(2, logDump.headIdx);
+        checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+        checkLogEntry(logDump.locklog.get(1), pageId2, READ_LOCK, STRUCTURE_ID, 2);
+        checkNextOp(logDump, 0, 0, 0);
+
+        lockLog.onBeforeReadLock(STRUCTURE_ID, pageId3, page3);
+
+        logDump = lockLog.dump();
+
+        out.println(logDump);
+
+        assertEquals(2, logDump.headIdx);
+        checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+        checkLogEntry(logDump.locklog.get(1), pageId2, READ_LOCK, STRUCTURE_ID, 2);
+        checkNextOp(logDump, pageId3, BEFORE_READ_LOCK, STRUCTURE_ID);
+
+        lockLog.onReadLock(STRUCTURE_ID, pageId3, page3, pageAddr3);
+
+        logDump = lockLog.dump();
+
+        out.println(logDump);
+
+        assertEquals(3, logDump.headIdx);
+        checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+        checkLogEntry(logDump.locklog.get(1), pageId2, READ_LOCK, STRUCTURE_ID, 2);
+        checkLogEntry(logDump.locklog.get(2), pageId3, READ_LOCK, STRUCTURE_ID, 3);
+        checkNextOp(logDump, 0, 0, 0);
+
+        lockLog.onReadUnlock(STRUCTURE_ID, pageId3, page3, pageAddr3);
+
+        logDump = lockLog.dump();
+
+        out.println(logDump);
+
+        assertEquals(4, logDump.headIdx);
+        checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+        checkLogEntry(logDump.locklog.get(1), pageId2, READ_LOCK, STRUCTURE_ID, 2);
+        checkLogEntry(logDump.locklog.get(2), pageId3, READ_LOCK, STRUCTURE_ID, 3);
+        checkLogEntry(logDump.locklog.get(3), pageId3, READ_UNLOCK, STRUCTURE_ID, 2);
+        checkNextOp(logDump, 0, 0, 0);
+
+        lockLog.onReadUnlock(STRUCTURE_ID, pageId2, page2, pageAddr2);
+
+        logDump = lockLog.dump();
+
+        out.println(logDump);
+
+        assertEquals(5, logDump.headIdx);
+        checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+        checkLogEntry(logDump.locklog.get(1), pageId2, READ_LOCK, STRUCTURE_ID, 2);
+        checkLogEntry(logDump.locklog.get(2), pageId3, READ_LOCK, STRUCTURE_ID, 3);
+        checkLogEntry(logDump.locklog.get(3), pageId3, READ_UNLOCK, STRUCTURE_ID, 2);
+        checkLogEntry(logDump.locklog.get(4), pageId2, READ_UNLOCK, STRUCTURE_ID, 1);
+        checkNextOp(logDump, 0, 0, 0);
+
+        lockLog.onReadUnlock(STRUCTURE_ID, pageId1, page1, pageAddr1);
+
+        logDump = lockLog.dump();
+
+        out.println(logDump);
+
+        assertEquals(0, logDump.headIdx);
+        assertTrue(logDump.locklog.isEmpty());
+        checkNextOp(logDump, 0, 0, 0);
+    }
+
+    /** */
+    @Test
+    public void testThreeReadPageLock_2() {
+        LockLog lockLog = createLogStackTracer(Thread.currentThread().getName());
+
+        long pageId1 = 1;
+        long pageId2 = 11;
+        long pageId3 = 111;
+        long page1 = 2;
+        long page2 = 12;
+        long page3 = 122;
+        long pageAddr1 = 3;
+        long pageAddr2 = 13;
+        long pageAddr3 = 133;
+
+        PageLockLogSnapshot logDump;
+
+        lockLog.onBeforeReadLock(STRUCTURE_ID, pageId1, page1);
+
+        logDump = lockLog.dump();
+
+        out.println(logDump);
+
+        assertEquals(0, logDump.headIdx);
+        assertTrue(logDump.locklog.isEmpty());
+        checkNextOp(logDump, pageId1, BEFORE_READ_LOCK, STRUCTURE_ID);
+
+        lockLog.onReadLock(STRUCTURE_ID, pageId1, page1, pageAddr1);
+
+        logDump = lockLog.dump();
+
+        out.println(logDump);
+
+        assertEquals(1, logDump.headIdx);
+        checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+        checkNextOp(logDump, 0, 0, 0);
+
+        lockLog.onBeforeReadLock(STRUCTURE_ID, pageId2, page2);
+
+        logDump = lockLog.dump();
+
+        out.println(logDump);
+
+        assertEquals(1, logDump.headIdx);
+        checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+        checkNextOp(logDump, pageId2, BEFORE_READ_LOCK, STRUCTURE_ID);
+
+        lockLog.onReadLock(STRUCTURE_ID, pageId2, page2, pageAddr2);
+
+        logDump = lockLog.dump();
+
+        out.println(logDump);
+
+        assertEquals(2, logDump.headIdx);
+        checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+        checkLogEntry(logDump.locklog.get(1), pageId2, READ_LOCK, STRUCTURE_ID, 2);
+        checkNextOp(logDump, 0, 0, 0);
+
+        lockLog.onReadUnlock(STRUCTURE_ID, pageId2, page2, pageAddr2);
+
+        logDump = lockLog.dump();
+
+        out.println(logDump);
+
+        assertEquals(3, logDump.headIdx);
+        checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+        checkLogEntry(logDump.locklog.get(1), pageId2, READ_LOCK, STRUCTURE_ID, 2);
+        checkLogEntry(logDump.locklog.get(2), pageId2, READ_UNLOCK, STRUCTURE_ID, 1);
+        checkNextOp(logDump, 0, 0, 0);
+
+        lockLog.onBeforeReadLock(STRUCTURE_ID, pageId3, page3);
+
+        logDump = lockLog.dump();
+
+        out.println(logDump);
+
+        assertEquals(3, logDump.headIdx);
+        checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+        checkLogEntry(logDump.locklog.get(1), pageId2, READ_LOCK, STRUCTURE_ID, 2);
+        checkLogEntry(logDump.locklog.get(2), pageId2, READ_UNLOCK, STRUCTURE_ID, 1);
+        checkNextOp(logDump, pageId3, BEFORE_READ_LOCK, STRUCTURE_ID);
+
+        lockLog.onReadLock(STRUCTURE_ID, pageId3, page3, pageAddr3);
+
+        logDump = lockLog.dump();
+
+        out.println(logDump);
+
+        assertEquals(4, logDump.headIdx);
+        checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+        checkLogEntry(logDump.locklog.get(1), pageId2, READ_LOCK, STRUCTURE_ID, 2);
+        checkLogEntry(logDump.locklog.get(2), pageId2, READ_UNLOCK, STRUCTURE_ID, 1);
+        checkLogEntry(logDump.locklog.get(3), pageId3, READ_LOCK, STRUCTURE_ID, 2);
+        checkNextOp(logDump, 0, 0, 0);
+
+        lockLog.onReadUnlock(STRUCTURE_ID, pageId3, page3, pageAddr3);
+
+        logDump = lockLog.dump();
+
+        out.println(logDump);
+
+        assertEquals(5, logDump.headIdx);
+        checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+        checkLogEntry(logDump.locklog.get(1), pageId2, READ_LOCK, STRUCTURE_ID, 2);
+        checkLogEntry(logDump.locklog.get(2), pageId2, READ_UNLOCK, STRUCTURE_ID, 1);
+        checkLogEntry(logDump.locklog.get(3), pageId3, READ_LOCK, STRUCTURE_ID, 2);
+        checkLogEntry(logDump.locklog.get(4), pageId3, READ_UNLOCK, STRUCTURE_ID, 1);
+        checkNextOp(logDump, 0, 0, 0);
+
+        lockLog.onReadUnlock(STRUCTURE_ID, pageId1, page1, pageAddr1);
+
+        logDump = lockLog.dump();
+
+        out.println(logDump);
+
+        assertEquals(0, logDump.headIdx);
+        assertTrue(logDump.locklog.isEmpty());
+        checkNextOp(logDump, 0, 0, 0);
+    }
+
+    /** */
+    @Test
+    public void testThreeReadPageLock_3() {
+        LockLog lockLog = createLogStackTracer(Thread.currentThread().getName());
+
+        long pageId1 = 1;
+        long pageId2 = 11;
+        long pageId3 = 111;
+        long page1 = 2;
+        long page2 = 12;
+        long page3 = 122;
+        long pageAddr1 = 3;
+        long pageAddr2 = 13;
+        long pageAddr3 = 133;
+
+        PageLockLogSnapshot logDump;
+
+        lockLog.onBeforeReadLock(STRUCTURE_ID, pageId1, page1);
+
+        logDump = lockLog.dump();
+
+        out.println(logDump);
+
+        assertEquals(0, logDump.headIdx);
+        assertTrue(logDump.locklog.isEmpty());
+        checkNextOp(logDump, pageId1, BEFORE_READ_LOCK, STRUCTURE_ID);
+
+        lockLog.onReadLock(STRUCTURE_ID, pageId1, page1, pageAddr1);
+
+        logDump = lockLog.dump();
+
+        out.println(logDump);
+
+        assertEquals(1, logDump.headIdx);
+        checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+        checkNextOp(logDump, 0, 0, 0);
+
+        lockLog.onBeforeReadLock(STRUCTURE_ID, pageId2, page2);
+
+        logDump = lockLog.dump();
+
+        out.println(logDump);
+
+        assertEquals(1, logDump.headIdx);
+        checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+        checkNextOp(logDump, pageId2, BEFORE_READ_LOCK, STRUCTURE_ID);
+
+        lockLog.onReadLock(STRUCTURE_ID, pageId2, page2, pageAddr2);
+
+        logDump = lockLog.dump();
+
+        out.println(logDump);
+
+        assertEquals(2, logDump.headIdx);
+        checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+        checkLogEntry(logDump.locklog.get(1), pageId2, READ_LOCK, STRUCTURE_ID, 2);
+        checkNextOp(logDump, 0, 0, 0);
+
+        lockLog.onBeforeReadLock(STRUCTURE_ID, pageId3, page3);
+
+        logDump = lockLog.dump();
+
+        out.println(logDump);
+
+        assertEquals(2, logDump.headIdx);
+        checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+        checkLogEntry(logDump.locklog.get(1), pageId2, READ_LOCK, STRUCTURE_ID, 2);
+        checkNextOp(logDump, pageId3, BEFORE_READ_LOCK, STRUCTURE_ID);
+
+        lockLog.onReadLock(STRUCTURE_ID, pageId3, page3, pageAddr3);
+
+        logDump = lockLog.dump();
+
+        out.println(logDump);
+
+        assertEquals(3, logDump.headIdx);
+        checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+        checkLogEntry(logDump.locklog.get(1), pageId2, READ_LOCK, STRUCTURE_ID, 2);
+        checkLogEntry(logDump.locklog.get(2), pageId3, READ_LOCK, STRUCTURE_ID, 3);
+        checkNextOp(logDump, 0,0, 0);
+
+        lockLog.onReadUnlock(STRUCTURE_ID, pageId2, page2, pageAddr2);
+
+        logDump = lockLog.dump();
+
+        out.println(logDump);
+
+        assertEquals(4, logDump.headIdx);
+        checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+        checkLogEntry(logDump.locklog.get(1), pageId2, READ_LOCK, STRUCTURE_ID, 2);
+        checkLogEntry(logDump.locklog.get(2), pageId3, READ_LOCK, STRUCTURE_ID, 3);
+        checkLogEntry(logDump.locklog.get(3), pageId2, READ_UNLOCK, STRUCTURE_ID, 2);
+        checkNextOp(logDump, 0,0, 0);
+
+        lockLog.onReadUnlock(STRUCTURE_ID, pageId3, page3, pageAddr3);
+
+        out.println(lockLog);
+
+        logDump = lockLog.dump();
+
+        assertEquals(5, logDump.headIdx);
+        checkLogEntry(logDump.locklog.get(0), pageId1, READ_LOCK, STRUCTURE_ID, 1);
+        checkLogEntry(logDump.locklog.get(1), pageId2, READ_LOCK, STRUCTURE_ID, 2);
+        checkLogEntry(logDump.locklog.get(2), pageId3, READ_LOCK, STRUCTURE_ID, 3);
+        checkLogEntry(logDump.locklog.get(3), pageId2, READ_UNLOCK, STRUCTURE_ID, 2);
+        checkLogEntry(logDump.locklog.get(4), pageId3, READ_UNLOCK, STRUCTURE_ID, 1);
+        checkNextOp(logDump, 0,0, 0);
+
+        lockLog.onReadUnlock(STRUCTURE_ID, pageId1, page1, pageAddr1);
+
+        logDump = lockLog.dump();
+
+        out.println(logDump);
+
+        assertEquals(0, logDump.headIdx);
+        assertTrue(logDump.locklog.isEmpty());
+        checkNextOp(logDump, 0, 0, 0);
+    }
+
+    /** */
+    @Test
+    public void testLogOverFlow() {
+        LockLog lockLog = createLogStackTracer(Thread.currentThread().getName());
+
+        long pageId = 1;
+        long page = 2;
+        long pageAddr = 3;
+
+        PageLockLogSnapshot log;
+
+        // Lock log should be invalid after this operation because we can get lock more that
+        // log capacity, +1 for overflow.
+        range(0, DEFAULT_CAPACITY + 1).forEach((i) -> {
+            lockLog.onReadLock(STRUCTURE_ID, pageId, page, pageAddr);
+        });
+
+        out.println(lockLog);
+
+        log = lockLog.dump();
+
+        Assert.assertTrue(lockLog.isInvalid());
+
+        String msg = lockLog.invalidContext().msg;
+
+        Assert.assertTrue(msg, msg.contains("Log overflow"));
+    }
+
+    /** */
+    @Test
+    public void testThreadlog() throws IgniteCheckedException {
+        LockLog lockLog = createLogStackTracer(Thread.currentThread().getName());
+
+        long pageId = 1;
+        long page = 2;
+        long pageAddr = 3;
+
+        int cntlogs = 5_000;
+
+        AtomicBoolean done = new AtomicBoolean();
+
+        int maxWaitTime = 500;
+
+        int maxdeep = 16;
+
+        IgniteInternalFuture f = GridTestUtils.runAsync(() -> {
+            while (!done.get()) {
+                int iter = nextRandomWaitTimeout(maxdeep);
+
+                doRunnable(iter, () -> {
+                    awaitRandom(100);
+
+                    lockLog.onBeforeReadLock(STRUCTURE_ID, pageId, page);
+
+                    awaitRandom(100);
+
+                    lockLog.onReadLock(STRUCTURE_ID, pageId, page, pageAddr);
+                });
+
+                try {
+                    awaitRandom(maxWaitTime);
+                }
+                finally {
+                    doRunnable(iter, () -> {
+                        lockLog.onReadUnlock(STRUCTURE_ID, pageId, page, pageAddr);
+                    });
+                }
+            }
+        });
+
+        long totalExecutionTime = 0L;
+
+        for (int i = 0; i < cntlogs; i++) {
+            awaitRandom(50);
+
+            long time = System.nanoTime();
+
+            PageLockLogSnapshot logDump = lockLog.dump();
+
+            long logTime = System.nanoTime() - time;
+
+            if (logDump.nextOp != 0)
+                assertTrue(logDump.nextOpPageId != 0);
+
+            assertTrue(logDump.time != 0);
+            Assert.assertNotNull(logDump.name);
+
+            if (logDump.headIdx > 0) {
+                //TODO
+               /* for (int j = 0; j < log.headIdx; j++)
+                    Assert.assertTrue(String.valueOf(log.headIdx), log.pageIdLocksStack[j] != 0);*/
+            }
+
+            Assert.assertNotNull(logDump);
+
+            totalExecutionTime += logTime;
+
+            assertTrue(logTime <= ofMinutes((long)(maxWaitTime + (maxWaitTime * 0.1))).toNanos());
+
+            if (i != 0 && i % 100 == 0)
+                out.println(">>> log:" + i);
+        }
+
+        done.set(true);
+
+        f.get();
+
+        out.println(">>> Avarage time log creation:" + (totalExecutionTime / cntlogs) + " ns");
+    }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/stack/HeapArrayLockStackTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/stack/HeapArrayLockStackTest.java
new file mode 100644
index 0000000..942f251
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/stack/HeapArrayLockStackTest.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.stack;
+
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory;
+
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory.HEAP_STACK;
+
+/** */
+public class HeapArrayLockStackTest extends PageLockStackTest {
+    /** {@inheritDoc} */
+    @Override protected LockStack createLockStackTracer(String name) {
+        return (LockStack)LockTrackerFactory.create(HEAP_STACK, name);
+    }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/stack/OffHeapLockStackTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/stack/OffHeapLockStackTest.java
new file mode 100644
index 0000000..2f44643
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/stack/OffHeapLockStackTest.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.stack;
+
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory;
+
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory.OFF_HEAP_STACK;
+
+/** */
+public class OffHeapLockStackTest extends PageLockStackTest {
+    /** {@inheritDoc} */
+    @Override protected LockStack createLockStackTracer(String name) {
+        return (LockStack)LockTrackerFactory.create(OFF_HEAP_STACK, name);
+    }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/stack/PageLockStackTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/stack/PageLockStackTest.java
new file mode 100644
index 0000000..d20d031
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/diagnostic/pagelocktracker/stack/PageLockStackTest.java
@@ -0,0 +1,751 @@
+/*
+ * Copyright 2019 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.stack;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.IgniteInternalFuture;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.AbstractPageLockTest;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.LockTrackerFactory;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker;
+import org.apache.ignite.testframework.GridTestUtils;
+import org.junit.Assert;
+import org.junit.Test;
+
+import static java.time.Duration.ofMinutes;
+import static java.util.stream.IntStream.range;
+import static org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTracker.BEFORE_READ_LOCK;
+
+/** */
+public abstract class PageLockStackTest extends AbstractPageLockTest {
+    /** */
+    protected static final int STRUCTURE_ID = 123;
+    /** */
+    protected abstract LockStack createLockStackTracer(String name);
+
+    /** */
+    @Test
+    public void testOneReadPageLock() {
+        LockStack lockStack = createLockStackTracer(Thread.currentThread().getName());
+
+        long pageId = 1;
+        long page = 2;
+        long pageAddr = 3;
+
+        PageLockStackSnapshot dump;
+
+        lockStack.onBeforeReadLock(STRUCTURE_ID, pageId, page);
+
+        dump = lockStack.dump();
+
+        Assert.assertEquals(0, dump.headIdx);
+        Assert.assertTrue(String.valueOf(dump.pageIdLocksStack), dump.pageIdLocksStack.isEmpty());
+        Assert.assertEquals(pageId, dump.nextOpPageId);
+        Assert.assertEquals(BEFORE_READ_LOCK, dump.nextOp);
+
+        lockStack.onReadLock(STRUCTURE_ID, pageId, page, pageAddr);
+
+        dump = lockStack.dump();
+
+        Assert.assertEquals(1, dump.headIdx);
+        Assert.assertEquals(pageId, dump.pageIdLocksStack.getPageId(0));
+        Assert.assertEquals(0, dump.nextOpPageId);
+        Assert.assertEquals(0, dump.nextOp);
+
+        lockStack.onReadUnlock(STRUCTURE_ID, pageId, page, pageAddr);
+
+        dump = lockStack.dump();
+
+        Assert.assertEquals(0, dump.headIdx);
+        Assert.assertTrue(dump.pageIdLocksStack.isEmpty());
+        Assert.assertEquals(0, dump.nextOpPageId);
+        Assert.assertEquals(0, dump.nextOp);
+    }
+
+    /** */
+    @Test
+    public void testTwoReadPageLock() {
+        LockStack lockStack = createLockStackTracer(Thread.currentThread().getName());
+
+        long pageId1 = 1;
+        long pageId2 = 11;
+        long page1 = 2;
+        long page2 = 12;
+        long pageAddr1 = 3;
+        long pageAddr2 = 13;
+
+        PageLockStackSnapshot dump;
+
+        lockStack.onBeforeReadLock(STRUCTURE_ID, pageId1, page1);
+
+        dump = lockStack.dump();
+
+        Assert.assertEquals(0, dump.headIdx);
+        Assert.assertTrue(dump.pageIdLocksStack.isEmpty());
+        Assert.assertEquals(pageId1, dump.nextOpPageId);
+        Assert.assertEquals(BEFORE_READ_LOCK, dump.nextOp);
+
+        lockStack.onReadLock(STRUCTURE_ID, pageId1, page1, pageAddr1);
+
+        dump = lockStack.dump();
+
+        Assert.assertEquals(1, dump.headIdx);
+        Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+        Assert.assertEquals(0, dump.nextOpPageId);
+        Assert.assertEquals(0, dump.nextOp);
+
+        lockStack.onBeforeReadLock(STRUCTURE_ID, pageId2, page2);
+
+        dump = lockStack.dump();
+
+        Assert.assertEquals(1, dump.headIdx);
+        Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+        Assert.assertEquals(pageId2, dump.nextOpPageId);
+        Assert.assertEquals(BEFORE_READ_LOCK, dump.nextOp);
+
+        lockStack.onReadLock(STRUCTURE_ID, pageId2, page2, pageAddr2);
+
+        dump = lockStack.dump();
+
+        Assert.assertEquals(2, dump.headIdx);
+        Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+        Assert.assertEquals(pageId2, dump.pageIdLocksStack.getPageId(1));
+        Assert.assertEquals(0, dump.nextOpPageId);
+        Assert.assertEquals(0, dump.nextOp);
+
+        lockStack.onReadUnlock(STRUCTURE_ID, pageId2, page2, pageAddr2);
+
+        dump = lockStack.dump();
+
+        Assert.assertEquals(1, dump.headIdx);
+        Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+        Assert.assertEquals(0, dump.pageIdLocksStack.getPageId(1));
+        Assert.assertEquals(0, dump.nextOpPageId);
+        Assert.assertEquals(0, dump.nextOp);
+
+        lockStack.onReadUnlock(STRUCTURE_ID, pageId1, page1, pageAddr1);
+
+        dump = lockStack.dump();
+
+        Assert.assertEquals(0, dump.headIdx);
+        Assert.assertTrue(dump.pageIdLocksStack.isEmpty());
+        Assert.assertEquals(0, dump.nextOpPageId);
+        Assert.assertEquals(0, dump.nextOp);
+    }
+
+    /** */
+    @Test
+    public void testThreeReadPageLock_1() {
+        LockStack lockStack = createLockStackTracer(Thread.currentThread().getName());
+
+        long pageId1 = 1;
+        long pageId2 = 11;
+        long pageId3 = 111;
+        long page1 = 2;
+        long page2 = 12;
+        long page3 = 122;
+        long pageAddr1 = 3;
+        long pageAddr2 = 13;
+        long pageAddr3 = 133;
+
+        PageLockStackSnapshot dump;
+
+        lockStack.onBeforeReadLock(STRUCTURE_ID, pageId1, page1);
+
+        dump = lockStack.dump();
+
+        Assert.assertEquals(0, dump.headIdx);
+        Assert.assertTrue(dump.pageIdLocksStack.isEmpty());
+        Assert.assertEquals(pageId1, dump.nextOpPageId);
+        Assert.assertEquals(BEFORE_READ_LOCK, dump.nextOp);
+
+        lockStack.onReadLock(STRUCTURE_ID, pageId1, page1, pageAddr1);
+
+        dump = lockStack.dump();
+
+        Assert.assertEquals(1, dump.headIdx);
+        Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+        Assert.assertEquals(0, dump.nextOpPageId);
+        Assert.assertEquals(0, dump.nextOp);
+
+        lockStack.onBeforeReadLock(STRUCTURE_ID, pageId2, page2);
+
+        dump = lockStack.dump();
+
+        Assert.assertEquals(1, dump.headIdx);
+        Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+        Assert.assertEquals(pageId2, dump.nextOpPageId);
+        Assert.assertEquals(BEFORE_READ_LOCK, dump.nextOp);
+
+        lockStack.onReadLock(STRUCTURE_ID, pageId2, page2, pageAddr2);
+
+        dump = lockStack.dump();
+
+        Assert.assertEquals(2, dump.headIdx);
+        Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+        Assert.assertEquals(pageId2, dump.pageIdLocksStack.getPageId(1));
+        Assert.assertEquals(0, dump.nextOpPageId);
+        Assert.assertEquals(0, dump.nextOp);
+
+        lockStack.onBeforeReadLock(STRUCTURE_ID, pageId3, page3);
+
+        dump = lockStack.dump();
+
+        Assert.assertEquals(2, dump.headIdx);
+        Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+        Assert.assertEquals(pageId2, dump.pageIdLocksStack.getPageId(1));
+        Assert.assertEquals(pageId3, dump.nextOpPageId);
+        Assert.assertEquals(BEFORE_READ_LOCK, dump.nextOp);
+
+        lockStack.onReadLock(STRUCTURE_ID, pageId3, page3, pageAddr3);
+
+        dump = lockStack.dump();
+
+        Assert.assertEquals(3, dump.headIdx);
+        Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+        Assert.assertEquals(pageId2, dump.pageIdLocksStack.getPageId(1));
+        Assert.assertEquals(pageId3, dump.pageIdLocksStack.getPageId(2));
+        Assert.assertEquals(0, dump.nextOpPageId);
+        Assert.assertEquals(0, dump.nextOp);
+
+        lockStack.onReadUnlock(STRUCTURE_ID, pageId3, page3, pageAddr3);
+
+        dump = lockStack.dump();
+
+        Assert.assertEquals(2, dump.headIdx);
+        Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+        Assert.assertEquals(pageId2, dump.pageIdLocksStack.getPageId(1));
+        Assert.assertEquals(0, dump.pageIdLocksStack.getPageId(2));
+        Assert.assertEquals(0, dump.nextOpPageId);
+        Assert.assertEquals(0, dump.nextOp);
+
+        lockStack.onReadUnlock(STRUCTURE_ID, pageId2, page2, pageAddr2);
+
+        dump = lockStack.dump();
+
+        Assert.assertEquals(1, dump.headIdx);
+        Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+        Assert.assertEquals(0, dump.pageIdLocksStack.getPageId(1));
+        Assert.assertEquals(0, dump.pageIdLocksStack.getPageId(2));
+        Assert.assertEquals(0, dump.nextOpPageId);
+        Assert.assertEquals(0, dump.nextOp);
+
+        lockStack.onReadUnlock(STRUCTURE_ID, pageId1, page1, pageAddr1);
+
+        dump = lockStack.dump();
+
+        Assert.assertEquals(0, dump.headIdx);
+        Assert.assertTrue(dump.pageIdLocksStack.isEmpty());
+        Assert.assertEquals(0, dump.nextOpPageId);
+        Assert.assertEquals(0, dump.nextOp);
+    }
+
+    /** */
+    @Test
+    public void testThreeReadPageLock_2() {
+        LockStack lockStack = createLockStackTracer(Thread.currentThread().getName());
+
+        long pageId1 = 1;
+        long pageId2 = 11;
+        long pageId3 = 111;
+        long page1 = 2;
+        long page2 = 12;
+        long page3 = 122;
+        long pageAddr1 = 3;
+        long pageAddr2 = 13;
+        long pageAddr3 = 133;
+
+        PageLockStackSnapshot dump;
+
+        lockStack.onBeforeReadLock(STRUCTURE_ID, pageId1, page1);
+
+        dump = lockStack.dump();
+
+        Assert.assertEquals(0, dump.headIdx);
+        Assert.assertTrue(dump.pageIdLocksStack.isEmpty());
+        Assert.assertEquals(pageId1, dump.nextOpPageId);
+        Assert.assertEquals(BEFORE_READ_LOCK, dump.nextOp);
+
+        lockStack.onReadLock(STRUCTURE_ID, pageId1, page1, pageAddr1);
+
+        dump = lockStack.dump();
+
+        Assert.assertEquals(1, dump.headIdx);
+        Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+        Assert.assertEquals(0, dump.nextOpPageId);
+        Assert.assertEquals(0, dump.nextOp);
+
+        lockStack.onBeforeReadLock(STRUCTURE_ID, pageId2, page2);
+
+        dump = lockStack.dump();
+
+        Assert.assertEquals(1, dump.headIdx);
+        Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+        Assert.assertEquals(pageId2, dump.nextOpPageId);
+        Assert.assertEquals(BEFORE_READ_LOCK, dump.nextOp);
+
+        lockStack.onReadLock(STRUCTURE_ID, pageId2, page2, pageAddr2);
+
+        dump = lockStack.dump();
+
+        Assert.assertEquals(2, dump.headIdx);
+        Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+        Assert.assertEquals(pageId2, dump.pageIdLocksStack.getPageId(1));
+        Assert.assertEquals(0, dump.nextOpPageId);
+        Assert.assertEquals(0, dump.nextOp);
+
+        lockStack.onReadUnlock(STRUCTURE_ID, pageId2, page2, pageAddr2);
+
+        dump = lockStack.dump();
+
+        Assert.assertEquals(1, dump.headIdx);
+        Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+        Assert.assertEquals(0, dump.pageIdLocksStack.getPageId(1));
+        Assert.assertEquals(0, dump.nextOpPageId);
+        Assert.assertEquals(0, dump.nextOp);
+
+        lockStack.onBeforeReadLock(STRUCTURE_ID, pageId3, page3);
+
+        dump = lockStack.dump();
+
+        Assert.assertEquals(1, dump.headIdx);
+        Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+        Assert.assertEquals(pageId3, dump.nextOpPageId);
+        Assert.assertEquals(BEFORE_READ_LOCK, dump.nextOp);
+
+        lockStack.onReadLock(STRUCTURE_ID, pageId3, page3, pageAddr3);
+
+        dump = lockStack.dump();
+
+        Assert.assertEquals(2, dump.headIdx);
+        Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+        Assert.assertEquals(pageId3, dump.pageIdLocksStack.getPageId(1));
+        Assert.assertEquals(0, dump.nextOpPageId);
+        Assert.assertEquals(0, dump.nextOp);
+
+        lockStack.onReadUnlock(STRUCTURE_ID, pageId3, page3, pageAddr3);
+
+        dump = lockStack.dump();
+
+        Assert.assertEquals(1, dump.headIdx);
+        Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+        Assert.assertEquals(0, dump.pageIdLocksStack.getPageId(1));
+        Assert.assertEquals(0, dump.nextOpPageId);
+        Assert.assertEquals(0, dump.nextOp);
+
+        lockStack.onReadUnlock(STRUCTURE_ID, pageId1, page1, pageAddr1);
+
+        dump = lockStack.dump();
+
+        Assert.assertEquals(0, dump.headIdx);
+        Assert.assertTrue(dump.pageIdLocksStack.isEmpty());
+        Assert.assertEquals(0, dump.nextOpPageId);
+        Assert.assertEquals(0, dump.nextOp);
+    }
+
+    /** */
+    @Test
+    public void testThreeReadPageLock_3() {
+        LockStack lockStack = createLockStackTracer(Thread.currentThread().getName());
+
+        long pageId1 = 1;
+        long pageId2 = 11;
+        long pageId3 = 111;
+        long page1 = 2;
+        long page2 = 12;
+        long page3 = 122;
+        long pageAddr1 = 3;
+        long pageAddr2 = 13;
+        long pageAddr3 = 133;
+
+        PageLockStackSnapshot dump;
+
+        lockStack.onBeforeReadLock(STRUCTURE_ID, pageId1, page1);
+
+        dump = lockStack.dump();
+
+        Assert.assertEquals(0, dump.headIdx);
+        Assert.assertTrue(dump.pageIdLocksStack.isEmpty());
+        Assert.assertEquals(pageId1, dump.nextOpPageId);
+        Assert.assertEquals(BEFORE_READ_LOCK, dump.nextOp);
+
+        lockStack.onReadLock(STRUCTURE_ID, pageId1, page1, pageAddr1);
+
+        dump = lockStack.dump();
+
+        Assert.assertEquals(1, dump.headIdx);
+        Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+        Assert.assertEquals(0, dump.nextOpPageId);
+        Assert.assertEquals(0, dump.nextOp);
+
+        lockStack.onBeforeReadLock(STRUCTURE_ID, pageId2, page2);
+
+        dump = lockStack.dump();
+
+        Assert.assertEquals(1, dump.headIdx);
+        Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+        Assert.assertEquals(pageId2, dump.nextOpPageId);
+        Assert.assertEquals(BEFORE_READ_LOCK, dump.nextOp);
+
+        lockStack.onReadLock(STRUCTURE_ID, pageId2, page2, pageAddr2);
+
+        dump = lockStack.dump();
+
+        Assert.assertEquals(2, dump.headIdx);
+        Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+        Assert.assertEquals(pageId2, dump.pageIdLocksStack.getPageId(1));
+        Assert.assertEquals(0, dump.nextOpPageId);
+        Assert.assertEquals(0, dump.nextOp);
+
+        lockStack.onBeforeReadLock(STRUCTURE_ID, pageId3, page3);
+
+        dump = lockStack.dump();
+
+        Assert.assertEquals(2, dump.headIdx);
+        Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+        Assert.assertEquals(pageId2, dump.pageIdLocksStack.getPageId(1));
+        Assert.assertEquals(pageId3, dump.nextOpPageId);
+        Assert.assertEquals(BEFORE_READ_LOCK, dump.nextOp);
+
+        lockStack.onReadLock(STRUCTURE_ID, pageId3, page3, pageAddr3);
+
+        dump = lockStack.dump();
+
+        Assert.assertEquals(3, dump.headIdx);
+        Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+        Assert.assertEquals(pageId2, dump.pageIdLocksStack.getPageId(1));
+        Assert.assertEquals(pageId3, dump.pageIdLocksStack.getPageId(2));
+        Assert.assertEquals(0, dump.nextOpPageId);
+        Assert.assertEquals(0, dump.nextOp);
+
+        lockStack.onReadUnlock(STRUCTURE_ID, pageId2, page2, pageAddr2);
+
+        dump = lockStack.dump();
+
+        Assert.assertEquals(3, dump.headIdx);
+        Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+        Assert.assertEquals(0, dump.pageIdLocksStack.getPageId(1));
+        Assert.assertEquals(pageId3, dump.pageIdLocksStack.getPageId(2));
+        Assert.assertEquals(0, dump.nextOpPageId);
+        Assert.assertEquals(0, dump.nextOp);
+
+        lockStack.onReadUnlock(STRUCTURE_ID, pageId3, page3, pageAddr3);
+
+        dump = lockStack.dump();
+
+        Assert.assertEquals(1, dump.headIdx);
+        Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+        Assert.assertEquals(0, dump.pageIdLocksStack.getPageId(1));
+        Assert.assertEquals(0, dump.pageIdLocksStack.getPageId(2));
+        Assert.assertEquals(0, dump.nextOpPageId);
+        Assert.assertEquals(0, dump.nextOp);
+
+        lockStack.onReadUnlock(STRUCTURE_ID, pageId1, page1, pageAddr1);
+
+        dump = lockStack.dump();
+
+        Assert.assertEquals(0, dump.headIdx);
+        Assert.assertTrue(dump.pageIdLocksStack.isEmpty());
+        Assert.assertEquals(0, dump.nextOpPageId);
+        Assert.assertEquals(0, dump.nextOp);
+    }
+
+    /** */
+    @Test
+    public void testUnlockUnexcpected() {
+        LockStack lockStack = createLockStackTracer(Thread.currentThread().getName());
+
+        long pageId = 1;
+        long page = 2;
+        long pageAddr = 3;
+
+        PageLockStackSnapshot dump;
+
+        // Lock stack should be invalid after this operation because we can not unlock page
+        // which was not locked.
+        lockStack.onReadUnlock(STRUCTURE_ID, pageId, page, pageAddr);
+
+        dump = lockStack.dump();
+
+        Assert.assertTrue(lockStack.isInvalid());
+        String msg = lockStack.invalidContext().msg;
+
+        Assert.assertTrue(msg, msg.contains("Stack is empty"));
+
+        Assert.assertEquals(0, dump.headIdx);
+        Assert.assertTrue(dump.pageIdLocksStack.isEmpty());
+        Assert.assertEquals(0, dump.nextOpPageId);
+        Assert.assertEquals(0, dump.nextOp);
+    }
+
+    /** */
+    @Test
+    public void testUnlockUnexcpectedOnNotEmptyStack() {
+        LockStack lockStack = createLockStackTracer(Thread.currentThread().getName());
+
+        long pageId1 = 1;
+        long pageId2 = 11;
+        long page1 = 2;
+        long page2 = 12;
+        long pageAddr1 = 3;
+        long pageAddr2 = 13;
+
+        PageLockStackSnapshot dump;
+
+        lockStack.onReadLock(STRUCTURE_ID, pageId1, page1, pageAddr1);
+
+        // Lock stack should be invalid after this operation because we can not unlock page
+        // which was not locked.
+        lockStack.onReadUnlock(STRUCTURE_ID, pageId2, page2, pageAddr2);
+
+        dump = lockStack.dump();
+
+        Assert.assertTrue(lockStack.isInvalid());
+        String msg = lockStack.invalidContext().msg;
+
+        Assert.assertTrue(msg, msg.contains("Can not find pageId in stack"));
+
+        Assert.assertEquals(1, dump.headIdx);
+        Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+        Assert.assertEquals(0, dump.nextOpPageId);
+        Assert.assertEquals(0, dump.nextOp);
+    }
+
+    /** */
+    @Test
+    public void testUnlockUnexcpectedOnNotEmptyStackMultiLocks() {
+        LockStack lockStack = createLockStackTracer(Thread.currentThread().getName());
+
+        long pageId1 = 1;
+        long pageId2 = 11;
+        long pageId3 = 111;
+        long pageId4 = 1111;
+        long page1 = 2;
+        long page2 = 12;
+        long page3 = 122;
+        long page4 = 1222;
+        long pageAddr1 = 3;
+        long pageAddr2 = 13;
+        long pageAddr3 = 133;
+        long pageAddr4 = 1333;
+
+        PageLockStackSnapshot dump;
+
+        lockStack.onReadLock(STRUCTURE_ID, pageId1, page1, pageAddr1);
+        lockStack.onReadLock(STRUCTURE_ID, pageId2, page2, pageAddr2);
+        lockStack.onReadLock(STRUCTURE_ID, pageId3, page3, pageAddr3);
+
+        // Lock stack should be invalid after this operation because we can not unlock page
+        // which was not locked.
+        lockStack.onReadUnlock(STRUCTURE_ID, pageId4, page4, pageAddr4);
+
+        dump = lockStack.dump();
+
+        Assert.assertTrue(lockStack.isInvalid());
+        String msg = lockStack.invalidContext().msg;
+
+        Assert.assertTrue(msg, msg.contains("Can not find pageId in stack"));
+
+        Assert.assertEquals(3, dump.headIdx);
+        Assert.assertEquals(pageId1, dump.pageIdLocksStack.getPageId(0));
+        Assert.assertEquals(pageId2, dump.pageIdLocksStack.getPageId(1));
+        Assert.assertEquals(pageId3, dump.pageIdLocksStack.getPageId(2));
+        Assert.assertEquals(0, dump.nextOpPageId);
+        Assert.assertEquals(0, dump.nextOp);
+    }
+
+    /** */
+    @Test
+    public void testStackOverFlow() {
+        LockStack lockStack = createLockStackTracer(Thread.currentThread().getName());
+
+        long pageId = 1;
+        long page = 2;
+        long pageAddr = 3;
+
+        PageLockStackSnapshot dump;
+
+        // Lock stack should be invalid after this operation because we can get lock more that
+        // stack capacity, +1 for overflow.
+        range(0, LockTrackerFactory.DEFAULT_CAPACITY + 1).forEach((i) -> {
+            lockStack.onReadLock(STRUCTURE_ID, pageId, page, pageAddr);
+        });
+
+        dump = lockStack.dump();
+
+        Assert.assertTrue(lockStack.isInvalid());
+        Assert.assertTrue(lockStack.invalidContext().msg.contains("Stack overflow"));
+
+        Assert.assertEquals(LockTrackerFactory.DEFAULT_CAPACITY, dump.headIdx);
+        Assert.assertFalse(dump.pageIdLocksStack.isEmpty());
+        Assert.assertEquals(0, dump.nextOpPageId);
+        Assert.assertEquals(0, dump.nextOp);
+    }
+
+    /** */
+    @Test
+    public void testStackOperationAfterInvalid() {
+        LockStack lockStack = createLockStackTracer(Thread.currentThread().getName());
+
+        long pageId = 1;
+        long page = 2;
+        long pageAddr = 3;
+
+        PageLockStackSnapshot dump;
+
+        // Lock stack should be invalid after this operation because we can not unlock page
+        // which was not locked.
+        lockStack.onReadUnlock(STRUCTURE_ID, pageId, page, pageAddr);
+
+        dump = lockStack.dump();
+
+        Assert.assertTrue(lockStack.isInvalid());
+        String msg = lockStack.invalidContext().msg;
+
+        Assert.assertTrue(msg, msg.contains("Stack is empty"));
+
+        System.out.println(lockStack.invalidContext());
+
+        Assert.assertEquals(0, dump.headIdx);
+        Assert.assertTrue(dump.pageIdLocksStack.isEmpty());
+        Assert.assertEquals(0, dump.nextOpPageId);
+        Assert.assertEquals(0, dump.nextOp);
+
+        lockStack.onBeforeReadLock(STRUCTURE_ID, pageId, page);
+
+        Assert.assertTrue(lockStack.isInvalid());
+        msg = lockStack.invalidContext().msg;
+
+        Assert.assertTrue(msg, msg.contains("Stack is empty"));
+
+        Assert.assertEquals(0, dump.headIdx);
+        Assert.assertTrue(dump.pageIdLocksStack.isEmpty());
+        Assert.assertEquals(0, dump.nextOpPageId);
+        Assert.assertEquals(0, dump.nextOp);
+
+        lockStack.onReadLock(STRUCTURE_ID, pageId, page, pageAddr);
+
+        Assert.assertTrue(lockStack.isInvalid());
+        msg = lockStack.invalidContext().msg;
+
+        Assert.assertTrue(msg, msg.contains("Stack is empty"));
+
+        Assert.assertEquals(0, dump.headIdx);
+        Assert.assertTrue(dump.pageIdLocksStack.isEmpty());
+        Assert.assertEquals(0, dump.nextOpPageId);
+        Assert.assertEquals(0, dump.nextOp);
+
+        lockStack.onReadUnlock(STRUCTURE_ID, pageId, page, pageAddr);
+
+        Assert.assertTrue(lockStack.isInvalid());
+        msg = lockStack.invalidContext().msg;
+
+        Assert.assertTrue(msg, msg.contains("Stack is empty"));
+
+        Assert.assertEquals(0, dump.headIdx);
+        Assert.assertTrue(dump.pageIdLocksStack.isEmpty());
+        Assert.assertEquals(0, dump.nextOpPageId);
+        Assert.assertEquals(0, dump.nextOp);
+    }
+
+    /** */
+    @Test
+    public void testThreadDump() throws IgniteCheckedException {
+        PageLockTracker<PageLockStackSnapshot> lockStack = createLockStackTracer(Thread.currentThread().getName());
+
+        long pageId = 1;
+        long page = 2;
+        long pageAddr = 3;
+
+        int cntDumps = 5_000;
+
+        AtomicBoolean done = new AtomicBoolean();
+
+        int maxWaitTime = 500;
+
+        int maxdeep = 16;
+
+        IgniteInternalFuture f = GridTestUtils.runAsync(() -> {
+            while (!done.get()) {
+                int iter = nextRandomWaitTimeout(maxdeep);
+
+                doRunnable(iter, () -> {
+                    awaitRandom(100);
+
+                    lockStack.onBeforeReadLock(STRUCTURE_ID, pageId, page);
+
+                    awaitRandom(100);
+
+                    lockStack.onReadLock(STRUCTURE_ID, pageId, page, pageAddr);
+                });
+
+                try {
+                    awaitRandom(maxWaitTime);
+                }
+                finally {
+                    doRunnable(iter, () -> {
+                        lockStack.onReadUnlock(STRUCTURE_ID, pageId, page, pageAddr);
+                    });
+                }
+            }
+        });
+
+        long totalExecutionTime = 0L;
+
+        for (int i = 0; i < cntDumps; i++) {
+            awaitRandom(50);
+
+            long time = System.nanoTime();
+
+            PageLockStackSnapshot dump = lockStack.dump();
+
+            long dumpTime = System.nanoTime() - time;
+
+            if (dump.nextOp != 0)
+                Assert.assertTrue(dump.nextOpPageId != 0);
+
+            Assert.assertTrue(dump.time != 0);
+            Assert.assertNotNull(dump.name);
+
+            if (dump.headIdx > 0) {
+                for (int itemIdx = 0; itemIdx < dump.headIdx; itemIdx++) {
+                    Assert.assertTrue(String.valueOf(dump.headIdx), dump.pageIdLocksStack.getPageId(itemIdx) != 0);
+                    Assert.assertTrue(dump.pageIdLocksStack.getOperation(itemIdx) != 0);
+                    Assert.assertTrue(dump.pageIdLocksStack.getStructureId(itemIdx) != 0);
+                    Assert.assertTrue(dump.pageIdLocksStack.getPageAddrHeader(itemIdx) != 0);
+                    Assert.assertTrue(dump.pageIdLocksStack.getPageAddr(itemIdx) != 0);
+                }
+            }
+
+            Assert.assertNotNull(dump);
+
+            totalExecutionTime += dumpTime;
+
+            Assert.assertTrue(dumpTime <= ofMinutes((long)(maxWaitTime + (maxWaitTime * 0.1))).toNanos());
+
+            if (i != 0 && i % 100 == 0)
+                System.out.println(">>> Dump:" + i);
+        }
+
+        done.set(true);
+
+        f.get();
+
+        System.out.println(">>> Avarage time dump creation:" + (totalExecutionTime / cntDumps) + " ns");
+    }
+
+}
\ No newline at end of file
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/BPlusTreePageMemoryImplTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/BPlusTreePageMemoryImplTest.java
index 43f51f7..7b81cfa 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/BPlusTreePageMemoryImplTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/BPlusTreePageMemoryImplTest.java
@@ -24,6 +24,7 @@ import org.apache.ignite.internal.mem.DirectMemoryProvider;
 import org.apache.ignite.internal.mem.unsafe.UnsafeMemoryProvider;
 import org.apache.ignite.internal.pagemem.FullPageId;
 import org.apache.ignite.internal.pagemem.PageMemory;
+import org.apache.ignite.internal.processors.cache.CacheDiagnosticManager;
 import org.apache.ignite.internal.processors.cache.GridCacheSharedContext;
 import org.apache.ignite.internal.processors.cache.persistence.CheckpointWriteProgressSupplier;
 import org.apache.ignite.internal.processors.cache.persistence.DataRegionMetricsImpl;
@@ -80,7 +81,8 @@ public class BPlusTreePageMemoryImplTest extends BPlusTreeSelfTest {
             null,
             null,
             null,
-            null
+            null,
+            new CacheDiagnosticManager()
         );
 
         PageMemory mem = new PageMemoryImpl(
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/BPlusTreeReuseListPageMemoryImplTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/BPlusTreeReuseListPageMemoryImplTest.java
index 20d55eb..8b62314 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/BPlusTreeReuseListPageMemoryImplTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/BPlusTreeReuseListPageMemoryImplTest.java
@@ -80,6 +80,7 @@ public class BPlusTreeReuseListPageMemoryImplTest extends BPlusTreeReuseSelfTest
             null,
             null,
             null,
+            null,
             null
         );
 
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/IndexStoragePageMemoryImplTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/IndexStoragePageMemoryImplTest.java
index 529f723..0673623 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/IndexStoragePageMemoryImplTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/IndexStoragePageMemoryImplTest.java
@@ -25,6 +25,7 @@ import org.apache.ignite.internal.mem.DirectMemoryProvider;
 import org.apache.ignite.internal.mem.file.MappedFileMemoryProvider;
 import org.apache.ignite.internal.pagemem.FullPageId;
 import org.apache.ignite.internal.pagemem.PageMemory;
+import org.apache.ignite.internal.processors.cache.CacheDiagnosticManager;
 import org.apache.ignite.internal.processors.cache.GridCacheSharedContext;
 import org.apache.ignite.internal.processors.cache.persistence.CheckpointWriteProgressSupplier;
 import org.apache.ignite.internal.processors.cache.persistence.DataRegionMetricsImpl;
@@ -95,7 +96,8 @@ public class IndexStoragePageMemoryImplTest extends IndexStorageSelfTest {
             null,
             null,
             null,
-            null
+            null,
+            new CacheDiagnosticManager()
         );
 
         return new PageMemoryImpl(
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryImplNoLoadTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryImplNoLoadTest.java
index becdaeb..66261c4 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryImplNoLoadTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryImplNoLoadTest.java
@@ -89,6 +89,7 @@ public class PageMemoryImplNoLoadTest extends PageMemoryNoLoadSelfTest {
             null,
             null,
             null,
+            null,
             null
         );
 
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryImplTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryImplTest.java
index 20114f5..8e7fb35 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryImplTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryImplTest.java
@@ -315,6 +315,7 @@ public class PageMemoryImplTest extends GridCommonAbstractTest {
             null,
             null,
             null,
+            null,
             null
         );
 
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/database/BPlusTreeReuseSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/database/BPlusTreeReuseSelfTest.java
index 992d897..f581334 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/database/BPlusTreeReuseSelfTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/database/BPlusTreeReuseSelfTest.java
@@ -24,6 +24,7 @@ import org.apache.ignite.internal.pagemem.wal.IgniteWriteAheadLogManager;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
 import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
 import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseListImpl;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
 
 import static org.apache.ignite.internal.pagemem.PageIdUtils.effectivePageId;
 
@@ -32,9 +33,20 @@ import static org.apache.ignite.internal.pagemem.PageIdUtils.effectivePageId;
  */
 public class BPlusTreeReuseSelfTest extends BPlusTreeSelfTest {
     /** {@inheritDoc} */
-    @Override protected ReuseList createReuseList(int cacheId, PageMemory pageMem, long rootId, boolean initNew)
-        throws IgniteCheckedException {
-        return new TestReuseList(cacheId, "test", pageMem, null, rootId, initNew);
+    @Override protected ReuseList createReuseList(
+        int cacheId,
+        PageMemory pageMem,
+        long rootId,
+        boolean initNew
+    ) throws IgniteCheckedException {
+        return new TestReuseList(
+            cacheId,
+            "test",
+            pageMem,
+            null,
+            rootId,
+            initNew
+        );
     }
 
     /** {@inheritDoc} */
@@ -48,19 +60,6 @@ public class BPlusTreeReuseSelfTest extends BPlusTreeSelfTest {
      *
      */
     private static class TestReuseList extends ReuseListImpl {
-        /** */
-        private static ThreadLocal<Set<Long>> readLocks = new ThreadLocal<Set<Long>>() {
-            @Override protected Set<Long> initialValue() {
-                return new HashSet<>();
-            }
-        };
-
-        /** */
-        private static ThreadLocal<Set<Long>> writeLocks = new ThreadLocal<Set<Long>>() {
-            @Override protected Set<Long> initialValue() {
-                return new HashSet<>();
-            }
-        };
 
         /**
          * @param cacheId    Cache ID.
@@ -79,9 +78,34 @@ public class BPlusTreeReuseSelfTest extends BPlusTreeSelfTest {
             long metaPageId,
             boolean initNew
         ) throws IgniteCheckedException {
-            super(cacheId, name, pageMem, wal, metaPageId, initNew);
+            super(cacheId, name, pageMem, wal, metaPageId, initNew, new TestPageLockListener());
         }
 
+        /**
+         *
+         */
+        static boolean checkNoLocks() {
+            return TestPageLockListener.readLocks.get().isEmpty() && TestPageLockListener.writeLocks.get().isEmpty();
+        }
+    }
+
+    /** */
+    private static class TestPageLockListener implements PageLockListener {
+
+        /** */
+        private static ThreadLocal<Set<Long>> readLocks = new ThreadLocal<Set<Long>>() {
+            @Override protected Set<Long> initialValue() {
+                return new HashSet<>();
+            }
+        };
+
+        /** */
+        private static ThreadLocal<Set<Long>> writeLocks = new ThreadLocal<Set<Long>>() {
+            @Override protected Set<Long> initialValue() {
+                return new HashSet<>();
+            }
+        };
+
         /** {@inheritDoc} */
         @Override public void onBeforeReadLock(int cacheId, long pageId, long page) {
             // No-op.
@@ -122,9 +146,5 @@ public class BPlusTreeReuseSelfTest extends BPlusTreeSelfTest {
 
             assertTrue(writeLocks.get().remove(pageId));
         }
-
-        static boolean checkNoLocks() {
-            return readLocks.get().isEmpty() && writeLocks.get().isEmpty();
-        }
     }
 }
\ No newline at end of file
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/database/BPlusTreeSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/database/BPlusTreeSelfTest.java
index 14cc760..6ccf667 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/database/BPlusTreeSelfTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/database/BPlusTreeSelfTest.java
@@ -63,6 +63,7 @@ import org.apache.ignite.internal.processors.cache.persistence.tree.io.BPlusLeaf
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.IOVersions;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
 import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
 import org.apache.ignite.internal.util.GridConcurrentHashSet;
 import org.apache.ignite.internal.util.GridRandom;
 import org.apache.ignite.internal.util.GridStripedLock;
@@ -83,6 +84,7 @@ import org.junit.runners.JUnit4;
 
 import static org.apache.ignite.internal.pagemem.PageIdUtils.effectivePageId;
 import static org.apache.ignite.internal.processors.cache.persistence.tree.BPlusTree.rnd;
+import static org.apache.ignite.internal.processors.database.BPlusTreeSelfTest.TestTree.threadId;
 import static org.apache.ignite.internal.util.IgniteTree.OperationType.NOOP;
 import static org.apache.ignite.internal.util.IgniteTree.OperationType.PUT;
 import static org.apache.ignite.internal.util.IgniteTree.OperationType.REMOVE;
@@ -143,7 +145,7 @@ public class BPlusTreeSelfTest extends GridCommonAbstractTest {
      * Check that we do not keep any locks at the moment.
      */
     protected void assertNoLocks() {
-        assertTrue(TestTree.checkNoLocks());
+        assertTrue(TestPageLockListener.checkNoLocks());
     }
 
     /** {@inheritDoc} */
@@ -2733,7 +2735,8 @@ public class BPlusTreeSelfTest extends GridCommonAbstractTest {
      * @throws IgniteCheckedException If failed.
      */
     protected TestTree createTestTree(boolean canGetRow) throws IgniteCheckedException {
-        TestTree tree = new TestTree(reuseList, canGetRow, CACHE_ID, pageMem, allocateMetaPage().pageId());
+        TestTree tree = new TestTree(
+            reuseList, canGetRow, CACHE_ID, pageMem, allocateMetaPage().pageId(), new TestPageLockListener());
 
         assertEquals(0, tree.size());
         assertEquals(0, tree.rootLevel());
@@ -2753,18 +2756,6 @@ public class BPlusTreeSelfTest extends GridCommonAbstractTest {
      * Test tree.
      */
     protected static class TestTree extends BPlusTree<Long, Long> {
-        /** */
-        private static ConcurrentMap<Object, Long> beforeReadLock = new ConcurrentHashMap<>();
-
-        /** */
-        private static ConcurrentMap<Object, Long> beforeWriteLock = new ConcurrentHashMap<>();
-
-        /** */
-        private static ConcurrentMap<Object, Map<Long, Long>> readLocks = new ConcurrentHashMap<>();
-
-        /** */
-        private static ConcurrentMap<Object, Map<Long, Long>> writeLocks = new ConcurrentHashMap<>();
-
         /** Number of retries. */
         private int numRetries = super.getLockRetries();
 
@@ -2776,10 +2767,27 @@ public class BPlusTreeSelfTest extends GridCommonAbstractTest {
          * @param metaPageId Meta page ID.
          * @throws IgniteCheckedException If failed.
          */
-        public TestTree(ReuseList reuseList, boolean canGetRow, int cacheId, PageMemory pageMem, long metaPageId)
-            throws IgniteCheckedException {
-            super("test", cacheId, pageMem, null, new AtomicLong(), metaPageId, reuseList,
-                new IOVersions<>(new LongInnerIO(canGetRow)), new IOVersions<>(new LongLeafIO()), null);
+        public TestTree(
+            ReuseList reuseList,
+            boolean canGetRow,
+            int cacheId,
+            PageMemory pageMem,
+            long metaPageId,
+            TestPageLockListener lsnr
+        ) throws IgniteCheckedException {
+            super(
+                "test",
+                cacheId,
+                pageMem,
+                null,
+                new AtomicLong(),
+                metaPageId,
+                reuseList,
+                new IOVersions<>(new LongInnerIO(canGetRow)),
+                new IOVersions<>(new LongLeafIO()),
+                null,
+                lsnr
+            );
 
             PageIO.registerTest(latestInnerIO(), latestLeafIO());
 
@@ -2805,114 +2813,10 @@ public class BPlusTreeSelfTest extends GridCommonAbstractTest {
         /**
          * @return Thread ID.
          */
-        private static Object threadId() {
+        static Object threadId() {
             return Thread.currentThread().getId(); //.getName();
         }
 
-        /**
-         * @param read Read or write locks.
-         * @return Locks map.
-         */
-        private static Map<Long, Long> locks(boolean read) {
-            ConcurrentMap<Object, Map<Long, Long>> m = read ? readLocks : writeLocks;
-
-            Object thId = threadId();
-
-            Map<Long, Long> locks = m.get(thId);
-
-            if (locks == null) {
-                locks = new ConcurrentLinkedHashMap<>();
-
-                if (m.putIfAbsent(thId, locks) != null)
-                    locks = m.get(thId);
-            }
-
-            return locks;
-        }
-
-        /** {@inheritDoc} */
-        @Override public void onBeforeReadLock(int cacheId, long pageId, long page) {
-            if (PRINT_LOCKS)
-                X.println("  onBeforeReadLock: " + U.hexLong(pageId));
-//
-//            U.dumpStack();
-
-            assertNull(beforeReadLock.put(threadId(), pageId));
-        }
-
-        /** {@inheritDoc} */
-        @Override public void onReadLock(int cacheId, long pageId, long page, long pageAddr) {
-            if (PRINT_LOCKS)
-                X.println("  onReadLock: " + U.hexLong(pageId));
-
-            if (pageAddr != 0L) {
-                long actual = PageIO.getPageId(pageAddr);
-
-                checkPageId(pageId, pageAddr);
-
-                assertNull(locks(true).put(pageId, actual));
-            }
-
-            assertEquals(Long.valueOf(pageId), beforeReadLock.remove(threadId()));
-        }
-
-        /** {@inheritDoc} */
-        @Override public void onReadUnlock(int cacheId, long pageId, long page, long pageAddr) {
-            if (PRINT_LOCKS)
-                X.println("  onReadUnlock: " + U.hexLong(pageId));
-
-            checkPageId(pageId, pageAddr);
-
-            long actual = PageIO.getPageId(pageAddr);
-
-            assertEquals(Long.valueOf(actual), locks(true).remove(pageId));
-        }
-
-        /** {@inheritDoc} */
-        @Override public void onBeforeWriteLock(int cacheId, long pageId, long page) {
-            if (PRINT_LOCKS)
-                X.println("  onBeforeWriteLock: " + U.hexLong(pageId));
-
-            assertNull(beforeWriteLock.put(threadId(), pageId));
-        }
-
-        /** {@inheritDoc} */
-        @Override public void onWriteLock(int cacheId, long pageId, long page, long pageAddr) {
-            if (PRINT_LOCKS)
-                X.println("  onWriteLock: " + U.hexLong(pageId));
-//
-//            U.dumpStack();
-
-            if (pageAddr != 0L) {
-                checkPageId(pageId, pageAddr);
-
-                long actual = PageIO.getPageId(pageAddr);
-
-                if (actual == 0L)
-                    actual = pageId; // It is a newly allocated page.
-
-                assertNull(locks(false).put(pageId, actual));
-            }
-
-            assertEquals(Long.valueOf(pageId), beforeWriteLock.remove(threadId()));
-        }
-
-        /** {@inheritDoc} */
-        @Override public void onWriteUnlock(int cacheId, long pageId, long page, long pageAddr) {
-            if (PRINT_LOCKS)
-                X.println("  onWriteUnlock: " + U.hexLong(pageId));
-
-            assertEquals(effectivePageId(pageId), effectivePageId(PageIO.getPageId(pageAddr)));
-
-            assertEquals(Long.valueOf(pageId), locks(false).remove(pageId));
-        }
-
-        /**
-         * @return {@code true} If current thread does not keep any locks.
-         */
-        static boolean checkNoLocks() {
-            return locks(true).isEmpty() && locks(false).isEmpty();
-        }
 
         /**
          * @param b String builder.
@@ -2952,11 +2856,11 @@ public class BPlusTreeSelfTest extends GridCommonAbstractTest {
 
             b.a("\n--------read---------\n");
 
-            printLocks(b, readLocks, beforeReadLock);
+            printLocks(b, TestPageLockListener.readLocks, TestPageLockListener.beforeReadLock);
 
             b.a("\n-+------write---------\n");
 
-            printLocks(b, writeLocks, beforeWriteLock);
+            printLocks(b, TestPageLockListener.writeLocks, TestPageLockListener.beforeWriteLock);
 
             return b.toString();
         }
@@ -3152,4 +3056,127 @@ public class BPlusTreeSelfTest extends GridCommonAbstractTest {
             return vals.contains(val);
         }
     }
+
+    private static class TestPageLockListener implements PageLockListener {
+        /** */
+        static ConcurrentMap<Object, Long> beforeReadLock = new ConcurrentHashMap<>();
+
+        /** */
+        static ConcurrentMap<Object, Long> beforeWriteLock = new ConcurrentHashMap<>();
+
+        /** */
+        static ConcurrentMap<Object, Map<Long, Long>> readLocks = new ConcurrentHashMap<>();
+
+        /** */
+        static ConcurrentMap<Object, Map<Long, Long>> writeLocks = new ConcurrentHashMap<>();
+
+
+        /** {@inheritDoc} */
+        @Override public void onBeforeReadLock(int cacheId, long pageId, long page) {
+            if (PRINT_LOCKS)
+                X.println("  onBeforeReadLock: " + U.hexLong(pageId));
+//
+//            U.dumpStack();
+
+            assertNull(beforeReadLock.put(threadId(), pageId));
+        }
+
+        /** {@inheritDoc} */
+        @Override public void onReadLock(int cacheId, long pageId, long page, long pageAddr) {
+            if (PRINT_LOCKS)
+                X.println("  onReadLock: " + U.hexLong(pageId));
+
+            if (pageAddr != 0L) {
+                long actual = PageIO.getPageId(pageAddr);
+
+                checkPageId(pageId, pageAddr);
+
+                assertNull(locks(true).put(pageId, actual));
+            }
+
+            assertEquals(Long.valueOf(pageId), beforeReadLock.remove(threadId()));
+        }
+
+        /** {@inheritDoc} */
+        @Override public void onReadUnlock(int cacheId, long pageId, long page, long pageAddr) {
+            if (PRINT_LOCKS)
+                X.println("  onReadUnlock: " + U.hexLong(pageId));
+
+            checkPageId(pageId, pageAddr);
+
+            long actual = PageIO.getPageId(pageAddr);
+
+            assertEquals(Long.valueOf(actual), locks(true).remove(pageId));
+        }
+
+        /** {@inheritDoc} */
+        @Override public void onBeforeWriteLock(int cacheId, long pageId, long page) {
+            if (PRINT_LOCKS)
+                X.println("  onBeforeWriteLock: " + U.hexLong(pageId));
+
+            assertNull(beforeWriteLock.put(threadId(), pageId));
+        }
+
+        /** {@inheritDoc} */
+        @Override public void onWriteLock(int cacheId, long pageId, long page, long pageAddr) {
+            if (PRINT_LOCKS)
+                X.println("  onWriteLock: " + U.hexLong(pageId));
+//
+//            U.dumpStack();
+
+            if (pageAddr != 0L) {
+                checkPageId(pageId, pageAddr);
+
+                long actual = PageIO.getPageId(pageAddr);
+
+                if (actual == 0L)
+                    actual = pageId; // It is a newly allocated page.
+
+                assertNull(locks(false).put(pageId, actual));
+            }
+
+            assertEquals(Long.valueOf(pageId), beforeWriteLock.remove(threadId()));
+        }
+
+        /** {@inheritDoc} */
+        @Override public void onWriteUnlock(int cacheId, long pageId, long page, long pageAddr) {
+            if (PRINT_LOCKS)
+                X.println("  onWriteUnlock: " + U.hexLong(pageId));
+
+            assertEquals(effectivePageId(pageId), effectivePageId(PageIO.getPageId(pageAddr)));
+
+            assertEquals(Long.valueOf(pageId), locks(false).remove(pageId));
+        }
+
+        /**
+         * @param read Read or write locks.
+         * @return Locks map.
+         */
+        private static Map<Long, Long> locks(boolean read) {
+            ConcurrentMap<Object, Map<Long, Long>> m = read ? readLocks : writeLocks;
+
+            Object thId = threadId();
+
+            Map<Long, Long> locks = m.get(thId);
+
+            if (locks == null) {
+                locks = new ConcurrentLinkedHashMap<>();
+
+                if (m.putIfAbsent(thId, locks) != null)
+                    locks = m.get(thId);
+            }
+
+            return locks;
+        }
+
+
+
+        /**
+         * @return {@code true} If current thread does not keep any locks.
+         */
+        static boolean checkNoLocks() {
+            return locks(true).isEmpty() && locks(false).isEmpty();
+        }
+
+    }
 }
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/database/CacheFreeListImplSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/database/CacheFreeListSelfTest.java
similarity index 98%
rename from modules/core/src/test/java/org/apache/ignite/internal/processors/database/CacheFreeListImplSelfTest.java
rename to modules/core/src/test/java/org/apache/ignite/internal/processors/database/CacheFreeListSelfTest.java
index a852321..d1e905c 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/database/CacheFreeListImplSelfTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/database/CacheFreeListSelfTest.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2019 GridGain Systems, Inc. and Contributors.
- * 
+ *
  * Licensed under the GridGain Community Edition License (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
- * 
+ *
  *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
- * 
+ *
  * 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.
@@ -42,7 +42,7 @@ import org.apache.ignite.internal.processors.cache.persistence.CacheDataRow;
 import org.apache.ignite.internal.processors.cache.persistence.DataRegion;
 import org.apache.ignite.internal.processors.cache.persistence.DataRegionMetricsImpl;
 import org.apache.ignite.internal.processors.cache.persistence.evict.NoOpPageEvictionTracker;
-import org.apache.ignite.internal.processors.cache.persistence.freelist.CacheFreeListImpl;
+import org.apache.ignite.internal.processors.cache.persistence.freelist.CacheFreeList;
 import org.apache.ignite.internal.processors.cache.persistence.freelist.FreeList;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.CacheVersionIO;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
@@ -59,7 +59,7 @@ import org.junit.runners.JUnit4;
  *
  */
 @RunWith(JUnit4.class)
-public class CacheFreeListImplSelfTest extends GridCommonAbstractTest {
+public class CacheFreeListSelfTest extends GridCommonAbstractTest {
     /** */
     private static final int CPUS = Runtime.getRuntime().availableProcessors();
 
@@ -361,7 +361,17 @@ public class CacheFreeListImplSelfTest extends GridCommonAbstractTest {
 
         DataRegion dataRegion = new DataRegion(pageMem, plcCfg, regionMetrics, new NoOpPageEvictionTracker());
 
-        return new CacheFreeListImpl(1, "freelist", regionMetrics, dataRegion, null, null, metaPageId, true);
+        return new CacheFreeList(
+            1,
+            "freelist",
+            regionMetrics,
+            dataRegion,
+            null,
+            null,
+            metaPageId,
+            true,
+            null
+        );
     }
 
     /**
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/database/IndexStorageSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/database/IndexStorageSelfTest.java
index b2e8e82..309633ba 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/database/IndexStorageSelfTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/database/IndexStorageSelfTest.java
@@ -111,6 +111,7 @@ public class IndexStorageSelfTest extends GridCommonAbstractTest {
                         null,
                         mem.allocatePage(cacheId, PageIdAllocator.INDEX_PARTITION, PageMemory.FLAG_IDX),
                         true,
+                        null,
                         null
                     );
 
diff --git a/modules/core/src/test/java/org/apache/ignite/loadtests/hashmap/GridCacheTestContext.java b/modules/core/src/test/java/org/apache/ignite/loadtests/hashmap/GridCacheTestContext.java
index e9edff1..46811bd 100644
--- a/modules/core/src/test/java/org/apache/ignite/loadtests/hashmap/GridCacheTestContext.java
+++ b/modules/core/src/test/java/org/apache/ignite/loadtests/hashmap/GridCacheTestContext.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2019 GridGain Systems, Inc. and Contributors.
- * 
+ *
  * Licensed under the GridGain Community Edition License (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
- * 
+ *
  *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
- * 
+ *
  * 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.
@@ -21,6 +21,7 @@ import org.apache.ignite.cache.store.CacheStore;
 import org.apache.ignite.configuration.CacheConfiguration;
 import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
 import org.apache.ignite.internal.processors.cache.CacheAffinitySharedManager;
+import org.apache.ignite.internal.processors.cache.CacheDiagnosticManager;
 import org.apache.ignite.internal.processors.cache.CacheOsConflictResolutionManager;
 import org.apache.ignite.internal.processors.cache.CacheType;
 import org.apache.ignite.internal.processors.cache.GridCacheAffinityManager;
@@ -83,7 +84,8 @@ public class GridCacheTestContext<K, V> extends GridCacheContext<K, V> {
                 new CacheNoopJtaManager(),
                 null,
                 null,
-                null
+                null,
+                new CacheDiagnosticManager()
             ),
             defaultCacheConfiguration(),
             null,
diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java
index 3d1ae50..e707f13 100644
--- a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java
+++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java
@@ -71,7 +71,7 @@ import org.apache.ignite.internal.processors.continuous.GridMessageListenSelfTes
 import org.apache.ignite.internal.processors.database.BPlusTreeFakeReuseSelfTest;
 import org.apache.ignite.internal.processors.database.BPlusTreeReuseSelfTest;
 import org.apache.ignite.internal.processors.database.BPlusTreeSelfTest;
-import org.apache.ignite.internal.processors.database.CacheFreeListImplSelfTest;
+import org.apache.ignite.internal.processors.database.CacheFreeListSelfTest;
 import org.apache.ignite.internal.processors.database.DataRegionMetricsSelfTest;
 import org.apache.ignite.internal.processors.database.IndexStorageSelfTest;
 import org.apache.ignite.internal.processors.database.SwapPathConstructionSelfTest;
@@ -202,7 +202,7 @@ public class IgniteBasicTestSuite {
         suite.addTest(new JUnit4TestAdapter(BPlusTreeFakeReuseSelfTest.class));
         suite.addTest(new JUnit4TestAdapter(BPlusTreeReuseSelfTest.class));
         suite.addTest(new JUnit4TestAdapter(IndexStorageSelfTest.class));
-        suite.addTest(new JUnit4TestAdapter(CacheFreeListImplSelfTest.class));
+        suite.addTest(new JUnit4TestAdapter(CacheFreeListSelfTest.class));
         suite.addTest(new JUnit4TestAdapter(DataRegionMetricsSelfTest.class));
         suite.addTest(new JUnit4TestAdapter(SwapPathConstructionSelfTest.class));
         suite.addTest(new JUnit4TestAdapter(BitSetIntSetTest.class));
diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgnitePdsTestSuite4.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgnitePdsTestSuite4.java
index c199c3c..92f1202 100644
--- a/modules/core/src/test/java/org/apache/ignite/testsuites/IgnitePdsTestSuite4.java
+++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgnitePdsTestSuite4.java
@@ -31,6 +31,13 @@ import org.apache.ignite.internal.processors.cache.persistence.db.IgnitePdsPageE
 import org.apache.ignite.internal.processors.cache.persistence.db.IgnitePdsPartitionPreloadTest;
 import org.apache.ignite.internal.processors.cache.persistence.db.IgnitePdsStartWIthEmptyArchive;
 import org.apache.ignite.internal.processors.cache.persistence.db.IgnitePdsTransactionsHangTest;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.PageLockTrackerManagerTest;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.SharedPageLockTrackerTest;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.dumpprocessors.ToFileDumpProcessorTest;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.log.HeapArrayLockLogTest;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.log.OffHeapLockLogTest;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.stack.HeapArrayLockStackTest;
+import org.apache.ignite.internal.processors.cache.persistence.diagnostic.pagelocktracker.stack.OffHeapLockStackTest;
 import org.apache.ignite.internal.processors.cache.persistence.file.FileDownloaderTest;
 import org.apache.ignite.testframework.GridTestUtils;
 import org.junit.runner.RunWith;
@@ -68,6 +75,15 @@ public class IgnitePdsTestSuite4 {
         GridTestUtils.addTestIfNeeded(suite, IgnitePdsStartWIthEmptyArchive.class, ignoredTests);
         GridTestUtils.addTestIfNeeded(suite, CorruptedTreeFailureHandlingTest.class, ignoredTests);
 
+        // Page lock tracker tests.
+        GridTestUtils.addTestIfNeeded(suite, PageLockTrackerManagerTest.class, ignoredTests);
+        GridTestUtils.addTestIfNeeded(suite, SharedPageLockTrackerTest.class, ignoredTests);
+        GridTestUtils.addTestIfNeeded(suite, ToFileDumpProcessorTest.class, ignoredTests);
+        GridTestUtils.addTestIfNeeded(suite, HeapArrayLockLogTest.class, ignoredTests);
+        GridTestUtils.addTestIfNeeded(suite, HeapArrayLockStackTest.class, ignoredTests);
+        GridTestUtils.addTestIfNeeded(suite, OffHeapLockLogTest.class, ignoredTests);
+        GridTestUtils.addTestIfNeeded(suite, OffHeapLockStackTest.class, ignoredTests);
+
         return suite;
     }
 
diff --git a/modules/core/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java b/modules/core/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java
index 1778498..e97f82d 100644
--- a/modules/core/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java
@@ -1066,7 +1066,7 @@ public class GridCommandHandlerTest extends GridCommandHandlerAbstractTest {
         for (CacheSubcommands cmd : CacheSubcommands.values()) {
             Class<? extends Enum<? extends CommandArg>> args = cmd.getCommandArgs();
 
-            if(args != null)
+            if (args != null)
                 for (Enum<? extends CommandArg> arg : args.getEnumConstants())
                     assertTrue(arg.toString(), p.matcher(arg.toString()).matches());
         }
@@ -2454,9 +2454,53 @@ public class GridCommandHandlerTest extends GridCommandHandlerAbstractTest {
     }
 
     /**
-     * Starts several long transactions in order to test --tx command.
-     * Transactions will last until unlock latch is released: first transaction will wait for unlock latch directly,
-     * some others will wait for key lock acquisition.
+     * Test execution of --diagnostic command.
+     *
+     * @throws Exception if failed.
+     */
+    @Test
+    public void testDiagnosticPageLocksTracker() throws Exception {
+        Ignite ignite = startGrids(4);
+
+        Collection<ClusterNode> nodes = ignite.cluster().nodes();
+
+        List<ClusterNode> nodes0 = new ArrayList<>(nodes);
+
+        ClusterNode node0 = nodes0.get(0);
+        ClusterNode node1 = nodes0.get(1);
+        ClusterNode node2 = nodes0.get(2);
+        ClusterNode node3 = nodes0.get(3);
+
+        ignite.cluster().active(true);
+
+        String dir = U.defaultWorkDirectory() + "/diagnostic/";
+
+        assertEquals(EXIT_CODE_OK, execute("--diagnostic"));
+        assertEquals(EXIT_CODE_OK, execute("--diagnostic", "help"));
+        assertEquals(EXIT_CODE_OK, execute("--diagnostic", "pageLocks", "help"));
+
+        assertEquals(EXIT_CODE_OK, execute("--diagnostic", "pageLocks", "dump"));
+        assertEquals(EXIT_CODE_OK, execute("--diagnostic", "pageLocks", "dump_log"));
+        assertEquals(EXIT_CODE_OK, execute("--diagnostic", "pageLocks", "dump", dir));
+
+        assertEquals(EXIT_CODE_OK, execute("--diagnostic", "pageLocks", "dump", "--all"));
+        assertEquals(EXIT_CODE_OK, execute("--diagnostic", "pageLocks", "dump_log", "--all"));
+        assertEquals(EXIT_CODE_OK, execute("--diagnostic", "pageLocks", "dump", dir, "--all"));
+
+        assertEquals(EXIT_CODE_OK, execute("--diagnostic", "pageLocks", "dump", "--nodes",
+            node0.id().toString(), node2.id().toString()));
+        assertEquals(EXIT_CODE_OK, execute("--diagnostic", "pageLocks", "dump", "--nodes",
+            node0.consistentId().toString(), node2.consistentId().toString()));
+
+        assertEquals(EXIT_CODE_OK, execute("--diagnostic", "pageLocks", "dump_log", "--nodes",
+            node1.id().toString(), node3.id().toString()));
+        assertEquals(EXIT_CODE_OK, execute("--diagnostic", "pageLocks", "dump", dir, "--nodes",
+            node1.consistentId().toString(), node3.consistentId().toString()));
+    }
+
+    /**
+     * Starts several long transactions in order to test --tx command. Transactions will last until unlock latch is
+     * released: first transaction will wait for unlock latch directly, some others will wait for key lock acquisition.
      *
      * @param lockLatch Lock latch. Will be released inside body of the first transaction.
      * @param unlockLatch Unlock latch. Should be released externally. First transaction won't be finished until unlock
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java
index 15e8993..b90093c 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java
@@ -79,6 +79,7 @@ import org.apache.ignite.internal.processors.cache.persistence.tree.io.BPlusIO;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.BPlusMetaIO;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
 import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
+import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
 import org.apache.ignite.internal.processors.cache.query.GridCacheQueryMarshallable;
 import org.apache.ignite.internal.processors.cache.query.GridCacheTwoStepQuery;
 import org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode;
@@ -3204,6 +3205,11 @@ public class IgniteH2Indexing implements GridQueryIndexing {
 
         int inlineSize = getInlineSize(page, grpId, pageMemory);
 
+        String grpName = ctx.cache().cacheGroup(grpId).cacheOrGroupName();
+
+        PageLockListener lockLsnr = ctx.cache().context().diagnostic()
+            .pageLockTracker().createPageLockTracker(grpName + "IndexTree##" + indexName);
+
         BPlusTree<GridH2SearchRow, GridH2Row> tree = new BPlusTree<GridH2SearchRow, GridH2Row>(
             indexName,
             grpId,
@@ -3214,7 +3220,8 @@ public class IgniteH2Indexing implements GridQueryIndexing {
             reuseList,
             H2ExtrasInnerIO.getVersions(inlineSize, mvccEnabled),
             H2ExtrasLeafIO.getVersions(inlineSize, mvccEnabled),
-            ctx.failure()
+            ctx.failure(),
+            lockLsnr
         ) {
             @Override protected int compare(BPlusIO io, long pageAddr, int idx, GridH2SearchRow row) {
                 throw new AssertionError();
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/H2Tree.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/H2Tree.java
index d41b9b3..cd3619e 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/H2Tree.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/H2Tree.java
@@ -156,7 +156,17 @@ public abstract class H2Tree extends BPlusTree<GridH2SearchRow, GridH2Row> {
         @Nullable FailureProcessor failureProcessor,
         IgniteLogger log
     ) throws IgniteCheckedException {
-        super(name, grpId, pageMem, wal, globalRmvId, metaPageId, reuseList, failureProcessor);
+        super(
+            name,
+            grpId,
+            pageMem,
+            wal,
+            globalRmvId,
+            metaPageId,
+            reuseList,
+            failureProcessor,
+            null
+        );
 
         if (!initNew) {
             // Page is ready - read inline size from it.