/*
 * 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.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import sun.misc.Unsafe;

import static org.apache.ignite.internal.util.IgniteUtils.jdkVersion;
import static org.apache.ignite.internal.util.IgniteUtils.majorJavaVersion;

/**
 * Wrapper for the {@link sun.misc.Unsafe} class.
 * <p>
 * All memory access operations have the following properties:
 * <ul>
 * <li>All {@code putXxx(long addr, xxx val)}, {@code getXxx(long addr)}, {@code putXxx(byte[] arr, long off, xxx val)},
 * {@code getXxx(byte[] arr, long off)} and corresponding methods with {@code LE} suffix are alignment aware
 * and can be safely used with unaligned pointers.</li>
 * <li>All {@code putXxxField(Object obj, long fieldOff, xxx val)} and {@code getXxxField(Object obj, long fieldOff)}
 * methods are not alignment aware and can't be safely used with unaligned pointers. This methods can be safely used
 * for object field values access because all object fields addresses are aligned.</li>
 * <li>All {@code putXxxLE(...)} and {@code getXxxLE(...)} methods assumes that the byte order is fixed as little-endian
 * while native byte order is big-endian. So it is client code responsibility to check native byte order before
 * invoking these methods.</li>
 * </ul>
 */
public abstract class GridUnsafe {
    /** */
    public static final ByteOrder NATIVE_BYTE_ORDER = ByteOrder.nativeOrder();

    /** Unsafe. */
    private static final Unsafe UNSAFE = unsafe();

    /** Unaligned flag. */
    private static final boolean UNALIGNED = unaligned();

    /** Per-byte copy threshold. */
    private static final long PER_BYTE_THRESHOLD = 0L;

    /** Big endian. */
    public static final boolean BIG_ENDIAN = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN;

    /** Address size. */
    public static final int ADDR_SIZE = UNSAFE.addressSize();

    /** */
    public static final long BYTE_ARR_OFF = UNSAFE.arrayBaseOffset(byte[].class);

    /** */
    public static final int BYTE_ARR_INT_OFF = UNSAFE.arrayBaseOffset(byte[].class);

    /** */
    public static final long SHORT_ARR_OFF = UNSAFE.arrayBaseOffset(short[].class);

    /** */
    public static final long INT_ARR_OFF = UNSAFE.arrayBaseOffset(int[].class);

    /** */
    public static final long LONG_ARR_OFF = UNSAFE.arrayBaseOffset(long[].class);

    /** */
    public static final long FLOAT_ARR_OFF = UNSAFE.arrayBaseOffset(float[].class);

    /** */
    public static final long DOUBLE_ARR_OFF = UNSAFE.arrayBaseOffset(double[].class);

    /** */
    public static final long CHAR_ARR_OFF = UNSAFE.arrayBaseOffset(char[].class);

    /** */
    public static final long BOOLEAN_ARR_OFF = UNSAFE.arrayBaseOffset(boolean[].class);

    /** {@link java.nio.Buffer#address} field offset. */
    private static final long DIRECT_BUF_ADDR_OFF = bufferAddressOffset();

    /** JavaNioAccess object. If {@code null} then {@link #NEW_DIRECT_BUF_CONSTRUCTOR} should be available. */
    @Nullable private static final Object JAVA_NIO_ACCESS_OBJ;

    /**
     * JavaNioAccess#newDirectByteBuffer method. Ususally {@code null} if {@link #JAVA_NIO_ACCESS_OBJ} is {@code null}.
     * If {@code null} then {@link #NEW_DIRECT_BUF_CONSTRUCTOR} should be available.
     */
    @Nullable private static final Method NEW_DIRECT_BUF_MTD;

    /**
     * New direct buffer class constructor obtained and tested using reflection. If {@code null} then both {@link
     * #JAVA_NIO_ACCESS_OBJ} and {@link #NEW_DIRECT_BUF_MTD} should be not {@code null}.
     */
    @Nullable private static final Constructor<?> NEW_DIRECT_BUF_CONSTRUCTOR;

    static {
        Object nioAccessObj = null;
        Method directBufMtd = null;

        Constructor<?> directBufCtor = null;

        if (majorJavaVersion(jdkVersion()) < 12) {
            // for old java prefer Java NIO & Shared Secrets obect init way
            try {
                nioAccessObj = javaNioAccessObject();
                directBufMtd = newDirectBufferMethod(nioAccessObj);
            }
            catch (Exception e) {
                nioAccessObj = null;
                directBufMtd = null;

                try {
                    directBufCtor = createAndTestNewDirectBufferCtor();
                }
                catch (Exception eFallback) {
                    //noinspection CallToPrintStackTrace
                    eFallback.printStackTrace();

                    e.addSuppressed(eFallback);

                    throw e; // fallback was not suceefull
                }

                if (directBufCtor == null)
                    throw e;
            }
        }
        else {
            try {
                directBufCtor = createAndTestNewDirectBufferCtor();
            }
            catch (Exception e) {
                try {
                    nioAccessObj = javaNioAccessObject();
                    directBufMtd = newDirectBufferMethod(nioAccessObj);
                }
                catch (Exception eFallback) {
                    //noinspection CallToPrintStackTrace
                    eFallback.printStackTrace();

                    e.addSuppressed(eFallback);

                    throw e; //fallback to shared secrets failed.
                }

                if (nioAccessObj == null || directBufMtd == null)
                    throw e;
            }
        }

        JAVA_NIO_ACCESS_OBJ = nioAccessObj;
        NEW_DIRECT_BUF_MTD = directBufMtd;

        NEW_DIRECT_BUF_CONSTRUCTOR = directBufCtor;
    }

    /**
     * Ensure singleton.
     */
    private GridUnsafe() {
        // No-op.
    }

    /**
     * Wraps a pointer to unmanaged memory into a direct byte buffer.
     *
     * @param ptr Pointer to wrap.
     * @param len Memory location length.
     * @return Byte buffer wrapping the given memory.
     */
    public static ByteBuffer wrapPointer(long ptr, int len) {
        if (NEW_DIRECT_BUF_MTD != null && JAVA_NIO_ACCESS_OBJ != null)
            return wrapPointerJavaNio(ptr, len, NEW_DIRECT_BUF_MTD, JAVA_NIO_ACCESS_OBJ);
        else if (NEW_DIRECT_BUF_CONSTRUCTOR != null)
            return wrapPointerDirectBufferConstructor(ptr, len, NEW_DIRECT_BUF_CONSTRUCTOR);
        else
            throw new RuntimeException("All alternatives for a new DirectByteBuffer() creation failed: " + FeatureChecker.JAVA_VER_SPECIFIC_WARN);
    }

    /**
     * Wraps a pointer to unmanaged memory with a direct byte buffer using the direct byte buffer's constructor.
     *
     * @param ptr Pointer to wrap.
     * @param len Memory location length.
     * @param constructor Constructor to use. Should create an instance of a direct ByteBuffer.
     * @return Byte buffer wrapping the given memory.
     */
    @NotNull private static ByteBuffer wrapPointerDirectBufferConstructor(long ptr, int len, Constructor<?> constructor) {
        try {
            Object newDirectBuf = constructor.newInstance(ptr, len);

            return ((ByteBuffer)newDirectBuf).order(NATIVE_BYTE_ORDER);
        }
        catch (ReflectiveOperationException e) {
            throw new RuntimeException("DirectByteBuffer#constructor is unavailable."
                + FeatureChecker.JAVA_VER_SPECIFIC_WARN, e);
        }
    }

