diff --git a/src/PMDK.sln b/src/PMDK.sln
index b35594d61d2f88b1d83921563ff945b7ce70785a..18f14b72e928f72893bccf4d24669de70a04930b 100644
--- a/src/PMDK.sln
+++ b/src/PMDK.sln
@@ -538,6 +538,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "obj_include", "test\obj_inc
 EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libpmempool_sync", "test\libpmempool_sync\libpmempool_sync.vcxproj", "{AE1C32FB-9B52-4760-ABFC-0D2FA2C7A6C8}"
 EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "obj_defrag_advanced", "test\obj_defrag_advanced\obj_defrag_advanced.vcxproj", "{AE952763-5C84-43FC-B344-CACC950F056C}"
+EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "signal_handle", "test\signal_handle\signal_handle.vcxproj", "{AE9E908D-BAEC-491F-9914-436B3CE35E94}"
 EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "obj_ctl_config", "test\obj_ctl_config\obj_ctl_config.vcxproj", "{AEAA72CD-E060-417C-9CA1-49B4738384E0}"
@@ -1416,6 +1418,10 @@ Global
 		{AE1C32FB-9B52-4760-ABFC-0D2FA2C7A6C8}.Debug|x64.Build.0 = Debug|x64
 		{AE1C32FB-9B52-4760-ABFC-0D2FA2C7A6C8}.Release|x64.ActiveCfg = Release|x64
 		{AE1C32FB-9B52-4760-ABFC-0D2FA2C7A6C8}.Release|x64.Build.0 = Release|x64
+		{AE952763-5C84-43FC-B344-CACC950F056C}.Debug|x64.ActiveCfg = Debug|x64
+		{AE952763-5C84-43FC-B344-CACC950F056C}.Debug|x64.Build.0 = Debug|x64
+		{AE952763-5C84-43FC-B344-CACC950F056C}.Release|x64.ActiveCfg = Release|x64
+		{AE952763-5C84-43FC-B344-CACC950F056C}.Release|x64.Build.0 = Release|x64
 		{AE9E908D-BAEC-491F-9914-436B3CE35E94}.Debug|x64.ActiveCfg = Debug|x64
 		{AE9E908D-BAEC-491F-9914-436B3CE35E94}.Debug|x64.Build.0 = Debug|x64
 		{AE9E908D-BAEC-491F-9914-436B3CE35E94}.Release|x64.ActiveCfg = Release|x64
@@ -1926,6 +1932,7 @@ Global
 		{A7CA7975-CEDB-48E6-9AEB-1209DCBD07F2} = {91C30620-70CA-46C7-AC71-71F3C602690E}
 		{AB15A115-E429-4123-BEBF-206FBA4CF615} = {63C9B3F8-437D-4AD9-B32D-D04AE38C35B6}
 		{AE1C32FB-9B52-4760-ABFC-0D2FA2C7A6C8} = {2F543422-4B8A-4898-BE6B-590F52B4E9D1}
+		{AE952763-5C84-43FC-B344-CACC950F056C} = {63C9B3F8-437D-4AD9-B32D-D04AE38C35B6}
 		{AE9E908D-BAEC-491F-9914-436B3CE35E94} = {B870D8A6-12CD-4DD0-B843-833695C2310A}
 		{AEAA72CD-E060-417C-9CA1-49B4738384E0} = {63C9B3F8-437D-4AD9-B32D-D04AE38C35B6}
 		{AF038868-2432-4159-A62F-941F11D12C5D} = {59AB6976-D16B-48D0-8D16-94360D3FE51D}
diff --git a/src/test/Makefile b/src/test/Makefile
index 7911541a7a015053ce368ae742eec8be367afa14..a4aaab44a29cd21b601312924e9c4f27bc432308 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -1,5 +1,5 @@
 #
-# Copyright 2014-2019, Intel Corporation
+# Copyright 2014-2020, Intel Corporation
 #
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions
@@ -88,6 +88,7 @@ OBJ_TESTS = \
 	obj_ctl_stats\
 	obj_debug\
 	obj_defrag\
+	obj_defrag_advanced\
 	obj_direct\
 	obj_direct_volatile\
 	obj_extend\
