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

mpetrov pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git


The following commit(s) were added to refs/heads/master by this push:
     new c8f225248f9 IGNITE-20688 Fixed orphaned handles after binary object 
detach operation. (#11272)
c8f225248f9 is described below

commit c8f225248f912083f184a7060f37c91902e3c2a9
Author: Mikhail Petrov <[email protected]>
AuthorDate: Thu May 30 14:28:19 2024 +0300

    IGNITE-20688 Fixed orphaned handles after binary object detach operation. 
(#11272)
---
 .../binary/BinaryArrayIdentityResolver.java        |  27 +-
 .../internal/binary/BinaryClassDescriptor.java     |   4 +-
 .../ignite/internal/binary/BinaryObjectImpl.java   |  27 +-
 .../ignite/internal/binary/BinaryReaderExImpl.java |   7 +-
 .../internal/binary/BinaryReaderHandles.java       |   5 +
 .../internal/binary/BinaryReaderHandlesHolder.java |   3 +
 .../binary/BinaryReaderHandlesHolderImpl.java      |   5 +
 .../apache/ignite/internal/binary/BinaryUtils.java |  27 +-
 .../internal/binary/BinaryWriterSchemaHolder.java  |   4 +-
 .../binary/CrossObjectReferenceResolver.java       | 358 +++++++++++++
 .../ignite/internal/binary/ObjectDetachHelper.java | 151 ++++++
 .../internal/binary/RawBinaryObjectExtractor.java  | 337 ++++++++++++
 .../binary/streams/BinaryAbstractOutputStream.java |   7 +
 .../binary/streams/BinaryOutputStream.java         |   9 +
 .../platform/memory/PlatformOutputStreamImpl.java  |   5 +
 .../CrossObjectReferenceSerializationTest.java     | 596 +++++++++++++++++++++
 .../binary/RawBinaryObjectExtractorTest.java       | 262 +++++++++
 .../binary/mutabletest/GridBinaryTestClasses.java  |  75 +++
 .../IgnitePdsBinarySortObjectFieldsTest.java       |  27 +-
 .../testsuites/IgniteBinaryObjectsTestSuite.java   |   5 +
 20 files changed, 1909 insertions(+), 32 deletions(-)

diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryArrayIdentityResolver.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryArrayIdentityResolver.java
index 14792e5abd5..13eaab2c234 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryArrayIdentityResolver.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryArrayIdentityResolver.java
@@ -50,30 +50,29 @@ public class BinaryArrayIdentityResolver extends 
BinaryAbstractIdentityResolver
 
     /** {@inheritDoc} */
     @Override protected int hashCode0(BinaryObject obj) {
-        int hash = 1;
-
         if (obj instanceof BinaryObjectExImpl) {
             BinaryObjectExImpl ex = (BinaryObjectExImpl)obj;
 
             int start = ex.dataStartOffset();
             int end = ex.footerStartOffset();
 
-            if (ex.hasArray()) {
-                // Handle heap object.
-                byte[] data = ex.array();
-
-                for (int i = start; i < end; i++)
-                    hash = 31 * hash + data[i];
-            }
+            if (ex.hasArray())
+                return hashCode(ex.array(), start, end);
             else {
                 // Handle offheap object.
+                int hash = 1;
+
                 long ptr = ex.offheapAddress();
 
                 for (int i = start; i < end; i++)
                     hash = 31 * hash + BinaryPrimitives.readByte(ptr, i);
+
+                return hash;
             }
         }
         else if (obj instanceof BinaryEnumObjectImpl) {
+            int hash = 1;
+
             int ord = obj.enumOrdinal();
 
             // Construct hash as if it was an int serialized in little-endian 
form.
@@ -81,10 +80,20 @@ public class BinaryArrayIdentityResolver extends 
BinaryAbstractIdentityResolver
             hash = 31 * hash + (ord & 0x0000FF00);
             hash = 31 * hash + (ord & 0x00FF0000);
             hash = 31 * hash + (ord & 0xFF000000);
+
+            return hash;
         }
         else
             throw new BinaryObjectException("Array identity resolver cannot be 
used with provided BinaryObject " +
                 "implementation: " + obj.getClass().getName());
+    }
+
+    /** */
+    public int hashCode(byte[] data, int startPos, int endPos) {
+        int hash = 1;
+
+        for (int i = startPos; i < endPos; i++)
+            hash = 31 * hash + data[i];
 
         return hash;
     }
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryClassDescriptor.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryClassDescriptor.java
index be1ffc3cb57..b0fde3168b6 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryClassDescriptor.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryClassDescriptor.java
@@ -335,12 +335,12 @@ public class BinaryClassDescriptor {
                     if (BinaryUtils.FIELDS_SORTED_ORDER) {
                         fields0 = new TreeMap<>();
 
-                        stableFieldsMeta = metaDataEnabled ? new 
TreeMap<String, BinaryFieldMetadata>() : null;
+                        stableFieldsMeta = metaDataEnabled ? new TreeMap<>() : 
null;
                     }
                     else {
                         fields0 = new LinkedHashMap<>();
 
-                        stableFieldsMeta = metaDataEnabled ? new 
LinkedHashMap<String, BinaryFieldMetadata>() : null;
+                        stableFieldsMeta = metaDataEnabled ? new 
LinkedHashMap<>() : null;
                     }
 
                     Set<String> duplicates = duplicateFields(cls);
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryObjectImpl.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryObjectImpl.java
index e10b33168fb..e8ef88c09cc 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryObjectImpl.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryObjectImpl.java
@@ -35,6 +35,8 @@ import org.apache.ignite.binary.BinaryType;
 import org.apache.ignite.internal.GridDirectTransient;
 import org.apache.ignite.internal.IgniteCodeGeneratingFail;
 import org.apache.ignite.internal.binary.streams.BinaryHeapInputStream;
+import org.apache.ignite.internal.binary.streams.BinaryHeapOutputStream;
+import org.apache.ignite.internal.binary.streams.BinaryOutputStream;
 import org.apache.ignite.internal.processors.cache.CacheObject;
 import org.apache.ignite.internal.processors.cache.CacheObjectAdapter;
 import org.apache.ignite.internal.processors.cache.CacheObjectContext;
@@ -282,16 +284,35 @@ public final class BinaryObjectImpl extends 
BinaryObjectExImpl implements Extern
      * @return Detached binary object.
      */
     public BinaryObjectImpl detach() {
+        return detach(false);
+    }
+
+    /**
+     * @return Detached binary object.
+     */
+    public BinaryObjectImpl detach(boolean checkCrossObjReferences) {
         if (!detachAllowed || detached())
             return this;
 
         int len = length();
 
-        byte[] arr0 = new byte[len];
+        if (checkCrossObjReferences) {
+            ObjectDetachHelper detachHelper = ObjectDetachHelper.create(arr, 
start);
+
+            if (detachHelper.isCrossObjectReferencesDetected()) {
+                try (BinaryOutputStream out = new BinaryHeapOutputStream(2 * 
len)) {
+                    detachHelper.detach(out);
+
+                    return new BinaryObjectImpl(ctx, out.arrayCopy(), 0);
+                }
+            }
+        }
+
+        byte[] detachedData = new byte[len];
 
-        U.arrayCopy(arr, start, arr0, 0, len);
+        U.arrayCopy(arr, start, detachedData, 0, len);
 
-        return new BinaryObjectImpl(ctx, arr0, 0);
+        return new BinaryObjectImpl(ctx, detachedData, 0);
     }
 
     /**
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryReaderExImpl.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryReaderExImpl.java
index a88ed7902a4..57e6d88198a 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryReaderExImpl.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryReaderExImpl.java
@@ -1338,7 +1338,7 @@ public class BinaryReaderExImpl implements BinaryReader, 
BinaryRawReaderEx, Bina
 
     /** {@inheritDoc} */
     @Nullable @Override public Object readObjectDetached(boolean deserialize) 
throws BinaryObjectException {
-        return BinaryUtils.unmarshal(in, ctx, ldr, this, true, deserialize);
+        return BinaryUtils.unmarshal(in, ctx, ldr, new 
BinaryReaderHandlesHolderImpl(), true, deserialize);
     }
 
     /** {@inheritDoc} */
@@ -2369,6 +2369,11 @@ public class BinaryReaderExImpl implements BinaryReader, 
BinaryRawReaderEx, Bina
         return in.remaining();
     }
 
+    /** {@inheritDoc} */
+    @Override public boolean isEmpty() {
+        return hnds == null || hnds.isEmpty();
+    }
+
     /** {@inheritDoc} */
     @Override public void close() throws IOException {
         // No-op.
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryReaderHandles.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryReaderHandles.java
index 72d054502f4..9582177dcf3 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryReaderHandles.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryReaderHandles.java
@@ -103,4 +103,9 @@ public class BinaryReaderHandles {
                 data0.put(pos, obj);
         }
     }
+
+    /** */
+    public boolean isEmpty() {
+        return mode == MODE_EMPTY;
+    }
 }
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryReaderHandlesHolder.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryReaderHandlesHolder.java
index 48c9e8ebdd9..a60276ced82 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryReaderHandlesHolder.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryReaderHandlesHolder.java
@@ -43,4 +43,7 @@ public interface BinaryReaderHandlesHolder {
      * @return Handles.
      */
     public BinaryReaderHandles handles();
+
+    /** */
+    public boolean isEmpty();
 }
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryReaderHandlesHolderImpl.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryReaderHandlesHolderImpl.java
index 95e5ff52d79..a1ee6a91a16 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryReaderHandlesHolderImpl.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryReaderHandlesHolderImpl.java
@@ -41,4 +41,9 @@ public class BinaryReaderHandlesHolderImpl implements 
BinaryReaderHandlesHolder
 
         return hnds;
     }