    /**
     * Wraps a pointer to unmanaged memory with a direct byte buffer using a JavaNioAccess object.
     *
     * @param ptr Pointer to wrap.
     * @param len Memory location length.
     * @param newDirectBufMtd Method which should return an instance of a direct byte buffer.
     * @param javaNioAccessObj Object to invoke method.
     * @return Byte buffer wrapping the given memory.
     */
    @NotNull private static ByteBuffer wrapPointerJavaNio(long ptr,
                                                          int len,
                                                          @NotNull Method newDirectBufMtd,
                                                          @NotNull Object javaNioAccessObj) {
        try {
            ByteBuffer buf = (ByteBuffer)newDirectBufMtd.invoke(javaNioAccessObj, ptr, len, null);

            assert buf.isDirect();

            buf.order(NATIVE_BYTE_ORDER);

            return buf;
        }
        catch (ReflectiveOperationException e) {
            throw new RuntimeException("JavaNioAccess#newDirectByteBuffer() method is unavailable."
                + FeatureChecker.JAVA_VER_SPECIFIC_WARN, e);
        }
    }

    /**
     * @param len Length.
     * @return Allocated direct buffer.
     */
    public static ByteBuffer allocateBuffer(int len) {
        long ptr = allocateMemory(len);

        return wrapPointer(ptr, len);
    }

    /**
     * @param buf Direct buffer allocated by {@link #allocateBuffer(int)}.
     */
    public static void freeBuffer(ByteBuffer buf) {
        long ptr = bufferAddress(buf);

        freeMemory(ptr);
    }

    /**
     *
     * @param buf Buffer.
     * @param len New length.
     * @return Reallocated direct buffer.
     */
    public static ByteBuffer reallocateBuffer(ByteBuffer buf, int len) {
        long ptr = bufferAddress(buf);

        long newPtr = reallocateMemory(ptr, len);

        return wrapPointer(newPtr, len);
    }

    /**
     * Gets a boolean value from an 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 a boolean value into an 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 a byte value from an 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 a byte value into an 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 a short value from an 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 a short value into an 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 a char value from an 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 a char value into an 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 an integer value from an 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 an integer value into an 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 a long value from an 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 a long value into an 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 a float value from an 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 a float value into an 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 a double value from an 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 a double value into an 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 a reference from an 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 a reference value into an 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);
    }

    /**
     * Gets a boolean value from a byte array.
     *
     * @param arr Byte array.
     * @param off Offset.
     * @return Boolean value from byte array.
     */
    public static boolean getBoolean(byte[] arr, long off) {
        return UNSAFE.getBoolean(arr, off);
    }

    /**
     * Stores a boolean value into a byte array.
     *
     * @param arr Byte array.
     * @param off Offset.
     * @param val Value.
     */
    public static void putBoolean(byte[] arr, long off, boolean val) {
        UNSAFE.putBoolean(arr, off, val);
    }

    /**
     * Gets a byte value from a byte array.
     *
     * @param arr Byte array.
     * @param off Offset.
     * @return Byte value from byte array.
     */
    public static byte getByte(byte[] arr, long off) {
        return UNSAFE.getByte(arr, off);
    }

    /**
     * Stores a byte value into a byte array.
     *
     * @param arr Byte array.
     * @param off Offset.
     * @param val Value.
     */
    public static void putByte(byte[] arr, long off, byte val) {
        UNSAFE.putByte(arr, off, val);
    }

    /**
     * Gets a short value from a byte array. Alignment aware.
     *
     * @param arr Byte array.
     * @param off Offset.
     * @return Short value from byte array.
     */
    public static short getShort(byte[] arr, long off) {
        return UNALIGNED ? UNSAFE.getShort(arr, off) : getShortByByte(arr, off, BIG_ENDIAN);
    }

    /**
     * Stores a short value into a byte array. Alignment aware.
     *
     * @param arr Byte array.
     * @param off Offset.
     * @param val Value.
     */
    public static void putShort(byte[] arr, long off, short val) {
        if (UNALIGNED)
            UNSAFE.putShort(arr, off, val);
        else
            putShortByByte(arr, off, val, BIG_ENDIAN);
    }

    /**
     * Gets a char value from a byte array. Alignment aware.
     *
     * @param arr Byte array.
     * @param off Offset.
     * @return Char value from byte array.
     */
    public static char getChar(byte[] arr, long off) {
        return UNALIGNED ? UNSAFE.getChar(arr, off) : getCharByByte(arr, off, BIG_ENDIAN);
    }

    /**
     * Stores a char value into a byte array. Alignment aware.
     *
     * @param arr Byte array.
     * @param off Offset.
     * @param val Value.
     */
    public static void putChar(byte[] arr, long off, char val) {
        if (UNALIGNED)
            UNSAFE.putChar(arr, off, val);
        else
            putCharByByte(arr, off, val, BIG_ENDIAN);
    }

    /**
     * Gets an integer value from a byte array. Alignment aware.
     *
     * @param arr Byte array.
     * @param off Offset.
     * @return Integer value from byte array.
     */
    public static int getInt(byte[] arr, long off) {
        return UNALIGNED ? UNSAFE.getInt(arr, off) : getIntByByte(arr, off, BIG_ENDIAN);
    }

    /**
     * Stores an integer value into a byte array. Alignment aware.
     *
     * @param arr Byte array.
     * @param off Offset.
     * @param val Value.
     */
    public static void putInt(byte[] arr, long off, int val) {
        if (UNALIGNED)
            UNSAFE.putInt(arr, off, val);
        else
            putIntByByte(arr, off, val, BIG_ENDIAN);
    }

    /**
     * Gets a long value from a byte array. Alignment aware.
     *
     * @param arr Byte array.
     * @param off Offset.
     * @return Long value from byte array.
     */
    public static long getLong(byte[] arr, long off) {
        return UNALIGNED ? UNSAFE.getLong(arr, off) : getLongByByte(arr, off, BIG_ENDIAN);
    }

    /**
     * Stores a long value into a byte array. Alignment aware.
     *
     * @param arr Byte array.
     * @param off Offset.
     * @param val Value.
     */
    public static void putLong(byte[] arr, long off, long val) {
        if (UNALIGNED)
            UNSAFE.putLong(arr, off, val);
        else
            putLongByByte(arr, off, val, BIG_ENDIAN);
    }

    /**
     * Gets a float value from a byte array. Alignment aware.
     *
     * @param arr Object.
     * @param off Offset.
     * @return Float value from byte array.
     */
    public static float getFloat(byte[] arr, long off) {
        return UNALIGNED ? UNSAFE.getFloat(arr, off) : Float.intBitsToFloat(getIntByByte(arr, off, BIG_ENDIAN));
    }

    /**
     * Stores a float value into a byte array. Alignment aware.
     *
     * @param arr Byte array.
     * @param off Offset.
     * @param val Value.
     */
    public static void putFloat(byte[] arr, long off, float val) {
        if (UNALIGNED)
            UNSAFE.putFloat(arr, off, val);
        else
            putIntByByte(arr, off, Float.floatToIntBits(val), BIG_ENDIAN);
    }