diff --git a/src/test/obj_defrag_advanced/.gitignore b/src/test/obj_defrag_advanced/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..55413109ceadc4e20192f558c06c425488b8cf3b
--- /dev/null
+++ b/src/test/obj_defrag_advanced/.gitignore
@@ -0,0 +1 @@
+obj_defrag_advanced
diff --git a/src/test/obj_defrag_advanced/Makefile b/src/test/obj_defrag_advanced/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..f2e8eda94bf761a84a119d58b963abcdcedeefdf
--- /dev/null
+++ b/src/test/obj_defrag_advanced/Makefile
@@ -0,0 +1,43 @@
+#
+# Copyright 2020, Intel Corporation
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+#
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in
+#       the documentation and/or other materials provided with the
+#       distribution.
+#
+#     * Neither the name of the copyright holder nor the names of its
+#       contributors may be used to endorse or promote products derived
+#       from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+#
+# src/test/obj_defrag_advanced/Makefile -- build obj_defrag_advanced test
+#
+TARGET = obj_defrag_advanced
+OBJS = vgraph.o pgraph.o obj_defrag_advanced.o
+
+LIBPMEMOBJ=y
+
+include ../Makefile.inc
+
+CFLAGS += -DDEBUG
diff --git a/src/test/obj_defrag_advanced/TESTS.py b/src/test/obj_defrag_advanced/TESTS.py
new file mode 100755
index 0000000000000000000000000000000000000000..8846f119b71b22fb1a313470c27a8f802ad23464
--- /dev/null
+++ b/src/test/obj_defrag_advanced/TESTS.py
@@ -0,0 +1,136 @@
+#!../env.py
+#
+# Copyright 2020, Intel Corporation
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+#
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in
+#       the documentation and/or other materials provided with the
+#       distribution.
+#
+#     * Neither the name of the copyright holder nor the names of its
+#       contributors may be used to endorse or promote products derived
+#       from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+import testframework as t
+
+
+class ObjDefragAdvanced(t.BaseTest):
+    test_type = t.Short
+
+    max_nodes = 50
+    max_edges = 10
+    graph_copies = 10
+    pool_size = 500 * t.MiB
+    max_rounds = 10
+    min_root_size = 0
+
+    def run(self, ctx):
+        path = ctx.create_holey_file(self.pool_size, 'testfile')
+        dump1 = 'dump1.log'
+        dump2 = 'dump2.log'
+
+        ctx.exec('obj_defrag_advanced',
+            'op_pool_create', path,
+            'op_graph_create', str(self.max_nodes), str(self.max_edges),
+            str(self.graph_copies), str(self.min_root_size),
+            'op_graph_dump', dump1,
+            'op_graph_defrag', str(self.max_rounds),
+            'op_graph_dump', dump2,
+            'op_pool_close',
+            'op_dump_compare', dump1, dump2)
+
+
+class TEST0(ObjDefragAdvanced):
+    max_nodes = 5
+    max_edges = 5
+    graph_copies = 5
+
+
+class TEST1(ObjDefragAdvanced):
+    max_nodes = 2048
+    max_edges = 5
+    graph_copies = 5
+
+
+class TEST2(ObjDefragAdvanced):
+    test_type = t.Medium
+    fs = t.Pmem
+
+    max_nodes = 512
+    max_edges = 64
+    graph_copies = 5
+    min_root_size = 4096
+
+
+class ObjDefragAdvancedMt(ObjDefragAdvanced):
+    test_type = t.Medium
+    fs = t.Pmem
+
+    nthreads = 2
+    ncycles = 2
+
+    def run(self, ctx):
+        path = ctx.create_holey_file(self.pool_size, 'testfile')
+
+        ctx.exec('obj_defrag_advanced',
+            'op_pool_create', path,
+            'op_graph_create_n_defrag_mt', str(self.max_nodes), str(self.max_edges),
+            str(self.graph_copies), str(self.min_root_size), str(self.max_rounds),
+            str(self.nthreads), str(self.ncycles),
+            'op_pool_close');
+
+
+class TEST3(ObjDefragAdvancedMt):
+    max_nodes = 256
+    max_edges = 64
+    graph_copies = 10
+    nthreads = 1
+    ncycles = 25
+
+
+class TEST4(ObjDefragAdvancedMt):
+    max_nodes = 128
+    max_edges = 32
+    graph_copies = 10
+    nthreads = 10
+    ncycles = 25
+
+
+class TEST5(ObjDefragAdvancedMt):
+    max_nodes = 256
+    max_edges = 32
+    graph_copies = 5
+    nthreads = 10
+    ncycles = 25
+
+
+# a testcase designed to verify the pool content in case of fail
+# class TESTX(ObjDefragAdvanced):
+#     def run(self, ctx):
+#         path = '/custom/pool/path'
+#         dump_prefix = 'dump'
+#
+#         ctx.exec('obj_defrag_advanced',
+#             'op_pool_open', path,
+#             'op_graph_dump_all', dump_prefix,
+#             'op_pool_close')
diff --git a/src/test/obj_defrag_advanced/obj_defrag_advanced.c b/src/test/obj_defrag_advanced/obj_defrag_advanced.c
new file mode 100644
index 0000000000000000000000000000000000000000..7dc42161998e87b58aec492712b982df18d930dc
--- /dev/null
+++ b/src/test/obj_defrag_advanced/obj_defrag_advanced.c
@@ -0,0 +1,595 @@
+/*
+ * Copyright 2020, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in
+ *       the documentation and/or other materials provided with the
+ *       distribution.
+ *
+ *     * Neither the name of the copyright holder nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * obj_defrag_advanced.c -- test for libpmemobj defragmentation feature
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <stddef.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include "rand.h"
+#include "vgraph.h"
+#include "pgraph.h"
+#include "os_thread.h"
+#include "unittest.h"
+
+struct create_params_t {
+	uint64_t seed;
+	rng_t rng;
+
+	struct vgraph_params vparams;
+	struct pgraph_params pparams;
+};
+
+/*
+ * graph_create -- create a graph
+ * - generate an intermediate volatile graph representation
+ * - use the volatile graph to allocate a persistent one
+ */
+static void
+graph_create(struct create_params_t *task, PMEMobjpool *pop, PMEMoid *oidp,
+		rng_t *rngp)
+{
+	struct vgraph_t *vgraph = vgraph_new(&task->vparams, rngp);
+	pgraph_new(pop, oidp, vgraph, &task->pparams, rngp);
+	vgraph_delete(vgraph);
+}
+
+/*
+ * graph_defrag -- defragment the pool
+ * - collect pointers to all PMEMoids
+ * - do a sanity checks
+ * - call pmemobj_defrag
+ * - return # of relocated objects
+ */
+static size_t
+graph_defrag(PMEMobjpool *pop, PMEMoid oid)
+{
+	struct pgraph_t *pgraph = (struct pgraph_t *)pmemobj_direct(oid);
+
+	/* count number of oids */
+	unsigned oidcnt = pgraph->nodes_num;
+	for (unsigned i = 0; i < pgraph->nodes_num; ++i) {
+		struct pnode_t *pnode = (struct pnode_t *)pmemobj_direct
+				(pgraph->nodes[i]);
+		oidcnt += pnode->edges_num;
+	}
+
+	/* create array of oid pointers */
+	PMEMoid **oidv = (PMEMoid **)MALLOC(sizeof(PMEMoid *) * oidcnt);
+	unsigned oidi = 0;
+	for (unsigned i = 0; i < pgraph->nodes_num; ++i) {
+		oidv[oidi++] = &pgraph->nodes[i];
+
+		struct pnode_t *pnode = (struct pnode_t *)pmemobj_direct
+				(pgraph->nodes[i]);
+		for (unsigned j = 0; j < pnode->edges_num; ++j) {
+			oidv[oidi++] = &pnode->edges[j];
+		}
+	}
+
+	UT_ASSERTeq(oidi, oidcnt);
+
+	/* check if all oids are valid */
+	for (unsigned i = 0; i < oidcnt; ++i) {
+		void *ptr = pmemobj_direct(*oidv[i]);
+		UT_ASSERTne(ptr, NULL);
+	}
+
+	/* check if all oids appear only once */
+	for (unsigned i = 0; i < oidcnt - 1; ++i) {
+		for (unsigned j = i + 1; j < oidcnt; ++j) {
+			UT_ASSERTne(oidv[i], oidv[j]);
+		}
+	}
+
+	struct pobj_defrag_result result;
+	int ret = pmemobj_defrag(pop, oidv, oidcnt, &result);
+	UT_ASSERTeq(ret, 0);
+	UT_ASSERTeq(result.total, pgraph->nodes_num);
+
+	FREE(oidv);
+
+	return result.relocated;
+}
+
+/*
+ * graph_defrag_ntimes -- defragment the graph N times
+ * - where N <= max_rounds
+ * - it stops defrag if # of relocated objects == 0
+ */
+static void
+graph_defrag_ntimes(PMEMobjpool *pop, PMEMoid oid, unsigned max_rounds)
+{
+	size_t relocated;
+	unsigned rounds = 0;
+	do {
+		relocated = graph_defrag(pop, oid);
+		++rounds;
+	} while (relocated > 0 && rounds < max_rounds);
+
+	UT_OUT("# of defragmentation rounds: %u", rounds);
+}
+
+#define HAS_TO_EXIST (1)
+
+/*
+ * graph_dump -- dump a graph from the pool to a text file
+ */
+static void
+graph_dump(PMEMoid oid, const char *path, int has_exist)
+{
+	struct pgraph_t *pgraph = (struct pgraph_t *)pmemobj_direct(oid);
+	if (has_exist)
+		UT_ASSERTne(pgraph, NULL);
+
+	if (pgraph)
+		pgraph_print(pgraph, path);
+}
+
+#define FGETS_BUFF_LEN 1024
+
+/*
+ * dump_compare -- compare graph dumps
+ * Test fails if the contents of dumps do not match
+ */
+static void
+dump_compare(const char *path1, const char *path2)
+{
+	FILE *dump1 = FOPEN(path1, "r");
+	FILE *dump2 = FOPEN(path2, "r");
+
+	char buff1[FGETS_BUFF_LEN];
+	char buff2[FGETS_BUFF_LEN];
+	char *sret1, *sret2;
+
+	do {
+		sret1 = fgets(buff1, FGETS_BUFF_LEN, dump1);
+		sret2 = fgets(buff2, FGETS_BUFF_LEN, dump2);
+
+		/* both files have to end at the same time */
+		if (!sret1) {
+			UT_ASSERTeq(sret2, NULL);
+
+			FCLOSE(dump1);
+			FCLOSE(dump2);
+
+			return;
+		}
+
+		UT_ASSERTeq(sret1, buff1);
+		UT_ASSERTeq(sret2, buff2);
+
+		UT_ASSERTeq(strcmp(buff1, buff2), 0);
+	} while (1);
+}
+
+/*
+ * create_params_init -- initialize create params
+ */
+static void
+create_params_init(struct create_params_t *params)
+{
+	params->seed = 1;
+
+	/* good enough defaults - no magic here */
+	params->vparams.max_nodes = 50;
+	params->vparams.max_edges = 10;
+	params->vparams.range_nodes = 10;
+	params->vparams.range_edges = 10;
+	params->vparams.min_pattern_size = 8;
+	params->vparams.max_pattern_size = 1024;
+
+	params->pparams.graph_copies = 10;
+}
+
+/* global state */
+static struct global_t {
+	PMEMobjpool *pop;
+} global;
+
+/*
+ * PMEMobj root object structure
+ */
+struct root_t {
+	unsigned graphs_num;
+	PMEMoid graphs[];
+};
+
+/*
+ * root_size -- calculate a root object size
+ */
+static inline size_t
+root_size(unsigned graph_num, size_t min_root_size)
+{
+	size_t size = sizeof(struct root_t) + sizeof(PMEMoid) * graph_num;
+	return MAX(size, min_root_size);
+}
+
+#define QUERY_GRAPHS_NUM UINT_MAX
+
+static struct root_t *
+get_root(unsigned graphs_num, size_t min_root_size)
+{
+	PMEMoid roid;
+	struct root_t *root;
+
+	if (graphs_num == QUERY_GRAPHS_NUM) {
+		/* allocate a root object without graphs */
+		roid = pmemobj_root(global.pop, root_size(0, 0));
+		if (OID_IS_NULL(roid))
+			UT_FATAL("!pmemobj_root:");
+		root = (struct root_t *)pmemobj_direct(roid);
+		UT_ASSERTne(root, NULL);
+
+		graphs_num = root->graphs_num;
+	}
+
+	UT_ASSERT(graphs_num > 0);
+
+	/* reallocate a root object with all known graphs */
+	roid = pmemobj_root(global.pop, root_size(graphs_num, min_root_size));
+	if (OID_IS_NULL(roid))
+		UT_FATAL("!pmemobj_root:");
+	root = (struct root_t *)pmemobj_direct(roid);
+	UT_ASSERTne(root, NULL);
+
+	return root;
+}
+
+/*
+ * parse_nonzero -- parse non-zero unsigned
+ */
+static void
+parse_nonzero(unsigned *var, const char *arg)
+{
+	unsigned long v = STRTOUL(arg, NULL, 10);
+	UT_ASSERTne(v, 0);
+	UT_ASSERT(v < UINT_MAX);
+
+	*var = v;
+}
+
+#define GRAPH_LAYOUT POBJ_LAYOUT_NAME(graph)
+
+/*
+ * op_pool_create -- create a pool
+ */
+static int
+op_pool_create(const struct test_case *tc, int argc, char *argv[])
+{
+	if (argc < 1)
+		UT_FATAL("usage: %s <path>", tc->name);
+
+	/* parse arguments */
+	const char *path = argv[0];
+
+	/* open a pool */
+	global.pop = pmemobj_create(path, GRAPH_LAYOUT, 0, S_IWUSR | S_IRUSR);
+	if (global.pop == NULL) {
+		UT_FATAL("!pmemobj_create: %s", path);
+	}
+
+	return 1;
+}
+
+/*
+ * op_pool_close -- close the poll
+ */
+static int
+op_pool_close(const struct test_case *tc, int argc, char *argv[])
+{
+	pmemobj_close(global.pop);
+	global.pop = NULL;
+
+	return 0;
+}
+
+/*
+ * op_graph_create -- create a graph
+ */
+static int
+op_graph_create(const struct test_case *tc, int argc, char *argv[])
+{
+	if (argc < 4)
+		UT_FATAL("usage: %s <max-nodes> <max-edges> <graph-copies>"
+				" <min-root-size>", tc->name);
+
+	/* parse arguments */
+	struct create_params_t cparams;
+	create_params_init(&cparams);
+	parse_nonzero(&cparams.vparams.max_nodes, argv[0]);
+	parse_nonzero(&cparams.vparams.max_edges, argv[1]);
+	parse_nonzero(&cparams.pparams.graph_copies, argv[2]);
+	size_t min_root_size = STRTOULL(argv[3], NULL, 10);
+
+	struct root_t *root = get_root(1, min_root_size);
+
+	randomize(cparams.seed);
+
+	/* generate a single graph */
+	graph_create(&cparams, global.pop, &root->graphs[0], NULL);
+	root->graphs_num = 1;
+	pmemobj_persist(global.pop, root, root_size(1, min_root_size));
+
+	return 4;
+}
+
+/*
+ * op_graph_dump -- dump the graph
+ */
+static int
+op_graph_dump(const struct test_case *tc, int argc, char *argv[])
+{
+	if (argc < 1)
+		UT_FATAL("usage: %s <dump>", tc->name);
+
+	/* parse arguments */
+	const char *dump = argv[0];
+
+	struct root_t *root = get_root(QUERY_GRAPHS_NUM, 0);
+	UT_ASSERTeq(root->graphs_num, 1);
+
+	/* dump the graph before defrag */
+	graph_dump(root->graphs[0], dump, HAS_TO_EXIST);
+
+	return 1;
+}
+
+/*
+ * op_graph_defrag -- defrag the graph
+ */
+static int
+op_graph_defrag(const struct test_case *tc, int argc, char *argv[])
+{
+	if (argc < 1)
+		UT_FATAL("usage: %s <max-rounds>", tc->name);
+
+	/* parse arguments */
+	unsigned max_rounds;
+	parse_nonzero(&max_rounds, argv[0]);
+
+	struct root_t *root = get_root(QUERY_GRAPHS_NUM, 0);
+	UT_ASSERTeq(root->graphs_num, 1);
+
+	/* do the defrag */
+	graph_defrag_ntimes(global.pop, root->graphs[0], max_rounds);
+
+	return 1;
+}
+
+/*
+ * op_dump_compare -- compare dumps
+ */
+static int
+op_dump_compare(const struct test_case *tc, int argc, char *argv[])
+{
+	if (argc < 2)
+		UT_FATAL("usage: %s <dump1> <dump2>", tc->name);
+
+	/* parse arguments */
+	const char *dump1 = argv[0];
+	const char *dump2 = argv[1];
+
+	dump_compare(dump1, dump2);
+
+	return 2;
+}
+
+struct create_n_defrag_params_t {
+	unsigned thread_id;
+
+	struct create_params_t cparams;
+
+	PMEMobjpool *pop;
+	PMEMoid *oidp;
+
+	unsigned max_rounds;
+	unsigned ncycles;
+};
+
+/*
+ * create_n_defrag_thread -- create and defrag graphs mutiple times
+ */
+static void *
+create_n_defrag_thread(void *arg)
+{
+	struct create_n_defrag_params_t *params =
+			(struct create_n_defrag_params_t *)arg;
+
+	char dump1[PATH_MAX];
+	char dump2[PATH_MAX];
+
+	snprintf(dump1, PATH_MAX, "dump_t%u_1.log", params->thread_id);
+	snprintf(dump2, PATH_MAX, "dump_t%u_2.log", params->thread_id);
+
+	struct create_params_t *cparams = &params->cparams;
+
+	for (unsigned i = 0; i < params->ncycles; ++i) {
+		graph_create(cparams, global.pop, params->oidp, &cparams->rng);
+		graph_dump(*params->oidp, dump1, HAS_TO_EXIST);
+
+		graph_defrag_ntimes(params->pop, *params->oidp,
+				params->max_rounds);
+		graph_dump(*params->oidp, dump2, HAS_TO_EXIST);
+
+		dump_compare(dump1, dump2);
+
+		pgraph_delete(params->oidp);
+	}
+
+	return NULL;
+}
+
+/*
+ * op_graph_create_n_defrag_mt -- multi-threaded graphs creation & defrag
+ */
+static int
+op_graph_create_n_defrag_mt(const struct test_case *tc, int argc, char *argv[])
+{
+	if (argc < 7)
+		UT_FATAL("usage: %s <max-nodes> <max-edges> <graph-copies>"
+				" <min-root-size> <max-defrag-rounds> <n-threads>"
+				"<n-create-defrag-cycles>", tc->name);
+
+	/* parse arguments */
+	struct create_params_t cparams;
+	create_params_init(&cparams);
+	parse_nonzero(&cparams.vparams.max_nodes, argv[0]);
+	parse_nonzero(&cparams.vparams.max_edges, argv[1]);
+	parse_nonzero(&cparams.pparams.graph_copies, argv[2]);
+	size_t min_root_size = STRTOULL(argv[3], NULL, 10);
+	unsigned max_rounds;
+	parse_nonzero(&max_rounds, argv[4]);
+	unsigned nthreads;
+	parse_nonzero(&nthreads, argv[5]);
+	unsigned ncycles;
+	parse_nonzero(&ncycles, argv[6]);
+
+	struct root_t *root = get_root(nthreads, min_root_size);
+	root->graphs_num = nthreads;
+	pmemobj_persist(global.pop, root, sizeof(*root));
+
+	/* prepare threads params */
+	struct create_n_defrag_params_t *paramss =
+			(struct create_n_defrag_params_t *)MALLOC(
+					sizeof(*paramss) * nthreads);
+
+	for (unsigned i = 0; i < nthreads; ++i) {
+		struct create_n_defrag_params_t *params = &paramss[i];
+
+		params->thread_id = i;
+		memcpy(&params->cparams, &cparams, sizeof(cparams));
+		params->cparams.seed += i;
+		randomize_r(&params->cparams.rng, params->cparams.seed);
+		params->pop = global.pop;
+		params->oidp = &root->graphs[i];
+		params->max_rounds = max_rounds;
+		params->ncycles = ncycles;
+	}
+
+	/* spawn threads */
+	os_thread_t *threads = (os_thread_t *)MALLOC(
+			sizeof(*threads) * nthreads);
+	for (unsigned i = 0; i < nthreads; ++i)
+		os_thread_create(&threads[i], NULL, create_n_defrag_thread,
+				&paramss[i]);
+
+	/* join all threads */
+	void *ret = NULL;
+	for (unsigned i = 0; i < nthreads; ++i) {
+		os_thread_join(&threads[i], &ret);
+		UT_ASSERTeq(ret, NULL);
+	}
+
+	FREE(threads);
+	FREE(paramss);
+
+	return 7;
+}
+
+/*
+ * op_pool_open -- open the pool
+ */
+static int
+op_pool_open(const struct test_case *tc, int argc, char *argv[])
+{
+	if (argc < 1)
+		UT_FATAL("usage: %s <path>", tc->name);
+
+	/* parse arguments */
+	const char *path = argv[0];
+
+	/* open a pool */
+	global.pop = pmemobj_open(path, GRAPH_LAYOUT);
+	if (global.pop == NULL)
+		UT_FATAL("!pmemobj_create: %s", path);
+
+	return 1;
+}
+
+/*
+ * op_graph_dump_all -- dump all graphs
+ */
+static int
+op_graph_dump_all(const struct test_case *tc, int argc, char *argv[])
+{
+	if (argc < 1)
+		UT_FATAL("usage: %s <dump-prefix>", tc->name);
+
+	/* parse arguments */
+	const char *dump_prefix = argv[0];
+
+	struct root_t *root = get_root(QUERY_GRAPHS_NUM, 0);
+
+	char dump[PATH_MAX];
+	for (unsigned i = 0; i < root->graphs_num; ++i) {
+		snprintf(dump, PATH_MAX, "%s_%u.log", dump_prefix, i);
+		graph_dump(root->graphs[i], dump, HAS_TO_EXIST);
+	}
+
+	return 1;
+}
+
+/*
+ * ops -- available ops
+ */
+static struct test_case ops[] = {
+	TEST_CASE(op_pool_create),
+	TEST_CASE(op_pool_close),
+	TEST_CASE(op_graph_create),
+	TEST_CASE(op_graph_dump),
+	TEST_CASE(op_graph_defrag),
+	TEST_CASE(op_dump_compare),
+	TEST_CASE(op_graph_create_n_defrag_mt),
+
+	/* for pool validation only */
+	TEST_CASE(op_pool_open),
+	TEST_CASE(op_graph_dump_all),
+};
+
+#define NOPS ARRAY_SIZE(ops)
+
+#define TEST_NAME "obj_defrag_advanced"
+
+int
+main(int argc, char *argv[])
+{
+	START(argc, argv, TEST_NAME);
+	TEST_CASE_PROCESS(argc, argv, ops, NOPS);
+	DONE(NULL);
+}
diff --git a/src/test/obj_defrag_advanced/obj_defrag_advanced.vcxproj b/src/test/obj_defrag_advanced/obj_defrag_advanced.vcxproj
new file mode 100644
index 0000000000000000000000000000000000000000..48e76ed69c40701c6bd8da64366fdfc55222e9b0
--- /dev/null
+++ b/src/test/obj_defrag_advanced/obj_defrag_advanced.vcxproj
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|x64">
+      <Configuration>Debug</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|x64">
+      <Configuration>Release</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+  </ItemGroup>
+  <PropertyGroup Label="Globals">
+    <ProjectGuid>{AE952763-5C84-43FC-B344-CACC950F056C}</ProjectGuid>
+    <Keyword>Win32Proj</Keyword>
+    <RootNamespace>obj_defrag_advanced</RootNamespace>
+    <WindowsTargetPlatformVersion>10.0.16299.0</WindowsTargetPlatformVersion>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>v140</PlatformToolset>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>v140</PlatformToolset>
+  </PropertyGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <ClCompile />
+    <Link />
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <ClCompile />
+    <Link>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+    </Link>
+  </ItemDefinitionGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <ImportGroup Label="ExtensionSettings">
+  </ImportGroup>
+  <ImportGroup Label="Shared">
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="..\test_debug.props" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+    <Import Project="..\test_release.props" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <LinkIncremental>true</LinkIncremental>
+    <IncludePath>$(IncludePath)</IncludePath>
+  </PropertyGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <ClCompile>
+      <PrecompiledHeader>NotUsing</PrecompiledHeader>
+      <Optimization>Disabled</Optimization>
+    </ClCompile>
+    <Link />
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <ClCompile>
+      <PrecompiledHeader>NotUsing</PrecompiledHeader>
+      <Optimization>MaxSpeed</Optimization>
+    </ClCompile>
+    <Link />
+  </ItemDefinitionGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\..\libpmemobj\libpmemobj.vcxproj">
+      <Project>{1baa1617-93ae-4196-8a1a-bd492fb18aef}</Project>
+    </ProjectReference>
+    <ProjectReference Include="..\..\libpmem\libpmem.vcxproj">
+      <Project>{9e9e3d25-2139-4a5d-9200-18148ddead45}</Project>
+    </ProjectReference>
+    <ProjectReference Include="..\..\windows\getopt\getopt.vcxproj">
+      <Project>{9186eac4-2f34-4f17-b940-6585d7869bcd}</Project>
+    </ProjectReference>
+    <ProjectReference Include="..\unittest\libut.vcxproj">
+      <Project>{ce3f2dfb-8470-4802-ad37-21caf6cb2681}</Project>
+    </ProjectReference>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="TESTS.py" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="obj_defrag_advanced.c" />
+    <ClCompile Include="pgraph.c" />
+    <ClCompile Include="vgraph.c" />
+    <ClCompile Include="..\..\common\rand.c" />
+    <ClCompile Include="..\..\common\os_thread_windows.c" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="pgraph.h" />
+    <ClInclude Include="vgraph.h" />
+    <ClInclude Include="..\..\common\os_thread.h" />
+    <ClInclude Include="..\..\common\rand.h" />
+    <ClInclude Include="..\unittest\unittest.h" />
+  </ItemGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+  <ImportGroup Label="ExtensionTargets">
+  </ImportGroup>
+</Project>
diff --git a/src/test/obj_defrag_advanced/obj_defrag_advanced.vcxproj.filters b/src/test/obj_defrag_advanced/obj_defrag_advanced.vcxproj.filters
new file mode 100644
index 0000000000000000000000000000000000000000..5a2693130b3d0447ebeaff2672fe5edc1537cad7
--- /dev/null
+++ b/src/test/obj_defrag_advanced/obj_defrag_advanced.vcxproj.filters
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup>
+    <None Include="README" />
+    <None Include="TESTS.py">
+      <Filter>Test Scripts</Filter>
+    </None>
+  </ItemGroup>
+  <ItemGroup>
+    <Filter Include="Source Files">
+      <UniqueIdentifier>{06b70714-04ff-4d74-8f1d-eb33a3318a3c}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="Header Files">
+      <UniqueIdentifier>{06b70714-04ff-4d74-8f1d-eb33a3318a3c}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="Test Scripts">
+      <UniqueIdentifier>{f6970093-c229-475a-a462-7718dc17b32e}</UniqueIdentifier>
+    </Filter>
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="obj_defrag_advanced.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="pgraph.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="vgraph.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\common\rand.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\common\os_thread_windows.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="pgraph.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="vgraph.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\common\os_thread.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\common\rand.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="..\unittest\unittest.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+  </ItemGroup>
+</Project>
\ No newline at end of file
diff --git a/src/test/obj_defrag_advanced/pgraph.c b/src/test/obj_defrag_advanced/pgraph.c
new file mode 100644
index 0000000000000000000000000000000000000000..e7a933ed030cf575c5cca9de123af19d0950a882
--- /dev/null
+++ b/src/test/obj_defrag_advanced/pgraph.c
@@ -0,0 +1,271 @@
+/*
+ * Copyright 2020, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in
+ *       the documentation and/or other materials provided with the
+ *       distribution.
+ *
+ *     * Neither the name of the copyright holder nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * pgraph.c -- persistent graph representation
+ */
+
+#include <inttypes.h>
+
+#include "unittest.h"
+
+#include "vgraph.h"
+#include "pgraph.h"
+
+#define PATTERN 'g'
+
+/*
+ * pnode_size -- return the entire of node size
+ */
+static size_t
+pnode_size(unsigned edges_num, size_t pattern_size)
+{
+	size_t node_size = sizeof(struct pnode_t);
+	node_size += sizeof(PMEMoid) * edges_num;
+	node_size += pattern_size;
+	return node_size;
+}
+
+/*
+ * pnode_init -- initialize the node
+ */
+static void
+pnode_init(PMEMobjpool *pop, PMEMoid pnode_oid, struct vnode_t *vnode,
+		PMEMoid pnodes[])
+{
+	struct pnode_t *pnode = (struct pnode_t *)pmemobj_direct(pnode_oid);
+	pnode->node_id = vnode->node_id;
+	pnode->size = vnode->psize;
+
+	/* set edges */
+	pnode->edges_num = vnode->edges_num;
+	for (unsigned i = 0; i < vnode->edges_num; ++i)
+		pnode->edges[i] = pnodes[vnode->edges[i]];
+
+	/* initialize pattern */
+	pnode->pattern_size = vnode->pattern_size;
+	void *pattern = (void *)&pnode->edges[pnode->edges_num];
+	pmemobj_memset(pop, pattern, PATTERN, pnode->pattern_size,
+			PMEMOBJ_F_MEM_NOFLUSH);
+
+	/* persist the whole node state */
+	pmemobj_persist(pop, (const void *)pnode, pnode->size);
+}
+
+/*
+ * order_shuffle -- shuffle the nodes in graph
+ */
+static void
+order_shuffle(unsigned *order, unsigned num, rng_t *rngp)
+{
+	for (unsigned i = 0; i < num; ++i) {
+		unsigned j = rand_range(0, num, rngp);
+		unsigned temp = order[j];
+		order[j] = order[i];
+		order[i] = temp;
+	}
+}
+
+/*
+ * order_new -- generate the sequence of the graph nodes allocation
+ */
+static unsigned *
+order_new(struct vgraph_t *vgraph, rng_t *rngp)
+{
+	unsigned *order = (unsigned *)MALLOC(sizeof(unsigned)
+		* vgraph->nodes_num);
+
+	/* initialize id list */
+	for (unsigned i = 0; i < vgraph->nodes_num; ++i)
+		order[i] = i;
+
+	order_shuffle(order, vgraph->nodes_num, rngp);
+
+	return order;
+}
+
+/*
+ * pgraph_copy_new -- allocate a persistent copy of the volatile graph
+ */
+static PMEMoid *
+pgraph_copy_new(PMEMobjpool *pop, struct vgraph_t *vgraph, rng_t *rngp)
+{
+	/* to be returned array of PMEMoids to raw nodes allocations */
+	PMEMoid *nodes = (PMEMoid *)MALLOC(sizeof(PMEMoid) * vgraph->nodes_num);
+
+	/* generates random order of nodes allocation */
+	unsigned *order = order_new(vgraph, rngp);
+
+	/* allocate the nodes in the random order */
+	int ret;
+	for (unsigned i = 0; i < vgraph->nodes_num; ++i) {
+		struct vnode_t vnode = vgraph->node[order[i]];
+		PMEMoid *node = &nodes[order[i]];
+		ret = pmemobj_alloc(pop, node, vnode.psize, 0, NULL, NULL);
+		UT_ASSERTeq(ret, 0);
+	}
+
+	FREE(order);
+
+	return nodes;
+}
+
+/*
+ * pgraph_copy_delete -- free copies of the graph
+ */
+static void
+pgraph_copy_delete(PMEMoid *nodes, unsigned num)
+{
+	for (unsigned i = 0; i < num; ++i) {
+		if (OID_IS_NULL(nodes[i]))
+			continue;
+
+		pmemobj_free(&nodes[i]);
+	}
+
+	FREE(nodes);
+}
+
+/*
+ * pgraph_size -- return the struct pgraph_t size
+ */
+static size_t
+pgraph_size(unsigned nodes_num)
+{
+	return sizeof(struct pgraph_t) + sizeof(PMEMoid) * nodes_num;
+}
+
+/*
+ * pgraph_new -- allocate a new persistent graph in such a way
+ * that the fragmentation is as large as possible
+ */
+void
+pgraph_new(PMEMobjpool *pop, PMEMoid *oidp, struct vgraph_t *vgraph,
+		struct pgraph_params *params, rng_t *rngp)
+{
+	int ret = pmemobj_alloc(pop, oidp, pgraph_size(vgraph->nodes_num),
+			0, NULL, NULL);
+	UT_ASSERTeq(ret, 0);
+
+	struct pgraph_t *pgraph = (struct pgraph_t *)pmemobj_direct(*oidp);
+	pgraph->nodes_num = vgraph->nodes_num;
+	pmemobj_persist(pop, pgraph, sizeof(*pgraph));
+
+	/* calculate size of pnodes */
+	for (unsigned i = 0; i < vgraph->nodes_num; ++i) {
+		struct vnode_t *vnode = &vgraph->node[i];
+		vnode->psize = pnode_size(vnode->edges_num,
+				vnode->pattern_size);
+	}
+
+	/* prepare multiple copies of the nodes */
+	unsigned copies_num = rand_range(1, params->graph_copies, rngp);
+	PMEMoid **copies = (PMEMoid **)MALLOC(sizeof(PMEMoid *) * copies_num);
+	for (unsigned i = 0; i < copies_num; ++i)
+		copies[i] = pgraph_copy_new(pop, vgraph, rngp);
+
+	/* peek exactly the one copy of each node */
+	for (unsigned i = 0; i < pgraph->nodes_num; ++i) {
+		unsigned copy_id = rand_range(0, copies_num, rngp);
+		pgraph->nodes[i] = copies[copy_id][i];
+		copies[copy_id][i] = OID_NULL;
+	}
+	pmemobj_persist(pop, pgraph->nodes,
+			sizeof(PMEMoid) * pgraph->nodes_num);
+
+	/* free unused copies of the nodes */
+	for (unsigned i = 0; i < copies_num; ++i)
+		pgraph_copy_delete(copies[i], vgraph->nodes_num);
+
+	FREE(copies);
+
+	/* initialize pnodes */
+	for (unsigned i = 0; i < pgraph->nodes_num; ++i)
+		pnode_init(pop, pgraph->nodes[i], &vgraph->node[i],
+				pgraph->nodes);
+}
+
+/*
+ * pgraph_delete -- free the persistent graph
+ */
+void
+pgraph_delete(PMEMoid *oidp)
+{
+	struct pgraph_t *pgraph = (struct pgraph_t *)pmemobj_direct(*oidp);
+
+	/* free pnodes */
+	for (unsigned i = 0; i < pgraph->nodes_num; ++i)
+		pmemobj_free(&pgraph->nodes[i]);
+
+	pmemobj_free(oidp);
+}
+
+/*
+ * pgraph_print --  print graph in human readable format
+ */
+void
+pgraph_print(struct pgraph_t *pgraph, const char *dump)
+{
+	UT_ASSERTne(dump, NULL);
+
+	FILE *out = FOPEN(dump, "w");
+
+	/* print the graph statistics */
+	fprintf(out, "# of nodes: %u\n", pgraph->nodes_num);
+
+	uint64_t total_edges_num = 0;
+	for (unsigned i = 0; i < pgraph->nodes_num; ++i) {
+		PMEMoid node_oid = pgraph->nodes[i];
+		struct pnode_t *pnode =
+				(struct pnode_t *)pmemobj_direct(node_oid);
+		total_edges_num += pnode->edges_num;
+	}
+	fprintf(out, "Total # of edges: %" PRIu64 "\n\n", total_edges_num);
+
+	/* print the graph itself */
+	for (unsigned i = 0; i < pgraph->nodes_num; ++i) {
+		PMEMoid node_oid = pgraph->nodes[i];
+		struct pnode_t *pnode =
+				(struct pnode_t *)pmemobj_direct(node_oid);
+		fprintf(out, "%u:", pnode->node_id);
+		for (unsigned j = 0; j < pnode->edges_num; ++j) {
+			PMEMoid edge_oid = pnode->edges[j];
+			struct pnode_t *edge =
+				(struct pnode_t *)pmemobj_direct(edge_oid);
+			UT_ASSERT(edge->node_id < pgraph->nodes_num);
+			fprintf(out, "%u, ", edge->node_id);
+		}
+		fprintf(out, "\n");
+	}
+
+	FCLOSE(out);
+}
diff --git a/src/test/obj_defrag_advanced/pgraph.h b/src/test/obj_defrag_advanced/pgraph.h
new file mode 100644
index 0000000000000000000000000000000000000000..ebe639ece1122b46d14e5527a619cd68f7186706
--- /dev/null
+++ b/src/test/obj_defrag_advanced/pgraph.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2020, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in
+ *       the documentation and/or other materials provided with the
+ *       distribution.
+ *
+ *     * Neither the name of the copyright holder nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * pgraph.h -- persistent graph representation
+ */
+
+#ifndef OBJ_DEFRAG_ADV_PGRAPH
+#define OBJ_DEFRAG_ADV_PGRAPH
+
+#include <libpmemobj/base.h>
+
+struct pgraph_params
+{
+	unsigned graph_copies;
+};
+
+struct pnode_t
+{
+	unsigned node_id;
+	unsigned edges_num;
+	size_t pattern_size;
+	size_t size;
+	PMEMoid edges[];
+};
+
+struct pgraph_t
+{
+	unsigned nodes_num;
+	PMEMoid nodes[];
+};
+
+void pgraph_new(PMEMobjpool *pop, PMEMoid *oidp, struct vgraph_t *vgraph,
+		struct pgraph_params *params, rng_t *rngp);
+void pgraph_delete(PMEMoid *oidp);
+
+void pgraph_print(struct pgraph_t *graph, const char *dump);
+
+#endif /* pgraph.h */
diff --git a/src/test/obj_defrag_advanced/vgraph.c b/src/test/obj_defrag_advanced/vgraph.c
new file mode 100644
index 0000000000000000000000000000000000000000..c05b2f8377426f72b70cdad19c6a9ee93c6e52b2
--- /dev/null
+++ b/src/test/obj_defrag_advanced/vgraph.c
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2020, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in
+ *       the documentation and/or other materials provided with the
+ *       distribution.
+ *
+ *     * Neither the name of the copyright holder nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * vgraph.c -- volatile graph representation
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "rand.h"
+#include "unittest.h"
+#include "vgraph.h"
+
+/*
+ * rand_range -- generate pseudo-random number from given interval [min, max]
+ */
+unsigned
+rand_range(unsigned min, unsigned max, rng_t *rngp)
+{
+	if (min == max)
+		return min;
+
+	if (min > max)
+		UT_FATAL("!rand_range");
+
+	unsigned ret;
+	if (rngp)
+		ret = (unsigned)rnd64_r(rngp);
+	else
+		ret = (unsigned)rnd64();
+
+	return ((unsigned)ret % (max - min)) + min;
+}
+
+/*
+ * vnode_new -- allocate a new volatile node
+ */
+static void
+vnode_new(struct vnode_t *node, unsigned v, struct vgraph_params *params,
+		rng_t *rngp)
+{
+	unsigned min_edges = 1;
+	if (params->max_edges > params->range_edges)
+		min_edges = params->max_edges - params->range_edges;
+	unsigned edges_num = rand_range(min_edges, params->max_edges, rngp);
+	node->node_id = v;
+	node->edges_num = edges_num;
+	node->edges = (unsigned *)MALLOC(sizeof(int) * edges_num);
+	node->pattern_size = rand_range(params->min_pattern_size,
+			params->max_pattern_size, rngp);
+}
+
+/*
+ * vnode_delete -- free a volatile node
+ */
+static void
+vnode_delete(struct vnode_t *node)
+{
+	FREE(node->edges);
+}
+
+/*
+ * vgraph_get_node -- return node in graph based on given id_node
+ */
+static struct vnode_t *
+vgraph_get_node(struct vgraph_t *graph, unsigned id_node)
+{
+	struct vnode_t *node;
+
+	node = &graph->node[id_node];
+	return node;
+}
+
+/*
+ * vgraph_add_edges -- randomly assign destination nodes to the edges
+ */
+static void
+vgraph_add_edges(struct vgraph_t *graph, rng_t *rngp)
+{
+	unsigned nodes_count = 0;
+	unsigned edges_count = 0;
+	struct vnode_t *node;
+	for (nodes_count = 0; nodes_count < graph->nodes_num; nodes_count++) {
+		node = vgraph_get_node(graph, nodes_count);
+		unsigned edges_num = node->edges_num;
+		for (edges_count = 0; edges_count < edges_num; edges_count++) {
+			unsigned node_link =
+					rand_range(0, graph->nodes_num, rngp);
+			node->edges[edges_count] = node_link;
+		}
+	}
+}
+
+/*
+ * vgraph_new -- allocate a new volatile graph
+ */
+struct vgraph_t *
+vgraph_new(struct vgraph_params *params, rng_t *rngp)
+{
+	unsigned min_nodes = 1;
+	if (params->max_nodes > params->range_nodes)
+		min_nodes = params->max_nodes - params->range_nodes;
+	unsigned nodes_num = rand_range(min_nodes, params->max_nodes, rngp);
+
+	struct vgraph_t *graph =
+			(struct vgraph_t *)MALLOC(sizeof(struct vgraph_t) +
+					sizeof(struct vnode_t) * nodes_num);
+	graph->nodes_num = nodes_num;
+
+	for (unsigned i = 0; i < nodes_num; i++) {
+		vnode_new(&graph->node[i], i, params, rngp);
+	}
+
+	vgraph_add_edges(graph, rngp);
+
+	return graph;
+}
+
+/*
+ * vgraph_delete -- free the volatile graph
+ */
+void
+vgraph_delete(struct vgraph_t *graph)
+{
+	for (unsigned i = 0; i < graph->nodes_num; i++)
+		vnode_delete(&graph->node[i]);
+
+	FREE(graph);
+}
diff --git a/src/test/obj_defrag_advanced/vgraph.h b/src/test/obj_defrag_advanced/vgraph.h
new file mode 100644
index 0000000000000000000000000000000000000000..25648c270ecd6148fcaf98de1c0a8c1d8d4dedf2
--- /dev/null
+++ b/src/test/obj_defrag_advanced/vgraph.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2020, Intel Corporation
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *
+ *     * Redistributions in binary form must reproduce the above copyright
+ *       notice, this list of conditions and the following disclaimer in
+ *       the documentation and/or other materials provided with the
+ *       distribution.
+ *
+ *     * Neither the name of the copyright holder nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * vgraph.h -- volatile graph representation
+ */
+
+#ifndef OBJ_DEFRAG_ADV_VGRAPH
+#define OBJ_DEFRAG_ADV_VGRAPH
+
+#include "rand.h"
+
+struct vgraph_params
+{
+	unsigned max_nodes; /* max # of nodes per graph */
+	unsigned max_edges; /* max # of edges per node */
+	/* # of nodes is between [max_nodes - range_nodes, max_nodes] */
+	unsigned range_nodes;
+	/* # of edges is between [max_edges - range_edges, max_edges] */
+	unsigned range_edges;
+	unsigned min_pattern_size;
+	unsigned max_pattern_size;
+};
+
+struct vnode_t
+{
+	unsigned node_id;
+	unsigned edges_num; /* # of edges starting from this node */
+	unsigned *edges; /* ids of nodes the edges are pointing to */
+
+	/* the persistent node attributes */
+	size_t pattern_size; /* size of the pattern allocated after the node */
+	size_t psize; /* the total size of the node */
+};
+
+struct vgraph_t
+{
+	unsigned nodes_num;
+	struct vnode_t node[];
+};
+
+unsigned rand_range(unsigned min, unsigned max, rng_t *rngp);
+
+struct vgraph_t *vgraph_new(struct vgraph_params *params, rng_t *rngp);
+void vgraph_delete(struct vgraph_t *graph);
+
+#endif