+
+    /** {@inheritDoc} */
+    @Override public boolean isEmpty() {
+        return hnds == null || hnds.isEmpty();
+    }
 }
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryUtils.java 
b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryUtils.java
index 72790b94317..c4d925de429 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryUtils.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryUtils.java
@@ -154,7 +154,7 @@ public class BinaryUtils {
         
!IgniteSystemProperties.getBoolean(IgniteSystemProperties.IGNITE_BINARY_DONT_WRAP_TREE_STRUCTURES);
 
     /** Whether to sort field in binary objects (doesn't affect 
Binarylizable). */
-    public static final boolean FIELDS_SORTED_ORDER =
+    public static boolean FIELDS_SORTED_ORDER =
         
IgniteSystemProperties.getBoolean(IgniteSystemProperties.IGNITE_BINARY_SORT_OBJECT_FIELDS);
 
     /** Field type names. */
@@ -817,6 +817,20 @@ public class BinaryUtils {
         return in.readIntPositioned(start + 
GridBinaryMarshaller.TOTAL_LEN_POS);
     }
 
+    /** */
+    public static int dataStartRelative(BinaryPositionReadable in, int start) {
+        int typeId = in.readIntPositioned(start + 
GridBinaryMarshaller.TYPE_ID_POS);
+
+        if (typeId == GridBinaryMarshaller.UNREGISTERED_TYPE_ID) {
+            // Gets the length of the type name which is stored as string.
+            int len = in.readIntPositioned(start + 
GridBinaryMarshaller.DFLT_HDR_LEN + /** object type */1);
+
+            return GridBinaryMarshaller.DFLT_HDR_LEN + /** object type */1 + 
/** string length */ 4 + len;
+        }
+        else
+            return GridBinaryMarshaller.DFLT_HDR_LEN;
+    }
+
     /**
      * Get footer start of the object.
      *
@@ -1910,10 +1924,11 @@ public class BinaryUtils {
                 BinaryObjectExImpl po;
 
                 if (detach) {
-                    // In detach mode we simply copy object's content.
-                    in.position(start);
+                    BinaryObjectImpl binObj = new BinaryObjectImpl(ctx, 
in.array(), start);
+
+                    binObj.detachAllowed(true);
 
-                    po = new BinaryObjectImpl(ctx, in.readByteArray(len), 0);
+                    po = binObj.detach(!handles.isEmpty());
                 }
                 else {
                     if (in.offheapPointer() == 0)
@@ -1921,10 +1936,10 @@ public class BinaryUtils {
                     else
                         po = new BinaryObjectOffheapImpl(ctx, 
in.offheapPointer(), start,
                             in.remaining() + in.position());
-
-                    in.position(start + po.length());
                 }
 
+                in.position(start + len);
+
                 handles.setHandle(po, start);
 
                 return po;
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryWriterSchemaHolder.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryWriterSchemaHolder.java
index 038a34e3edf..b1d372a6196 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryWriterSchemaHolder.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryWriterSchemaHolder.java
@@ -71,7 +71,9 @@ public class BinaryWriterSchemaHolder {
     }
 
     /**
-     * Write collected frames and pop them.
+     * Write collected frames.
+     * Note, that after writing collected frames, you must call {@link 
BinaryWriterSchemaHolder#pop(int)} method with
+     * the total number of written frames as an argument.
      *
      * @param out Output stream.
      * @param fieldCnt Count.
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/binary/CrossObjectReferenceResolver.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/binary/CrossObjectReferenceResolver.java
new file mode 100644
index 00000000000..ce5fd0c70e9
--- /dev/null
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/binary/CrossObjectReferenceResolver.java
@@ -0,0 +1,358 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.binary;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.ignite.internal.binary.streams.BinaryOutputStream;
+
+import static 
org.apache.ignite.internal.binary.BinaryUtils.FLAG_OFFSET_ONE_BYTE;
+import static 
org.apache.ignite.internal.binary.BinaryUtils.FLAG_OFFSET_TWO_BYTES;
+import static org.apache.ignite.internal.binary.BinaryUtils.OFFSET_1;
+import static org.apache.ignite.internal.binary.BinaryUtils.OFFSET_2;
+import static org.apache.ignite.internal.binary.BinaryUtils.dataStartRelative;
+import static org.apache.ignite.internal.binary.BinaryUtils.fieldOffsetLength;
+import static 
org.apache.ignite.internal.binary.BinaryUtils.footerStartAbsolute;
+import static org.apache.ignite.internal.binary.BinaryUtils.hasRaw;
+import static org.apache.ignite.internal.binary.BinaryUtils.hasSchema;
+import static org.apache.ignite.internal.binary.BinaryUtils.isCompactFooter;
+import static org.apache.ignite.internal.binary.BinaryUtils.length;
+import static org.apache.ignite.internal.binary.BinaryUtils.rawOffsetAbsolute;
+import static 
org.apache.ignite.internal.binary.GridBinaryMarshaller.DFLT_HDR_LEN;
+import static org.apache.ignite.internal.binary.GridBinaryMarshaller.FLAGS_POS;
+import static 
org.apache.ignite.internal.binary.GridBinaryMarshaller.HASH_CODE_POS;
+import static 
org.apache.ignite.internal.binary.GridBinaryMarshaller.SCHEMA_OR_RAW_OFF_POS;
+import static 
org.apache.ignite.internal.binary.GridBinaryMarshaller.TOTAL_LEN_POS;
+
+/** */
+public class CrossObjectReferenceResolver {
+    /** */
+    private final RawBinaryObjectExtractor in;
+
+    /** */
+    private final BinaryOutputStream out;
+
+    /** */
+    private final int inRootObjStartPos;
+
+    /** */
+    private final Map<Integer, Integer> objPosTranslation = new HashMap<>();
+
+    /** */
+    private final BinaryWriterSchemaHolder schema = new 
BinaryWriterSchemaHolder();
+
+    /** */
+    private CrossObjectReferenceResolver(RawBinaryObjectExtractor in, 
BinaryOutputStream out) {
+        this.in = in;
+
+        inRootObjStartPos = in.position();
+
+        this.out = out;
+    }
+
+    /** */
+    static void copyObject(RawBinaryObjectExtractor in, BinaryOutputStream 
out) {
+        CrossObjectReferenceResolver resolver = new 
CrossObjectReferenceResolver(in, out);
+
+        resolver.reassembleNextObject();
+    }
+
+    /** */
+    private void reassembleNextObject() {
+        int inObjStartPos = in.position();
+        int outObjStartPos = out.position();
+
+        byte objType = in.readBytePositioned(inObjStartPos);
+
+        switch (objType) {
+            case GridBinaryMarshaller.OBJ: {
+                doObjectProcessing();
+
+                break;
+            }
+
+            case GridBinaryMarshaller.HANDLE: {
+                doHandleProcessing();
+
+                break;
+            }
+
+            case GridBinaryMarshaller.OBJ_ARR: {
+                objPosTranslation.put(inObjStartPos, outObjStartPos);
+
+                copyBytes(Byte.BYTES); // Object type.
+
+                in.copyTypeId(out);
+
+                int size = copyInt();
+
+                reassembleNextCortege(size);
+
+                break;
+            }
+
+            case GridBinaryMarshaller.COL: {
+                objPosTranslation.put(inObjStartPos, outObjStartPos);
+
+                copyBytes(Byte.BYTES); // Object type.
+
+                int size = copyInt();
+
+                copyBytes(Byte.BYTES); // Collection type.
+
+                reassembleNextCortege(size);
+
+                break;
+            }
+
+            case GridBinaryMarshaller.MAP: {
+                objPosTranslation.put(inObjStartPos, outObjStartPos);
+
+                copyBytes(Byte.BYTES); // Object type.
+
+                int size = copyInt() * 2;
+
+                copyBytes(Byte.BYTES); // Map type.
+
+                reassembleNextCortege(size);
+
+                break;
+            }
+
+            default:
+                in.copyObject(out);
+        }
+    }
+
+    /** */
+    private void doObjectProcessing() {
+        int inObjStartPos = in.position();
+        int outObjStartPos = out.position();
+
+        objPosTranslation.put(inObjStartPos, outObjStartPos);
+
+        BinaryObjectDescriptor inObjDesc = BinaryObjectDescriptor.parse(in, 
inObjStartPos);
+
+        int fieldsCnt = 0;
+
+        try {
+            // Copy object's header. Update with actual data length and field 
offsets will be performed later.
+            copyBytes(dataStartRelative(in, inObjStartPos));
+
+            // Process object fields.
+            while (in.position() < inObjDesc.rawDataStartPos)
+                reassembleField(fieldsCnt++, offset(outObjStartPos, 
out.position()), inObjDesc);
+
+            int outRawDataStartPos = -1;
+
+            if (inObjDesc.hasRaw) {
+                outRawDataStartPos = out.position();
+
+                copyBytes(inObjDesc.footerStartPos - in.position());
+            }
+
+            int outFooterStartPos = out.position();
+
+            int schemaOrRawOffsetPos;
+            int footerFieldOffsetLen;
+
+            // Write footer and raw data offset if required.
+            if (inObjDesc.hasSchema) {
+                schemaOrRawOffsetPos = offset(outObjStartPos, 
outFooterStartPos);
+                footerFieldOffsetLen = schema.write(out, fieldsCnt, 
inObjDesc.isCompactFooter);
+
+                if (inObjDesc.hasRaw)
+                    out.writeInt(offset(outObjStartPos, outRawDataStartPos));
+            }
+            else {
+                schemaOrRawOffsetPos = inObjDesc.hasRaw ? 
offset(outObjStartPos, outRawDataStartPos) : DFLT_HDR_LEN;
+                footerFieldOffsetLen = 0;
+            }
+
+            // Update header to reflect changes after cross object references 
are resolved.
+            overrideHeader(
+                outObjStartPos,
+                /** flags */ setFieldOffsetFlag(inObjDesc.flags, 
footerFieldOffsetLen),
+                /** hash */ 
BinaryArrayIdentityResolver.instance().hashCode(out.array(), outObjStartPos + 
DFLT_HDR_LEN, outFooterStartPos),
+                /** total length */ out.position() - outObjStartPos,
+                schemaOrRawOffsetPos
+            );
+
+            in.position(inObjDesc.endPos);
+        }
+        finally {
+            schema.pop(fieldsCnt);
+        }
+    }
+
+    /** */
+    private void doHandleProcessing() {
+        int inObjStartPos = in.position();
+
+        in.skipBytes(1); // Object type.
+
+        int offset = in.readInt();
+
+        int inHandleObjPos = inObjStartPos - offset;
+
+        Integer outHandleObjPos = objPosTranslation.get(inHandleObjPos);
+
+        if (outHandleObjPos != null) {
+            int outObjStartPos = out.position();
+
+            out.writeByte(GridBinaryMarshaller.HANDLE);
+            out.writeInt(offset(outHandleObjPos, outObjStartPos));
+        }
+        else {
+            assert inHandleObjPos < inRootObjStartPos;
+
+            objPosTranslation.put(inHandleObjPos, out.position());
+
+            copyObjectPositioned(inHandleObjPos);
+        }
+    }
+
+    /** */
+    private void overrideHeader(int writeObjStartPos, short flags, int 
hashCode, int totalLen, int schemaOrRawOffsetPos) {
+        out.unsafeWriteShort(writeObjStartPos + FLAGS_POS, flags);
+        out.unsafeWriteInt(writeObjStartPos + HASH_CODE_POS, hashCode);
+        out.unsafeWriteInt(writeObjStartPos + TOTAL_LEN_POS, totalLen);
+        out.unsafeWriteInt(writeObjStartPos + SCHEMA_OR_RAW_OFF_POS, 
schemaOrRawOffsetPos);
+    }
+
+    /** */
+    private short setFieldOffsetFlag(short flags, int fieldOffsetLength) {
+        if (fieldOffsetLength == 0)
+            return flags;
+
+        flags &= ~FLAG_OFFSET_ONE_BYTE;
+        flags &= ~FLAG_OFFSET_TWO_BYTES;
+
+        if (fieldOffsetLength == OFFSET_1)
+            flags |= FLAG_OFFSET_ONE_BYTE;
+        else if (fieldOffsetLength == OFFSET_2)
+            flags |= FLAG_OFFSET_TWO_BYTES;
+
+        return flags;
+    }
+
+    /** */
+    private void reassembleField(int fieldOrder, int fieldOffset, 
BinaryObjectDescriptor binObjDesc) {
+        int fieldId = binObjDesc.isCompactFooter
+            ? -1
+            : in.readIntPositioned(binObjDesc.fieldIdPosition(fieldOrder));
+
+        schema.push(fieldId, fieldOffset);
+
+        reassembleNextObject();
+    }
+
+    /** */
+    private void reassembleNextCortege(int cortegeSize) {
+        for (int elemIdx = 0; elemIdx < cortegeSize; elemIdx++)
+            reassembleNextObject();
+    }
+
+    /** */
+    private void copyObjectPositioned(int inPos) {
+        int inRetPos = in.position();
+
+        in.position(inPos);
+
+        ObjectDetachHelper detachHelper = 
ObjectDetachHelper.create(in.array(), inPos);
+
+        if (detachHelper.isCrossObjectReferencesDetected())
+            detachHelper.detach(out);
+        else
+            in.copyObject(out);
+
+        in.position(inRetPos);
+    }
+
+    /** */
+    private int copyInt() {
+        int res = in.peekInt();
+
+        copyBytes(Integer.BYTES);
+
+        return res;
+    }
+
+    /** */
+    private void copyBytes(int cnt) {
+        in.copyBytes(cnt, out);
+    }
+
+    /** */
+    private int offset(int startPos, int pos) {
+        assert pos - startPos >= 0;
+
+        return pos - startPos;
+    }
+
+    /** */
+    private static class BinaryObjectDescriptor {
+        /** */
+        private final int rawDataStartPos;
+
+        /** */
+        private final int footerStartPos;
+
+        /** */
+        private final int endPos;
+
+        /** */
+        private final short flags;
+
+        /** */
+        private final boolean hasRaw;
+
+        /** */
+        private final boolean hasSchema;
+
+        /** */
+        private final boolean isCompactFooter;
+
+        /** */
+        private final int fieldOffsetLength;
+
+        /** */
+        private BinaryObjectDescriptor(BinaryPositionReadable in, int 
startPos) {
+            rawDataStartPos = rawOffsetAbsolute(in, startPos);
+            footerStartPos = footerStartAbsolute(in, startPos);
+            endPos = startPos + length(in, startPos);
+
+            flags = in.readShortPositioned(startPos + FLAGS_POS);
+
+            hasRaw = hasRaw(flags);
+            hasSchema = hasSchema(flags);
+            isCompactFooter = isCompactFooter(flags);
+            fieldOffsetLength = fieldOffsetLength(flags);
+        }
+
+        /** */
+        private static BinaryObjectDescriptor parse(BinaryPositionReadable in, 
int startPos) {
+            return new BinaryObjectDescriptor(in, startPos);
+        }
+
+        /** */
+        private int fieldIdPosition(int fieldOrder) {
+            return footerStartPos + (/** field ID */ 4 + fieldOffsetLength) * 
fieldOrder;
+        }
+    }
+}
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/binary/ObjectDetachHelper.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/binary/ObjectDetachHelper.java
new file mode 100644
index 00000000000..3b7ca117156
--- /dev/null
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/binary/ObjectDetachHelper.java
@@ -0,0 +1,151 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.binary;
+
+import org.apache.ignite.internal.binary.streams.BinaryHeapInputStream;
+import org.apache.ignite.internal.binary.streams.BinaryInputStream;
+import org.apache.ignite.internal.binary.streams.BinaryOutputStream;
+
+import static org.apache.ignite.internal.binary.BinaryUtils.dataStartRelative;
+import static org.apache.ignite.internal.binary.BinaryUtils.length;
+import static org.apache.ignite.internal.binary.BinaryUtils.rawOffsetAbsolute;
+
+/** */
+class ObjectDetachHelper {
+    /** */
+    private final RawBinaryObjectExtractor reader;
+
+    /** */
+    private final int rootObjStartPos;
+    
+    /** */
+    private boolean isCrossObjReferenceDetected;
+
+    /** */
+    private ObjectDetachHelper(BinaryInputStream in) {
+        reader = new RawBinaryObjectExtractor(in);
+
+        rootObjStartPos = in.position();
+    }
+
+    /** */
+    static ObjectDetachHelper create(byte[] data, int offset) {
+        ObjectDetachHelper res = new 
ObjectDetachHelper(BinaryHeapInputStream.create(data, offset));
+
+        res.findCrossObjectReferences();
+
+        return res;
+    }
+
+    /** */
+    public boolean isCrossObjectReferencesDetected() {
+        return isCrossObjReferenceDetected;
+    } 
+    
+    /** */
+    public void detach(BinaryOutputStream out) {
+        reader.position(rootObjStartPos);
+
+        CrossObjectReferenceResolver.copyObject(reader, out);
+    }
+
+    /** */
+    private void findCrossObjectReferences() {
+        isCrossObjReferenceDetected = findInNextObject();
+    }
+
+    /** */
+    private boolean findInNextObject() {
+        int objStartPos = reader.position();
+
+        byte objType = reader.readBytePositioned(objStartPos);
+
+        switch (objType) {
+            case GridBinaryMarshaller.OBJ: {
+                int objDataStartPos = objStartPos + dataStartRelative(reader, 
objStartPos);
+                int objDataEndPos = rawOffsetAbsolute(reader, objStartPos);
+                int objEndPos = objStartPos + length(reader, objStartPos);
+
+                reader.position(objDataStartPos);
+
+                while (reader.position() < objDataEndPos) {
+                    if (findInNextObject())
+                        return true;
+                }
+
+                reader.position(objEndPos);
+
+                return false;
+            }
+
+            case GridBinaryMarshaller.HANDLE: {
+                reader.skipBytes(1); // Object type.
+
+                int offset = reader.readInt();
+
+                return objStartPos - offset < rootObjStartPos;
+            }
+
+            case GridBinaryMarshaller.OBJ_ARR: {
+                reader.skipBytes(1); // Object type.
+
+                reader.skipTypeId();
+
+                int size = reader.readInt();
+
+                return findInNextCortege(size);
+            }
+
+            case GridBinaryMarshaller.COL: {
+                reader.skipBytes(1); // Object type.
+
+                int size = reader.readInt();
+
+                reader.skipBytes(1); // Collection type.
+
+                return findInNextCortege(size);
+            }
+
+            case GridBinaryMarshaller.MAP: {
+                reader.skipBytes(1); // Object type.
+
+                int size = reader.readInt() * 2;
+
+                reader.skipBytes(1); // Map type.
+
+                return findInNextCortege(size);
+            }
+
+            default: {
+                reader.skipObject();
+
+                return false;
+            }
+        }
+    }
+
+    /** */
+    private boolean findInNextCortege(int cortegeSize) {
+        for (int elemIdx = 0; elemIdx < cortegeSize; elemIdx++) {
+            if (findInNextObject())
+                return true;
+        }
+
+        return false;
+    }
+}
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/binary/RawBinaryObjectExtractor.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/binary/RawBinaryObjectExtractor.java
new file mode 100644
index 00000000000..b75f6a53d9c
--- /dev/null
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/binary/RawBinaryObjectExtractor.java
@@ -0,0 +1,337 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.binary;
+
+import org.apache.ignite.binary.BinaryObjectException;
+import org.apache.ignite.internal.binary.streams.BinaryInputStream;
+import org.apache.ignite.internal.binary.streams.BinaryOutputStream;
+
+/** */
+class RawBinaryObjectExtractor implements BinaryPositionReadable {
+    /** */
+    private final BinaryInputStream in;
+
+    /** */
+    RawBinaryObjectExtractor(BinaryInputStream in) {
+        this.in = in;
+    }
+
+    /** */
+    byte[] extractObject() {
+        int startPos = in.position();
+
+        skipObject();
+
+        int endPos = in.position();
+
+        in.position(startPos);
+
+        return in.readByteArray(endPos - startPos);
+    }
+
+    /** */
+    void copyObject(BinaryOutputStream out) {
+        int startPos = in.position();
+
+        skipObject();
+
+        out.writeByteArray(in.array(), startPos, in.position() - startPos);
+    }
+
+    /** */
+    void copyTypeId(BinaryOutputStream out) {
+        int startPos = in.position();
+
+        skipTypeId();
+
+        out.writeByteArray(in.array(), startPos, in.position() - startPos);
+    }
+
+    /** */
+    void copyBytes(int cnt, BinaryOutputStream out) {
+        assert cnt >= 0;
+
+        if (cnt == 0)
+            return;
+
+        out.writeByteArray(in.array(), in.position(), cnt);
+
+        skipBytes(cnt);
+    }
+
+    /** */
+    int readInt() {
+        return in.readInt();
+    }
+
+    /** */
+    void skipObject() {
+        int objStartPos = in.position();
+
+        byte type = in.readByte();
+
+        switch (type) {
+            case GridBinaryMarshaller.NULL:
+                break;
+
+            case GridBinaryMarshaller.OBJ:
+                skipBytes(BinaryUtils.length(in, objStartPos) - /** Object 
type. */ Byte.BYTES);
+
+                break;
+
+            case GridBinaryMarshaller.BINARY_OBJ:
+                skipBytes(in.readInt());
+
+                skipBytes(Integer.BYTES); // Offset.
+
+                break;
+
+            case GridBinaryMarshaller.BYTE:
+            case GridBinaryMarshaller.BOOLEAN:
+                skipBytes(Byte.BYTES);
+
+                break;
+
+            case GridBinaryMarshaller.CHAR:
+                skipBytes(Character.BYTES);
+
+                break;
+
+            case GridBinaryMarshaller.SHORT:
+                skipBytes(Short.BYTES);
+
+                break;
+
+            case GridBinaryMarshaller.FLOAT:
+                skipBytes(Float.BYTES);
+
+                break;
+
+            case GridBinaryMarshaller.HANDLE:
+            case GridBinaryMarshaller.INT:
+                skipBytes(Integer.BYTES);
+
+                break;
+
+            case GridBinaryMarshaller.ENUM:
+            case GridBinaryMarshaller.BINARY_ENUM: {
+                skipTypeId();
+
+                skipBytes(Integer.BYTES); // Ordinal.
+
+                break;
+            }
+
+            case GridBinaryMarshaller.LONG:
+            case GridBinaryMarshaller.DATE:
+            case GridBinaryMarshaller.TIME:
+                skipBytes(Long.BYTES);
+
+                break;
+
+            case GridBinaryMarshaller.DOUBLE:
+                skipBytes(Double.BYTES);
+
+                break;
+
+            case GridBinaryMarshaller.OPTM_MARSH:
+            case GridBinaryMarshaller.STRING:
+                skipBytes(in.readInt());
+
+                break;
+
+            case GridBinaryMarshaller.DECIMAL:
+                skipBytes(Integer.BYTES); // Scale.
+                skipBytes(in.readInt());
+
+                break;
+
+            case GridBinaryMarshaller.UUID:
+                skipBytes(Long.BYTES + Long.BYTES);
+
+                break;
+
+            case GridBinaryMarshaller.TIMESTAMP:
+                skipBytes(Long.BYTES + Integer.BYTES);
+
+                break;
+
+            case GridBinaryMarshaller.BYTE_ARR:
+            case GridBinaryMarshaller.BOOLEAN_ARR:
+                skipBytes(in.readInt() * Byte.BYTES);
+
+                break;
+
+            case GridBinaryMarshaller.CHAR_ARR:
+                skipBytes(in.readInt() * Character.BYTES);
+
+                break;
+
+            case GridBinaryMarshaller.SHORT_ARR:
+                skipBytes(in.readInt() * Short.BYTES);
+
+                break;
+
+            case GridBinaryMarshaller.INT_ARR:
+                skipBytes(in.readInt() * Integer.BYTES);
+
+                break;
+
+            case GridBinaryMarshaller.FLOAT_ARR:
+                skipBytes(in.readInt() * Float.BYTES);
+
+                break;
+
+            case GridBinaryMarshaller.LONG_ARR:
+                skipBytes(in.readInt() * Long.BYTES);
+
+                break;
+
+            case GridBinaryMarshaller.DOUBLE_ARR:
+                skipBytes(in.readInt() * Double.BYTES);
+
+                break;
+
+            case GridBinaryMarshaller.DECIMAL_ARR:
+            case GridBinaryMarshaller.DATE_ARR:
+            case GridBinaryMarshaller.TIMESTAMP_ARR:
+            case GridBinaryMarshaller.TIME_ARR:
+            case GridBinaryMarshaller.UUID_ARR:
+            case GridBinaryMarshaller.STRING_ARR: {
+                skipCortege();
+
+                break;
+            }
+
+            case GridBinaryMarshaller.ENUM_ARR:
+            case GridBinaryMarshaller.OBJ_ARR: {
+                skipTypeId();
+
+                skipCortege();
+
+                break;
+            }
+
+            case GridBinaryMarshaller.COL: {
+                int size = in.readInt();
+
+                skipBytes(Byte.BYTES); // Collection type.
+
+                skipCortege(size);
+
+                break;
+            }
+
+            case GridBinaryMarshaller.MAP: {
+                int size = in.readInt() * 2;
+
+                skipBytes(Byte.BYTES); // Map type.
+
+                skipCortege(size);
+
+                break;
+            }
+
+            case GridBinaryMarshaller.CLASS: {
+                skipTypeId();
+
+                break;
+            }
+
+            case GridBinaryMarshaller.PROXY: {
+                int size = in.readInt();
+
+                for (int i = 0; i < size; i++)
+                    skipTypeId(); // Interface type.
+
+                skipObject();
+
+                break;
+            }
+
+            default:
+                throw new BinaryObjectException("Unsupported binary type 
[type=" + type + ']');
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public byte readBytePositioned(int pos) {
+        return in.readBytePositioned(pos);
+    }
+
+    /** {@inheritDoc} */
+    @Override public short readShortPositioned(int pos) {
+        return in.readShortPositioned(pos);
+    }
+
+    /** {@inheritDoc} */
+    @Override public int readIntPositioned(int pos) {
+        return in.readIntPositioned(pos);
+    }
+
+    /** */
+    int position() {
+        return in.position();
+    }
+
+    /** */
+    void position(int position) {
+        in.position(position);
+    }
+
+    /** */
+    byte peekByte() {
+        return in.readBytePositioned(in.position());
+    }
+
+    /** */
+    int peekInt() {
+        return in.readIntPositioned(in.position());
+    }
+
+    /** */
+    void skipBytes(int count) {
+        int curPos = in.position();
+
+        in.position(curPos + count);
+    }
+
+    /** */
+    void skipTypeId() {
+        int typeId = in.readInt();
+
+        if (typeId == GridBinaryMarshaller.UNREGISTERED_TYPE_ID)
+            skipObject();
+    }
+
+    /** */
+    byte[] array() {
+        return in.array();
+    }
+
+    /** */
+    private void skipCortege() {
+        skipCortege(in.readInt());
+    }
+
+    /** */
+    private void skipCortege(int size) {
+        for (int i = 0; i < size; i++)
+            skipObject();
+    }
+}
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/binary/streams/BinaryAbstractOutputStream.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/binary/streams/BinaryAbstractOutputStream.java
index e87309dab1a..165bb8801e2 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/binary/streams/BinaryAbstractOutputStream.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/binary/streams/BinaryAbstractOutputStream.java
@@ -52,6 +52,13 @@ public abstract class BinaryAbstractOutputStream extends 
BinaryAbstractStream
         copyAndShift(val, GridUnsafe.BYTE_ARR_OFF, val.length);
     }
 
+    /** {@inheritDoc} */
+    @Override public void writeByteArray(byte[] val, int off, int len) {
+        ensureCapacity(pos + len);
+
+        copyAndShift(val, GridUnsafe.BYTE_ARR_OFF + off, len);
+    }
+
     /** {@inheritDoc} */
     @Override public void writeBoolean(boolean val) {
         writeByte(val ? BYTE_ONE : BYTE_ZERO);
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/binary/streams/BinaryOutputStream.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/binary/streams/BinaryOutputStream.java
index 84941203acd..16423e2153e 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/binary/streams/BinaryOutputStream.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/binary/streams/BinaryOutputStream.java
@@ -35,6 +35,15 @@ public interface BinaryOutputStream extends BinaryStream, 
AutoCloseable {
      */
     public void writeByteArray(byte[] val);
 
+    /**
+     * Write byte array.
+     *
+     * @param val Byte array.
+     * @param off Offset.
+     * @param len Array length.
+     */
+    public void writeByteArray(byte[] val, int off, int len);
+
     /**
      * Write boolean value.
      *
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/memory/PlatformOutputStreamImpl.java
 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/memory/PlatformOutputStreamImpl.java
index 3f5e1d29356..3fa70540d9e 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/memory/PlatformOutputStreamImpl.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/memory/PlatformOutputStreamImpl.java
@@ -59,6 +59,11 @@ public class PlatformOutputStreamImpl implements 
PlatformOutputStream {
         copyAndShift(val, GridUnsafe.BYTE_ARR_OFF, val.length);
     }
 
+    /** {@inheritDoc} */
+    @Override public void writeByteArray(byte[] val, int off, int len) {
+        copyAndShift(val, GridUnsafe.BYTE_ARR_OFF + off, len);
+    }
+
     /** {@inheritDoc} */
     @Override public void writeBoolean(boolean val) {
         writeByte(val ? (byte)1 : (byte)0);
diff --git 
a/modules/core/src/test/java/org/apache/ignite/internal/binary/CrossObjectReferenceSerializationTest.java
 
b/modules/core/src/test/java/org/apache/ignite/internal/binary/CrossObjectReferenceSerializationTest.java
new file mode 100644
index 00000000000..ec169a993af
--- /dev/null
+++ 
b/modules/core/src/test/java/org/apache/ignite/internal/binary/CrossObjectReferenceSerializationTest.java
@@ -0,0 +1,596 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.binary;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.Ignition;
+import org.apache.ignite.binary.BinaryObjectException;
+import org.apache.ignite.binary.BinaryRawReader;
+import org.apache.ignite.binary.BinaryRawWriter;
+import org.apache.ignite.binary.BinaryReader;
+import org.apache.ignite.binary.BinaryWriter;
+import org.apache.ignite.binary.Binarylizable;
+import org.apache.ignite.client.ClientCache;
+import org.apache.ignite.client.IgniteClient;
+import org.apache.ignite.configuration.BinaryConfiguration;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.ClientConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import 
org.apache.ignite.internal.binary.mutabletest.GridBinaryTestClasses.TestObjectAllTypes;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+/** */
+@RunWith(Parameterized.class)
+public class CrossObjectReferenceSerializationTest extends 
GridCommonAbstractTest {
+    /** */
+    private static boolean prevFieldsSortedOrderFlag;
+
+    /** */
+    private static Ignite srv;
+
+    /** */
+    private static IgniteClient cli;
+
+    /** */
+    private static IgniteCache<Object, Object> srvCache;
+
+    /** */
+    private static ClientCache<Object, Object> cliCache;
+
+    /** */
+    @Parameterized.Parameter
+    public ObjectType innerObjType;
+
+    /** */
+    @Parameterized.Parameter(1)
+    public ObjectType outerObjType;
+
+    /** */
+    @Parameterized.Parameter(2)
+    public boolean isCompactFooterEnabled;
+
+    /** */
+    @Parameterized.Parameter(3)
+    public SerializationMode serializationMode;
+
+    /** Test parameters. */
+    @Parameterized.Parameters(name = "innerObjectType={0}, 
outerObjectType={1}, isCompactFooterEnabled={2}, serializationMode={3}")
+    public static Iterable<Object[]> parameters() {
+        List<Object[]> res = new ArrayList<>();
+
+        for (ObjectType innerObjType : ObjectType.values()) {
+            for (ObjectType outerObjType : ObjectType.values()) {
+                for (boolean isCompactFooterEnabled : new boolean[] {true, 
false}) {
+                    for (SerializationMode serializationMode : 
SerializationMode.values())
+                        res.add(new Object[] {innerObjType, outerObjType, 
isCompactFooterEnabled, serializationMode});
+                }
+            }
+        }
+
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected IgniteConfiguration getConfiguration(String 
igniteInstanceName) throws Exception {
+        return super.getConfiguration(igniteInstanceName)
+            .setCacheConfiguration(new 
CacheConfiguration<>(DEFAULT_CACHE_NAME))
+            .setBinaryConfiguration(new BinaryConfiguration()
+                .setCompactFooter(isCompactFooterEnabled));
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTestsStarted() throws Exception {
+        super.beforeTestsStarted();
+
+        srv = startGrid(0);
+        cli = Ignition.startClient(new ClientConfiguration()
+            .setAddresses("127.0.0.1:10800")
+            .setBinaryConfiguration(new BinaryConfiguration()
+                .setCompactFooter(isCompactFooterEnabled)));
+
+        srvCache = srv.cache(DEFAULT_CACHE_NAME);
+        cliCache = cli.cache(DEFAULT_CACHE_NAME);
+
+        prevFieldsSortedOrderFlag = BinaryUtils.FIELDS_SORTED_ORDER;
+
+        BinaryUtils.FIELDS_SORTED_ORDER = true;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTestsStopped() throws Exception {
+        BinaryUtils.FIELDS_SORTED_ORDER = prevFieldsSortedOrderFlag;
+
+        super.afterTestsStopped();
+
+        cli.close();
+        srv.close();
+    }
+
+    /** */
+    @Test
+    public void testArray() {
+        Object outerObj = createObject(outerObjType);
+
+        Object[] arr = new Object[] {createReferencesHolder(outerObj), 
createReferencesHolder(outerObj)};
+
+        checkPutGetRemove(arr, arr);
+    }
+
+    /** */
+    @Test
+    public void testInnerArray() {
+        Object outerObj = createObject(outerObjType);
+
+        Object[] innerArr = new Object[] {new TestObject(), 
createReferencesHolder(outerObj)};
+
+        Object[] arr = new Object[] {createReferencesHolder(outerObj), 
innerArr};
+
+        checkPutGetRemove(arr, arr);
+    }
+
+    /** */
+    @Test
+    public void testCollection() {
+        Object outerObj = createObject(outerObjType);
+
+        Collection<Object> col = new ArrayList<>();
+
+        col.add(createReferencesHolder(outerObj));
+        col.add(createReferencesHolder(outerObj));
+
+        checkPutGetRemove(col, col);
+    }
+
+    /** */
+    @Test
+    public void testInnerCollection() {
+        Object outerObj = createObject(outerObjType);
+
+        Collection<Object> col = new ArrayList<>();
+
+        Collection<Object> innerCol = new ArrayList<>();
+
+        innerCol.add(new TestObject());
+        innerCol.add(createReferencesHolder(outerObj));
+
+        col.add(createReferencesHolder(outerObj));
+        col.add(innerCol);
+
+        checkPutGetRemove(col, col);
+    }
+
+    /** */
+    @Test
+    public void testMapReferenceBetweenKeyAndValue() {
+        Object outerObj = createObject(outerObjType);
+
+        Map<Object, Object> map = new HashMap<>();
+
+        map.put(createReferencesHolder(outerObj), 
createReferencesHolder(outerObj));
+
+        checkPutGetRemove(map, map);
+    }
+
+    /** */
+    @Test
+    public void testMapReferenceBetweenEntries() {
+        Object outerObj = createObject(outerObjType);
+
+        Map<Object, Object> map = new HashMap<>();
+
+        map.put(0, createReferencesHolder(outerObj));
+        map.put(1, createReferencesHolder(outerObj));
+
+        checkPutGetRemove(map, map);
+    }
+
+    /** */
+    @Test
+    public void testMapInnerCollection() {
+        Object outerObj = createObject(outerObjType);
+
+        Collection<Object> col = new ArrayList<>();
+
+        col.add(createReferencesHolder(outerObj));
+        col.add(createReferencesHolder(outerObj));
+
+        Map<Object, Object> map = new HashMap<>();
+
+        map.put(0, col);
+
+        checkPutGetRemove(map, map);
+    }
+
+    /** */
+    @Test
+    public void testMapInnerArray() {
+        Object outerObj = createObject(outerObjType);
+
+        Map<Object, Object> map = new HashMap<>();
+
+        map.put(0, new Object[] {createReferencesHolder(outerObj), 
createReferencesHolder(outerObj)});
+
+        checkPutGetRemove(0, map);
+    }
+
+    /** */
+    @Test
+    public void testConsecutiveCrossObjectReferences() {
+        Object outerObj = createObject(outerObjType);
+
+        Object holder = createReferencesHolder(outerObj);
+
+        Object enclosingHolder = createReferencesHolder(holder);
+
+        Object doubleEnclosingHolder = createReferencesHolder(enclosingHolder);
+
+        Object[] arr = new Object[] {createReferencesHolder(outerObj), holder, 
enclosingHolder, doubleEnclosingHolder};
+
+        checkPutGetRemove(arr, arr);
+    }
+
+    /** */
+    @Test
+    public void testMultipleCrossObjectReferences() {
+        Object firstOuterObj = createObject(outerObjType);
+        Object secondOuterObj = createObject(outerObjType);
+
+        Object[] arr = new Object[] {
+            createReferencesHolder(new Object[] {firstOuterObj, 
secondOuterObj}),
+            createReferencesHolder(new Object[] {firstOuterObj, 
secondOuterObj})
+        };
+
+        checkPutGetRemove(arr, arr);
+    }
+
+    /** */
+    private Object createReferencesHolder(Object outerObj) {
+        switch (serializationMode) {
+            case RAW:
+                return new RawObject(createObject(innerObjType), outerObj);
+            case MIXED:
+                return new MixedObject(createObject(innerObjType), outerObj);
+            case SCHEMA:
+                return new SchemaObject(createObject(innerObjType), outerObj);
+            default:
+                throw new IllegalStateException();
+        }
+    }
+
+    /** */
+    private Object createObject(ObjectType type) {
+        switch (type) {
+            case OBJECT: {
+                return new TestObject();
+            }
+
+            case ARRAY: {
+                TestObjectAllTypes obj = new TestObject();
+
+                return new Object[] {obj, obj};
+            }
+
+            case COLLECTION: {
+                TestObjectAllTypes obj = new TestObject();
+
+                Collection<Object> col = new ArrayList<>();
+
+                col.add(obj);
+                col.add(obj);
+
+                return col;
+            }
+
+            case MAP: {
+                TestObjectAllTypes obj = new TestObject();
+
+                Map<Object, Object> map = new HashMap<>();
+
+                map.put(0, obj);
+                map.put(1, obj);
+
+                return map;
+            }
+
+            default:
+                throw new IllegalStateException();
+        }
+    }
+
+    /** */
+    private void checkPutGetRemove(Object key, Object val) {
+        srvCache.put(key, val);
+
+        assertDeepEquals(val, srvCache.get(key));
+        assertDeepEquals(val, cliCache.get(key));
+
+        srvCache.remove(key);
+
+        assertNull(srvCache.get(key));
+        assertNull(cliCache.get(key));
+
+        cliCache.put(key, val);
+
+        assertDeepEquals(val, cliCache.get(key));
+        assertDeepEquals(val, srvCache.get(key));
+
+        cliCache.remove(key);
+
+        assertNull(srvCache.get(key));
+        assertNull(cliCache.get(key));
+    }
+
+    /** */
+    private static void assertDeepEquals(Object exp, Object actual) {
+        assertTrue(deepEquals(exp, actual));
+    }
+
+    /** */
+    private static boolean deepEquals(Object lhs, Object rhs) {
+        if (lhs instanceof Map && rhs instanceof Map) {
+            Map<Object, Object> lhsMap = (Map<Object, Object>)lhs;
+            Map<Object, Object> rhsMap = (Map<Object, Object>)rhs;
+
+            assertEquals(lhsMap.size(), rhsMap.size());
+
+            return lhsMap.entrySet().stream().allMatch(e -> 
deepEquals(e.getValue(), rhsMap.get(e.getKey())));
+        }
+        else if (lhs instanceof List && rhs instanceof List) {
+            List<Object> lhsList = (List<Object>)lhs;
+            List<Object> rhsList = (List<Object>)rhs;
+
+            assertEquals(lhsList.size(), rhsList.size());
+
+            boolean res = true;
+
+            for (int i = 0; i < lhsList.size(); i++) {
+                if (!deepEquals(lhsList.get(i), rhsList.get(i))) {
+                    res = false;
+
+                    break;
+                }
+            }
+
+            return res;
+        }
+        else
+            return Objects.deepEquals(lhs, rhs);
+    }
+
+    /** */
+    private static int hashCodeArraysAware(Object obj) {
+        return obj != null && obj.getClass().isArray() ? 
Arrays.deepHashCode((Object[])obj) : Objects.hash(obj);
+    }
+
+    /** */
+    public enum ObjectType {
+        /** */
+        OBJECT,
+
+        /** */
+        ARRAY,
+
+        /** */
+        COLLECTION,
+
+        /** */
+        MAP
+    }
+
+    /** */
+    public enum SerializationMode {
+        /** */
+        RAW,
+
+        /** */
+        MIXED,
+
+        /** */
+        SCHEMA
+    }
+
+    /** */
+    private static class MixedObject extends SchemaObject implements 
Binarylizable {
+        /** */
+        public MixedObject() {
+            // No-op.
+        }
+
+        /** */
+        public MixedObject(Object innerObj, Object outerObj) {
+            super(innerObj, outerObj);
+        }
+
+        /** {@inheritDoc} */
+        @Override public void writeBinary(BinaryWriter writer) throws 
BinaryObjectException {
+            writer.writeObject("aWrapperOfOuterRefToReplaceWithObj", 
aWrapperOfOuterRefToReplaceWithObj);
+            writer.writeObject("bInnerObj", bInnerObj);
+            writer.writeObject("cRefToOuterObjToReplaceWithInnerRef", 
cRefToOuterObjToReplaceWithInnerRef);
+            writer.writeObject("dRefToInnerObjToRecalculate", 
dRefToInnerObjToRecalculate);
+
+            BinaryRawWriter rawWriter = writer.rawWriter();
+
+            rawWriter.writeByte(eInnerBytePrimitive);
+        }
+
+        /** {@inheritDoc} */
+        @Override public void readBinary(BinaryReader reader) throws 
BinaryObjectException {
+            aWrapperOfOuterRefToReplaceWithObj = 
reader.readObject("aWrapperOfOuterRefToReplaceWithObj");
+            bInnerObj = reader.readObject("bInnerObj");
+            cRefToOuterObjToReplaceWithInnerRef = 
reader.readObject("cRefToOuterObjToReplaceWithInnerRef");
+            dRefToInnerObjToRecalculate = 
reader.readObject("dRefToInnerObjToRecalculate");
+
+            BinaryRawReader rawReader = reader.rawReader();
+
+            eInnerBytePrimitive = rawReader.readByte();
+        }
+    }
+
+    /** */
+    private static class RawObject extends SchemaObject implements 
Binarylizable {
+        /** */
+        public RawObject() {
+            // No-op.
+        }
+        
+        /** */
+        public RawObject(Object innerObj, Object outerObj) {
+            super(innerObj, outerObj);
+
+            // Reference recalculation for objects written with Raw Binary 
Writer currently is not supported.
+            aWrapperOfOuterRefToReplaceWithObj = null;
+            cRefToOuterObjToReplaceWithInnerRef = null;
+        }
+
+        /** {@inheritDoc} */
+        @Override public void writeBinary(BinaryWriter writer) throws 
BinaryObjectException {
+            BinaryRawWriter rawWriter = writer.rawWriter();
+            
+            rawWriter.writeObject(aWrapperOfOuterRefToReplaceWithObj);
+            rawWriter.writeObject(bInnerObj);
+            rawWriter.writeObject(cRefToOuterObjToReplaceWithInnerRef);
+            rawWriter.writeObject(dRefToInnerObjToRecalculate);
+            rawWriter.writeByte(eInnerBytePrimitive);
+        }
+
+        /** {@inheritDoc} */
+        @Override public void readBinary(BinaryReader reader) throws 
BinaryObjectException {
+            BinaryRawReader rawReader = reader.rawReader();
+            
+            aWrapperOfOuterRefToReplaceWithObj = rawReader.readObject();
+            bInnerObj = rawReader.readObject();
+            cRefToOuterObjToReplaceWithInnerRef = rawReader.readObject();
+            dRefToInnerObjToRecalculate = rawReader.readObject();
+            eInnerBytePrimitive = rawReader.readByte();
+        }
+    }
+
+    /** */
+    private static class SchemaObject {
+        /** */
+        protected Object aWrapperOfOuterRefToReplaceWithObj;
+
+        /** */
+        protected Object bInnerObj;
+
+        /** */
+        protected Object cRefToOuterObjToReplaceWithInnerRef;
+
+        /** */
+        protected Object dRefToInnerObjToRecalculate;
+
+        /** */
+        protected byte eInnerBytePrimitive;
+
+        /** */
+        public SchemaObject() {
+            // No-op.
+        }
+
+        /** */
+        public SchemaObject(Object innerObj, Object outerObj) {
+            aWrapperOfOuterRefToReplaceWithObj = new ComplexWrapper(outerObj);
+
+            bInnerObj = innerObj;
+
+            cRefToOuterObjToReplaceWithInnerRef = outerObj;
+
+            dRefToInnerObjToRecalculate = innerObj;
+
+            eInnerBytePrimitive = 127;
+        }
+
+        /** {@inheritDoc} */
+        @Override public boolean equals(Object o) {
+            if (this == o)
+                return true;
+
+            if (!(o instanceof SchemaObject))
+                return false;
+
+            SchemaObject that = (SchemaObject)o;
+
+            return deepEquals(aWrapperOfOuterRefToReplaceWithObj, 
that.aWrapperOfOuterRefToReplaceWithObj)
+                && deepEquals(bInnerObj, that.bInnerObj)
+                && deepEquals(cRefToOuterObjToReplaceWithInnerRef, 
that.cRefToOuterObjToReplaceWithInnerRef)
+                && deepEquals(dRefToInnerObjToRecalculate, 
that.dRefToInnerObjToRecalculate)
+                && eInnerBytePrimitive == that.eInnerBytePrimitive;
+        }
+
+        /** {@inheritDoc} */
+        @Override public int hashCode() {
+            int res = 1;
+
+            res = 31 * res + 
hashCodeArraysAware(aWrapperOfOuterRefToReplaceWithObj);
+            res = 31 * res + hashCodeArraysAware(bInnerObj);
+            res = 31 * res + 
hashCodeArraysAware(cRefToOuterObjToReplaceWithInnerRef);
+            res = 31 * res + hashCodeArraysAware(dRefToInnerObjToRecalculate);
+            res = 31 * res + hashCodeArraysAware(eInnerBytePrimitive);
+
+            return res;
+        }
+    }
+    
+    /** */
+    private static class TestObject extends TestObjectAllTypes {
+        /** */
+        public TestObject() {
+            setDefaultData();
+        }
+    }
+
+    /** */
+    public static class ComplexWrapper extends TestObject {
+        /** */
+        private final Object data;
+
+        /** */
+        public ComplexWrapper(Object data) {
+            this.data = data;
+        }
+
+        /** {@inheritDoc} */
+        @Override public boolean equals(Object o) {
+            if (this == o)
+                return true;
+
+            if (!(o instanceof ComplexWrapper))
+                return false;
+
+            ComplexWrapper that = (ComplexWrapper)o;
+
+            return super.equals(o) && deepEquals(data, that.data);
+        }
+
+        /** {@inheritDoc} */
+        @Override public int hashCode() {
+            return Objects.hash(super.hashCode(), hashCodeArraysAware(data));
+        }
+    }
+}
diff --git 
a/modules/core/src/test/java/org/apache/ignite/internal/binary/RawBinaryObjectExtractorTest.java
 
b/modules/core/src/test/java/org/apache/ignite/internal/binary/RawBinaryObjectExtractorTest.java
new file mode 100644
index 00000000000..186cbef0ad3
--- /dev/null
+++ 
b/modules/core/src/test/java/org/apache/ignite/internal/binary/RawBinaryObjectExtractorTest.java
@@ -0,0 +1,262 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.binary;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.ignite.binary.BinaryObject;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.binary.builder.BinaryObjectBuilderImpl;
+import 
org.apache.ignite.internal.binary.mutabletest.GridBinaryTestClasses.TestObjectAllTypes;
+import org.apache.ignite.internal.binary.streams.BinaryHeapInputStream;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.lang.IgnitePredicate;
+import org.apache.ignite.marshaller.MarshallerContext;
+import org.apache.ignite.marshaller.jdk.JdkMarshaller;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.junit.Test;
+
+/** */
+public class RawBinaryObjectExtractorTest extends GridCommonAbstractTest {
+    /** */
+    @Test
+    public void test() throws Exception {
+        BinaryContext ctx = createTestBinaryContext();
+
+        Collection<Object> testObjects = createObjectsOfAllTypes(ctx);
+        
+        byte[] serializedTestObjectsBytes;
+
+        try (BinaryWriterExImpl writer = new BinaryWriterExImpl(ctx)) {
+            testObjects.forEach(writer::writeObject);
+
+            serializedTestObjectsBytes = writer.array();
+        }
+
+        RawBinaryObjectExtractor rawReader = new 
RawBinaryObjectExtractor(BinaryHeapInputStream.create(serializedTestObjectsBytes,
 0));
+
+        for (Object testObj : testObjects) {
+            byte[] objRawBytes = rawReader.extractObject();
+
+            try (BinaryReaderExImpl binReader = new BinaryReaderExImpl(ctx, 
BinaryHeapInputStream.create(objRawBytes, 0), null, false)) {
+                Object deserializedObj = binReader.readObject();
+
+                if (testObj instanceof Proxy)
+                    assertEquals(String.valueOf(testObj), 
String.valueOf(deserializedObj));
+                else
+                    assertEqualsArraysAware(testObj, deserializedObj);
+
+                assertEquals(objRawBytes.length, binReader.in().position());
+            }
+        }
+
+        assertEquals(serializedTestObjectsBytes.length, rawReader.position());
+    }
+
+    /** */
+    public static BinaryContext createTestBinaryContext() {
+        BinaryContext ctx = new 
BinaryContext(BinaryCachingMetadataHandler.create(), new IgniteConfiguration(), 
null);
+
+        BinaryMarshaller marsh = new BinaryMarshaller();
+
+        marsh.setContext(new TestMarshallerContext());
+
+        ctx.configure(marsh);
+
+        return ctx;
+    }
+
+    /** */
+    private static Collection<Object> createObjectsOfAllTypes(BinaryContext 
ctx) throws IllegalAccessException {
+        Collection<Object> res = new ArrayList<>();
+
+        TestObjectAllTypesEx allTypesObj = new TestObjectAllTypesEx(ctx);
+
+        for (Field field : TestObjectAllTypesEx.class.getFields())
+            res.add(field.get(allTypesObj));
+
+        return res;
+    }
+
+    /** */
+    private Object createTestObject() {
+        TestObjectAllTypes res = new TestObjectAllTypes();
+
+        res.setDefaultData();
+
+        return res;
+    }
+
+    /** */
+    private interface RegisteredClass { }
+
+    /** */
+    private interface UnregisteredClass { }
+
+    /** */
+    private static class TestMarshallerContext implements MarshallerContext {
+        /** */
+        Map<Integer, String> clsNamesByTypeId = new HashMap<>();
+
+        /** {@inheritDoc} */
+        @Override public boolean registerClassName(
+            byte platformId,
+            int typeId,
+            String clsName
+        ) {
+            if (Objects.equals(clsName, UnregisteredClass.class.getName()))
+                return false;
+
+            clsNamesByTypeId.put(typeId, clsName);
+
+            return true;
+        }
+
+        /** {@inheritDoc} */
+        @Override public boolean registerClassNameLocally(byte platformId, int 
typeId, String clsName) {
+            return registerClassName(platformId, typeId, clsName);
+        }
+
+        /** {@inheritDoc} */
+        @Override public Class<?> getClass(int typeId, ClassLoader ldr) throws 
ClassNotFoundException {
+            return U.forName(clsNamesByTypeId.get(typeId), ldr);
+        }
+
+        /** {@inheritDoc} */
+        @Override public String getClassName(byte platformId, int typeId) {
+            return clsNamesByTypeId.get(typeId);
+        }
+
+        /** {@inheritDoc} */
+        @Override public boolean isSystemType(String typeName) {
+            return false;
+        }
+
+        /** {@inheritDoc} */
+        @Override public IgnitePredicate<String> classNameFilter() {
+            return null;
+        }
+
+        /** {@inheritDoc} */
+        @Override public JdkMarshaller jdkMarshaller() {
+            return new JdkMarshaller();
+        }
+    }
+
+    /** */
+    public static class TestInvocationHandler implements InvocationHandler {
+        /** */
+        private final String proxyClsName;
+
+        /** */
+        public TestInvocationHandler(Class<?> proxyCls) {
+            proxyClsName = proxyCls.getName();
+        }
+
+        /** {@inheritDoc} */
+        @Override public Object invoke(Object p, Method method, Object[] 
methodArgs) throws Throwable {
+            if ("toString".equals(method.getName()))
+                return proxyClsName;
+
+            throw new IllegalStateException();
+        }
+    }
+    
+    /** */
+    public static class TestObjectAllTypesEx extends TestObjectAllTypes {
+        /** */
+        public Object nullObj;
+
+        /** */
+        public Object obj;
+
+        /** */
+        public Object[] emptyArr;
+
+        /** */
+        public Object[] objArr;
+
+        /** */
+        public Collection<Object> col;
+
+        /** */
+        public Map<Object, Object> map;
+
+        /** */
+        public Class<?> registeredClass;
+
+        /** */
+        public Class<?> unregisteredClass;
+
+        /** */
+        public Object registeredClsProxy;
+
+        /** */
+        public Object unregisteredClsProxy;
+
+        /** */
+        public BinaryObject binObj;
+
+        /** */
+        public TestObjectAllTypesEx(BinaryContext ctx) {
+            setDefaultData();
+
+            nullObj = null;
+            obj = new TestObjectAllTypes();
+
+            emptyArr = new Object[] {};
+            objArr = new Object[] {"test", new TestObjectAllTypes(), new 
Object[]{new TestObjectAllTypes()}};
+
+            col = new ArrayList<>();
+
+            col.add(0);
+            col.add(new TestObjectAllTypes());
+            col.add(new ArrayList<>());
+            col.add(new HashMap<>());
+
+            map = new HashMap<>();
+
+            map.put(0, 0);
+            map.put(1, new TestObjectAllTypes());
+            map.put(2, new ArrayList<>());
+            map.put(3, new HashMap<>());
+
+            registeredClass = RegisteredClass.class;
+            unregisteredClass = UnregisteredClass.class;
+
+            registeredClsProxy = Proxy.newProxyInstance(
+                RawBinaryObjectExtractorTest.class.getClassLoader(),
+                new Class[] { RegisteredClass.class },
+                new TestInvocationHandler(RegisteredClass.class));
+
+            unregisteredClsProxy = Proxy.newProxyInstance(
+                RawBinaryObjectExtractorTest.class.getClassLoader(),
+                new Class[] { UnregisteredClass.class },
+                new TestInvocationHandler(UnregisteredClass.class));
+
+            binObj = new BinaryObjectBuilderImpl(ctx, 
"TestBinaryType").setField("test-field", "test-value").build();
+        }
+    }
+}
diff --git 
a/modules/core/src/test/java/org/apache/ignite/internal/binary/mutabletest/GridBinaryTestClasses.java
 
b/modules/core/src/test/java/org/apache/ignite/internal/binary/mutabletest/GridBinaryTestClasses.java
index 067866c094a..8d1ff662782 100644
--- 
a/modules/core/src/test/java/org/apache/ignite/internal/binary/mutabletest/GridBinaryTestClasses.java
+++ 
b/modules/core/src/test/java/org/apache/ignite/internal/binary/mutabletest/GridBinaryTestClasses.java
@@ -26,9 +26,11 @@ import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.sql.Timestamp;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Date;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.TreeMap;
 import java.util.UUID;
 import com.google.common.base.Throwables;
@@ -317,6 +319,79 @@ public class GridBinaryTestClasses {
 
             entry = new GridMapEntry<>(1, "a");
         }
+
+        /** {@inheritDoc} */
+        @Override public boolean equals(Object o) {
+            if (this == o)
+                return true;
+
+            if (!(o instanceof TestObjectAllTypes))
+                return false;
+
+            TestObjectAllTypes allTypesObj = (TestObjectAllTypes)o;
+
+            return b == allTypesObj.b
+                && s == allTypesObj.s
+                && i == allTypesObj.i
+                && l == allTypesObj.l
+                && Float.compare(f, allTypesObj.f) == 0
+                && Double.compare(d, allTypesObj.d) == 0
+                && c == allTypesObj.c
+                && z == allTypesObj.z
+                && Objects.equals(b_, allTypesObj.b_)
+                && Objects.equals(s_, allTypesObj.s_)
+                && Objects.equals(i_, allTypesObj.i_)
+                && Objects.equals(bi_, allTypesObj.bi_)
+                && Objects.equals(l_, allTypesObj.l_)
+                && Objects.equals(f_, allTypesObj.f_)
+                && Objects.equals(d_, allTypesObj.d_)
+                && (bd_ == null ? allTypesObj.bd_ == null : 
bd_.compareTo(allTypesObj.bd_) == 0)
+                && Objects.equals(c_, allTypesObj.c_)
+                && Objects.equals(z_, allTypesObj.z_)
+                && Objects.equals(str, allTypesObj.str)
+                && Objects.equals(uuid, allTypesObj.uuid)
+                && Objects.equals(date, allTypesObj.date)
+                && Objects.equals(ts, allTypesObj.ts)
+                && Arrays.equals(bArr, allTypesObj.bArr)
+                && Arrays.equals(sArr, allTypesObj.sArr)
+                && Arrays.equals(iArr, allTypesObj.iArr)
+                && Arrays.equals(lArr, allTypesObj.lArr)
+                && Arrays.equals(fArr, allTypesObj.fArr)
+                && Arrays.equals(dArr, allTypesObj.dArr)
+                && Arrays.equals(cArr, allTypesObj.cArr)
+                && Arrays.equals(zArr, allTypesObj.zArr)
+                && Arrays.equals(bdArr, allTypesObj.bdArr)
+                && Arrays.equals(strArr, allTypesObj.strArr)
+                && Arrays.equals(uuidArr, allTypesObj.uuidArr)
+                && Arrays.equals(dateArr, allTypesObj.dateArr)
+                && Arrays.equals(tsArr, allTypesObj.tsArr)
+                && anEnum == allTypesObj.anEnum
+                && Arrays.equals(enumArr, allTypesObj.enumArr)
+                && Objects.equals(entry, allTypesObj.entry);
+        }
+
+        /** {@inheritDoc} */
+        @Override public int hashCode() {
+            int res =
+                Objects.hash(b_, s_, i_, bi_, l_, f_, d_, bd_, c_, z_, b, s, 
i, l, f, d, c, z, str, uuid, date, ts, anEnum, entry);
+
+            res = 31 * res + Arrays.hashCode(bArr);
+            res = 31 * res + Arrays.hashCode(sArr);
+            res = 31 * res + Arrays.hashCode(iArr);
+            res = 31 * res + Arrays.hashCode(lArr);
+            res = 31 * res + Arrays.hashCode(fArr);
+            res = 31 * res + Arrays.hashCode(dArr);
+            res = 31 * res + Arrays.hashCode(cArr);
+            res = 31 * res + Arrays.hashCode(zArr);
+            res = 31 * res + Arrays.hashCode(bdArr);
+            res = 31 * res + Arrays.hashCode(strArr);
+            res = 31 * res + Arrays.hashCode(uuidArr);
+            res = 31 * res + Arrays.hashCode(dateArr);
+            res = 31 * res + Arrays.hashCode(tsArr);
+            res = 31 * res + Arrays.hashCode(enumArr);
+
+            return res;
+        }
     }
 
     /**
diff --git 
a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/IgnitePdsBinarySortObjectFieldsTest.java
 
b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/IgnitePdsBinarySortObjectFieldsTest.java
index b3d2e60a4cf..4103b22df88 100644
--- 
a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/IgnitePdsBinarySortObjectFieldsTest.java
+++ 
b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/IgnitePdsBinarySortObjectFieldsTest.java
@@ -19,7 +19,6 @@ package 
org.apache.ignite.internal.processors.cache.persistence;
 
 import java.util.concurrent.TimeUnit;
 import org.apache.ignite.IgniteCache;
-import org.apache.ignite.IgniteSystemProperties;
 import org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunction;
 import org.apache.ignite.cluster.ClusterState;
 import org.apache.ignite.configuration.CacheConfiguration;
@@ -27,8 +26,8 @@ import 
org.apache.ignite.configuration.DataRegionConfiguration;
 import org.apache.ignite.configuration.DataStorageConfiguration;
 import org.apache.ignite.configuration.IgniteConfiguration;
 import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.binary.BinaryUtils;
 import org.apache.ignite.testframework.GridTestUtils;
-import org.apache.ignite.testframework.junits.WithSystemProperty;
 import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
 import org.junit.Test;
 
@@ -78,19 +77,27 @@ public class IgnitePdsBinarySortObjectFieldsTest extends 
GridCommonAbstractTest
      * @throws Exception if failed.
      */
     @Test
-    @WithSystemProperty(key = 
IgniteSystemProperties.IGNITE_BINARY_SORT_OBJECT_FIELDS, value = "true")
     public void testGivenCacheWithPojoValueAndPds_WhenPut_ThenNoHangup() 
throws Exception {
-        IgniteEx ignite = startGrid(0);
+        boolean prev = BinaryUtils.FIELDS_SORTED_ORDER;
 
-        ignite.cluster().state(ClusterState.ACTIVE);
+        BinaryUtils.FIELDS_SORTED_ORDER = true;
 
-        final IgniteCache<Long, Value> cache = ignite.getOrCreateCache(
-            new CacheConfiguration<Long, Value>(CACHE_NAME)
-                .setAffinity(new 
RendezvousAffinityFunction().setPartitions(32)));
+        try {
+            IgniteEx ignite = startGrid(0);
 
-        GridTestUtils.assertTimeout(5, TimeUnit.SECONDS, () -> cache.put(1L, 
new Value(1L)));
+            ignite.cluster().state(ClusterState.ACTIVE);
 
-        assertEquals(1, cache.size());
+            final IgniteCache<Long, Value> cache = ignite.getOrCreateCache(
+                new CacheConfiguration<Long, Value>(CACHE_NAME)
+                    .setAffinity(new 
RendezvousAffinityFunction().setPartitions(32)));
+
+            GridTestUtils.assertTimeout(5, TimeUnit.SECONDS, () -> 
cache.put(1L, new Value(1L)));
+
+            assertEquals(1, cache.size());
+        }
+        finally {
+            BinaryUtils.FIELDS_SORTED_ORDER = prev;
+        }
     }
 
     /**
diff --git 
a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBinaryObjectsTestSuite.java
 
b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBinaryObjectsTestSuite.java
index b52df8fe875..9dc3ec83c00 100644
--- 
a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBinaryObjectsTestSuite.java
+++ 
b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBinaryObjectsTestSuite.java
@@ -41,11 +41,13 @@ import 
org.apache.ignite.internal.binary.BinaryObjectTypeCompatibilityTest;
 import 
org.apache.ignite.internal.binary.BinarySerialiedFieldComparatorSelfTest;
 import org.apache.ignite.internal.binary.BinarySimpleNameTestPropertySelfTest;
 import org.apache.ignite.internal.binary.BinaryTreeSelfTest;
+import org.apache.ignite.internal.binary.CrossObjectReferenceSerializationTest;
 import org.apache.ignite.internal.binary.GridBinaryAffinityKeySelfTest;
 import 
org.apache.ignite.internal.binary.GridBinaryMarshallerCtxDisabledSelfTest;
 import org.apache.ignite.internal.binary.GridBinaryWildcardsSelfTest;
 import 
org.apache.ignite.internal.binary.GridDefaultBinaryMappersBinaryMetaDataSelfTest;
 import 
org.apache.ignite.internal.binary.GridSimpleLowerCaseBinaryMappersBinaryMetaDataSelfTest;
+import org.apache.ignite.internal.binary.RawBinaryObjectExtractorTest;
 import 
org.apache.ignite.internal.binary.noncompact.BinaryFieldsHeapNonCompactSelfTest;
 import 
org.apache.ignite.internal.binary.noncompact.BinaryFieldsOffheapNonCompactSelfTest;
 import 
org.apache.ignite.internal.binary.noncompact.BinaryFooterOffsetsHeapNonCompactSelfTest;
@@ -179,6 +181,9 @@ import org.junit.runners.Suite;
 
     BinaryMetadataMoveLegacyFolderTest.class,
     BinaryContextPredefinedTypesTest.class,
+
+    RawBinaryObjectExtractorTest.class,
+    CrossObjectReferenceSerializationTest.class,
 })
 public class IgniteBinaryObjectsTestSuite {
 }

Reply via email to