    /**
     * Gets a double value from a byte array. Alignment aware.
     *
     * @param arr byte array.
     * @param off Offset.
     * @return Double value from byte array. Alignment aware.
     */
    public static double getDouble(byte[] arr, long off) {
        return UNALIGNED ? UNSAFE.getDouble(arr, off) : Double.longBitsToDouble(getLongByByte(arr, off, BIG_ENDIAN));
    }

    /**
     * Stores a double value into a byte array. Alignment aware.
     *
     * @param arr Byte array.
     * @param off Offset.
     * @param val Value.
     */
    public static void putDouble(byte[] arr, long off, double val) {
        if (UNALIGNED)
            UNSAFE.putDouble(arr, off, val);
        else
            putLongByByte(arr, off, Double.doubleToLongBits(val), BIG_ENDIAN);
    }

    /**
     * Gets short value from byte array assuming that value stored in little-endian byte order and native byte order
     * is big-endian. Alignment aware.
     *
     * @param arr Byte array.
     * @param off Offset.
     * @return Short value from byte array.
     */
    public static short getShortLE(byte[] arr, long off) {
        return UNALIGNED ? Short.reverseBytes(UNSAFE.getShort(arr, off)) : getShortByByte(arr, off, false);
    }

    /**
     * Stores short value into byte array assuming that value should be stored in little-endian byte order and native
     * byte order is big-endian. Alignment aware.
     *
     * @param arr Byte array.
     * @param off Offset.
     * @param val Value.
     */
    public static void putShortLE(byte[] arr, long off, short val) {
        if (UNALIGNED)
            UNSAFE.putShort(arr, off, Short.reverseBytes(val));
        else
            putShortByByte(arr, off, val, false);
    }

    /**
     * Gets char value from byte array assuming that value stored in little-endian byte order and native byte order
     * is big-endian. Alignment aware.
     *
     * @param arr Byte array.
     * @param off Offset.
     * @return Char value from byte array.
     */
    public static char getCharLE(byte[] arr, long off) {
        return UNALIGNED ? Character.reverseBytes(UNSAFE.getChar(arr, off)) : getCharByByte(arr, off, false);
    }

    /**
     * Stores char value into byte array assuming that value should be stored in little-endian byte order and native
     * byte order is big-endian. Alignment aware.
     *
     * @param arr Byte array.
     * @param off Offset.
     * @param val Value.
     */
    public static void putCharLE(byte[] arr, long off, char val) {
        if (UNALIGNED)
            UNSAFE.putChar(arr, off, Character.reverseBytes(val));
        else
            putCharByByte(arr, off, val, false);
    }

    /**
     * Gets integer value from byte array assuming that value stored in little-endian byte order and native byte order
     * is big-endian. Alignment aware.
     *
     * @param arr Byte array.
     * @param off Offset.
     * @return Integer value from byte array.
     */
    public static int getIntLE(byte[] arr, long off) {
        return UNALIGNED ? Integer.reverseBytes(UNSAFE.getInt(arr, off)) : getIntByByte(arr, off, false);
    }

    /**
     * Stores integer value into byte array assuming that value should be stored in little-endian byte order and
     * native byte order is big-endian. Alignment aware.
     *
     * @param arr Byte array.
     * @param off Offset.
     * @param val Value.
     */
    public static void putIntLE(byte[] arr, long off, int val) {
        if (UNALIGNED)
            UNSAFE.putInt(arr, off, Integer.reverseBytes(val));
        else
            putIntByByte(arr, off, val, false);
    }

    /**
     * Gets long value from byte array assuming that value stored in little-endian byte order and native byte order
     * is big-endian. Alignment aware.
     *
     * @param arr Byte array.
     * @param off Offset.
     * @return Long value from byte array.
     */
    public static long getLongLE(byte[] arr, long off) {
        return UNALIGNED ? Long.reverseBytes(UNSAFE.getLong(arr, off)) : getLongByByte(arr, off, false);
    }

    /**
     * Stores long value into byte array assuming that value should be stored in little-endian byte order and native
     * byte order is big-endian. Alignment aware.
     *
     * @param arr Byte array.
     * @param off Offset.
     * @param val Value.
     */
    public static void putLongLE(byte[] arr, long off, long val) {
        if (UNALIGNED)
            UNSAFE.putLong(arr, off, Long.reverseBytes(val));
        else
            putLongByByte(arr, off, val, false);
    }

    /**
     * Gets float value from byte array assuming that value stored in little-endian byte order and native byte order
     * is big-endian. Alignment aware.
     *
     * @param arr Byte array.
     * @param off Offset.
     * @return Float value from byte array.
     */
    public static float getFloatLE(byte[] arr, long off) {
        return Float.intBitsToFloat(
            UNALIGNED ? Integer.reverseBytes(UNSAFE.getInt(arr, off)) : getIntByByte(arr, off, false)
        );
    }

    /**
     * Stores float value into byte array assuming that value should be stored in little-endian byte order and native
     * byte order is big-endian. Alignment aware.
     *
     * @param arr Byte array.
     * @param off Offset.
     * @param val Value.
     */
    public static void putFloatLE(byte[] arr, long off, float val) {
        int intVal = Float.floatToIntBits(val);

        if (UNALIGNED)
            UNSAFE.putInt(arr, off, Integer.reverseBytes(intVal));
        else
            putIntByByte(arr, off, intVal, false);
    }

    /**
     * Gets double value from byte array assuming that value stored in little-endian byte order and native byte order
     * is big-endian. Alignment aware.
     *
     * @param arr Byte array.
     * @param off Offset.
     * @return Double value from byte array.
     */
    public static double getDoubleLE(byte[] arr, long off) {
        return Double.longBitsToDouble(
            UNALIGNED ? Long.reverseBytes(UNSAFE.getLong(arr, off)) : getLongByByte(arr, off, false)
        );
    }

    /**
     * Stores double value into byte array assuming that value should be stored in little-endian byte order and
     * native byte order is big-endian. Alignment aware.
     *
     * @param arr Byte array.
     * @param off Offset.
     * @param val Value.
     */
    public static void putDoubleLE(byte[] arr, long off, double val) {
        long longVal = Double.doubleToLongBits(val);

        if (UNALIGNED)
            UNSAFE.putLong(arr, off, Long.reverseBytes(longVal));
        else
            putLongByByte(arr, off, longVal, false);
    }

    /**
     * Gets byte value from given address.
     *
     * @param addr Address.
     * @return Byte value from given address.
     */
    public static byte getByte(long addr) {
        return UNSAFE.getByte(addr);
    }

    /**
     * Stores given byte value.
     *
     * @param addr Address.
     * @param val Value.
     */
    public static void putByte(long addr, byte val) {
        UNSAFE.putByte(addr, val);
    }

    /**
     * Gets short value from given address. Alignment aware.
     *
     * @param addr Address.
     * @return Short value from given address.
     */
    public static short getShort(long addr) {
        return UNALIGNED ? UNSAFE.getShort(addr) : getShortByByte(addr, BIG_ENDIAN);
    }

    /**
     * Stores given short value. Alignment aware.
     *
     * @param addr Address.
     * @param val Value.
     */
    public static void putShort(long addr, short val) {
        if (UNALIGNED)
            UNSAFE.putShort(addr, val);
        else
            putShortByByte(addr, val, BIG_ENDIAN);
    }

    /**
     * Gets char value from given address. Alignment aware.
     *
     * @param addr Address.
     * @return Char value from given address.
     */
    public static char getChar(long addr) {
        return UNALIGNED ? UNSAFE.getChar(addr) : getCharByByte(addr, BIG_ENDIAN);
    }

