You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by am...@apache.org on 2020/12/10 15:01:44 UTC

[ignite-3] 01/01: IGNITE-13618: Provide generated and reflection-based class (de)serializers.

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

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

commit 2cafc7b399875cf0ced4e1609ed2c76f6707dad0
Author: Andrew Mashenkov <an...@gmail.com>
AuthorDate: Thu Dec 10 18:01:26 2020 +0300

    IGNITE-13618: Provide generated and reflection-based class (de)serializers.
    
    Implemented reflection-based serializer using Unsafe.
    Implemented generated serializer using Janino comp.
    Benchmark for serializer added.
---
 modules/commons/pom.xml                            |  41 ++
 .../ignite/internal/schema/TupleAssembler.java     |   4 -
 .../internal/schema/marshaller/BinaryMode.java     |  90 ++++
 .../internal/schema/marshaller/MarshallerUtil.java | 103 ++++
 .../schema/marshaller/SerializationException.java  |  43 ++
 .../internal/schema/marshaller/Serializer.java     |  42 ++
 .../schema/marshaller/SerializerFactory.java       |  52 ++
 .../generator/FieldAccessExprGenerator.java        | 360 ++++++++++++++
 .../IdentityObjectMarshallerExprGenerator.java     |  45 ++
 .../generator/JaninoSerializerGenerator.java       | 346 ++++++++++++++
 .../generator/MarshallerExprGenerator.java         |  94 ++++
 .../marshaller/reflection/JavaSerializer.java      | 316 ++++++++++++
 .../reflection/JavaSerializerFactory.java          |  32 ++
 .../schema/marshaller/reflection/Marshaller.java   | 153 ++++++
 .../marshaller/reflection/UnsafeFieldAccessor.java | 527 +++++++++++++++++++++
 .../org/apache/ignite/internal/util/Factory.java   |  32 ++
 .../ignite/internal/util/IgniteUnsafeUtils.java    | 273 +++++++++++
 .../apache/ignite/internal/util/ObjectFactory.java |  62 +++
 .../java/org/apache/ignite/internal/util/Pair.java |  57 +++
 .../benchmarks/SerializerBenchmarkTest.java        | 185 ++++++++
 .../apache/ignite/internal/schema/TestUtils.java   | 121 +++++
 .../apache/ignite/internal/schema/TupleTest.java   |  64 +--
 .../schema/marshaller/JavaSerializerTest.java      | 494 +++++++++++++++++++
 .../marshaller/reflection/FieldAccessorTest.java   | 396 ++++++++++++++++
 pom.xml                                            |   7 +-
 25 files changed, 3871 insertions(+), 68 deletions(-)