    /**
     * Stores given char value. Alignment aware.
     *
     * @param addr Address.
     * @param val Value.
     */
    public static void putChar(long addr, char val) {
        if (UNALIGNED)
            UNSAFE.putChar(addr, val);
        else
            putCharByByte(addr, val, BIG_ENDIAN);
    }

    /**
     * Gets integer value from given address. Alignment aware.
     *
     * @param addr Address.
     * @return Integer value from given address.
     */
    public static int getInt(long addr) {
        return UNALIGNED ? UNSAFE.getInt(addr) : getIntByByte(addr, BIG_ENDIAN);
    }

    /**
     * Stores given integer value. Alignment aware.
     *
     * @param addr Address.
     * @param val Value.
     */
    public static void putInt(long addr, int val) {
        if (UNALIGNED)
            UNSAFE.putInt(addr, val);
        else
            putIntByByte(addr, val, BIG_ENDIAN);
    }

    /**
     * Gets long value from given address. Alignment aware.
     *
     * @param addr Address.
     * @return Long value from given address.
     */
    public static long getLong(long addr) {
        return UNALIGNED ? UNSAFE.getLong(addr) : getLongByByte(addr, BIG_ENDIAN);
    }

    /**
     * Stores given integer value. Alignment aware.
     *
     * @param addr Address.
     * @param val Value.
     */
    public static void putLong(long addr, long val) {
        if (UNALIGNED)
            UNSAFE.putLong(addr, val);
        else
            putLongByByte(addr, val, BIG_ENDIAN);
    }

    /**
     * Gets float value from given address. Alignment aware.
     *
     * @param addr Address.
     * @return Float value from given address.
     */
    public static float getFloat(long addr) {
        return UNALIGNED ? UNSAFE.getFloat(addr) : Float.intBitsToFloat(getIntByByte(addr, BIG_ENDIAN));
    }

    /**
     * Stores given float value. Alignment aware.
     *
     * @param addr Address.
     * @param val Value.
     */
    public static void putFloat(long addr, float val) {
        if (UNALIGNED)
            UNSAFE.putFloat(addr, val);
        else
            putIntByByte(addr, Float.floatToIntBits(val), BIG_ENDIAN);
    }

    /**
     * Gets double value from given address. Alignment aware.
     *
     * @param addr Address.
     * @return Double value from given address.
     */
    public static double getDouble(long addr) {
        return UNALIGNED ? UNSAFE.getDouble(addr) : Double.longBitsToDouble(getLongByByte(addr, BIG_ENDIAN));
    }

    /**
     * Stores given double value. Alignment aware.
     *
     * @param addr Address.
     * @param val Value.
     */
    public static void putDouble(long addr, double val) {
        if (UNALIGNED)
            UNSAFE.putDouble(addr, val);
        else
            putLongByByte(addr, Double.doubleToLongBits(val), BIG_ENDIAN);
    }

    /**
     * Gets short value from given address assuming that value stored in little-endian byte order and native byte order
     * is big-endian. Alignment aware.
     *
     * @param addr Address.
     * @return Short value from given address.
     */
    public static short getShortLE(long addr) {
        return UNALIGNED ? Short.reverseBytes(UNSAFE.getShort(addr)) : getShortByByte(addr, false);
    }

    /**
     * Stores given short value assuming that value should be stored in little-endian byte order and native byte
     * order is big-endian. Alignment aware.
     *
     * @param addr Address.
     * @param val Value.
     */
    public static void putShortLE(long addr, short val) {
        if (UNALIGNED)
            UNSAFE.putShort(addr, Short.reverseBytes(val));
        else
            putShortByByte(addr, val, false);
    }

    /**
     * Gets char value from given address assuming that value stored in little-endian byte order and native byte order
     * is big-endian. Alignment aware.
     *
     * @param addr Address.
     * @return Char value from given address.
     */
    public static char getCharLE(long addr) {
        return UNALIGNED ? Character.reverseBytes(UNSAFE.getChar(addr)) : getCharByByte(addr, false);
    }

    /**
     * Stores given char value assuming that value should be stored in little-endian byte order and native byte order
     * is big-endian. Alignment aware.
     *
     * @param addr Address.
     * @param val Value.
     */
    public static void putCharLE(long addr, char val) {
        if (UNALIGNED)
            UNSAFE.putChar(addr, Character.reverseBytes(val));
        else
            putCharByByte(addr, val, false);
    }

    /**
     * Gets integer value from given address assuming that value stored in little-endian byte order
     * and native byte order is big-endian. Alignment aware.
     *
     * @param addr Address.
     * @return Integer value from given address.
     */
    public static int getIntLE(long addr) {
        return UNALIGNED ? Integer.reverseBytes(UNSAFE.getInt(addr)) : getIntByByte(addr, false);
    }

    /**
     * Stores given integer value assuming that value should be stored in little-endian byte order
     * and native byte order is big-endian. Alignment aware.
     *
     * @param addr Address.
     * @param val Value.
     */
    public static void putIntLE(long addr, int val) {
        if (UNALIGNED)
            UNSAFE.putInt(addr, Integer.reverseBytes(val));
        else
            putIntByByte(addr, val, false);
    }

    /**
     * Gets long value from given address assuming that value stored in little-endian byte order
     * and native byte order is big-endian. Alignment aware.
     *
     * @param addr Address.
     * @return Long value from given address.
     */
    public static long getLongLE(long addr) {
        return UNALIGNED ? Long.reverseBytes(UNSAFE.getLong(addr)) : getLongByByte(addr, false);
    }

    /**
     * Stores given integer value assuming that value should be stored in little-endian byte order
     * and native byte order is big-endian. Alignment aware.
     *
     * @param addr Address.
     * @param val Value.
     */
    public static void putLongLE(long addr, long val) {
        if (UNALIGNED)
            UNSAFE.putLong(addr, Long.reverseBytes(val));
        else
            putLongByByte(addr, val, false);
    }

    /**
     * Gets float value from given address assuming that value stored in little-endian byte order
     * and native byte order is big-endian. Alignment aware.
     *
     * @param addr Address.
     * @return Float value from given address.
     */
    public static float getFloatLE(long addr) {
        return Float.intBitsToFloat(UNALIGNED ? Integer.reverseBytes(UNSAFE.getInt(addr)) : getIntByByte(addr, false));
    }

    /**
     * Stores given float value assuming that value should be stored in little-endian byte order
     * and native byte order is big-endian. Alignment aware.
     *
     * @param addr Address.
     * @param val Value.
     */
    public static void putFloatLE(long addr, float val) {
        int intVal = Float.floatToIntBits(val);

        if (UNALIGNED)
            UNSAFE.putInt(addr, Integer.reverseBytes(intVal));
        else
            putIntByByte(addr, intVal, false);
    }

    /**
     * Gets double value from given address assuming that value stored in little-endian byte order
     * and native byte order is big-endian. Alignment aware.
     *
     * @param addr Address.
     * @return Double value from given address.
     */
    public static double getDoubleLE(long addr) {
        return Double.longBitsToDouble(
            UNALIGNED ? Long.reverseBytes(UNSAFE.getLong(addr)) : getLongByByte(addr, false)
        );
    }

    /**
     * Stores given double value assuming that value should be stored in little-endian byte order
     * and native byte order is big-endian. Alignment aware.
     *
     * @param addr Address.
     * @param val Value.
     */
    public static void putDoubleLE(long addr, double val) {
        long longVal = Double.doubleToLongBits(val);

        if (UNALIGNED)
            UNSAFE.putLong(addr, Long.reverseBytes(longVal));
        else
            putLongByByte(addr, longVal, false);
    }

    /**
     * Returns static field offset.
     *
     * @param field Field.
     * @return Static field offset.
     */
    public static long staticFieldOffset(Field field) {
        return UNSAFE.staticFieldOffset(field);
    }

    /**
     * Returns object field offset.
     *
     * @param field Field.
     * @return Object field offset.
     */
    public static long objectFieldOffset(Field field) {
        return UNSAFE.objectFieldOffset(field);
    }

    /**
     * Returns static field base.
     *
     * @param field Field.
     * @return Static field base.
     */
    public static Object staticFieldBase(Field field) {
        return UNSAFE.staticFieldBase(field);
    }

    /**
     * Allocates memory.
     *
     * @param size Size.
     * @return address.
     */
    public static long allocateMemory(long size) {
        return UNSAFE.allocateMemory(size);
    }

    /**
     * Reallocates memory.
     *
     * @param addr Address.
     * @param len Length.
     * @return address.
     */
    public static long reallocateMemory(long addr, long len) {
        return UNSAFE.reallocateMemory(addr, len);
    }

    /**
     * Fills memory with given value.
     *
     * @param addr Address.
     * @param len Length.
     * @param val Value.
     */
    public static void setMemory(long addr, long len, byte val) {
        UNSAFE.setMemory(addr, len, val);
    }

    /**
     * Copy memory between offheap locations.
     *
     * @param srcAddr Source address.
     * @param dstAddr Destination address.
     * @param len Length.
     */
    public static void copyOffheapOffheap(long srcAddr, long dstAddr, long len) {
        if (len <= PER_BYTE_THRESHOLD) {
            for (int i = 0; i < len; i++)
                UNSAFE.putByte(dstAddr + i, UNSAFE.getByte(srcAddr + i));
        }
        else
            UNSAFE.copyMemory(srcAddr, dstAddr, len);
    }

    /**
     * Copy memory from offheap to heap.
     *
     * @param srcAddr Source address.
     * @param dstBase Destination base.
     * @param dstOff Destination offset.
     * @param len Length.
     */
    public static void copyOffheapHeap(long srcAddr, Object dstBase, long dstOff, long len) {
        if (len <= PER_BYTE_THRESHOLD) {
            for (int i = 0; i < len; i++)
                UNSAFE.putByte(dstBase, dstOff + i, UNSAFE.getByte(srcAddr + i));
        }
        else
            UNSAFE.copyMemory(null, srcAddr, dstBase, dstOff, len);
    }

    /**
     * Copy memory from heap to offheap.
     *
     * @param srcBase Source base.
     * @param srcOff Source offset.
     * @param dstAddr Destination address.
     * @param len Length.
     */
    public static void copyHeapOffheap(Object srcBase, long srcOff, long dstAddr, long len) {
        if (len <= PER_BYTE_THRESHOLD) {
            for (int i = 0; i < len; i++)
                UNSAFE.putByte(dstAddr + i, UNSAFE.getByte(srcBase, srcOff + i));
        }
        else
            UNSAFE.copyMemory(srcBase, srcOff, null, dstAddr, len);
    }

    /**
     * Copies memory.
     *
     * @param src Source.
     * @param dst Dst.
     * @param len Length.
     */
    public static void copyMemory(long src, long dst, long len) {
        UNSAFE.copyMemory(src, dst, len);
    }

    /**
     * Sets all bytes in a given block of memory to a copy of another block.
     *
     * @param srcBase Source base.
     * @param srcOff Source offset.
     * @param dstBase Dst base.
     * @param dstOff Dst offset.
     * @param len Length.
     */
    public static void copyMemory(Object srcBase, long srcOff, Object dstBase, long dstOff, long len) {
        if (len <= PER_BYTE_THRESHOLD && srcBase != null && dstBase != null) {
            for (int i = 0; i < len; i++)
                UNSAFE.putByte(dstBase, dstOff + i, UNSAFE.getByte(srcBase, srcOff + i));
        }
        else
            UNSAFE.copyMemory(srcBase, srcOff, dstBase, dstOff, len);
    }

    /**
     * Frees memory.
     *
     * @param addr Address.
     */
    public static void freeMemory(long addr) {
        UNSAFE.freeMemory(addr);
    }

    /**
     * Returns the offset of the first element in the storage allocation of a given array class.
     *
     * @param cls Class.
     * @return the offset of the first element in the storage allocation of a given array class.
     */
    public static int arrayBaseOffset(Class cls) {
        return UNSAFE.arrayBaseOffset(cls);
    }

    /**
     * Allocates instance of given class.
     *
     * @param cls Class.
     * @return Allocated instance.
     * @throws InstantiationException If failed to instantiate class.
     */
    public static Object allocateInstance(Class cls) throws InstantiationException {
        return UNSAFE.allocateInstance(cls);
    }

    /**
     * Integer CAS.
     *
     * @param obj Object.
     * @param off Offset.
     * @param exp Expected.
     * @param upd Upd.
     * @return {@code True} if operation completed successfully, {@code false} - otherwise.
     */
    public static boolean compareAndSwapInt(Object obj, long off, int exp, int upd) {
        return UNSAFE.compareAndSwapInt(obj, off, exp, upd);
    }

    /**
     * Long CAS.
     *
     * @param obj Object.
     * @param off Offset.
     * @param exp Expected.
     * @param upd Upd.
     * @return {@code True} if operation completed successfully, {@code false} - otherwise.
     */
    public static boolean compareAndSwapLong(Object obj, long off, long exp, long upd) {
        return UNSAFE.compareAndSwapLong(obj, off, exp, upd);
    }

    /**
     * Atomically increments value stored in an integer pointed by {@code ptr}.
     *
     * @param ptr Pointer to an integer.
     * @return Updated value.
     */
    public static int incrementAndGetInt(long ptr) {
        return UNSAFE.getAndAddInt(null, ptr, 1) + 1;
    }

    /**
     * Atomically increments value stored in an integer pointed by {@code ptr}.
     *
     * @param ptr Pointer to an integer.
     * @return Updated value.
     */
    public static int decrementAndGetInt(long ptr) {
        return UNSAFE.getAndAddInt(null, ptr, -1) - 1;
    }

    /**
     * Gets byte value with volatile semantic.
     *
     * @param obj Object.
     * @param off Offset.
     * @return Byte value.
     */
    public static byte getByteVolatile(Object obj, long off) {
        return UNSAFE.getByteVolatile(obj, off);
    }

    /**
     * Stores byte value with volatile semantic.
     *
     * @param obj Object.
     * @param off Offset.
     * @param val Value.
     */
    public static void putByteVolatile(Object obj, long off, byte val) {
        UNSAFE.putByteVolatile(obj, off, val);
    }

    /**
     * Gets integer value with volatile semantic.
     *
     * @param obj Object.
     * @param off Offset.
     * @return Integer value.
     */
    public static int getIntVolatile(Object obj, long off) {
        return UNSAFE.getIntVolatile(obj, off);
    }