diff --git a/modules/commons/pom.xml b/modules/commons/pom.xml
index 0599be4..fc78231 100644
--- a/modules/commons/pom.xml
+++ b/modules/commons/pom.xml
@@ -42,5 +42,46 @@
             <artifactId>annotations</artifactId>
             <version>${jetbrains.annotations.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.codehaus.janino</groupId>
+            <artifactId>janino</artifactId>
+            <version>${janino.version}</version>
+        </dependency>
+
+
+        <!-- Test dependencies -->
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-params</artifactId>
+            <version>${junit.jupiter.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <version>${mockito.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+        <!-- Benchmarks dependencies -->
+        <dependency>
+            <groupId>org.openjdk.jmh</groupId>
+            <artifactId>jmh-core</artifactId>
+            <version>${jmh.framework.verion}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.openjdk.jmh</groupId>
+            <artifactId>jmh-generator-annprocess</artifactId>
+            <version>${jmh.framework.verion}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>javax.annotation</groupId>
+            <artifactId>javax.annotation-api</artifactId>
+            <version>${javax.annotation.api.version}</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/TupleAssembler.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/TupleAssembler.java
index 45d3982..71c3eda 100644
--- a/modules/commons/src/main/java/org/apache/ignite/internal/schema/TupleAssembler.java
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/TupleAssembler.java
@@ -17,12 +17,8 @@
 
 package org.apache.ignite.internal.schema;
 
-import java.nio.BufferUnderflowException;
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
 import java.nio.charset.CharacterCodingException;
 import java.nio.charset.CharsetEncoder;
-import java.nio.charset.CoderResult;
 import java.nio.charset.StandardCharsets;
 import java.util.BitSet;
 import java.util.UUID;
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/BinaryMode.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/BinaryMode.java
new file mode 100644
index 0000000..db5a77f
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/BinaryMode.java
@@ -0,0 +1,90 @@
+/*
+ * 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.schema.marshaller;
+
+import org.apache.ignite.internal.schema.NativeTypeSpec;
+
+/**
+ * Various read/write modes for binary objects that maps Java types to binary types.
+ */
+public enum BinaryMode {
+    /** Primitive byte. */
+    P_BYTE(NativeTypeSpec.BYTE),
+
+    /** Primitive short. */
+    P_SHORT(NativeTypeSpec.SHORT),
+
+    /** Primitive int. */
+    P_INT(NativeTypeSpec.INTEGER),
+
+    /** Primitive long. */
+    P_LONG(NativeTypeSpec.LONG),
+
+    /** Primitive float. */
+    P_FLOAT(NativeTypeSpec.FLOAT),
+
+    /** Primitive int. */
+    P_DOUBLE(NativeTypeSpec.DOUBLE),
+
+    /** */
+    BYTE(NativeTypeSpec.BYTE),
+
+    /** */
+    SHORT(NativeTypeSpec.SHORT),
+
+    /** */
+    INT(NativeTypeSpec.INTEGER),
+
+    /** */
+    LONG(NativeTypeSpec.LONG),
+
+    /** */
+    FLOAT(NativeTypeSpec.FLOAT),
+
+    /** */
+    DOUBLE(NativeTypeSpec.DOUBLE),
+
+    /** */
+    STRING(NativeTypeSpec.STRING),
+
+    /** */
+    UUID(NativeTypeSpec.UUID),
+
+    /** */
+    BYTE_ARR(NativeTypeSpec.BYTES),
+
+    /** */
+    BITSET(NativeTypeSpec.BITMASK);
+
+    /** Natove type spec. */
+    private final NativeTypeSpec typeSpec;
+
+    /**
+     * @param typeSpec Native type spec.
+     */
+    BinaryMode(NativeTypeSpec typeSpec) {
+        this.typeSpec = typeSpec;
+    }
+
+    /**
+     * @return Native type spec.
+     */
+    public NativeTypeSpec typeSpec() {
+        return typeSpec;
+    }
+}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/MarshallerUtil.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/MarshallerUtil.java
new file mode 100644
index 0000000..c111ba8
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/MarshallerUtil.java
@@ -0,0 +1,103 @@
+/*
+ * 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.schema.marshaller;
+
+import java.util.BitSet;
+import java.util.UUID;
+import org.apache.ignite.internal.schema.NativeType;
+import org.apache.ignite.internal.schema.TupleAssembler;
+
+/**
+ * Marshaller utility class.
+ */
+public final class MarshallerUtil {
+    /**
+     * Calculates size for serialized value of varlen type.
+     *
+     * @param val Field value.
+     * @param type Mapped type.
+     * @return Serialized value size.
+     */
+    public static int getValueSize(Object val, NativeType type) {
+        switch (type.spec()) {
+            case BYTES:
+                return ((byte[])val).length;
+
+            case STRING:
+                return TupleAssembler.utf8EncodedLength((CharSequence)val);
+
+            default:
+                throw new IllegalStateException("Unsupported test varsize type: " + type);
+        }
+    }
+
+    /**
+     * Gets binary read/write mode for given class.
+     *
+     * @param cls Type.
+     * @return Binary mode.
+     */
+    public static BinaryMode mode(Class<?> cls) {
+        assert cls != null;
+
+        // Primitives.
+        if (cls == byte.class)
+            return BinaryMode.P_BYTE;
+        else if (cls == short.class)
+            return BinaryMode.P_SHORT;
+        else if (cls == int.class)
+            return BinaryMode.P_INT;
+        else if (cls == long.class)
+            return BinaryMode.P_LONG;
+        else if (cls == float.class)
+            return BinaryMode.P_FLOAT;
+        else if (cls == double.class)
+            return BinaryMode.P_DOUBLE;
+
+            // Boxed primitives.
+        else if (cls == Byte.class)
+            return BinaryMode.BYTE;
+        else if (cls == Short.class)
+            return BinaryMode.SHORT;
+        else if (cls == Integer.class)
+            return BinaryMode.INT;
+        else if (cls == Long.class)
+            return BinaryMode.LONG;
+        else if (cls == Float.class)
+            return BinaryMode.FLOAT;
+        else if (cls == Double.class)
+            return BinaryMode.DOUBLE;
+
+            // Other types
+        else if (cls == byte[].class)
+            return BinaryMode.BYTE_ARR;
+        else if (cls == String.class)
+            return BinaryMode.STRING;
+        else if (cls == UUID.class)
+            return BinaryMode.UUID;
+        else if (cls == BitSet.class)
+            return BinaryMode.BITSET;
+
+        return null;
+    }
+
+    /**
+     * Stub.
+     */
+    private MarshallerUtil() {}
+}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/SerializationException.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/SerializationException.java
new file mode 100644
index 0000000..e690710
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/SerializationException.java
@@ -0,0 +1,43 @@
+/*
+ * 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.schema.marshaller;
+
+/**
+ * Serialization exception.
+ */
+public class SerializationException extends Exception {
+    /**
+     * Constructor.
+     *
+     * @param cause Cause.
+     */
+    public SerializationException(Throwable cause) {
+        // Used by serializers generated with Janino.
+        super(cause);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param message Message.
+     * @param cause Cause.
+     */
+    public SerializationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/Serializer.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/Serializer.java
new file mode 100644
index 0000000..21a1560
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/Serializer.java
@@ -0,0 +1,42 @@
+/*
+ * 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.schema.marshaller;
+
+/**
+ * Key-value objects (de)serializer.
+ */
+public interface Serializer {
+    /**
+     * Writes key-value pair to tuple.
+     *
+     * @param key Key object.
+     * @param val Value object.
+     * @return Serialized key-value pair.
+     */
+    byte[] serialize(Object key, Object val) throws SerializationException;
+
+    /**
+     * @return Key object.
+     */
+    Object deserializeKey(byte[] data) throws SerializationException;
+
+    /**
+     * @return Value object.
+     */
+    Object deserializeValue(byte[] data) throws SerializationException;
+}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/SerializerFactory.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/SerializerFactory.java
new file mode 100644
index 0000000..d9a8295
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/SerializerFactory.java
@@ -0,0 +1,52 @@
+/*
+ * 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.schema.marshaller;
+
+import org.apache.ignite.internal.schema.SchemaDescriptor;
+import org.apache.ignite.internal.schema.marshaller.generator.JaninoSerializerGenerator;
+import org.apache.ignite.internal.schema.marshaller.reflection.JavaSerializerFactory;
+
+/**
+ * (De)Serializer factory interface.
+ */
+@FunctionalInterface
+public interface SerializerFactory {
+    /**
+     * @return Serializer factory back by code generator.
+     */
+    public static SerializerFactory createJaninoSerializerFactory() {
+        return new JaninoSerializerGenerator();
+    }
+
+    /**
+     * @return Reflection-based serializer factory.
+     */
+    public static SerializerFactory createJavaSerializerFactory() {
+        return new JavaSerializerFactory();
+    }
+
+    /**
+     * Creates serializer.
+     *
+     * @param schema Schema descriptor.
+     * @param keyClass Key class.
+     * @param valClass Value class.
+     * @return Serializer.
+     */
+    public Serializer create(SchemaDescriptor schema, Class<?> keyClass, Class<?> valClass);
+}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/FieldAccessExprGenerator.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/FieldAccessExprGenerator.java
new file mode 100644
index 0000000..fb3736a
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/FieldAccessExprGenerator.java
@@ -0,0 +1,360 @@
+/*
+ * 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.schema.marshaller.generator;
+
+import org.apache.ignite.internal.schema.marshaller.BinaryMode;
+import org.jetbrains.annotations.Nullable;
+
+import static org.apache.ignite.internal.schema.marshaller.generator.JaninoSerializerGenerator.LF;
+
+/**
+ * Object field access expression generators.
+ */
+class FieldAccessExprGenerator {
+    /** Append null expression. */
+    private static final String WRITE_NULL_EXPR = "asm.appendNull();";
+
+    /**
+     * Created object access expressions generator.
+     *
+     * @param mode Field access binary mode.
+     * @param colIdx Column absolute index in schema.
+     * @return Object field access expressions generator.
+     */
+    static FieldAccessExprGenerator createIdentityAccessor(BinaryMode mode, int colIdx) {
+        return createAccessor(mode, colIdx, -1L);
+    }
+
+    /**
+     * Created object field access expressions generator.
+     *
+     * @param mode Field access binary mode.
+     * @param colIdx Column absolute index in schema.
+     * @param offset Object field offset.
+     * @return Object field access expressions generator.
+     */
+    static FieldAccessExprGenerator createAccessor(BinaryMode mode, int colIdx, long offset) {
+        switch (mode) {
+            case BYTE:
+                return new FieldAccessExprGenerator(
+                    colIdx,
+                    "Byte",
+                    "tuple.byteValueBoxed",
+                    "asm.appendByte",
+                    offset);
+
+            case P_BYTE:
+                return new FieldAccessExprGenerator(
+                    colIdx,
+                    "tuple.byteValue",
+                    "asm.appendByte",
+                    offset,
+                    "IgniteUnsafeUtils.getByteField",
+                    "IgniteUnsafeUtils.putByteField"
+                );
+
+            case SHORT:
+                return new FieldAccessExprGenerator(
+                    colIdx,
+                    "Short",
+                    "tuple.shortValueBoxed",
+                    "asm.appendShort",
+                    offset);
+
+            case P_SHORT:
+                return new FieldAccessExprGenerator(
+                    colIdx,
+                    "tuple.shortValue",
+                    "asm.appendShort",
+                    offset,
+                    "IgniteUnsafeUtils.getShortField",
+                    "IgniteUnsafeUtils.putShortField"
+                );
+
+            case INT:
+                return new FieldAccessExprGenerator(
+                    colIdx,
+                    "Integer",
+                    "tuple.intValueBoxed",
+                    "asm.appendInt",
+                    offset);
+
+            case P_INT:
+                return new FieldAccessExprGenerator(
+                    colIdx,
+                    "tuple.intValue",
+                    "asm.appendInt",
+                    offset,
+                    "IgniteUnsafeUtils.getIntField",
+                    "IgniteUnsafeUtils.putIntField"
+                );
+
+            case LONG:
+                return new FieldAccessExprGenerator(
+                    colIdx,
+                    "Long",
+                    "tuple.longValueBoxed",
+                    "asm.appendLong",
+                    offset);
+
+            case P_LONG:
+                return new FieldAccessExprGenerator(
+                    colIdx,
+                    "tuple.longValue",
+                    "asm.appendLong",
+                    offset,
+                    "IgniteUnsafeUtils.getLongField",
+                    "IgniteUnsafeUtils.putLongField"
+                );
+
+            case FLOAT:
+                return new FieldAccessExprGenerator(
+                    colIdx,
+                    "Float",
+                    "tuple.floatValueBoxed",
+                    "asm.appendFloat",
+                    offset);
+
+            case P_FLOAT:
+                return new FieldAccessExprGenerator(
+                    colIdx,
+                    "tuple.floatValue",
+                    "asm.appendFloat",
+                    offset,
+                    "IgniteUnsafeUtils.getFloatField",
+                    "IgniteUnsafeUtils.putFloatField"
+                );
+
+            case DOUBLE:
+                return new FieldAccessExprGenerator(
+                    colIdx,
+                    "Double",
+                    "tuple.doubleValueBoxed",
+                    "asm.appendDouble",
+                    offset);
+
+            case P_DOUBLE:
+                return new FieldAccessExprGenerator(
+                    colIdx,
+                    "tuple.doubleValue",
+                    "asm.appendDouble",
+                    offset,
+                    "IgniteUnsafeUtils.getDoubleField",
+                    "IgniteUnsafeUtils.putDoubleField"
+                );
+
+            case UUID:
+                return new FieldAccessExprGenerator(
+                    colIdx,
+                    "UUID",
+                    "tuple.uuidValue", "asm.appendUuid",
+                    offset);
+
+            case BITSET:
+                return new FieldAccessExprGenerator(
+                    colIdx,
+                    "BitSet",
+                    "tuple.bitmaskValue", "asm.appendBitmask",
+                    offset);
+
+            case STRING:
+                return new FieldAccessExprGenerator(
+                    colIdx,
+                    "String",
+                    "tuple.stringValue", "asm.appendString",
+                    offset);
+
+            case BYTE_ARR:
+                return new FieldAccessExprGenerator(
+                    colIdx,
+                    "byte[]",
+                    "tuple.bytesValue", "asm.appendBytes",
+                    offset);
+            default:
+                throw new IllegalStateException("Unsupportd binary mode");
+        }
+    }
+
+    /** Object field offset or {@code -1} for identity accessor. */
+    private final long offset;
+
+    /** Absolute schema index. */
+    private final int colIdx;
+
+    /** Class cast expression. */
+    private final String classExpr;
+
+    /** Write column value expression. */
+    private final String writeColMethod;
+
+    /** Read column value expression. */
+    private final String readColMethod;
+
+    /** Read object field expression. */
+    private final String getFieldMethod;
+
+    /** Write object field expression. */
+    private final String putFieldMethod;
+
+    /**
+     * Constructor.
+     *
+     * @param colIdx Absolute schema index in schema.
+     * @param castClassExpr Class cast expression
+     * @param readColMethod Read column value expression.
+     * @param writeColMethod Write column value expression.
+     * @param offset Field offset or {@code -1} for identity accessor.
+     */
+    private FieldAccessExprGenerator(
+        int colIdx,
+        String castClassExpr,
+        String readColMethod,
+        String writeColMethod,
+        long offset
+    ) {
+        this(colIdx, castClassExpr, readColMethod, writeColMethod, offset,
+            "IgniteUnsafeUtils.getObjectField", "IgniteUnsafeUtils.putObjectField");
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param colIdx Absolute schema index in schema.
+     * @param readColMethod Read column value expression.
+     * @param writeColMethod Write column value expression.
+     * @param offset Field offset or {@code -1} for identity accessor.
+     * @param getFieldMethod Read object field expression.
+     * @param putFieldMethod Read object field expression.
+     */
+    public FieldAccessExprGenerator(
+        int colIdx,
+        String readColMethod,
+        String writeColMethod,
+        long offset,
+        String getFieldMethod,
+        String putFieldMethod
+    ) {
+        this(colIdx, null /* primitive type */, readColMethod, writeColMethod, offset, getFieldMethod, putFieldMethod);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param colIdx Absolute schema index in schema.
+     * @param castClassExpr Class cast expression or {@code null} if not applicable.
+     * @param readColMethod Read column value expression.
+     * @param writeColMethod Write column value expression.
+     * @param offset Field offset or {@code -1} for identity accessor.
+     * @param getFieldMethod Read object field expression.
+     * @param putFieldMethod Read object field expression.
+     */
+    private FieldAccessExprGenerator(
+        int colIdx,
+        @Nullable String castClassExpr,
+        String readColMethod,
+        String writeColMethod,
+        long offset,
+        String getFieldMethod,
+        String putFieldMethod
+    ) {
+        this.offset = offset;
+        this.colIdx = colIdx;
+        this.classExpr = castClassExpr;
+        this.putFieldMethod = putFieldMethod;
+        this.getFieldMethod = getFieldMethod;
+        this.writeColMethod = writeColMethod;
+        this.readColMethod = readColMethod;
+    }
+
+    /**
+     * @return {@code true} if it is primitive typed field accessor, {@code false} otherwise.
+     */
+    private boolean isPrimitive() {
+        return classExpr == null;
+    }
+
+    /**
+     * @return {@code true} if is identity accessor, {@code false} otherwise.
+     */
+    private boolean isIdentityAccessor() {
+        return offset == -1;
+    }
+
+    /**
+     * @return Object field value access expression or object value expression for simple types.
+     */
+    public String getFieldExpr() {
+        if (isIdentityAccessor())
+            return "obj"; // Identity accessor.
+
+        return getFieldMethod + "(obj, " + offset + ')';
+    }
+
+    /**
+     * Appends write value to field expression.
+     *
+     * @param sb String bulder.
+     * @param valueExpression Value expression.
+     * @param indent Line indentation.
+     */
+    public final void appendPutFieldExpr(StringBuilder sb, String valueExpression, String indent) {
+        sb.append(indent).append(putFieldMethod).append("(obj, ").append(offset).append(", ").append(valueExpression).append(')');
+        sb.append(";" + LF);
+    }
+
+    /**
+     * Appends write value to column expression.
+     *
+     * @param sb String bulder.
+     * @param valueExpr Value expression.
+     * @param indent Line indentation.
+     */
+    public final void appendWriteColumnExpr(StringBuilder sb, String valueExpr, String indent) {
+        if (isPrimitive() || isIdentityAccessor()) {
+            // Translate to:
+            // asm.appendX((T) %value%);
+            // or for primitive value:
+            // asm.appendX(%value%);
+            sb.append(indent).append(writeColMethod).append('(');
+
+            if (classExpr != null)
+                sb.append("(").append(classExpr).append(")");
+
+            sb.append(valueExpr).append(");" + LF);
+
+            return;
+        }
+
+        assert classExpr != null;
+
+        // Translate to:
+        // { T fVal = (T)%value%;
+        //  if (fVal == null) asm.appendNull() else asm.appendX(fVal); }
+        sb.append(indent).append("{ ").append(classExpr).append(" fVal = (").append(classExpr).append(')').append(valueExpr).append(";" + LF);
+        sb.append(indent).append("if (fVal == null) " + WRITE_NULL_EXPR + LF);
+        sb.append(indent).append("else ").append(writeColMethod).append("(fVal); }" + LF);
+
+    }
+
+    /**
+     * @return Column value read expression.
+     */
+    public String readColumnExpr() {
+        return readColMethod + "(" + colIdx + ")";
+    }
+}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/IdentityObjectMarshallerExprGenerator.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/IdentityObjectMarshallerExprGenerator.java
new file mode 100644
index 0000000..e574e61
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/IdentityObjectMarshallerExprGenerator.java
@@ -0,0 +1,45 @@
+/*
+ * 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.schema.marshaller.generator;
+
+import org.apache.ignite.internal.schema.marshaller.Serializer;
+
+/**
+ * Generate {@link Serializer} method's bodies for simple types.
+ */
+class IdentityObjectMarshallerExprGenerator extends MarshallerExprGenerator {
+    /**
+     * Constructor.
+     *
+     * @param accessor Object field access expression generators.
+     */
+    IdentityObjectMarshallerExprGenerator(FieldAccessExprGenerator accessor) {
+        super(null /* no instantiation needed */, new FieldAccessExprGenerator[] {accessor});
+    }
+
+    /** {@inheritDoc} */
+    @Override public void appendMarshallObjectExpr(StringBuilder sb, String indent) {
+        for (int i = 0; i < accessors.length; i++)
+            accessors[i].appendWriteColumnExpr(sb, "obj", indent);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void appendUnmarshallObjectExpr(StringBuilder sb, String indent) {
+        sb.append(indent).append("Object obj = ").append(accessors[0].readColumnExpr()).append(";" + JaninoSerializerGenerator.LF);
+    }
+}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/JaninoSerializerGenerator.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/JaninoSerializerGenerator.java
new file mode 100644
index 0000000..654bf97
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/JaninoSerializerGenerator.java
@@ -0,0 +1,346 @@
+/*
+ * 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.schema.marshaller.generator;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import org.apache.ignite.internal.schema.Columns;
+import org.apache.ignite.internal.schema.SchemaDescriptor;
+import org.apache.ignite.internal.schema.marshaller.BinaryMode;
+import org.apache.ignite.internal.schema.marshaller.MarshallerUtil;
+import org.apache.ignite.internal.schema.marshaller.Serializer;
+import org.apache.ignite.internal.schema.marshaller.SerializerFactory;
+import org.apache.ignite.internal.util.IgniteUnsafeUtils;
+import org.codehaus.commons.compiler.CompilerFactoryFactory;
+import org.codehaus.commons.compiler.IClassBodyEvaluator;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * {@link Serializer} code generator backed with Janino.
+ */
+public class JaninoSerializerGenerator implements SerializerFactory {
+    /** Tabulate. */
+    static final String TAB = "    ";
+
+    /** Line feed. */
+    static final char LF = '\n';
+
+    /** String buffer initial size. */
+    public static final int INITIAL_BUFFER_SIZE = 8 * 1024;
+
+    /** Debug flag. */
+    private static final boolean enabledDebug = true;
+
+    /** {@inheritDoc} */
+    @Override public Serializer create(
+        SchemaDescriptor schema,
+        Class<?> keyClass,
+        Class<?> valClass
+    ) {
+        try {
+            final IClassBodyEvaluator ce = CompilerFactoryFactory.getDefaultCompilerFactory().newClassBodyEvaluator();
+
+            // Generate Serializer code.
+            String code = generateSerializerClassCode(ce, schema, keyClass, valClass);
+
+            //TODO: pass code to logger on trace level.
+
+            if (enabledDebug) {
+                ce.setDebuggingInformation(true, true, true);
+
+                //TODO: dump code to log.
+//                System.out.println(code);
+            }
+
+            try {  // Compile and load class.
+                ce.setParentClassLoader(getClass().getClassLoader());
+                ce.cook(code);
+
+                // Create and return Serializer instance.
+                final Constructor<Serializer> ctor = (Constructor<Serializer>)ce.getClazz()
+                    .getDeclaredConstructor(schema.getClass(), Class.class, Class.class);
+
+                return ctor.newInstance(schema, keyClass, valClass);
+            }
+            catch (Exception ex) {
+                if (enabledDebug)
+                    throw new IllegalStateException("Failed to compile/instantiate generated Serializer: code=" +
+                        LF + code + LF, ex);
+                else
+                    throw new IllegalStateException("Failed to compile/instantiate generated Serializer.", ex);
+            }
+        }
+        catch (Exception ex) {
+            //TODO: fallback to java serializer?
+            throw new IllegalStateException(ex);
+        }
+    }
+
+    /**
+     * Generates serializer code.
+     *
+     * @param ce Class body evaluator.
+     * @param schema Schema descriptor.
+     * @param keyClass Key class.
+     * @param valClass Value class.
+     * @return Generated class code.
+     */
+    private String generateSerializerClassCode(
+        IClassBodyEvaluator ce,
+        SchemaDescriptor schema,
+        Class<?> keyClass,
+        Class<?> valClass
+    ) {
+        final String packageName = "org.apache.ignite.internal.schema.marshaller.";
+        final String className = "JaninoSerializerForSchema_" + schema.version();
+
+        // Prerequisites.
+        ce.setClassName(packageName + className);
+        ce.setImplementedInterfaces(new Class[] {Serializer.class});
+        ce.setDefaultImports(
+            "java.util.UUID",
+            "java.util.BitSet",
+
+            "org.apache.ignite.internal.schema.ByteBufferTuple",
+            "org.apache.ignite.internal.schema.Columns",
+            "org.apache.ignite.internal.schema.SchemaDescriptor",
+            "org.apache.ignite.internal.schema.Tuple",
+            "org.apache.ignite.internal.schema.TupleAssembler",
+            "org.apache.ignite.internal.util.IgniteUnsafeUtils",
+            "org.apache.ignite.internal.util.ObjectFactory"
+        );
+
+        // Build field accessor generators.
+        final MarshallerExprGenerator keyMarsh = createObjectMarshaller(keyClass, "keyFactory", schema.keyColumns(), 0);
+        final MarshallerExprGenerator valMarsh = createObjectMarshaller(valClass, "valFactory", schema.valueColumns(), schema.keyColumns().length());
+
+        // Create buffer.
+        final StringBuilder sb = new StringBuilder(INITIAL_BUFFER_SIZE);
+
+        // Append class fields desctiption.
+        sb.append("private final SchemaDescriptor schema;" + LF);
+
+        if (!keyMarsh.isSimpleTypeMarshaller())
+            sb.append("private final ObjectFactory keyFactory;" + LF);
+        if (!valMarsh.isSimpleTypeMarshaller())
+            sb.append("private final ObjectFactory valFactory;" + LF);
+
+        // Append constructor code.
+        sb.append(LF + "public ").append(className).append("(SchemaDescriptor schema, Class kClass, Class vClass) {" + LF);
+        sb.append(TAB + "this.schema = schema; " + LF);
+        if (!keyMarsh.isSimpleTypeMarshaller())
+            sb.append(TAB + "keyFactory = new ObjectFactory(kClass);" + LF);
+        if (!valMarsh.isSimpleTypeMarshaller())
+            sb.append(TAB + "valFactory = new ObjectFactory(vClass);" + LF);
+        sb.append("}" + LF);
+
+        // Generate and append helper-methods.
+        generateTupleFactoryMethod(sb, schema, keyMarsh, valMarsh);
+
+        // Generate and append Serializer interface methods.
+        appendSerializeMethod(sb, keyMarsh, valMarsh);
+        writeDeserializeKeyMethod(sb, keyMarsh);
+        writeDeserializeValueMethod(sb, valMarsh);
+
+        return sb.toString();
+    }
+
+    /**
+     * Creates marshal/unmarshall expressions generator for object.
+     *
+     * @param aClass Object class.
+     * @param factoryRefExpr Factory reference expression.
+     * @param columns Columns that aClass mapped to.
+     * @param firstColIdx First column absolute index in schema.
+     * @return Marshal/unmarshall expression generator.
+     */
+    private MarshallerExprGenerator createObjectMarshaller(
+        Class<?> aClass,
+        @Nullable String factoryRefExpr,
+        Columns columns,
+        int firstColIdx
+    ) {
+        BinaryMode mode = MarshallerUtil.mode(aClass);
+
+        if (mode != null)
+            return new IdentityObjectMarshallerExprGenerator(FieldAccessExprGenerator.createIdentityAccessor(mode, firstColIdx));
+
+        FieldAccessExprGenerator[] accessors = new FieldAccessExprGenerator[columns.length()];
+        try {
+            for (int i = 0; i < columns.length(); i++) {
+                final Field field = aClass.getDeclaredField(columns.column(i).name());
+
+                accessors[i] = FieldAccessExprGenerator.createAccessor(
+                    MarshallerUtil.mode(field.getType()),
+                    firstColIdx + i /* schma absolute index. */,
+                    IgniteUnsafeUtils.objectFieldOffset(field));
+            }
+        }
+        catch (NoSuchFieldException ex) {
+            throw new IllegalStateException(ex);
+        }
+
+        return new MarshallerExprGenerator(factoryRefExpr, accessors);
+    }
+
+    /**
+     * Appends {@link Serializer#serialize(Object, Object)} method code.
+     *
+     * @param sb String buffer to append to.
+     * @param keyMarsh Marshall expression generator for key.
+     * @param valMarsh Marshall expression generator for value.
+     */
+    private void appendSerializeMethod(
+        StringBuilder sb,
+        MarshallerExprGenerator keyMarsh,
+        MarshallerExprGenerator valMarsh
+    ) {
+        // Mehtod signature.
+        sb.append(LF + "@Override public byte[] serialize(Object key, Object val) throws SerializationException {" + LF);
+        sb.append(TAB + "TupleAssembler asm = createAssembler(key, val);" + LF);
+
+        // Key marshal script.
+        sb.append(TAB + "{" + LF);
+        sb.append(TAB + TAB + "Object obj = key;" + LF);
+        keyMarsh.appendMarshallObjectExpr(sb, TAB + TAB);
+        sb.append(TAB + "}" + LF);
+
+        // Value marshal script.
+        sb.append(TAB + " {" + LF);
+        sb.append(TAB + TAB + "Object obj = val;" + LF);
+        valMarsh.appendMarshallObjectExpr(sb, TAB + TAB);
+        sb.append(TAB + "}" + LF);
+
+        // Return statement.
+        sb.append(TAB + "return asm.build();" + LF);
+        sb.append("}" + LF);
+    }
+
+    /**
+     * Appends {@link Serializer#deserializeKey(byte[])} method code.
+     *
+     * @param sb String buffer to append to.
+     * @param keyMarsh Unmarshall expression generator for key.
+     */
+    private void writeDeserializeKeyMethod(StringBuilder sb, MarshallerExprGenerator keyMarsh) {
+        // Mehtod signature.
+        sb.append(LF + "@Override public Object deserializeKey(byte[] data) throws SerializationException {" + LF);
+        sb.append(TAB + "Tuple tuple = new ByteBufferTuple(schema, data);" + LF);
+
+        // Key unmarshal script.
+        keyMarsh.appendUnmarshallObjectExpr(sb, TAB);
+
+        // Return statement.
+        sb.append(TAB + "return obj;" + LF);
+        sb.append("}" + LF);
+    }
+
+    /**
+     * Appends {@link Serializer#deserializeValue(byte[])} method code.
+     *
+     * @param sb String buffer to append to.
+     * @param valMarsh Unmarshall expression generator for value.
+     */
+    private void writeDeserializeValueMethod(StringBuilder sb, MarshallerExprGenerator valMarsh) {
+        // Mehtod signature.
+        sb.append(LF + "@Override public Object deserializeValue(byte[] data) throws SerializationException {" + LF);
+        sb.append(TAB + "Tuple tuple = new ByteBufferTuple(schema, data);" + LF);
+
+        // Key unmarshal script.
+        valMarsh.appendUnmarshallObjectExpr(sb, TAB);
+
+        // Return statement.
+        sb.append(TAB + "return obj;" + LF);
+        sb.append("}" + LF);
+    }
+
+    /**
+     * Appends helper methods code.
+     *
+     * @param sb String buffer to append to.
+     * @param schema Schema descriptor.
+     * @param keyMarsh Marshall expression generator for key.
+     * @param valMarsh Marshall expression generator for value.
+     */
+    private void generateTupleFactoryMethod(
+        StringBuilder sb,
+        SchemaDescriptor schema,
+        MarshallerExprGenerator keyMarsh,
+        MarshallerExprGenerator valMarsh
+    ) {
+        // Method signature.
+        sb.append(LF + "TupleAssembler createAssembler(Object key, Object val) {" + LF);
+        // Local variables.
+        sb.append(TAB + "int nonNullVarlenKeys = 0; int nonNullVarlenValues = 0;" + LF);
+        sb.append(TAB + "int nonNullVarlenKeysSize = 0; int nonNullVarlenValuesSize = 0;" + LF);
+        sb.append(LF);
+        sb.append(TAB + "Columns keyCols = schema.keyColumns();" + LF);
+        sb.append(TAB + "Columns valCols = schema.valueColumns();" + LF);
+        sb.append(LF);
+
+        Columns keyCols = schema.keyColumns();
+        if (keyCols.firstVarlengthColumn() >= 0) {
+            // Appends key analyzer code-block.
+            sb.append(TAB + "{" + LF);
+            sb.append(TAB + TAB + "Object fVal, obj = key;" + LF); // Temporary vars.
+
+            for (int i = keyCols.firstVarlengthColumn(); i < keyCols.length(); i++) {
+                assert !keyCols.column(i).type().spec().fixedLength();
+
+                sb.append(TAB + TAB + "assert !keyCols.column(").append(i).append(").type().spec().fixedLength();" + LF);
+                sb.append(TAB + TAB + "fVal = ").append(keyMarsh.accessors[i].getFieldExpr()).append(";" + LF);
+                sb.append(TAB + TAB + "if (fVal != null) {" + LF);
+                sb.append(TAB + TAB + TAB + "nonNullVarlenKeysSize += MarshallerUtil.getValueSize(fVal, keyCols.column(").append(i).append(").type());").append(LF);
+                sb.append(TAB + TAB + TAB + "nonNullVarlenKeys++;" + LF);
+                sb.append(TAB + TAB + "}" + LF);
+            }
+
+            sb.append(TAB + "}" + LF);
+        }
+
+        Columns valCols = schema.valueColumns();
+        if (valCols.firstVarlengthColumn() >= 0) {
+            // Appends value analyzer code-block.
+            sb.append(TAB + "{" + LF);
+            sb.append(TAB + TAB + "Object fVal, obj = val;" + LF); // Temporary vars.
+
+            for (int i = valCols.firstVarlengthColumn(); i < valCols.length(); i++) {
+                assert !valCols.column(i).type().spec().fixedLength();
+
+                sb.append(TAB + TAB + "assert !valCols.column(").append(i).append(").type().spec().fixedLength();" + LF);
+                sb.append(TAB + TAB + "fVal = ").append(valMarsh.accessors[i].getFieldExpr()).append(";" + LF);
+                sb.append(TAB + TAB + "if (fVal != null) {" + LF);
+                sb.append(TAB + TAB + TAB + "nonNullVarlenValuesSize += MarshallerUtil.getValueSize(fVal, valCols.column(").append(i).append(").type());" + LF);
+                sb.append(TAB + TAB + TAB + "nonNullVarlenValues++;" + LF);
+                sb.append(TAB + TAB + "}" + LF);
+            }
+            sb.append(TAB + "}" + LF);
+        }
+
+        // Calculate tuple size.
+        sb.append(LF);
+        sb.append(TAB + "int size = TupleAssembler.tupleSize(" + LF);
+        sb.append(TAB + TAB + "keyCols, nonNullVarlenKeys, nonNullVarlenKeysSize, " + LF);
+        sb.append(TAB + TAB + "valCols, nonNullVarlenValues, nonNullVarlenValuesSize); " + LF);
+        sb.append(LF);
+
+        // Return statement.
+        sb.append(TAB + "return new TupleAssembler(schema, size, nonNullVarlenKeys, nonNullVarlenValues);" + LF);
+        sb.append("}" + LF);
+    }
+}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/MarshallerExprGenerator.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/MarshallerExprGenerator.java
new file mode 100644
index 0000000..433b567
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/generator/MarshallerExprGenerator.java
@@ -0,0 +1,94 @@
+/*
+ * 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.schema.marshaller.generator;
+
+import org.apache.ignite.internal.schema.marshaller.Serializer;
+
+/**
+ * Generate {@link Serializer} method's bodies.
+ */
+class MarshallerExprGenerator {
+    /** Object factory regerence expression. */
+    private final String factoryRefExpr;
+
+    /** Object field access expression generators. */
+    protected FieldAccessExprGenerator[] accessors;
+
+    /**
+     * Constructor.
+     *
+     * @param factoryRefExpr Object factory regerence expression.
+     * @param accessors Object field access expression generators.
+     */
+    public MarshallerExprGenerator(String factoryRefExpr, FieldAccessExprGenerator[] accessors) {
+        this.accessors = accessors;
+        this.factoryRefExpr = factoryRefExpr;
+    }
+
+    /**
+     * @return {@code true} if it is simple object marshaller, {@code false} otherwise.
+     */
+    boolean isSimpleTypeMarshaller() {
+        return factoryRefExpr == null;
+    }
+
+    /**
+     * Appends unmashall object code to string builder.
+     *
+     * @param sb String builder.
+     * @param indent Line indentation.
+     */
+    public void appendUnmarshallObjectExpr(StringBuilder sb, String indent) {
+        assert factoryRefExpr != null;
+
+        sb.append(indent).append("Object obj;" + JaninoSerializerGenerator.LF);
+        // Try.
+        sb.append(indent).append("try {" + JaninoSerializerGenerator.LF);
+        sb.append(indent).append(JaninoSerializerGenerator.TAB + "obj = ").append(factoryRefExpr).append(".create();" + JaninoSerializerGenerator.LF);
+
+        // Read column from tuple to object field.
+        for (int i = 0; i < accessors.length; i++)
+            accessors[i].appendPutFieldExpr(sb, accessors[i].readColumnExpr(), indent + JaninoSerializerGenerator.TAB);
+
+        // Catch and rethrow wrapped exeption.
+        sb.append(indent).append("} catch (Exception ex) {" + JaninoSerializerGenerator.LF);
+        sb.append(indent).append(JaninoSerializerGenerator.TAB + "throw new SerializationException(\"Failed to instantiate object: \" + ")
+            .append(factoryRefExpr).append(".getClazz().getName(), ex);").append(JaninoSerializerGenerator.LF);
+        sb.append(indent).append("}" + JaninoSerializerGenerator.LF);
+    }
+
+    /**
+     * Appends mashall object code to string builder.
+     *
+     * @param sb String builder.
+     * @param indent Line indentation.
+     */
+    public void appendMarshallObjectExpr(StringBuilder sb, String indent) {
+        // Try.
+        sb.append(indent).append("try {" + JaninoSerializerGenerator.LF);
+
+        // Write object field to tuple assembler.
+        for (int i = 0; i < accessors.length; i++)
+            accessors[i].appendWriteColumnExpr(sb, accessors[i].getFieldExpr(), indent + JaninoSerializerGenerator.TAB);
+
+        // Catch and rethrow wrapped exeption.
+        sb.append(indent).append("} catch (Exception ex) {" + JaninoSerializerGenerator.LF);
+        sb.append(indent).append(JaninoSerializerGenerator.TAB + "throw new SerializationException(ex);").append(JaninoSerializerGenerator.LF);
+        sb.append(indent).append("}" + JaninoSerializerGenerator.LF);
+    }
+}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/reflection/JavaSerializer.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/reflection/JavaSerializer.java
new file mode 100644
index 0000000..4bbb630
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/reflection/JavaSerializer.java
@@ -0,0 +1,316 @@
+/*
+ * 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.schema.marshaller.reflection;
+
+import java.util.BitSet;
+import java.util.UUID;
+import org.apache.ignite.internal.schema.ByteBufferTuple;
+import org.apache.ignite.internal.schema.Columns;
+import org.apache.ignite.internal.schema.SchemaDescriptor;
+import org.apache.ignite.internal.schema.Tuple;
+import org.apache.ignite.internal.schema.TupleAssembler;
+import org.apache.ignite.internal.schema.marshaller.BinaryMode;
+import org.apache.ignite.internal.schema.marshaller.SerializationException;
+import org.apache.ignite.internal.schema.marshaller.Serializer;
+
+import static org.apache.ignite.internal.schema.marshaller.MarshallerUtil.getValueSize;
+
+/**
+ * Reflection based (de)serializer.
+ */
+public class JavaSerializer implements Serializer {
+
+    /**
+     * Reads value object from tuple.
+     *
+     * @param reader Reader.
+     * @param colIdx Column index.
+     * @param mode Binary read mode.
+     * @return Read value object.
+     */
+    static Object readRefValue(Tuple reader, int colIdx, BinaryMode mode) {
+        assert reader != null;
+        assert colIdx >= 0;
+
+        Object val = null;
+
+        switch (mode) {
+            case BYTE:
+                val = reader.byteValueBoxed(colIdx);
+
+                break;
+
+            case SHORT:
+                val = reader.shortValueBoxed(colIdx);
+
+                break;
+
+            case INT:
+                val = reader.intValueBoxed(colIdx);
+
+                break;
+
+            case LONG:
+                val = reader.longValueBoxed(colIdx);
+
+                break;
+
+            case FLOAT:
+                val = reader.floatValueBoxed(colIdx);
+
+                break;
+
+            case DOUBLE:
+                val = reader.doubleValueBoxed(colIdx);
+
+                break;
+
+            case STRING:
+                val = reader.stringValue(colIdx);
+
+                break;
+
+            case UUID:
+                val = reader.uuidValue(colIdx);
+
+                break;
+
+            case BYTE_ARR:
+                val = reader.bytesValue(colIdx);
+
+                break;
+
+            case BITSET:
+                val = reader.bitmaskValue(colIdx);
+
+                break;
+
+            default:
+                assert false : "Invalid mode: " + mode;
+        }
+
+        return val;
+    }
+
+    /**
+     * Writes reference value to tuple.
+     *
+     * @param val Value object.
+     * @param writer Writer.
+     * @param mode Write binary mode.
+     */
+    static void writeRefObject(Object val, TupleAssembler writer, BinaryMode mode) {
+        assert writer != null;
+
+        if (val == null) {
+            writer.appendNull();
+
+            return;
+        }
+
+        switch (mode) {
+            case BYTE:
+                writer.appendByte((Byte)val);
+
+                break;
+
+            case SHORT:
+                writer.appendShort((Short)val);
+
+                break;
+
+            case INT:
+                writer.appendInt((Integer)val);
+
+                break;
+
+            case LONG:
+                writer.appendLong((Long)val);
+
+                break;
+
+            case FLOAT:
+                writer.appendFloat((Float)val);
+
+                break;
+
+            case DOUBLE:
+                writer.appendDouble((Double)val);
+
+                break;
+
+            case STRING:
+                writer.appendString((String)val);
+
+                break;
+
+            case UUID:
+                writer.appendUuid((UUID)val);
+
+                break;
+
+            case BYTE_ARR:
+                writer.appendBytes((byte[])val);
+
+                break;
+
+            case BITSET:
+                writer.appendBitmask((BitSet)val);
+
+                break;
+
+            default:
+                assert false : "Invalid mode: " + mode;
+        }
+    }
+
+    /** Schema. */
+    private final SchemaDescriptor schema;
+
+    /** Key class. */
+    private final Class<?> keyClass;
+
+    /** Value class. */
+    private final Class<?> valClass;
+
+    /** Key marshaller. */
+    private final Marshaller keyMarsh;
+
+    /** Value marshaller. */
+    private final Marshaller valMarsh;
+
+    /**
+     * Constructor.
+     *
+     * @param schema Schema.
+     * @param keyClass Key type.
+     * @param valClass Value type.
+     */
+    public JavaSerializer(SchemaDescriptor schema, Class<?> keyClass, Class<?> valClass) {
+        this.schema = schema;
+        this.keyClass = keyClass;
+        this.valClass = valClass;
+
+        keyMarsh = Marshaller.createMarshaller(schema.keyColumns(), 0, keyClass);
+        valMarsh = Marshaller.createMarshaller(schema.valueColumns(), schema.keyColumns().length(), valClass);
+    }
+
+    /** {@inheritDoc} */
+    @Override public byte[] serialize(Object key, Object val) throws SerializationException {
+        assert keyClass.isInstance(key);
+        assert val == null || valClass.isInstance(val);
+
+        final TupleAssembler asm = createAssembler(key, val);
+
+        keyMarsh.writeObject(key, asm);
+
+        if (val != null)
+            valMarsh.writeObject(val, asm);
+        else
+            assert false; // TODO: add tomstone support and remove assertion.
+
+        return asm.build();
+    }
+
+    /**
+     * Creates TupleAssebler for key-value pair.
+     *
+     * @param key Key object.
+     * @param val Value object.
+     * @return Tuple assembler.
+     */
+    private TupleAssembler createAssembler(Object key, Object val) {
+        ObjectStatistic keyStat = collectObjectStats(schema.keyColumns(), keyMarsh, key);
+        ObjectStatistic valStat = collectObjectStats(schema.valueColumns(), valMarsh, val);
+
+        int size = TupleAssembler.tupleSize(
+            schema.keyColumns(), keyStat.nonNullFields, keyStat.nonNullFieldsSize,
+            schema.valueColumns(), valStat.nonNullFields, valStat.nonNullFieldsSize);
+
+        return new TupleAssembler(schema, size, keyStat.nonNullFields, valStat.nonNullFields);
+    }
+
+    /**
+     * Reads object fields and gather statistic.
+     *
+     * @param cols Schema columns.
+     * @param marsh Marshaller.
+     * @param obj Object.
+     * @return Object statistic.
+     */
+    private ObjectStatistic collectObjectStats(Columns cols, Marshaller marsh, Object obj) {
+        if (obj == null || cols.firstVarlengthColumn() < 0 /* No varlen columns */)
+            return new ObjectStatistic(0, 0);
+
+        int cnt = 0;
+        int size = 0;
+
+        for (int i = cols.firstVarlengthColumn(); i < cols.length(); i++) {
+            final Object val = marsh.value(obj, i);
+
+            if (val == null || cols.column(i).type().spec().fixedLength())
+                continue;
+
+            size += getValueSize(val, cols.column(i).type());
+            cnt++;
+        }
+
+        return new ObjectStatistic(cnt, size);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Object deserializeKey(byte[] data) throws SerializationException {
+        final Tuple tuple = new ByteBufferTuple(schema, data);
+
+        final Object o = keyMarsh.readObject(tuple);
+
+        assert keyClass.isInstance(o);
+
+        return o;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Object deserializeValue(byte[] data) throws SerializationException {
+        final Tuple tuple = new ByteBufferTuple(schema, data);
+
+        // TODO: add tomstone support.
+
+        final Object o = valMarsh.readObject(tuple);
+
+        assert valClass.isInstance(o);
+
+        return o;
+    }
+
+    /**
+     * Object statistic.
+     */
+    private static class ObjectStatistic {
+        /** Non-null fields of varlen type. */
+        int nonNullFields;
+
+        /** Length of all non-null fields of varlen types. */
+        int nonNullFieldsSize;
+
+        /** Constructor. */
+        public ObjectStatistic(int nonNullFields, int nonNullFieldsSize) {
+            this.nonNullFields = nonNullFields;
+            this.nonNullFieldsSize = nonNullFieldsSize;
+        }
+    }
+}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/reflection/JavaSerializerFactory.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/reflection/JavaSerializerFactory.java
new file mode 100644
index 0000000..ec17ee4
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/reflection/JavaSerializerFactory.java
@@ -0,0 +1,32 @@
+/*
+ * 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.schema.marshaller.reflection;
+
+import org.apache.ignite.internal.schema.SchemaDescriptor;
+import org.apache.ignite.internal.schema.marshaller.Serializer;
+import org.apache.ignite.internal.schema.marshaller.SerializerFactory;
+
+/**
+ * Factory for reflection-based serializer.
+ */
+public class JavaSerializerFactory implements SerializerFactory {
+    /** {@inheritDoc} */
+    @Override public Serializer create(SchemaDescriptor schema, Class<?> keyClass, Class<?> valClass) {
+        return new JavaSerializer(schema, keyClass, valClass);
+    }
+}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/reflection/Marshaller.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/reflection/Marshaller.java
new file mode 100644
index 0000000..506c3a3
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/reflection/Marshaller.java
@@ -0,0 +1,153 @@
+/*
+ * 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.schema.marshaller.reflection;
+
+import java.util.Objects;
+import org.apache.ignite.internal.schema.Column;
+import org.apache.ignite.internal.schema.Columns;
+import org.apache.ignite.internal.schema.Tuple;
+import org.apache.ignite.internal.schema.TupleAssembler;
+import org.apache.ignite.internal.schema.marshaller.BinaryMode;
+import org.apache.ignite.internal.schema.marshaller.MarshallerUtil;
+import org.apache.ignite.internal.schema.marshaller.SerializationException;
+import org.apache.ignite.internal.util.Factory;
+import org.apache.ignite.internal.util.ObjectFactory;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Marshaller.
+ */
+public class Marshaller {
+    /**
+     * Creates marshaller for class.
+     *
+     * @param cols Columns.
+     * @param firstColId First column position in schema.
+     * @param aClass Type.
+     * @return Marshaller.
+     */
+    public static Marshaller createMarshaller(Columns cols, int firstColId, Class<? extends Object> aClass) {
+        final BinaryMode mode = MarshallerUtil.mode(aClass);
+
+        if (mode != null) {
+            final Column col = cols.column(0);
+
+            assert cols.length() == 1;
+            assert mode.typeSpec() == col.type().spec() : "Target type is not compatible.";
+            assert !aClass.isPrimitive() : "Non-nullable types are not allowed.";
+
+            return new Marshaller(UnsafeFieldAccessor.createIdentityAccessor(col, firstColId, mode));
+        }
+
+        UnsafeFieldAccessor[] fieldAccessors = new UnsafeFieldAccessor[cols.length()];
+
+        // Build accessors
+        for (int i = 0; i < cols.length(); i++) {
+            final Column col = cols.column(i);
+
+            final int colIdx = firstColId + i; /* Absolute column idx in schema. */
+            fieldAccessors[i] = UnsafeFieldAccessor.create(aClass, col, colIdx);
+        }
+
+        return new Marshaller(new ObjectFactory<>(aClass), fieldAccessors);
+    }
+
+    /**
+     * Field accessors for mapped columns.
+     * Array has same size and order as columns.
+     */
+    private final UnsafeFieldAccessor[] fieldAccessors;
+
+    /**
+     * Object factory for complex types or {@code null} for basic type.
+     */
+    private final Factory<?> factory;
+
+    /**
+     * Constructor.
+     * Creates marshaller for complex types.
+     *
+     * @param factory Object factory.
+     * @param fieldAccessors Object field accessors for mapped columns.
+     */
+    @SuppressWarnings("AssignmentOrReturnOfFieldWithMutableType")
+    public Marshaller(Factory<?> factory, UnsafeFieldAccessor[] fieldAccessors) {
+        this.fieldAccessors = fieldAccessors;
+        this.factory = Objects.requireNonNull(factory);
+    }
+
+    /**
+     * Constructor.
+     * Creates marshaller for basic types.
+     *
+     * @param fieldAccessor Identity field accessor for object of basic type.
+     */
+    public Marshaller(UnsafeFieldAccessor fieldAccessor) {
+        fieldAccessors = new UnsafeFieldAccessor[] {fieldAccessor};
+        factory = null;
+    }
+
+    /**
+     * Reads object field.
+     *
+     * @param obj Object.
+     * @param fldIdx Field index.
+     * @return Field value.
+     */
+    public @Nullable Object value(Object obj, int fldIdx) {
+        return fieldAccessors[fldIdx].value(obj);
+    }
+
+    /**
+     * Reads object from tuple.
+     *
+     * @param reader Tuple reader.
+     * @return Object.
+     * @throws SerializationException If failed.
+     */
+    public Object readObject(Tuple reader) throws SerializationException {
+        if (isBasicTypeMarshaller())
+            return fieldAccessors[0].read(reader);
+
+        final Object obj = factory.create();
+
+        for (int fldIdx = 0; fldIdx < fieldAccessors.length; fldIdx++)
+            fieldAccessors[fldIdx].read(obj, reader);
+
+        return obj;
+    }
+
+    /**
+     * Write object to tuple.
+     *
+     * @param obj Object.
+     * @param writer Tuple writer.
+     * @throws SerializationException If failed.
+     */
+    public void writeObject(Object obj, TupleAssembler writer) throws SerializationException {
+        for (int fldIdx = 0; fldIdx < fieldAccessors.length; fldIdx++)
+            fieldAccessors[fldIdx].write(obj, writer);
+    }
+
+    /**
+     * @return {@code true} if it is marshaller for basic type, {@code false} otherwise.
+     */
+    private boolean isBasicTypeMarshaller() {
+        return factory == null;
+    }
+}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/reflection/UnsafeFieldAccessor.java b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/reflection/UnsafeFieldAccessor.java
new file mode 100644
index 0000000..72b2491
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/schema/marshaller/reflection/UnsafeFieldAccessor.java
@@ -0,0 +1,527 @@
+/*
+ * 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.schema.marshaller.reflection;
+
+import java.lang.reflect.Field;
+import java.util.Objects;
+import org.apache.ignite.internal.schema.Column;
+import org.apache.ignite.internal.schema.Columns;
+import org.apache.ignite.internal.schema.Tuple;
+import org.apache.ignite.internal.schema.TupleAssembler;
+import org.apache.ignite.internal.schema.marshaller.BinaryMode;
+import org.apache.ignite.internal.schema.marshaller.MarshallerUtil;
+import org.apache.ignite.internal.schema.marshaller.SerializationException;
+import org.apache.ignite.internal.util.IgniteUnsafeUtils;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Field accessor to speedup access.
+ */
+// TODO: Extract interface, move to java-8 profile and add Java9+ implementation using VarHandles.
+public abstract class UnsafeFieldAccessor {
+    /**
+     * TODO: implement sesitive information filtering.
+     *
+     * @return {@code False} if sensitive information exoising is prohibited, {@code false} otherwise.
+     */
+    private static boolean includeSensitive() {
+        return true;
+    }
+
+    /** Offset. */
+    protected final long offset;
+
+    /** Mode. */
+    protected final BinaryMode mode;
+
+    /** Field name */
+    protected final String name;
+
+    /**
+     * Mapped column position in schema.
+     * <p>
+     * NODE: Do not mix up with column index in {@link Columns} container.
+     */
+    protected final int colIdx;
+
+    /**
+     * Create accessor for the field.
+     *
+     * @param type Object class.
+     * @param col Mapped column.
+     * @param colIdx Column index in schema.
+     * @return Accessor.
+     */
+    static UnsafeFieldAccessor create(Class<?> type, Column col, int colIdx) {
+        try {
+            final Field field = type.getDeclaredField(col.name());
+
+            if (field.getType().isPrimitive() && col.nullable())
+                throw new IllegalArgumentException("Failed to map non-nullable field to nullable column [name=" + field.getName() + ']');
+
+            BinaryMode mode = MarshallerUtil.mode(field.getType());
+
+            switch (mode) {
+                case P_BYTE:
+                    return new BytePrimitiveAccessor(field, colIdx);
+
+                case P_SHORT:
+                    return new ShortPrimitiveAccessor(field, colIdx);
+
+                case P_INT:
+                    return new IntPrimitiveAccessor(field, colIdx);
+
+                case P_LONG:
+                    return new LongPrimitiveAccessor(field, colIdx);
+
+                case P_FLOAT:
+                    return new FloatPrimitiveAccessor(field, colIdx);
+
+                case P_DOUBLE:
+                    return new DoublePrimitiveAccessor(field, colIdx);
+
+                case BYTE:
+                case SHORT:
+                case INT:
+                case LONG:
+                case FLOAT:
+                case DOUBLE:
+                case STRING:
+                case UUID:
+                case BYTE_ARR:
+                case BITSET:
+                    return new ReferenceFieldAccessor(field, colIdx, mode);
+
+                default:
+                    assert false : "Invalid mode " + mode;
+            }
+
+            throw new IllegalArgumentException("Failed to create accessor for field [name=" + field.getName() + ']');
+        }
+        catch (NoSuchFieldException | SecurityException ex) {
+            throw new IllegalArgumentException(ex);
+        }
+    }
+
+    /**
+     * Create accessor for the field.
+     *
+     * @param col Column.
+     * @param colIdx Column index.
+     * @param mode Binary mode.
+     * @return Accessor.
+     */
+    static UnsafeFieldAccessor createIdentityAccessor(Column col, int colIdx, BinaryMode mode) {
+        switch (mode) {
+            //  Marshaller read/write object contract methods allowed boxed types only.
+            case P_BYTE:
+            case P_SHORT:
+            case P_INT:
+            case P_LONG:
+            case P_FLOAT:
+            case P_DOUBLE:
+                throw new IllegalArgumentException("Primitive key/value types are not possible by API contract.");
+
+            case BYTE:
+            case SHORT:
+            case INT:
+            case LONG:
+            case FLOAT:
+            case DOUBLE:
+            case STRING:
+            case UUID:
+            case BYTE_ARR:
+            case BITSET:
+                return new IdentityAccessor(colIdx, mode);
+
+            default:
+                assert false : "Invalid mode " + mode;
+        }
+
+        throw new IllegalArgumentException("Failed to create accessor for column [name=" + col.name() + ']');
+    }
+
+    /**
+     * Protected constructor.
+     *
+     * @param field Field.
+     * @param colIdx Column index.
+     * @param mode Binary mode;
+     */
+    protected UnsafeFieldAccessor(Field field, int colIdx, BinaryMode mode) {
+        assert field != null;
+        assert colIdx >= 0;
+        assert mode != null;
+
+        this.colIdx = colIdx;
+        this.mode = mode;
+        offset = IgniteUnsafeUtils.objectFieldOffset(field);
+        name = field.getName();
+    }
+
+    /**
+     * Protected constructor.
+     *
+     * @param colIdx Column index.
+     * @param mode Binary mode;
+     */
+    private UnsafeFieldAccessor(int colIdx, BinaryMode mode) {
+        assert colIdx >= 0;
+        assert mode != null;
+
+        this.colIdx = colIdx;
+        this.mode = mode;
+        offset = 0;
+        name = null;
+    }
+
+    /**
+     * Get binary read/write mode.
+     *
+     * @return Binary mode.
+     */
+    public BinaryMode mode() {
+        return mode;
+    }
+
+    /**
+     * Write object field value to tuple.
+     *
+     * @param obj Source object.
+     * @param writer Tuple writer.
+     * @throws SerializationException If failed.
+     */
+    public void write(Object obj, TupleAssembler writer) throws SerializationException {
+        try {
+            write0(Objects.requireNonNull(obj), writer);
+        }
+        catch (Exception ex) {
+            if (includeSensitive() && name != null)
+                throw new SerializationException("Failed to read field [id=" + colIdx + ']', ex);
+            else
+                throw new SerializationException("Failed to write field [id=" + colIdx + ']', ex);
+        }
+    }
+
+    /**
+     * Write object field value to tuple.
+     *
+     * @param obj Source object.
+     * @param writer Tuple writer.
+     * @throws IllegalAccessException If failed.
+     */
+    protected abstract void write0(Object obj, TupleAssembler writer) throws IllegalAccessException;
+
+    /**
+     * Reads value fom tuple to object field.
+     *
+     * @param obj Target object.
+     * @param reader Tuple reader.
+     * @throws SerializationException If failed.
+     */
+    public void read(Object obj, Tuple reader) throws SerializationException {
+        try {
+            read0(Objects.requireNonNull(obj), reader);
+        }
+        catch (Exception ex) {
+            if (includeSensitive() && name != null)
+                throw new SerializationException("Failed to read field [name=" + name + ']', ex);
+            else
+                throw new SerializationException("Failed to read field [id=" + colIdx + ']', ex);
+        }
+    }
+
+    /**
+     * Reads value fom tuple to object field.
+     *
+     * @param obj Target object.
+     * @param reader Tuple reader.
+     * @throws IllegalAccessException If failed.
+     */
+    protected abstract void read0(Object obj, Tuple reader) throws IllegalAccessException;
+
+    /**
+     * Read value.
+     *
+     * @param reader Tuple reader.
+     * @return Object.
+     */
+    public Object read(Tuple reader) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Reads object field value.
+     *
+     * @param obj Object.
+     * @return Field value of given object.
+     */
+    @Nullable Object value(Object obj) {
+        return IgniteUnsafeUtils.getObjectField(Objects.requireNonNull(obj), offset);
+    }
+
+    /**
+     * Accessor for field of primitive {@code byte} type.
+     */
+    private static class IdentityAccessor extends UnsafeFieldAccessor {
+        /**
+         * Constructor.
+         *
+         * @param colIdx Column index.
+         * @param mode Binary mode.
+         */
+        public IdentityAccessor(int colIdx, BinaryMode mode) {
+            super(colIdx, mode);
+        }
+
+        /** {@inheritDoc} */
+        @Override protected void write0(Object obj, TupleAssembler writer) {
+            JavaSerializer.writeRefObject(Objects.requireNonNull(obj, "Null values are not supported."), writer, mode);
+        }
+
+        /** {@inheritDoc} */
+        @Override protected void read0(Object obj, Tuple reader) {
+            throw new UnsupportedOperationException("Called identity accessor for object field.");
+        }
+
+        /** {@inheritDoc} */
+        @Override public Object read(Tuple reader) {
+            return JavaSerializer.readRefValue(reader, colIdx, mode);
+        }
+
+        /** {@inheritDoc} */
+        @Override @Nullable Object value(Object obj) {
+            return obj;
+        }
+    }
+
+    /**
+     * Accessor for field of primitive {@code byte} type.
+     */
+    private static class BytePrimitiveAccessor extends UnsafeFieldAccessor {
+        /**
+         * Constructor.
+         *
+         * @param field Field.
+         * @param colIdx Column index.
+         */
+        public BytePrimitiveAccessor(Field field, int colIdx) {
+            super(field, colIdx, BinaryMode.P_BYTE);
+        }
+
+        /** {@inheritDoc} */
+        @Override protected void write0(Object obj, TupleAssembler writer) {
+            final byte val = IgniteUnsafeUtils.getByteField(obj, offset);
+
+            writer.appendByte(val);
+        }
+
+        /** {@inheritDoc} */
+        @Override protected void read0(Object obj, Tuple reader) {
+            final byte val = reader.byteValue(colIdx);
+
+            IgniteUnsafeUtils.putByteField(obj, offset, val);
+        }
+    }
+
+    /**
+     * Accessor for field of primitive {@code short} type.
+     */
+    private static class ShortPrimitiveAccessor extends UnsafeFieldAccessor {
+        /**
+         * Constructor.
+         *
+         * @param field Field.
+         * @param colIdx Column index.
+         */
+        public ShortPrimitiveAccessor(Field field, int colIdx) {
+            super(field, colIdx, BinaryMode.P_SHORT);
+        }
+
+        /** {@inheritDoc} */
+        @Override protected void write0(Object obj, TupleAssembler writer) {
+            final short val = IgniteUnsafeUtils.getShortField(obj, offset);
+
+            writer.appendShort(val);
+        }
+
+        /** {@inheritDoc} */
+        @Override protected void read0(Object obj, Tuple reader) {
+            final short val = reader.shortValue(colIdx);
+
+            IgniteUnsafeUtils.putShortField(obj, offset, val);
+        }
+    }
+
+    /**
+     * Accessor for field of primitive {@code int} type.
+     */
+    private static class IntPrimitiveAccessor extends UnsafeFieldAccessor {
+        /**
+         * Constructor.
+         *
+         * @param field Field.
+         * @param colIdx Column index.
+         */
+        public IntPrimitiveAccessor(Field field, int colIdx) {
+            super(field, colIdx, BinaryMode.P_INT);
+        }
+
+        /** {@inheritDoc} */
+        @Override protected void write0(Object obj, TupleAssembler writer) {
+            final int val = IgniteUnsafeUtils.getIntField(obj, offset);
+
+            writer.appendInt(val);
+        }
+
+        /** {@inheritDoc} */
+        @Override protected void read0(Object obj, Tuple reader) {
+            final int val = reader.intValue(colIdx);
+
+            IgniteUnsafeUtils.putIntField(obj, offset, val);
+        }
+    }
+
+    /**
+     * Accessor for field of primitive {@code long} type.
+     */
+    private static class LongPrimitiveAccessor extends UnsafeFieldAccessor {
+        /**
+         * Constructor.
+         *
+         * @param field Field.
+         * @param colIdx Column index.
+         */
+        public LongPrimitiveAccessor(Field field, int colIdx) {
+            super(field, colIdx, BinaryMode.P_LONG);
+        }
+
+        /** {@inheritDoc} */
+        @Override protected void write0(Object obj, TupleAssembler writer) {
+            final long val = IgniteUnsafeUtils.getLongField(obj, offset);
+
+            writer.appendLong(val);
+        }
+
+        /** {@inheritDoc} */
+        @Override protected void read0(Object obj, Tuple reader) {
+            final long val = reader.longValue(colIdx);
+
+            IgniteUnsafeUtils.putLongField(obj, offset, val);
+        }
+    }
+
+    /**
+     * Accessor for field of primitive {@code float} type.
+     */
+    private static class FloatPrimitiveAccessor extends UnsafeFieldAccessor {
+        /**
+         * Constructor.
+         *
+         * @param field Field.
+         * @param colIdx Column index.
+         */
+        public FloatPrimitiveAccessor(Field field, int colIdx) {
+            super(field, colIdx, BinaryMode.P_FLOAT);
+        }
+
+        /** {@inheritDoc} */
+        @Override protected void write0(Object obj, TupleAssembler writer) {
+            final float val = IgniteUnsafeUtils.getFloatField(obj, offset);
+
+            writer.appendFloat(val);
+        }
+
+        /** {@inheritDoc} */
+        @Override protected void read0(Object obj, Tuple reader) {
+            final float val = reader.floatValue(colIdx);
+
+            IgniteUnsafeUtils.putFloatField(obj, offset, val);
+        }
+    }
+
+    /**
+     * Accessor for field of primitive {@code double} type.
+     */
+    private static class DoublePrimitiveAccessor extends UnsafeFieldAccessor {
+        /**
+         * Constructor.
+         *
+         * @param field Field.
+         * @param colIdx Column index.
+         */
+        public DoublePrimitiveAccessor(Field field, int colIdx) {
+            super(field, colIdx, BinaryMode.P_DOUBLE);
+        }
+
+        /** {@inheritDoc} */
+        @Override protected void write0(Object obj, TupleAssembler writer) {
+            final double val = IgniteUnsafeUtils.getDoubleField(obj, offset);
+
+            writer.appendDouble(val);
+        }
+
+        /** {@inheritDoc} */
+        @Override protected void read0(Object obj, Tuple reader) {
+            final double val = reader.doubleValue(colIdx);
+
+            IgniteUnsafeUtils.putDoubleField(obj, offset, val);
+        }
+    }
+
+    /**
+     * Accessor for field of reference type.
+     */
+    private static class ReferenceFieldAccessor extends UnsafeFieldAccessor {
+        /**
+         * Constructor.
+         *
+         * @param field Field.
+         * @param colIdx Column index.
+         * @param mode Binary mode.
+         */
+        ReferenceFieldAccessor(Field field, int colIdx, BinaryMode mode) {
+            super(field, colIdx, mode);
+        }
+
+        /** {@inheritDoc} */
+        @Override protected void write0(Object obj, TupleAssembler writer) {
+            assert obj != null;
+            assert writer != null;
+
+            Object val;
+
+            val = IgniteUnsafeUtils.getObjectField(obj, offset);
+
+            if (val == null) {
+                writer.appendNull();
+
+                return;
+            }
+
+            JavaSerializer.writeRefObject(val, writer, mode);
+        }
+
+        /** {@inheritDoc} */
+        @Override public void read0(Object obj, Tuple reader) {
+            Object val = JavaSerializer.readRefValue(reader, colIdx, mode);
+
+            IgniteUnsafeUtils.putObjectField(obj, offset, val);
+        }
+    }
+}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/util/Factory.java b/modules/commons/src/main/java/org/apache/ignite/internal/util/Factory.java
new file mode 100644
index 0000000..663b739
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/util/Factory.java
@@ -0,0 +1,32 @@
+/*
+ * 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.util;
+
+/**
+ * Factory interface.
+ *
+ * @param <T> Object type.
+ */
+public interface Factory<T> {
+    /**
+     * Creates object.
+     *
+     * @return Object.
+     */
+    public T create();
+}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/util/IgniteUnsafeUtils.java b/modules/commons/src/main/java/org/apache/ignite/internal/util/IgniteUnsafeUtils.java
new file mode 100644
index 0000000..74ea3a3
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/util/IgniteUnsafeUtils.java
@@ -0,0 +1,273 @@
+/*
+ * 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.util;
+
+import java.lang.reflect.Field;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import sun.misc.Unsafe;
+
+/**
+ * Unsafe utility.
+ */
+//TODO Move class to 'java-8' profile. Java9+ should use varhandles instead.
+public final class IgniteUnsafeUtils {
+    /** Unsafe. */
+    private static final Unsafe UNSAFE = unsafe();
+
+    /**
+     * @return Instance of Unsafe class.
+     */
+    private static Unsafe unsafe() {
+        try {
+            return Unsafe.getUnsafe();
+        }
+        catch (SecurityException ignored) {
+            try {
+                return AccessController.doPrivileged(
+                    new PrivilegedExceptionAction<Unsafe>() {
+                        @Override public Unsafe run() throws Exception {
+                            Field f = Unsafe.class.getDeclaredField("theUnsafe");
+
+                            f.setAccessible(true);
+
+                            return (Unsafe)f.get(null);
+                        }
+                    });
+            }
+            catch (PrivilegedActionException e) {
+                throw new RuntimeException("Could not initialize intrinsics.", e.getCause());
+            }
+        }
+    }
+
+    /**
+     * Returns object field offset.
+     *
+     * @param field Field.
+     * @return Object field offset.
+     */
+    public static long objectFieldOffset(Field field) {
+        return UNSAFE.objectFieldOffset(field);
+    }
+
+    /**
+     * Gets boolean value from object field.
+     *
+     * @param obj Object.
+     * @param fieldOff Field offset.
+     * @return Boolean value from object field.
+     */
+    public static boolean getBooleanField(Object obj, long fieldOff) {
+        return UNSAFE.getBoolean(obj, fieldOff);
+    }
+
+    /**
+     * Stores boolean value into object field.
+     *
+     * @param obj Object.
+     * @param fieldOff Field offset.
+     * @param val Value.
+     */
+    public static void putBooleanField(Object obj, long fieldOff, boolean val) {
+        UNSAFE.putBoolean(obj, fieldOff, val);
+    }
+
+    /**
+     * Gets byte value from object field.
+     *
+     * @param obj Object.
+     * @param fieldOff Field offset.
+     * @return Byte value from object field.
+     */
+    public static byte getByteField(Object obj, long fieldOff) {
+        return UNSAFE.getByte(obj, fieldOff);
+    }
+
+    /**
+     * Stores byte value into object field.
+     *
+     * @param obj Object.
+     * @param fieldOff Field offset.
+     * @param val Value.
+     */
+    public static void putByteField(Object obj, long fieldOff, byte val) {
+        UNSAFE.putByte(obj, fieldOff, val);
+    }
+
+    /**
+     * Gets short value from object field.
+     *
+     * @param obj Object.
+     * @param fieldOff Field offset.
+     * @return Short value from object field.
+     */
+    public static short getShortField(Object obj, long fieldOff) {
+        return UNSAFE.getShort(obj, fieldOff);
+    }
+
+    /**
+     * Stores short value into object field.
+     *
+     * @param obj Object.
+     * @param fieldOff Field offset.
+     * @param val Value.
+     */
+    public static void putShortField(Object obj, long fieldOff, short val) {
+        UNSAFE.putShort(obj, fieldOff, val);
+    }
+
+    /**
+     * Gets char value from object field.
+     *
+     * @param obj Object.
+     * @param fieldOff Field offset.
+     * @return Char value from object field.
+     */
+    public static char getCharField(Object obj, long fieldOff) {
+        return UNSAFE.getChar(obj, fieldOff);
+    }
+
+    /**
+     * Stores char value into object field.
+     *
+     * @param obj Object.
+     * @param fieldOff Field offset.
+     * @param val Value.
+     */
+    public static void putCharField(Object obj, long fieldOff, char val) {
+        UNSAFE.putChar(obj, fieldOff, val);
+    }
+
+    /**
+     * Gets integer value from object field.
+     *
+     * @param obj Object.
+     * @param fieldOff Field offset.
+     * @return Integer value from object field.
+     */
+    public static int getIntField(Object obj, long fieldOff) {
+        return UNSAFE.getInt(obj, fieldOff);
+    }
+
+    /**
+     * Stores integer value into object field.
+     *
+     * @param obj Object.
+     * @param fieldOff Field offset.
+     * @param val Value.
+     */
+    public static void putIntField(Object obj, long fieldOff, int val) {
+        UNSAFE.putInt(obj, fieldOff, val);
+    }
+
+    /**
+     * Gets long value from object field.
+     *
+     * @param obj Object.
+     * @param fieldOff Field offset.
+     * @return Long value from object field.
+     */
+    public static long getLongField(Object obj, long fieldOff) {
+        return UNSAFE.getLong(obj, fieldOff);
+    }
+
+    /**
+     * Stores long value into object field.
+     *
+     * @param obj Object.
+     * @param fieldOff Field offset.
+     * @param val Value.
+     */
+    public static void putLongField(Object obj, long fieldOff, long val) {
+        UNSAFE.putLong(obj, fieldOff, val);
+    }
+
+    /**
+     * Gets float value from object field.
+     *
+     * @param obj Object.
+     * @param fieldOff Field offset.
+     * @return Float value from object field.
+     */
+    public static float getFloatField(Object obj, long fieldOff) {
+        return UNSAFE.getFloat(obj, fieldOff);
+    }
+
+    /**
+     * Stores float value into object field.
+     *
+     * @param obj Object.
+     * @param fieldOff Field offset.
+     * @param val Value.
+     */
+    public static void putFloatField(Object obj, long fieldOff, float val) {
+        UNSAFE.putFloat(obj, fieldOff, val);
+    }
+
+    /**
+     * Gets double value from object field.
+     *
+     * @param obj Object.
+     * @param fieldOff Field offset.
+     * @return Double value from object field.
+     */
+    public static double getDoubleField(Object obj, long fieldOff) {
+        return UNSAFE.getDouble(obj, fieldOff);
+    }
+
+    /**
+     * Stores double value into object field.
+     *
+     * @param obj Object.
+     * @param fieldOff Field offset.
+     * @param val Value.
+     */
+    public static void putDoubleField(Object obj, long fieldOff, double val) {
+        UNSAFE.putDouble(obj, fieldOff, val);
+    }
+
+    /**
+     * Gets reference from object field.
+     *
+     * @param obj Object.
+     * @param fieldOff Field offset.
+     * @return Reference from object field.
+     */
+    public static Object getObjectField(Object obj, long fieldOff) {
+        return UNSAFE.getObject(obj, fieldOff);
+    }
+
+    /**
+     * Stores reference value into object field.
+     *
+     * @param obj Object.
+     * @param fieldOff Field offset.
+     * @param val Value.
+     */
+    public static void putObjectField(Object obj, long fieldOff, Object val) {
+        UNSAFE.putObject(obj, fieldOff, val);
+    }
+
+    /**
+     * Stub.
+     */
+    private IgniteUnsafeUtils() {
+    }
+}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/util/ObjectFactory.java b/modules/commons/src/main/java/org/apache/ignite/internal/util/ObjectFactory.java
new file mode 100644
index 0000000..f31965f
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/util/ObjectFactory.java
@@ -0,0 +1,62 @@
+/*
+ * 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.util;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * Object factory.
+ */
+public class ObjectFactory<T> implements Factory<T> {
+    /** Class default constructor. */
+    private final Constructor<T> cnstr;
+
+    /**
+     * Constructor.
+     *
+     * @param tClass Class.
+     */
+    public ObjectFactory(Class<T> tClass) {
+        try {
+            cnstr = tClass.getDeclaredConstructor();
+
+            cnstr.setAccessible(true);
+        }
+        catch (NoSuchMethodException e) {
+            throw new IllegalStateException("Class has no default constructor: class=" + tClass.getName(), e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public T create() throws IllegalStateException {
+        try {
+            return cnstr.newInstance();
+        }
+        catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
+            throw new IllegalStateException("Failed to instantiate class: " + cnstr.getDeclaringClass().getName(), e);
+        }
+    }
+
+    /**
+     * @return Class of object created by the factory.
+     */
+    public Class<T> getClazz() {
+        return cnstr.getDeclaringClass();
+    }
+}
diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/util/Pair.java b/modules/commons/src/main/java/org/apache/ignite/internal/util/Pair.java
new file mode 100644
index 0000000..a88fc7d
--- /dev/null
+++ b/modules/commons/src/main/java/org/apache/ignite/internal/util/Pair.java
@@ -0,0 +1,57 @@
+/*
+ * 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.util;
+
+/**
+ * Pair of objects.
+ *
+ * @param <T> First object.
+ * @param <V> Second object.
+ */
+public class Pair<T, V> {
+    /** Fisrt obj. */
+    private final T first;
+
+    /** Second obj. */
+    private final V second;
+
+    /**
+     * Constructor.
+     *
+     * @param first First object.
+     * @param second Second object.
+     */
+    public Pair(T first, V second) {
+        this.first = first;
+        this.second = second;
+    }
+
+    /**
+     * @return First object.
+     */
+    public T getFirst() {
+        return first;
+    }
+
+    /**
+     * @return Second object.
+     */
+    public V getSecond() {
+        return second;
+    }
+}
diff --git a/modules/commons/src/test/java/org/apache/ignite/internal/benchmarks/SerializerBenchmarkTest.java b/modules/commons/src/test/java/org/apache/ignite/internal/benchmarks/SerializerBenchmarkTest.java
new file mode 100644
index 0000000..23193ba
--- /dev/null
+++ b/modules/commons/src/test/java/org/apache/ignite/internal/benchmarks/SerializerBenchmarkTest.java
@@ -0,0 +1,185 @@
+/*
+ * 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.benchmarks;
+
+import java.lang.reflect.Field;
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+import org.apache.ignite.internal.schema.Column;
+import org.apache.ignite.internal.schema.Columns;
+import org.apache.ignite.internal.schema.SchemaDescriptor;
+import org.apache.ignite.internal.schema.marshaller.Serializer;
+import org.apache.ignite.internal.schema.marshaller.SerializerFactory;
+import org.apache.ignite.internal.util.Factory;
+import org.apache.ignite.internal.util.ObjectFactory;
+import org.codehaus.commons.compiler.CompilerFactoryFactory;
+import org.codehaus.commons.compiler.IClassBodyEvaluator;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.infra.Blackhole;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.RunnerException;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+
+import static org.apache.ignite.internal.schema.NativeType.LONG;
+
+/**
+ * Serializer benchmark.
+ */
+@State(Scope.Benchmark)
+@Warmup(time = 10, iterations = 3, timeUnit = TimeUnit.SECONDS)
+@Measurement(time = 10, iterations = 5, timeUnit = TimeUnit.SECONDS)
+@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
+@OutputTimeUnit(TimeUnit.MICROSECONDS)
+@Fork(1)
+public class SerializerBenchmarkTest {
+    /** Random. */
+    private Random rnd = new Random();
+
+    /** Reflection-based Serializer. */
+    private Serializer serializer;
+
+    /** Test object factory. */
+    private Factory<?> objectFactory;
+
+    /** Object fields count. */
+    @Param({"10", "100"})
+    public int fieldsCount;
+
+    /** Serializer. */
+    @Param({"Janino", "Java"})
+    public String serializerName;
+
+    /**
+     * Runner.
+     */
+    public static void main(String[] args) throws RunnerException {
+        Options opt = new OptionsBuilder()
+            .include(SerializerBenchmarkTest.class.getSimpleName())
+            .build();
+
+        new Runner(opt).run();
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    @Setup
+    public void init() throws Exception {
+        long seed = System.currentTimeMillis();
+
+        rnd = new Random(seed);
+
+        final Class<?> valClass = createGeneratedObjectClass(fieldsCount, Long.TYPE);
+        objectFactory = new ObjectFactory<>(valClass);
+
+        Columns keyCols = new Columns(new Column("key", LONG, true));
+        Columns valCols = mapFieldsToColumns(valClass);
+        final SchemaDescriptor schema = new SchemaDescriptor(1, keyCols, valCols);
+
+        if ("Java".equals(serializerName))
+            serializer = SerializerFactory.createJavaSerializerFactory().create(schema, Long.class, valClass);
+        else
+            serializer = SerializerFactory.createJaninoSerializerFactory().create(schema, Long.class, valClass);
+    }
+
+    /**
+     * Measure serialization-deserialization operation cost.
+     *
+     * @param bh Black hole.
+     * @throws Exception If failed.
+     */
+    @Benchmark
+    public void measureSerializeDeserializeCost(Blackhole bh) throws Exception {
+        Long key = rnd.nextLong();
+
+        Object val = objectFactory.create();
+        byte[] bytes = serializer.serialize(key, val);
+
+        Object restoredKey = serializer.deserializeKey(bytes);
+        Object restoredVal = serializer.deserializeValue(bytes);
+
+        bh.consume(restoredVal);
+        bh.consume(restoredKey);
+    }
+
+    /**
+     * Map fields to columns.
+     *
+     * @param aClass Object class.
+     * @return Columns for schema
+     */
+    private Columns mapFieldsToColumns(Class<?> aClass) {
+        final Field[] fields = aClass.getDeclaredFields();
+        final Column[] cols = new Column[fields.length];
+
+        for (int i = 0; i < fields.length; i++) {
+            assert fields[i].getType() == Long.TYPE : "Only 'long' field type is supported.";
+
+            cols[i] = new Column("col" + i, LONG, false);
+        }
+
+        return new Columns(cols);
+    }
+
+    /**
+     * Generate class for test objects.
+     *
+     * @param maxFields Max class member fields.
+     * @param fieldType Field type.
+     * @return Generated test object class.
+     * @throws Exception If failed.
+     */
+    private Class<?> createGeneratedObjectClass(int maxFields, Class<?> fieldType) throws Exception {
+        final IClassBodyEvaluator ce = CompilerFactoryFactory.getDefaultCompilerFactory().newClassBodyEvaluator();
+
+        ce.setClassName("TestObject");
+        ce.setDefaultImports("java.util.Random");
+
+        final StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < maxFields; i++)
+            sb.append(fieldType.getName()).append(" col").append(i).append(";\n");
+
+        // Constructor.
+        sb.append("public TestObject() {\n");
+        sb.append("    Random rnd = new Random();\n");
+        for (int i = 0; i < maxFields; i++)
+            sb.append("    col").append(i).append(" = rnd.nextLong();\n");
+        sb.append("}\n");
+
+        try {
+            ce.setParentClassLoader(getClass().getClassLoader());
+            ce.cook(sb.toString());
+
+            return ce.getClazz();
+        }
+        catch (Exception ex) {
+            throw new IllegalStateException("Failed to compile/instantiate generated Serializer.", ex);
+        }
+    }
+}
diff --git a/modules/commons/src/test/java/org/apache/ignite/internal/schema/TestUtils.java b/modules/commons/src/test/java/org/apache/ignite/internal/schema/TestUtils.java
new file mode 100644
index 0000000..4ed5fec
--- /dev/null
+++ b/modules/commons/src/test/java/org/apache/ignite/internal/schema/TestUtils.java
@@ -0,0 +1,121 @@
+/*
+ * 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.schema;
+
+import java.util.BitSet;
+import java.util.Random;
+
+/**
+ * Test utility class.
+ */
+public class TestUtils {
+    /**
+     * Generates randon value of given type.
+     *
+     * @param rnd Random generator.
+     * @param type Type.
+     * @return Random object of asked type.
+     */
+    public static Object generateRandomValue(Random rnd, NativeType type) {
+        switch (type.spec()) {
+            case BYTE:
+                return (byte)rnd.nextInt(255);
+
+            case SHORT:
+                return (short)rnd.nextInt(65535);
+
+            case INTEGER:
+                return rnd.nextInt();
+
+            case LONG:
+                return rnd.nextLong();
+
+            case FLOAT:
+                return rnd.nextFloat();
+
+            case DOUBLE:
+                return rnd.nextDouble();
+
+            case UUID:
+                return new java.util.UUID(rnd.nextLong(), rnd.nextLong());
+
+            case STRING:
+                return randomString(rnd, rnd.nextInt(255));
+
+            case BYTES:
+                return randomBytes(rnd, rnd.nextInt(255));
+
+            case BITMASK: {
+                Bitmask maskType = (Bitmask)type;
+
+                return randomBitSet(rnd, maskType.bits());
+            }
+
+            default:
+                throw new IllegalStateException("Unsupported type: " + type);
+        }
+    }
+
+    /**
+     * @param rnd Random generator.
+     * @param bits Amount of bits in bitset.
+     * @return Random BitSet.
+     */
+    public static BitSet randomBitSet(Random rnd, int bits) {
+        BitSet set = new BitSet();
+
+        for (int i = 0; i < bits; i++) {
+            if (rnd.nextBoolean())
+                set.set(i);
+        }
+
+        return set;
+    }
+
+    /**
+     * @param rnd Random generator.
+     * @param len Byte array length.
+     * @return Radmon byte array.
+     */
+    public static byte[] randomBytes(Random rnd, int len) {
+        byte[] data = new byte[len];
+        rnd.nextBytes(data);
+
+        return data;
+    }
+
+    /**
+     * @param rnd Random generator.
+     * @param len String length.
+     * @return Randon string.
+     */
+    public static String randomString(Random rnd, int len) {
+        StringBuilder sb = new StringBuilder();
+
+        while (sb.length() < len) {
+            char pt = (char)rnd.nextInt(Character.MAX_VALUE + 1);
+
+            if (Character.isDefined(pt) &&
+                Character.getType(pt) != Character.PRIVATE_USE &&
+                !Character.isSurrogate(pt))
+                sb.append(pt);
+        }
+
+        return sb.toString();
+    }
+}
diff --git a/modules/commons/src/test/java/org/apache/ignite/internal/schema/TupleTest.java b/modules/commons/src/test/java/org/apache/ignite/internal/schema/TupleTest.java
index 746e4d5..2b8efee 100644
--- a/modules/commons/src/test/java/org/apache/ignite/internal/schema/TupleTest.java
+++ b/modules/commons/src/test/java/org/apache/ignite/internal/schema/TupleTest.java
@@ -191,69 +191,7 @@ public class TupleTest {
      *
      */
     private Object generateRandomValue(NativeType type) {
-        switch (type.spec()) {
-            case BYTE:
-                return (byte)rnd.nextInt(255);
-
-            case SHORT:
-                return (short)rnd.nextInt(65535);
-
-            case INTEGER:
-                return rnd.nextInt();
-
-            case LONG:
-                return rnd.nextLong();
-
-            case FLOAT:
-                return rnd.nextFloat();
-
-            case DOUBLE:
-                return rnd.nextDouble();
-
-            case UUID:
-                return new java.util.UUID(rnd.nextLong(), rnd.nextLong());
-
-            case STRING: {
-                int size = rnd.nextInt(255);
-
-                StringBuilder sb = new StringBuilder();
-
-                while (sb.length() < size) {
-                    char pt = (char)rnd.nextInt(Character.MAX_VALUE + 1);
-
-                    if (Character.isDefined(pt) &&
-                        Character.getType(pt) != Character.PRIVATE_USE &&
-                        !Character.isSurrogate(pt))
-                        sb.append(pt);
-                }
-
-                return sb.toString();
-            }
-
-            case BYTES: {
-                int size = rnd.nextInt(255);
-                byte[] data = new byte[size];
-                rnd.nextBytes(data);
-
-                return data;
-            }
-
-            case BITMASK: {
-                Bitmask maskType = (Bitmask)type;
-
-                BitSet set = new BitSet();
-
-                for (int i = 0; i < maskType.bits(); i++) {
-                    if (rnd.nextBoolean())
-                        set.set(i);
-                }
-
-                return set;
-            }
-
-            default:
-                throw new IllegalStateException("Unsupported type: " + type);
-        }
+        return TestUtils.generateRandomValue(rnd, type);
     }
 
     /**
diff --git a/modules/commons/src/test/java/org/apache/ignite/internal/schema/marshaller/JavaSerializerTest.java b/modules/commons/src/test/java/org/apache/ignite/internal/schema/marshaller/JavaSerializerTest.java
new file mode 100644
index 0000000..49ef554
--- /dev/null
+++ b/modules/commons/src/test/java/org/apache/ignite/internal/schema/marshaller/JavaSerializerTest.java
@@ -0,0 +1,494 @@
+/*
+ * 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.schema.marshaller;
+
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Random;
+import java.util.UUID;
+import java.util.stream.Stream;
+import org.apache.ignite.internal.schema.Bitmask;
+import org.apache.ignite.internal.schema.Column;
+import org.apache.ignite.internal.schema.Columns;
+import org.apache.ignite.internal.schema.NativeType;
+import org.apache.ignite.internal.schema.NativeTypeSpec;
+import org.apache.ignite.internal.schema.SchemaDescriptor;
+import org.apache.ignite.internal.schema.TestUtils;
+import org.apache.ignite.internal.schema.marshaller.generator.JaninoSerializerGenerator;
+import org.apache.ignite.internal.schema.marshaller.reflection.JavaSerializerFactory;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DynamicNode;
+import org.junit.jupiter.api.TestFactory;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import static org.apache.ignite.internal.schema.NativeType.BYTE;
+import static org.apache.ignite.internal.schema.NativeType.BYTES;
+import static org.apache.ignite.internal.schema.NativeType.DOUBLE;
+import static org.apache.ignite.internal.schema.NativeType.FLOAT;
+import static org.apache.ignite.internal.schema.NativeType.INTEGER;
+import static org.apache.ignite.internal.schema.NativeType.LONG;
+import static org.apache.ignite.internal.schema.NativeType.SHORT;
+import static org.apache.ignite.internal.schema.NativeType.STRING;
+import static org.apache.ignite.internal.schema.NativeType.UUID;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.DynamicContainer.dynamicContainer;
+import static org.junit.jupiter.api.DynamicTest.dynamicTest;
+
+/**
+ * Serializer test.
+ */
+public class JavaSerializerTest {
+    /**
+     * @return List of serializers for test.
+     */
+    private static List<SerializerFactory> serializerFactoryProvider() {
+        return Arrays.asList(
+            new JaninoSerializerGenerator(),
+            new JavaSerializerFactory()
+        );
+    }
+
+    /** Random. */
+    private Random rnd;
+
+    /**
+     *
+     */
+    @BeforeEach
+    public void initRandom() {
+        long seed = System.currentTimeMillis();
+
+        System.out.println("Using seed: " + seed + "L;");
+
+        rnd = new Random(seed);
+    }
+
+    /**
+     *
+     */
+    @TestFactory
+    public Stream<DynamicNode> testBasicTypes() {
+        NativeType[] types = new NativeType[] {BYTE, SHORT, INTEGER, LONG, FLOAT, DOUBLE, UUID, STRING, BYTES, Bitmask.of(5)};
+
+        return serializerFactoryProvider().stream().map(factory ->
+            dynamicContainer(
+                factory.getClass().getSimpleName(),
+                Stream.concat(
+                    // Test pure types.
+                    Stream.of(types).map(type ->
+                        dynamicTest("testBasicTypes(" + type.spec().name() + ')', () -> checkBasicType(factory, type, type))
+                    ),
+
+                    // Test pairs of mixed types.
+                    Stream.of(
+                        dynamicTest("testMixTypes 1", () -> checkBasicType(factory, LONG, INTEGER)),
+                        dynamicTest("testMixTypes 1", () -> checkBasicType(factory, FLOAT, DOUBLE)),
+                        dynamicTest("testMixTypes 1", () -> checkBasicType(factory, INTEGER, BYTES)),
+                        dynamicTest("testMixTypes 1", () -> checkBasicType(factory, STRING, LONG)),
+                        dynamicTest("testMixTypes 1", () -> checkBasicType(factory, Bitmask.of(9), BYTES))
+                    )
+                )
+            ));
+    }
+
+    /**
+     * @throws SerializationException If serialization failed.
+     */
+    @ParameterizedTest
+    @MethodSource("serializerFactoryProvider")
+    public void testComplexType(SerializerFactory factory) throws SerializationException {
+        Column[] cols = new Column[] {
+            new Column("pByteCol", BYTE, false),
+            new Column("pShortCol", SHORT, false),
+            new Column("pIntCol", INTEGER, false),
+            new Column("pLongCol", LONG, false),
+            new Column("pFloatCol", FLOAT, false),
+            new Column("pDoubleCol", DOUBLE, false),
+
+            new Column("byteCol", BYTE, true),
+            new Column("shortCol", SHORT, true),
+            new Column("intCol", INTEGER, true),
+            new Column("longCol", LONG, true),
+            new Column("nullLongCol", LONG, true),
+            new Column("floatCol", FLOAT, true),
+            new Column("doubleCol", DOUBLE, true),
+
+            new Column("uuidCol", UUID, true),
+            new Column("bitmaskCol", Bitmask.of(42), true),
+            new Column("stringCol", STRING, true),
+            new Column("nullBytesCol", BYTES, true),
+            new Column("bytesCol", BYTES, true),
+        };
+
+        SchemaDescriptor schema = new SchemaDescriptor(1, new Columns(cols), new Columns(cols.clone()));
+
+        final Object key = TestObject.randomObject(rnd);
+        final Object val = TestObject.randomObject(rnd);
+
+        Serializer serializer = factory.create(schema, key.getClass(), val.getClass());
+
+        byte[] bytes = serializer.serialize(key, val);
+
+        // Try different order.
+        Object restoredVal = serializer.deserializeValue(bytes);
+        Object restoredKey = serializer.deserializeKey(bytes);
+
+        assertTrue(key.getClass().isInstance(restoredKey));
+        assertTrue(val.getClass().isInstance(restoredVal));
+
+        assertEquals(key, restoredKey);
+        assertEquals(val, restoredVal);
+    }
+
+    /**
+     *
+     */
+    @ParameterizedTest
+    @MethodSource("serializerFactoryProvider")
+    public void testClassWithIncorrectBitmaskSize(SerializerFactory factory) {
+        Column[] cols = new Column[] {
+            new Column("pLongCol", LONG, false),
+            new Column("bitmaskCol", Bitmask.of(9), true),
+        };
+
+        SchemaDescriptor schema = new SchemaDescriptor(1, new Columns(cols), new Columns(cols.clone()));
+
+        final Object key = TestObject.randomObject(rnd);
+        final Object val = TestObject.randomObject(rnd);
+
+        Serializer serializer = factory.create(schema, key.getClass(), val.getClass());
+
+        assertThrows(
+            SerializationException.class,
+            () -> serializer.serialize(key, val),
+            "Failed to write field [name=bitmaskCol]"
+        );
+    }
+
+    /**
+     *
+     */
+    @ParameterizedTest
+    @MethodSource("serializerFactoryProvider")
+    public void testClassWithWrongFieldType(SerializerFactory factory) {
+        Column[] cols = new Column[] {
+            new Column("bitmaskCol", Bitmask.of(42), true),
+            new Column("shortCol", UUID, true)
+        };
+
+        SchemaDescriptor schema = new SchemaDescriptor(1, new Columns(cols), new Columns(cols.clone()));
+
+        final Object key = TestObject.randomObject(rnd);
+        final Object val = TestObject.randomObject(rnd);
+
+        Serializer serializer = factory.create(schema, key.getClass(), val.getClass());
+
+        assertThrows(
+            SerializationException.class,
+            () -> serializer.serialize(key, val),
+            "Failed to write field [name=shortCol]"
+        );
+    }
+
+    /**
+     *
+     */
+    @ParameterizedTest
+    @MethodSource("serializerFactoryProvider")
+    public void testClassWithPrivateConstructor(SerializerFactory factory) throws SerializationException {
+        Column[] cols = new Column[] {
+            new Column("pLongCol", LONG, false),
+        };
+
+        SchemaDescriptor schema = new SchemaDescriptor(1, new Columns(cols), new Columns(cols.clone()));
+
+        final Object key = PrivateTestObject.randomObject(rnd);
+        final Object val = PrivateTestObject.randomObject(rnd);
+
+        Serializer serializer = factory.create(schema, key.getClass(), val.getClass());
+
+        byte[] bytes = serializer.serialize(key, val);
+
+        Object key1 = serializer.deserializeKey(bytes);
+        Object val1 = serializer.deserializeValue(bytes);
+
+        assertTrue(key.getClass().isInstance(key1));
+        assertTrue(val.getClass().isInstance(val1));
+
+        assertEquals(key, key);
+        assertEquals(val, val1);
+    }
+
+    /**
+     *
+     */
+    @ParameterizedTest
+    @MethodSource("serializerFactoryProvider")
+    public void testClassWithNoDefaultConstructor(SerializerFactory factory) throws SerializationException {
+        Column[] cols = new Column[] {
+            new Column("pLongCol", LONG, false),
+        };
+
+        SchemaDescriptor schema = new SchemaDescriptor(1, new Columns(cols), new Columns(cols.clone()));
+
+        final Object key = WrongTestObject.randomObject(rnd);
+        final Object val = WrongTestObject.randomObject(rnd);
+
+        assertThrows(IllegalStateException.class,
+            () -> factory.create(schema, key.getClass(), val.getClass()),
+            "Class has no default constructor: class=org.apache.ignite.internal.schema.marshaller.JavaSerializerTest$WrongTestObject"
+            );
+    }
+
+    /**
+     * Generate random key-value pair of given types and
+     * check serialization and deserialization works fine.
+     *
+     * @param factory Serializer factory.
+     * @param keyType Key type.
+     * @param valType Value type.
+     * @throws SerializationException If (de)serialization failed.
+     */
+    private void checkBasicType(SerializerFactory factory, NativeType keyType,
+        NativeType valType) throws SerializationException {
+        final Object key = generateRandomValue(keyType);
+        final Object val = generateRandomValue(valType);
+
+        Column[] keyCols = new Column[] {new Column("key", keyType, false)};
+        Column[] valCols = new Column[] {new Column("val", valType, false)};
+
+        SchemaDescriptor schema = new SchemaDescriptor(1, new Columns(keyCols), new Columns(valCols));
+
+        Serializer serializer = factory.create(schema, key.getClass(), val.getClass());
+
+        byte[] bytes = serializer.serialize(key, val);
+
+        Object key1 = serializer.deserializeKey(bytes);
+        Object val1 = serializer.deserializeValue(bytes);
+
+        assertTrue(key.getClass().isInstance(key1));
+        assertTrue(val.getClass().isInstance(val1));
+
+        compareObjects(keyType, key, key);
+        compareObjects(valType, val, val1);
+    }
+
+    /**
+     * Compare object regarding NativeType.
+     *
+     * @param type Native type.
+     * @param exp Expected value.
+     * @param act Actual value.
+     */
+    private void compareObjects(NativeType type, Object exp, Object act) {
+        if (type.spec() == NativeTypeSpec.BYTES)
+            assertArrayEquals((byte[])exp, (byte[])act);
+        else
+            assertEquals(exp, act);
+    }
+
+    /**
+     * Generates randon value of given type.
+     *
+     * @param type Type.
+     */
+    private Object generateRandomValue(NativeType type) {
+        return TestUtils.generateRandomValue(rnd, type);
+    }
+
+    /**
+     * Test object.
+     */
+    public static class TestObject {
+        /**
+         * @return Random TestObject.
+         */
+        public static TestObject randomObject(Random rnd) {
+            final TestObject obj = new TestObject();
+
+            obj.pByteCol = (byte)rnd.nextInt(255);
+            obj.pShortCol = (short)rnd.nextInt(65535);
+            obj.pIntCol = rnd.nextInt();
+            obj.pLongCol = rnd.nextLong();
+            obj.pFloatCol = rnd.nextFloat();
+            obj.pDoubleCol = rnd.nextDouble();
+
+            obj.byteCol = (byte)rnd.nextInt(255);
+            obj.shortCol = (short)rnd.nextInt(65535);
+            obj.intCol = rnd.nextInt();
+            obj.longCol = rnd.nextLong();
+            obj.floatCol = rnd.nextFloat();
+            obj.doubleCol = rnd.nextDouble();
+
+            obj.uuidCol = new UUID(rnd.nextLong(), rnd.nextLong());
+            obj.bitmaskCol = TestUtils.randomBitSet(rnd, 42);
+            obj.stringCol = TestUtils.randomString(rnd, rnd.nextInt(255));
+            obj.bytesCol = TestUtils.randomBytes(rnd, rnd.nextInt(255));
+
+            return obj;
+        }
+
+        // Primitive typed
+        private byte pByteCol;
+        private short pShortCol;
+        private int pIntCol;
+        private long pLongCol;
+        private float pFloatCol;
+        private double pDoubleCol;
+
+        // Reference typed
+        private Byte byteCol;
+        private Short shortCol;
+        private Integer intCol;
+        private Long longCol;
+        private Long nullLongCol;
+        private Float floatCol;
+        private Double doubleCol;
+
+        private UUID uuidCol;
+        private BitSet bitmaskCol;
+        private String stringCol;
+        private byte[] bytesCol;
+        private byte[] nullBytesCol;
+
+        /** {@inheritDoc} */
+        @Override public boolean equals(Object o) {
+            if (this == o)
+                return true;
+
+            if (o == null || getClass() != o.getClass())
+                return false;
+
+            TestObject object = (TestObject)o;
+
+            return pByteCol == object.pByteCol &&
+                pShortCol == object.pShortCol &&
+                pIntCol == object.pIntCol &&
+                pLongCol == object.pLongCol &&
+                Float.compare(object.pFloatCol, pFloatCol) == 0 &&
+                Double.compare(object.pDoubleCol, pDoubleCol) == 0 &&
+                Objects.equals(byteCol, object.byteCol) &&
+                Objects.equals(shortCol, object.shortCol) &&
+                Objects.equals(intCol, object.intCol) &&
+                Objects.equals(longCol, object.longCol) &&
+                Objects.equals(nullLongCol, object.nullLongCol) &&
+                Objects.equals(floatCol, object.floatCol) &&
+                Objects.equals(doubleCol, object.doubleCol) &&
+                Objects.equals(uuidCol, object.uuidCol) &&
+                Objects.equals(bitmaskCol, object.bitmaskCol) &&
+                Objects.equals(stringCol, object.stringCol) &&
+                Arrays.equals(bytesCol, object.bytesCol) &&
+                Arrays.equals(nullBytesCol, object.nullBytesCol);
+        }
+
+        /** {@inheritDoc} */
+        @Override public int hashCode() {
+            return 73;
+        }
+    }
+
+    /**
+     * Test object with private constructor.
+     */
+    private static class PrivateTestObject {
+        /**
+         * @return Random TestObject.
+         */
+        static PrivateTestObject randomObject(Random rnd) {
+            final PrivateTestObject obj = new PrivateTestObject();
+
+            obj.pLongCol = rnd.nextLong();
+
+            return obj;
+        }
+
+        /** Value. */
+        private long pLongCol;
+
+        /**
+         * Private constructor.
+         */
+        @SuppressWarnings("RedundantNoArgConstructor")
+        private PrivateTestObject() {
+        }
+
+        /** {@inheritDoc} */
+        @Override public boolean equals(Object o) {
+            if (this == o)
+                return true;
+
+            if (o == null || getClass() != o.getClass())
+                return false;
+
+            PrivateTestObject object = (PrivateTestObject)o;
+
+            return pLongCol == object.pLongCol;
+        }
+
+        /** {@inheritDoc} */
+        @Override public int hashCode() {
+            return Objects.hash(pLongCol);
+        }
+    }
+
+    /**
+     * Test object without default constructor.
+     */
+    private static class WrongTestObject {
+        /**
+         * @return Random TestObject.
+         */
+        static WrongTestObject randomObject(Random rnd) {
+            return new WrongTestObject(rnd.nextLong());
+        }
+
+        /** Value. */
+        private final long pLongCol;
+
+        /**
+         * Private constructor.
+         */
+        private WrongTestObject(long val) {
+            pLongCol = val;
+        }
+
+        /** {@inheritDoc} */
+        @Override public boolean equals(Object o) {
+            if (this == o)
+                return true;
+
+            if (o == null || getClass() != o.getClass())
+                return false;
+
+            WrongTestObject object = (WrongTestObject)o;
+
+            return pLongCol == object.pLongCol;
+        }
+
+        /** {@inheritDoc} */
+        @Override public int hashCode() {
+            return Objects.hash(pLongCol);
+        }
+    }
+}
diff --git a/modules/commons/src/test/java/org/apache/ignite/internal/schema/marshaller/reflection/FieldAccessorTest.java b/modules/commons/src/test/java/org/apache/ignite/internal/schema/marshaller/reflection/FieldAccessorTest.java
new file mode 100644
index 0000000..8e864d6
--- /dev/null
+++ b/modules/commons/src/test/java/org/apache/ignite/internal/schema/marshaller/reflection/FieldAccessorTest.java
@@ -0,0 +1,396 @@
+/*
+ * 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.schema.marshaller.reflection;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Objects;
+import java.util.Random;
+import java.util.UUID;
+import org.apache.ignite.internal.schema.Bitmask;
+import org.apache.ignite.internal.schema.Column;
+import org.apache.ignite.internal.schema.TestUtils;
+import org.apache.ignite.internal.schema.Tuple;
+import org.apache.ignite.internal.schema.TupleAssembler;
+import org.apache.ignite.internal.schema.marshaller.BinaryMode;
+import org.apache.ignite.internal.schema.marshaller.SerializationException;
+import org.apache.ignite.internal.util.Pair;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import static org.apache.ignite.internal.schema.NativeType.BYTE;
+import static org.apache.ignite.internal.schema.NativeType.BYTES;
+import static org.apache.ignite.internal.schema.NativeType.DOUBLE;
+import static org.apache.ignite.internal.schema.NativeType.FLOAT;
+import static org.apache.ignite.internal.schema.NativeType.INTEGER;
+import static org.apache.ignite.internal.schema.NativeType.LONG;
+import static org.apache.ignite.internal.schema.NativeType.SHORT;
+import static org.apache.ignite.internal.schema.NativeType.STRING;
+import static org.apache.ignite.internal.schema.NativeType.UUID;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Check field accessor correctness.
+ */
+public class FieldAccessorTest {
+    /** Random. */
+    private Random rnd;
+
+    /**
+     *
+     */
+    @BeforeEach
+    public void initRandom() {
+        long seed = System.currentTimeMillis();
+
+        System.out.println("Using seed: " + seed + "L;");
+
+        rnd = new Random(seed);
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testFieldAccessor() throws Exception {
+        Column[] cols = new Column[] {
+            new Column("pByteCol", BYTE, false),
+            new Column("pShortCol", SHORT, false),
+            new Column("pIntCol", INTEGER, false),
+            new Column("pLongCol", LONG, false),
+            new Column("pFloatCol", FLOAT, false),
+            new Column("pDoubleCol", DOUBLE, false),
+
+            new Column("byteCol", BYTE, false),
+            new Column("shortCol", SHORT, false),
+            new Column("intCol", INTEGER, false),
+            new Column("longCol", LONG, false),
+            new Column("floatCol", FLOAT, false),
+            new Column("doubleCol", DOUBLE, false),
+
+            new Column("uuidCol", UUID, false),
+            new Column("bitmaskCol", Bitmask.of(9), false),
+            new Column("stringCol", STRING, false),
+            new Column("bytesCol", BYTES, false),
+        };
+
+        final Pair<TupleAssembler, Tuple> mocks = createMocks();
+
+        final TupleAssembler tupleAssembler = mocks.getFirst();
+        final Tuple tuple = mocks.getSecond();
+
+        final TestObject obj = TestObject.randomObject(rnd);
+
+        for (int i = 0; i < cols.length; i++) {
+            UnsafeFieldAccessor accessor = UnsafeFieldAccessor.create(TestObject.class, cols[i], i);
+
+            accessor.write(obj, tupleAssembler);
+        }
+
+        final TestObject restoredObj = new TestObject();
+
+        for (int i = 0; i < cols.length; i++) {
+            UnsafeFieldAccessor accessor = UnsafeFieldAccessor.create(TestObject.class, cols[i], i);
+
+            accessor.read(restoredObj, tuple);
+        }
+
+        assertEquals(obj.pByteCol, restoredObj.pByteCol);
+        assertEquals(obj.pShortCol, restoredObj.pShortCol);
+        assertEquals(obj.pIntCol, restoredObj.pIntCol);
+        assertEquals(obj.pLongCol, restoredObj.pLongCol);
+        assertEquals(obj.pFloatCol, restoredObj.pFloatCol);
+        assertEquals(obj.pDoubleCol, restoredObj.pDoubleCol);
+
+        assertEquals(obj.byteCol, restoredObj.byteCol);
+        assertEquals(obj.shortCol, restoredObj.shortCol);
+        assertEquals(obj.intCol, restoredObj.intCol);
+        assertEquals(obj.longCol, restoredObj.longCol);
+        assertEquals(obj.floatCol, restoredObj.floatCol);
+        assertEquals(obj.doubleCol, restoredObj.doubleCol);
+
+        assertEquals(obj.uuidCol, restoredObj.uuidCol);
+        assertEquals(obj.bitmaskCol, restoredObj.bitmaskCol);
+        assertEquals(obj.stringCol, restoredObj.stringCol);
+        assertArrayEquals(obj.bytesCol, restoredObj.bytesCol);
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testNullableFieldsAccessor() throws Exception {
+        Column[] cols = new Column[] {
+            new Column("intCol", INTEGER, true),
+            new Column("longCol", LONG, true),
+
+            new Column("stringCol", STRING, true),
+            new Column("bytesCol", BYTES, true),
+        };
+
+        final Pair<TupleAssembler, Tuple> mocks = createMocks();
+
+        final TupleAssembler tupleAssembler = mocks.getFirst();
+        final Tuple tuple = mocks.getSecond();
+
+        final TestSimpleObject obj = new TestSimpleObject();
+        obj.longCol = rnd.nextLong();
+        obj.stringCol = TestUtils.randomString(rnd, 255);
+
+        for (int i = 0; i < cols.length; i++) {
+            UnsafeFieldAccessor accessor = UnsafeFieldAccessor.create(TestSimpleObject.class, cols[i], i);
+
+            accessor.write(obj, tupleAssembler);
+        }
+
+        final TestSimpleObject restoredObj = new TestSimpleObject();
+
+        for (int i = 0; i < cols.length; i++) {
+            UnsafeFieldAccessor accessor = UnsafeFieldAccessor.create(TestSimpleObject.class, cols[i], i);
+
+            accessor.read(restoredObj, tuple);
+        }
+
+        assertEquals(obj.intCol, restoredObj.intCol);
+        assertEquals(obj.longCol, restoredObj.longCol);
+
+        assertEquals(obj.stringCol, restoredObj.stringCol);
+        assertArrayEquals(obj.bytesCol, restoredObj.bytesCol);
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testIdentityAccessor() throws Exception {
+        final UnsafeFieldAccessor accessor = UnsafeFieldAccessor.createIdentityAccessor(
+            new Column("col0", STRING, true),
+            0,
+            BinaryMode.STRING);
+
+        assertEquals("Some string", accessor.value("Some string"));
+
+        final Pair<TupleAssembler, Tuple> mocks = createMocks();
+
+        accessor.write("Other string", mocks.getFirst());
+        assertEquals("Other string", accessor.read(mocks.getSecond()));
+    }
+
+    /**
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testWrongIdentityAccessor() throws Exception {
+        final UnsafeFieldAccessor accessor = UnsafeFieldAccessor.createIdentityAccessor(
+            new Column("col0", STRING, true),
+            42,
+            BinaryMode.UUID);
+
+        assertEquals("Some string", accessor.value("Some string"));
+
+        final Pair<TupleAssembler, Tuple> mocks = createMocks();
+
+        assertThrows(
+            SerializationException.class,
+            () -> accessor.write("Other string", mocks.getFirst()),
+            "Failed to write field [id=42]"
+        );
+    }
+
+    /**
+     * Creates mock pair for {@link Tuple} and {@link TupleAssembler).
+     *
+     * @return Pair of mocks.
+     */
+    private Pair<TupleAssembler, Tuple> createMocks() {
+        final ArrayList<Object> vals = new ArrayList<>();
+
+        final TupleAssembler mockedAsm = Mockito.mock(TupleAssembler.class);
+        final Tuple mockedTuple = Mockito.mock(Tuple.class);
+
+        final Answer<Void> asmAnswer = new Answer<Void>() {
+            @Override public Void answer(InvocationOnMock invocation) {
+                if ("appendNull".equals(invocation.getMethod().getName()))
+                    vals.add(null);
+                else
+                    vals.add(invocation.getArguments()[0]);
+
+                return null;
+            }
+        };
+
+        final Answer<Object> tupleAnswer = new Answer<Object>() {
+            @Override public Object answer(InvocationOnMock invocation) {
+                final int idx = invocation.getArgumentAt(0, Integer.class);
+
+                return vals.get(idx);
+            }
+        };
+
+        Mockito.doAnswer(asmAnswer).when(mockedAsm).appendNull();
+        Mockito.doAnswer(asmAnswer).when(mockedAsm).appendByte(Mockito.anyByte());
+        Mockito.doAnswer(asmAnswer).when(mockedAsm).appendShort(Mockito.anyShort());
+        Mockito.doAnswer(asmAnswer).when(mockedAsm).appendInt(Mockito.anyInt());
+        Mockito.doAnswer(asmAnswer).when(mockedAsm).appendLong(Mockito.anyLong());
+        Mockito.doAnswer(asmAnswer).when(mockedAsm).appendFloat(Mockito.anyFloat());
+        Mockito.doAnswer(asmAnswer).when(mockedAsm).appendDouble(Mockito.anyDouble());
+
+        Mockito.doAnswer(asmAnswer).when(mockedAsm).appendUuid(Mockito.any(java.util.UUID.class));
+        Mockito.doAnswer(asmAnswer).when(mockedAsm).appendBitmask(Mockito.any(BitSet.class));
+        Mockito.doAnswer(asmAnswer).when(mockedAsm).appendString(Mockito.anyString());
+        Mockito.doAnswer(asmAnswer).when(mockedAsm).appendBytes(Mockito.any(byte[].class));
+
+        Mockito.doAnswer(tupleAnswer).when(mockedTuple).byteValue(Mockito.anyInt());
+        Mockito.doAnswer(tupleAnswer).when(mockedTuple).byteValueBoxed(Mockito.anyInt());
+        Mockito.doAnswer(tupleAnswer).when(mockedTuple).shortValue(Mockito.anyInt());
+        Mockito.doAnswer(tupleAnswer).when(mockedTuple).shortValueBoxed(Mockito.anyInt());
+        Mockito.doAnswer(tupleAnswer).when(mockedTuple).intValue(Mockito.anyInt());
+        Mockito.doAnswer(tupleAnswer).when(mockedTuple).intValueBoxed(Mockito.anyInt());
+        Mockito.doAnswer(tupleAnswer).when(mockedTuple).longValue(Mockito.anyInt());
+        Mockito.doAnswer(tupleAnswer).when(mockedTuple).longValueBoxed(Mockito.anyInt());
+        Mockito.doAnswer(tupleAnswer).when(mockedTuple).floatValue(Mockito.anyInt());
+        Mockito.doAnswer(tupleAnswer).when(mockedTuple).floatValueBoxed(Mockito.anyInt());
+        Mockito.doAnswer(tupleAnswer).when(mockedTuple).doubleValue(Mockito.anyInt());
+        Mockito.doAnswer(tupleAnswer).when(mockedTuple).doubleValueBoxed(Mockito.anyInt());
+
+        Mockito.doAnswer(tupleAnswer).when(mockedTuple).uuidValue(Mockito.anyInt());
+        Mockito.doAnswer(tupleAnswer).when(mockedTuple).bitmaskValue(Mockito.anyInt());
+        Mockito.doAnswer(tupleAnswer).when(mockedTuple).stringValue(Mockito.anyInt());
+        Mockito.doAnswer(tupleAnswer).when(mockedTuple).bytesValue(Mockito.anyInt());
+
+        return new Pair<>(mockedAsm, mockedTuple);
+    }
+
+    /**
+     * Test object.
+     */
+    private static class TestObject {
+        /**
+         * @return Random TestObject.
+         */
+        public static TestObject randomObject(Random rnd) {
+            final TestObject obj = new TestObject();
+
+            obj.pByteCol = (byte)rnd.nextInt(255);
+            obj.pShortCol = (short)rnd.nextInt(65535);
+            obj.pIntCol = rnd.nextInt();
+            obj.pLongCol = rnd.nextLong();
+            obj.pFloatCol = rnd.nextFloat();
+            obj.pDoubleCol = rnd.nextDouble();
+
+            obj.byteCol = (byte)rnd.nextInt(255);
+            obj.shortCol = (short)rnd.nextInt(65535);
+            obj.intCol = rnd.nextInt();
+            obj.longCol = rnd.nextLong();
+            obj.floatCol = rnd.nextFloat();
+            obj.doubleCol = rnd.nextDouble();
+
+            obj.uuidCol = new UUID(rnd.nextLong(), rnd.nextLong());
+            obj.bitmaskCol = TestUtils.randomBitSet(rnd, rnd.nextInt(42));
+            obj.stringCol = TestUtils.randomString(rnd, rnd.nextInt(255));
+            obj.bytesCol = TestUtils.randomBytes(rnd, rnd.nextInt(255));
+
+            return obj;
+        }
+
+        // Primitive typed
+        private byte pByteCol;
+        private short pShortCol;
+        private int pIntCol;
+        private long pLongCol;
+        private float pFloatCol;
+        private double pDoubleCol;
+
+        // Reference typed
+        private Byte byteCol;
+        private Short shortCol;
+        private Integer intCol;
+        private Long longCol;
+        private Float floatCol;
+        private Double doubleCol;
+
+        private UUID uuidCol;
+        private BitSet bitmaskCol;
+        private String stringCol;
+        private byte[] bytesCol;
+
+        /** {@inheritDoc} */
+        @Override public boolean equals(Object o) {
+            if (this == o)
+                return true;
+            if (o == null || getClass() != o.getClass())
+                return false;
+            TestObject object = (TestObject)o;
+            return pByteCol == object.pByteCol &&
+                pShortCol == object.pShortCol &&
+                pIntCol == object.pIntCol &&
+                pLongCol == object.pLongCol &&
+                Float.compare(object.pFloatCol, pFloatCol) == 0 &&
+                Double.compare(object.pDoubleCol, pDoubleCol) == 0 &&
+                Objects.equals(byteCol, object.byteCol) &&
+                Objects.equals(shortCol, object.shortCol) &&
+                Objects.equals(intCol, object.intCol) &&
+                Objects.equals(longCol, object.longCol) &&
+                Objects.equals(floatCol, object.floatCol) &&
+                Objects.equals(doubleCol, object.doubleCol) &&
+                Objects.equals(uuidCol, object.uuidCol) &&
+                Objects.equals(bitmaskCol, object.bitmaskCol) &&
+                Objects.equals(stringCol, object.stringCol) &&
+                Arrays.equals(bytesCol, object.bytesCol);
+        }
+
+        /** {@inheritDoc} */
+        @Override public int hashCode() {
+            return 73;
+        }
+    }
+
+    /**
+     * Test object.
+     */
+    private static class TestSimpleObject {
+        Long longCol;
+        Integer intCol;
+        byte[] bytesCol;
+        String stringCol;
+
+        /** {@inheritDoc} */
+        @Override public boolean equals(Object o) {
+            if (this == o)
+                return true;
+            if (o == null || getClass() != o.getClass())
+                return false;
+            TestSimpleObject object = (TestSimpleObject)o;
+            return Objects.equals(longCol, object.longCol) &&
+                Objects.equals(intCol, object.intCol) &&
+                Arrays.equals(bytesCol, object.bytesCol) &&
+                Objects.equals(stringCol, object.stringCol);
+        }
+
+        /** {@inheritDoc} */
+        @Override public int hashCode() {
+            return 42;
+        }
+    }
+}
diff --git a/pom.xml b/pom.xml
index d825ae1..2d460d4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -48,12 +48,17 @@
     <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 
+        <!-- Dependencies versions. -->
+        <janino.version>3.0.11</janino.version>
+        <javax.annotation.api.version>1.3.2</javax.annotation.api.version>
         <jetbrains.annotations.version>20.1.0</jetbrains.annotations.version>
+        <jmh.framework.verion>1.9.3</jmh.framework.verion>
         <junit.jupiter.version>5.7.0</junit.jupiter.version>
+        <mockito.version>1.10.19</mockito.version>
 
+        <!-- Maven plugins -->
         <maven.compiler.plugin.version>3.8.1</maven.compiler.plugin.version>
         <maven.surefire.plugin.version>3.0.0-M4</maven.surefire.plugin.version>
-
         <apache.rat.plugin.version>0.13</apache.rat.plugin.version>
     </properties>