    /**
     * Stores integer value with volatile semantic.
     *
     * @param obj Object.
     * @param off Offset.
     * @param val Value.
     */
    public static void putIntVolatile(Object obj, long off, int val) {
        UNSAFE.putIntVolatile(obj, off, val);
    }

    /**
     * Gets long value with volatile semantic.
     *
     * @param obj Object.
     * @param off Offset.
     * @return Long value.
     */
    public static long getLongVolatile(Object obj, long off) {
        return UNSAFE.getLongVolatile(obj, off);
    }

    /**
     * Stores long value with volatile semantic.
     *
     * @param obj Object.
     * @param off Offset.
     * @param val Value.
     */
    public static void putLongVolatile(Object obj, long off, long val) {
        UNSAFE.putLongVolatile(obj, off, val);
    }

    /**
     * Stores reference value with volatile semantic.
     *
     * @param obj Object.
     * @param off Offset.
     * @param val Value.
     */
    public static void putObjectVolatile(Object obj, long off, Object val) {
        UNSAFE.putObjectVolatile(obj, off, val);
    }

    /**
     * Returns page size.
     *
     * @return Page size.
     */
    public static int pageSize() {
        return UNSAFE.pageSize();
    }

    /**
     * Returns address of {@link Buffer} instance.
     *
     * @param buf Buffer.
     * @return Buffer memory address.
     */
    public static long bufferAddress(ByteBuffer buf) {
        assert buf.isDirect();
        return UNSAFE.getLong(buf, DIRECT_BUF_ADDR_OFF);
    }

    /**
     * Invokes some method on {@code sun.misc.Unsafe} instance.
     *
     * @param mtd Method.
     * @param args Arguments.
     * @return Method invocation result.
     */
    public static Object invoke(Method mtd, Object... args) {
        try {
            return mtd.invoke(UNSAFE, args);
        }
        catch (IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeException("Unsafe invocation failed [cls=" + UNSAFE.getClass() + ", mtd=" + mtd + ']', e);
        }
    }

    /**
     * Cleans direct {@code java.nio.ByteBuffer}
     *
     * @param buf Direct buffer.
     */
    public static void cleanDirectBuffer(ByteBuffer buf) {
        assert buf.isDirect();

        UNSAFE.invokeCleaner(buf);
    }

    /**
     * Returns unaligned flag.
     */
    private static boolean unaligned() {
        String arch = System.getProperty("os.arch");

        return "i386".equals(arch) || "x86".equals(arch) || "amd64".equals(arch) || "x86_64".equals(arch);
    }

    /**
     * @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());
            }
        }
    }

    /** */
    private static long bufferAddressOffset() {
        final ByteBuffer maybeDirectBuf = ByteBuffer.allocateDirect(1);

        Field addrField = AccessController.doPrivileged(new PrivilegedAction<Field>() {
            @Override public Field run() {
                try {
                    Field addrFld = Buffer.class.getDeclaredField("address");

                    addrFld.setAccessible(true);

                    if (addrFld.getLong(maybeDirectBuf) == 0)
                        throw new RuntimeException("java.nio.DirectByteBuffer.address field is unavailable.");

                    return addrFld;
                }
                catch (Exception e) {
                    throw new RuntimeException("java.nio.DirectByteBuffer.address field is unavailable.", e);
                }
            }
        });

        return UNSAFE.objectFieldOffset(addrField);
    }

    /**
     * Returns {@code JavaNioAccess} instance from private API for corresponding Java version.
     *
     * @return {@code JavaNioAccess} instance for corresponding Java version.
     * @throws RuntimeException If getting access to the private API is failed.
     */
    private static Object javaNioAccessObject() {
        String pkgName = miscPackage();

        try {
            Class<?> cls = Class.forName(pkgName + ".misc.SharedSecrets");

            Method mth = cls.getMethod("getJavaNioAccess");

            return mth.invoke(null);
        }
        catch (ReflectiveOperationException e) {
            throw new RuntimeException(pkgName + ".misc.JavaNioAccess class is unavailable."
                + FeatureChecker.JAVA_VER_SPECIFIC_WARN, e);
        }
    }

    /**
     * Returns reference to {@code JavaNioAccess.newDirectByteBuffer} method
     * from private API for corresponding Java version.
     *
     * @param nioAccessObj Java NIO access object.
     * @return Reference to {@code JavaNioAccess.newDirectByteBuffer} method
     * @throws RuntimeException If getting access to the private API is failed.
     */
    private static Method newDirectBufferMethod(Object nioAccessObj) {
        try {
            Class<?> cls = nioAccessObj.getClass();

            Method mtd = cls.getMethod("newDirectByteBuffer", long.class, int.class, Object.class);

            mtd.setAccessible(true);

            return mtd;
        }
        catch (ReflectiveOperationException e) {
            throw new RuntimeException(miscPackage() + ".JavaNioAccess#newDirectByteBuffer() method is unavailable."
                + FeatureChecker.JAVA_VER_SPECIFIC_WARN, e);
        }
    }

    /** */
    @NotNull private static String miscPackage() {
        int javaVer = majorJavaVersion(jdkVersion());

        return javaVer < 9 ? "sun" : "jdk.internal";
    }


    /**
     * Creates and tests contructor for Direct ByteBuffer. Test is wrapping one-byte unsafe memory into a buffer.
     *
     * @return constructor for creating direct ByteBuffers.
     */
    @NotNull
    private static Constructor<?> createAndTestNewDirectBufferCtor() {
        Constructor<?> ctorCandidate = createNewDirectBufferCtor();

        int l = 1;
        long ptr = UNSAFE.allocateMemory(l);

        try {
            ByteBuffer buf = wrapPointerDirectBufferConstructor(ptr, l, ctorCandidate);

            if (!buf.isDirect())
                throw new IllegalArgumentException("Buffer expected to be direct, internal error during #wrapPointerDirectBufCtor()");
        }
        finally {
            UNSAFE.freeMemory(ptr);
        }

        return ctorCandidate;
    }


    /**
     * Simply create some instance of direct Byte Buffer and try to get it's class declared constructor.
     *
     * @return constructor for creating direct ByteBuffers.
     */
    @NotNull
    private static Constructor<?> createNewDirectBufferCtor() {
        try {
            ByteBuffer buf = ByteBuffer.allocateDirect(1).order(NATIVE_BYTE_ORDER);

            Constructor<?> ctor = buf.getClass().getDeclaredConstructor(long.class, int.class);

            ctor.setAccessible(true);

            return ctor;
        }
        catch (NoSuchMethodException | SecurityException e) {
            throw new RuntimeException("Unable to set up byte buffer creation using reflections :" + e.getMessage(), e);
        }
    }

    /**
     * @param obj Object.
     * @param off Offset.
     * @param bigEndian Order of value bytes in memory. If {@code true} - big-endian, otherwise little-endian.
     */
    private static short getShortByByte(Object obj, long off, boolean bigEndian) {
        if (bigEndian)
            return (short)(UNSAFE.getByte(obj, off) << 8 | (UNSAFE.getByte(obj, off + 1) & 0xff));
        else
            return (short)(UNSAFE.getByte(obj, off + 1) << 8 | (UNSAFE.getByte(obj, off) & 0xff));
    }

    /**
     * @param obj Object.
     * @param off Offset.
     * @param val Value.
     * @param bigEndian Order of value bytes in memory. If {@code true} - big-endian, otherwise little-endian.
     */
    private static void putShortByByte(Object obj, long off, short val, boolean bigEndian) {
        if (bigEndian) {
            UNSAFE.putByte(obj, off, (byte)(val >> 8));
            UNSAFE.putByte(obj, off + 1, (byte)val);
        }
        else {
            UNSAFE.putByte(obj, off + 1, (byte)(val >> 8));
            UNSAFE.putByte(obj, off, (byte)val);
        }
    }

    /**
     * @param obj Object.
     * @param off Offset.
     * @param bigEndian Order of value bytes in memory. If {@code true} - big-endian, otherwise little-endian.
     */
    private static char getCharByByte(Object obj, long off, boolean bigEndian) {
        if (bigEndian)
            return (char)(UNSAFE.getByte(obj, off) << 8 | (UNSAFE.getByte(obj, off + 1) & 0xff));
        else
            return (char)(UNSAFE.getByte(obj, off + 1) << 8 | (UNSAFE.getByte(obj, off) & 0xff));
    }

    /**
     * @param obj Object.
     * @param addr Address.
     * @param val Value.
     * @param bigEndian Order of value bytes in memory. If {@code true} - big-endian, otherwise little-endian.
     */
    private static void putCharByByte(Object obj, long addr, char val, boolean bigEndian) {
        if (bigEndian) {
            UNSAFE.putByte(obj, addr, (byte)(val >> 8));
            UNSAFE.putByte(obj, addr + 1, (byte)val);
        }
        else {
            UNSAFE.putByte(obj, addr + 1, (byte)(val >> 8));
            UNSAFE.putByte(obj, addr, (byte)val);
        }
    }

    /**
     * @param obj Object.
     * @param addr Address.
     * @param bigEndian Order of value bytes in memory. If {@code true} - big-endian, otherwise little-endian.
     */
    private static int getIntByByte(Object obj, long addr, boolean bigEndian) {
        if (bigEndian) {
            return (((int)UNSAFE.getByte(obj, addr)) << 24) |
                (((int)UNSAFE.getByte(obj, addr + 1) & 0xff) << 16) |
                (((int)UNSAFE.getByte(obj, addr + 2) & 0xff) << 8) |
                (((int)UNSAFE.getByte(obj, addr + 3) & 0xff));
        }
        else {
            return (((int)UNSAFE.getByte(obj, addr + 3)) << 24) |
                (((int)UNSAFE.getByte(obj, addr + 2) & 0xff) << 16) |
                (((int)UNSAFE.getByte(obj, addr + 1) & 0xff) << 8) |
                (((int)UNSAFE.getByte(obj, addr) & 0xff));
        }
    }

    /**
     * @param obj Object.
     * @param addr Address.
     * @param val Value.
     * @param bigEndian Order of value bytes in memory. If {@code true} - big-endian, otherwise little-endian.
     */
    private static void putIntByByte(Object obj, long addr, int val, boolean bigEndian) {
        if (bigEndian) {
            UNSAFE.putByte(obj, addr, (byte)(val >> 24));
            UNSAFE.putByte(obj, addr + 1, (byte)(val >> 16));
            UNSAFE.putByte(obj, addr + 2, (byte)(val >> 8));
            UNSAFE.putByte(obj, addr + 3, (byte)(val));
        }
        else {
            UNSAFE.putByte(obj, addr + 3, (byte)(val >> 24));
            UNSAFE.putByte(obj, addr + 2, (byte)(val >> 16));
            UNSAFE.putByte(obj, addr + 1, (byte)(val >> 8));
            UNSAFE.putByte(obj, addr, (byte)(val));
        }
    }

    /**
     * @param obj Object.
     * @param addr Address.
     * @param bigEndian Order of value bytes in memory. If {@code true} - big-endian, otherwise little-endian.
     */
    private static long getLongByByte(Object obj, long addr, boolean bigEndian) {
        if (bigEndian) {
            return (((long)UNSAFE.getByte(obj, addr)) << 56) |
                (((long)UNSAFE.getByte(obj, addr + 1) & 0xff) << 48) |
                (((long)UNSAFE.getByte(obj, addr + 2) & 0xff) << 40) |
                (((long)UNSAFE.getByte(obj, addr + 3) & 0xff) << 32) |
                (((long)UNSAFE.getByte(obj, addr + 4) & 0xff) << 24) |
                (((long)UNSAFE.getByte(obj, addr + 5) & 0xff) << 16) |
                (((long)UNSAFE.getByte(obj, addr + 6) & 0xff) << 8) |
                (((long)UNSAFE.getByte(obj, addr + 7) & 0xff));
        }
        else {
            return (((long)UNSAFE.getByte(obj, addr + 7)) << 56) |
                (((long)UNSAFE.getByte(obj, addr + 6) & 0xff) << 48) |
                (((long)UNSAFE.getByte(obj, addr + 5) & 0xff) << 40) |
                (((long)UNSAFE.getByte(obj, addr + 4) & 0xff) << 32) |
                (((long)UNSAFE.getByte(obj, addr + 3) & 0xff) << 24) |
                (((long)UNSAFE.getByte(obj, addr + 2) & 0xff) << 16) |
                (((long)UNSAFE.getByte(obj, addr + 1) & 0xff) << 8) |
                (((long)UNSAFE.getByte(obj, addr) & 0xff));
        }
    }

    /**
     * @param obj Object.
     * @param addr Address.
     * @param val Value.
     * @param bigEndian Order of value bytes in memory. If {@code true} - big-endian, otherwise little-endian.
     */
    private static void putLongByByte(Object obj, long addr, long val, boolean bigEndian) {
        if (bigEndian) {
            UNSAFE.putByte(obj, addr, (byte)(val >> 56));
            UNSAFE.putByte(obj, addr + 1, (byte)(val >> 48));
            UNSAFE.putByte(obj, addr + 2, (byte)(val >> 40));
            UNSAFE.putByte(obj, addr + 3, (byte)(val >> 32));
            UNSAFE.putByte(obj, addr + 4, (byte)(val >> 24));
            UNSAFE.putByte(obj, addr + 5, (byte)(val >> 16));
            UNSAFE.putByte(obj, addr + 6, (byte)(val >> 8));
            UNSAFE.putByte(obj, addr + 7, (byte)(val));
        }
        else {
            UNSAFE.putByte(obj, addr + 7, (byte)(val >> 56));
            UNSAFE.putByte(obj, addr + 6, (byte)(val >> 48));
            UNSAFE.putByte(obj, addr + 5, (byte)(val >> 40));
            UNSAFE.putByte(obj, addr + 4, (byte)(val >> 32));
            UNSAFE.putByte(obj, addr + 3, (byte)(val >> 24));
            UNSAFE.putByte(obj, addr + 2, (byte)(val >> 16));
            UNSAFE.putByte(obj, addr + 1, (byte)(val >> 8));
            UNSAFE.putByte(obj, addr, (byte)(val));
        }
    }

    /**
     * @param addr Address.
     * @param bigEndian Order of value bytes in memory. If {@code true} - big-endian, otherwise little-endian.
     */
    private static short getShortByByte(long addr, boolean bigEndian) {
        if (bigEndian)
            return (short)(UNSAFE.getByte(addr) << 8 | (UNSAFE.getByte(addr + 1) & 0xff));
        else
            return (short)(UNSAFE.getByte(addr + 1) << 8 | (UNSAFE.getByte(addr) & 0xff));
    }

    /**
     * @param addr Address.
     * @param val Value.
     * @param bigEndian Order of value bytes in memory. If {@code true} - big-endian, otherwise little-endian.
     */
    private static void putShortByByte(long addr, short val, boolean bigEndian) {
        if (bigEndian) {
            UNSAFE.putByte(addr, (byte)(val >> 8));
            UNSAFE.putByte(addr + 1, (byte)val);
        }
        else {
            UNSAFE.putByte(addr + 1, (byte)(val >> 8));
            UNSAFE.putByte(addr, (byte)val);
        }
    }

    /**
     * @param addr Address.
     * @param bigEndian Order of value bytes in memory. If {@code true} - big-endian, otherwise little-endian.
     */
    private static char getCharByByte(long addr, boolean bigEndian) {
        if (bigEndian)
            return (char)(UNSAFE.getByte(addr) << 8 | (UNSAFE.getByte(addr + 1) & 0xff));
        else
            return (char)(UNSAFE.getByte(addr + 1) << 8 | (UNSAFE.getByte(addr) & 0xff));
    }

    /**
     * @param addr Address.
     * @param val Value.
     * @param bigEndian Order of value bytes in memory. If {@code true} - big-endian, otherwise little-endian.
     */
    private static void putCharByByte(long addr, char val, boolean bigEndian) {
        if (bigEndian) {
            UNSAFE.putByte(addr, (byte)(val >> 8));
            UNSAFE.putByte(addr + 1, (byte)val);
        }
        else {
            UNSAFE.putByte(addr + 1, (byte)(val >> 8));
            UNSAFE.putByte(addr, (byte)val);
        }
    }

    /**
     * @param addr Address.
     * @param bigEndian Order of value bytes in memory. If {@code true} - big-endian, otherwise little-endian.
     */
    private static int getIntByByte(long addr, boolean bigEndian) {
        if (bigEndian) {
            return (((int)UNSAFE.getByte(addr)) << 24) |
                (((int)UNSAFE.getByte(addr + 1) & 0xff) << 16) |
                (((int)UNSAFE.getByte(addr + 2) & 0xff) << 8) |
                (((int)UNSAFE.getByte(addr + 3) & 0xff));
        }
        else {
            return (((int)UNSAFE.getByte(addr + 3)) << 24) |
                (((int)UNSAFE.getByte(addr + 2) & 0xff) << 16) |
                (((int)UNSAFE.getByte(addr + 1) & 0xff) << 8) |
                (((int)UNSAFE.getByte(addr) & 0xff));
        }
    }

    /**
     * @param addr Address.
     * @param val Value.
     * @param bigEndian Order of value bytes in memory. If {@code true} - big-endian, otherwise little-endian.
     */
    private static void putIntByByte(long addr, int val, boolean bigEndian) {
        if (bigEndian) {
            UNSAFE.putByte(addr, (byte)(val >> 24));
            UNSAFE.putByte(addr + 1, (byte)(val >> 16));
            UNSAFE.putByte(addr + 2, (byte)(val >> 8));
            UNSAFE.putByte(addr + 3, (byte)(val));
        }
        else {
            UNSAFE.putByte(addr + 3, (byte)(val >> 24));
            UNSAFE.putByte(addr + 2, (byte)(val >> 16));
            UNSAFE.putByte(addr + 1, (byte)(val >> 8));
            UNSAFE.putByte(addr, (byte)(val));
        }
    }

    /**
     * @param addr Address.
     * @param bigEndian Order of value bytes in memory. If {@code true} - big-endian, otherwise little-endian.
     */
    private static long getLongByByte(long addr, boolean bigEndian) {
        if (bigEndian) {
            return (((long)UNSAFE.getByte(addr)) << 56) |
                (((long)UNSAFE.getByte(addr + 1) & 0xff) << 48) |
                (((long)UNSAFE.getByte(addr + 2) & 0xff) << 40) |
                (((long)UNSAFE.getByte(addr + 3) & 0xff) << 32) |
                (((long)UNSAFE.getByte(addr + 4) & 0xff) << 24) |
                (((long)UNSAFE.getByte(addr + 5) & 0xff) << 16) |
                (((long)UNSAFE.getByte(addr + 6) & 0xff) << 8) |
                (((long)UNSAFE.getByte(addr + 7) & 0xff));
        }
        else {
            return (((long)UNSAFE.getByte(addr + 7)) << 56) |
                (((long)UNSAFE.getByte(addr + 6) & 0xff) << 48) |
                (((long)UNSAFE.getByte(addr + 5) & 0xff) << 40) |
                (((long)UNSAFE.getByte(addr + 4) & 0xff) << 32) |
                (((long)UNSAFE.getByte(addr + 3) & 0xff) << 24) |
                (((long)UNSAFE.getByte(addr + 2) & 0xff) << 16) |
                (((long)UNSAFE.getByte(addr + 1) & 0xff) << 8) |
                (((long)UNSAFE.getByte(addr) & 0xff));
        }
    }

    /**
     * @param addr Address.
     * @param val Value.
     * @param bigEndian Order of value bytes in memory. If {@code true} - big-endian, otherwise little-endian.
     */
    private static void putLongByByte(long addr, long val, boolean bigEndian) {
        if (bigEndian) {
            UNSAFE.putByte(addr, (byte)(val >> 56));
            UNSAFE.putByte(addr + 1, (byte)(val >> 48));
            UNSAFE.putByte(addr + 2, (byte)(val >> 40));
            UNSAFE.putByte(addr + 3, (byte)(val >> 32));
            UNSAFE.putByte(addr + 4, (byte)(val >> 24));
            UNSAFE.putByte(addr + 5, (byte)(val >> 16));
            UNSAFE.putByte(addr + 6, (byte)(val >> 8));
            UNSAFE.putByte(addr + 7, (byte)(val));
        }
        else {
            UNSAFE.putByte(addr + 7, (byte)(val >> 56));
            UNSAFE.putByte(addr + 6, (byte)(val >> 48));
            UNSAFE.putByte(addr + 5, (byte)(val >> 40));
            UNSAFE.putByte(addr + 4, (byte)(val >> 32));
            UNSAFE.putByte(addr + 3, (byte)(val >> 24));
            UNSAFE.putByte(addr + 2, (byte)(val >> 16));
            UNSAFE.putByte(addr + 1, (byte)(val >> 8));
            UNSAFE.putByte(addr, (byte)(val));
        }
    }

    /**
     * @param ptr1 First pointer.
     * @param ptr2 Second pointer.
     * @param size Memory size.
     * @return {@code True} if equals.
     */
    public static boolean compare(long ptr1, long ptr2, int size) {
        assert ptr1 > 0 : ptr1;
        assert ptr2 > 0 : ptr2;
        assert size > 0 : size;

        if (ptr1 == ptr2)
            return true;

        int words = size / 8;

        for (int i = 0; i < words; i++) {
            long w1 = getLong(ptr1);
            long w2 = getLong(ptr2);

            if (w1 != w2)
                return false;

            ptr1 += 8;
            ptr2 += 8;
        }

        int left = size % 8;

        for (int i = 0; i < left; i++) {
            byte b1 = getByte(ptr1);
            byte b2 = getByte(ptr2);

            if (b1 != b2)
                return false;

            ptr1++;
            ptr2++;
        }

        return true;
    }
}
