/*
* 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.offheap.unsafe;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.ignite.IgniteSystemProperties;
import org.apache.ignite.internal.util.GridUnsafe;
import org.apache.ignite.internal.util.offheap.GridOffHeapEventListener;
import org.apache.ignite.internal.util.offheap.GridOffHeapOutOfMemoryException;
import org.apache.ignite.internal.util.tostring.GridToStringInclude;
import org.apache.ignite.internal.util.typedef.internal.S;
import org.apache.ignite.lang.IgniteBiTuple;
import static org.apache.ignite.IgniteSystemProperties.IGNITE_OFFHEAP_SAFE_RELEASE;
import static org.apache.ignite.internal.util.offheap.GridOffHeapEvent.ALLOCATE;
import static org.apache.ignite.internal.util.offheap.GridOffHeapEvent.RELEASE;
/**
* Unsafe memory.
*/
public class GridUnsafeMemory {
/** Free byte. */
private static final byte FREE = (byte)0;
/** Safe offheap release flag. */
private static final boolean SAFE_RELEASE = IgniteSystemProperties.getBoolean(IGNITE_OFFHEAP_SAFE_RELEASE);
/** Total size. */
@GridToStringInclude
private final long total;
/** Occupied size. */
@GridToStringInclude
private final AtomicLong allocated;
/** Total amount of memory allocated for system structures. */
@GridToStringInclude
private final AtomicLong sysAllocated;
/** Event listener. */
private GridOffHeapEventListener lsnr;
/**
* @param total Total size, {@code 0} for unlimited.
*/
public GridUnsafeMemory(long total) {
assert total >= 0;
this.total = total;
allocated = new AtomicLong();
sysAllocated = new AtomicLong();
}
/**
* Sets event listener.
*
* @param lsnr Event listener.
*/
public void listen(GridOffHeapEventListener lsnr) {
this.lsnr = lsnr;
}
/**
* Reserves memory.
*
* @param size Size to reserve.
* @return {@code True} if memory is under allowed size, {@code false} otherwise.
*/
public boolean reserve(long size) {
if (total == 0) {
allocated.addAndGet(size);
return true;
}
long mem = allocated.addAndGet(size);
long max = total;
return mem <= max;
}
/**
* Allocates memory of given size in bytes.
*
* @param size Size of allocated block.
* @return Allocated block address.
* @throws GridOffHeapOutOfMemoryException If Memory could not be allocated.
*/
public long allocate(long size) throws GridOffHeapOutOfMemoryException {
return allocate(size, false, false);
}
/**
* Allocates memory of given size in bytes.
*
* @param size Size of allocated block.
* @param init Flag to zero-out the initialized memory or not.
* @return Allocated block address.
* @throws GridOffHeapOutOfMemoryException If Memory could not be allocated.
*/
public long allocate(long size, boolean init) throws GridOffHeapOutOfMemoryException {
return allocate(size, init, false);
}
/**
* Allocates memory of given size in bytes.
*
* @param size Size of allocated block.
* @param init Flag to zero-out the initialized memory or not.
* @param reserved Flag indicating that memory being allocated was reserved before.
* @return Allocated block address.
* @throws GridOffHeapOutOfMemoryException If memory could not be allocated.
*/
public long allocate(long size, boolean init, boolean reserved) throws GridOffHeapOutOfMemoryException {
return allocate0(size, init, reserved, allocated);
}
/**
* Allocates memory of given size in bytes, adds to system memory counter.
*
* @param size Size of allocated block.
* @param init Whether or not allocated block is zeroed upon return.
* @return Allocated block address.
* @throws GridOffHeapOutOfMemoryException If memory could not be allocated.
*/
public long allocateSystem(long size, boolean init) throws GridOffHeapOutOfMemoryException {
return allocate0(size, init, false, sysAllocated);
}
/**
* Performs actual memory allocation.
*
* @param size Memory size to allocate.
* @param init Flag indicating whether requested memory should be zeroed.
* @param reserved If {@code false}, means that memory counter was reserved and size will not
* be added to counter.
* @param cnt Counter to account allocated memory.
* @throws GridOffHeapOutOfMemoryException In case of out of the off-heap memory.
* @return Pointer to the allocated memory.
*/
@SuppressWarnings("ErrorNotRethrown")
private long allocate0(long size, boolean init, boolean reserved,
AtomicLong cnt) throws GridOffHeapOutOfMemoryException {
assert size > 0;
if (!reserved)
cnt.addAndGet(size);
try {
long ptr = GridUnsafe.allocateMemory(size);
if (init)
fill(ptr, size, FREE);
if (lsnr != null)
lsnr.onEvent(ALLOCATE);
return ptr;
}
catch (OutOfMemoryError ignore) {
if (!reserved)
cnt.addAndGet(-size);
throw new GridOffHeapOutOfMemoryException(totalSize(), size);
}
}
/**
* @param ptr Pointer.
* @param size Count of long values to fill.
* @param b Value.
*/
public void fill(long ptr, long size, byte b) {
GridUnsafe.setMemory(ptr, size, b);
}
/**
* Releases memory at the given address.
*
* @param ptr Pointer to memory.
* @param size Memory region size.
*/
public void release(long ptr, long size) {
release0(ptr, size, allocated);
}
/**
* Releases memory allocated by {@link #allocateSystem(long, boolean)}.
*
* @param ptr Address of memory block to deallocate.
* @param size Size of allocated block.
*/
public void releaseSystem(long ptr, long size) {
release0(ptr, size, sysAllocated);
}
/**
* Internal release procedure. Decreases size of corresponding counter.
*
* @param ptr Address of memory block to deallocate.
* @param size Size of allocated block.
* @param cnt Counter to update.
*/
private void release0(long ptr, long size, AtomicLong cnt) {
if (ptr != 0) {
if (SAFE_RELEASE)
fill(ptr, size, (byte)0xAB);
GridUnsafe.freeMemory(ptr);
cnt.addAndGet(-size);
if (lsnr != null)
lsnr.onEvent(RELEASE);
}
}
/**
* @param ptr Pointer.
* @return Long value.
*/
public long readLong(long ptr) {
return GridUnsafe.getLong(ptr);
}
/**
* @param ptr Pointer.
* @param v Long value.
*/
public void writeLong(long ptr, long v) {
GridUnsafe.putLong(ptr, v);
}
/**
* @param ptr Pointer.
* @return Long value.
*/
public long readLongVolatile(long ptr) {
return GridUnsafe.getLongVolatile(null, ptr);
}
/**
* @param ptr Pointer.
* @param v Long value.
*/
public void writeLongVolatile(long ptr, long v) {
GridUnsafe.putLongVolatile(null, ptr, v);
}
/**
* @param ptr Pointer.
* @param exp Expected.
* @param v New value.
* @return {@code true} If operation succeeded.
*/
public boolean casLong(long ptr, long exp, long v) {
return GridUnsafe.compareAndSwapLong(null, ptr, exp, v);
}
/**
* @param ptr Pointer.
* @return Integer value.
*/
public int readInt(long ptr) {
return GridUnsafe.getInt(ptr);
}
/**
* @param ptr Pointer.
* @param v Integer value.
*/
public void writeInt(long ptr, int v) {
GridUnsafe.putInt(ptr, v);
}
/**
* @param ptr Pointer.
* @return Integer value.
*/
public int readIntVolatile(long ptr) {
return GridUnsafe.getIntVolatile(null, ptr);
}
/**
* @param ptr Pointer.
* @param v Integer value.
*/
public void writeIntVolatile(long ptr, int v) {
GridUnsafe.putIntVolatile(null, ptr, v);
}
/**
* @param ptr Pointer.
* @param exp Expected.
* @param v New value.
* @return {@code true} If operation succeeded.
*/
public boolean casInt(long ptr, int exp, int v) {
return GridUnsafe.compareAndSwapInt(null, ptr, exp, v);
}
/**
* @param ptr Pointer.
* @return Float value.
*/
public float readFloat(long ptr) {
return GridUnsafe.getFloat(ptr);
}
/**
* @param ptr Pointer.
* @param v Value.
*/
public void writeFloat(long ptr, float v) {
GridUnsafe.putFloat(ptr, v);
}
/**
* @param ptr Pointer.
* @return Double value.
*/
public double readDouble(long ptr) {
return GridUnsafe.getDouble(ptr);
}
/**
* @param ptr Pointer.
* @param v Value.
*/
public void writeDouble(long ptr, double v) {
GridUnsafe.putDouble(ptr, v);
}
/**
* @param ptr Pointer.
* @return Short value.
*/
public short readShort(long ptr) {
return GridUnsafe.getShort(ptr);
}
/**
* @param ptr Pointer.
* @param v Short value.
*/
public void writeShort(long ptr, short v) {
GridUnsafe.putShort(ptr, v);
}
/**
* @param ptr Pointer.
* @return Integer value.
*/
public byte readByte(long ptr) {
return GridUnsafe.getByte(ptr);
}
/**
* @param ptr Pointer.
* @return Integer value.
*/
public byte readByteVolatile(long ptr) {
return GridUnsafe.getByteVolatile(null, ptr);
}
/**
* @param ptr Pointer.
* @param v Integer value.
*/
public void writeByte(long ptr, byte v) {
GridUnsafe.putByte(ptr, v);
}
/**
* @param ptr Pointer.
* @param v Integer value.
*/
public void writeByteVolatile(long ptr, byte v) {
GridUnsafe.putByteVolatile(null, ptr, v);
}
/**
* Stores value to the specified memory location. If specified pointer is {@code 0}, then will
* allocate required space. If size of allocated space is not enough to hold given values, will
* reallocate memory.
*
* @param ptr Optional pointer to allocated memory. First 4 bytes in allocated region must contain
* size of allocated chunk.
* @param val Value to store.
* @param type Value type.
* @return Pointer.
*/
public long putOffHeap(long ptr, byte[] val, byte type) {
int size = val.length;
assert size != 0;
int allocated = ptr == 0 ? 0 : readInt(ptr);
if (allocated != size) {
if (ptr != 0)
release(ptr, allocated + 5);
ptr = allocate(size + 5);
writeInt(ptr, size);
}
writeByte(ptr + 4, type);
writeBytes(ptr + 5, val);
return ptr;
}
/**
* Releases off-heap memory allocated by {@link #putOffHeap} method.
*
* @param ptr Optional pointer returned by {@link #putOffHeap}.
*/
public void removeOffHeap(long ptr) {
if (ptr != 0)
release(ptr, readInt(ptr) + 5);
}
/**
* Get value stored in offheap along with a value type.
*
* @param ptr Pointer to read.
* @return Stored byte array and "raw bytes" flag.
*/
public IgniteBiTuple<byte[], Byte> get(long ptr) {
assert ptr != 0;
int size = readInt(ptr);
byte type = readByte(ptr + 4);
byte[] bytes = readBytes(ptr + 5, size);
return new IgniteBiTuple<>(bytes, type);
}
/**
* @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 = GridUnsafe.getLong(ptr1);
long w2 = GridUnsafe.getLong(ptr2);
if (w1 != w2)
return false;
ptr1 += 8;
ptr2 += 8;
}
int left = size % 8;
for (int i = 0; i < left; i++) {
byte b1 = GridUnsafe.getByte(ptr1);
byte b2 = GridUnsafe.getByte(ptr2);
if (b1 != b2)
return false;
ptr1++;
ptr2++;
}
return true;
}
/**
* Compares memory.
*
* @param ptr Pointer.
* @param bytes Bytes to compare.
* @return {@code True} if equals.
*/
public static boolean compare(long ptr, byte[] bytes) {
return compare(ptr, bytes, 0, bytes.length);
}
/**
* Compares memory.
*
* @param ptr Pointer.
* @param bytes Bytes to compare.
* @param bytesOff Offset in the bytes array.
* @param len Count of compared bytes.
* @return {@code True} if equals.
*/
public static boolean compare(long ptr, byte[] bytes, int bytesOff, int len) {
assert bytesOff + len <= bytes.length : "Check compare bounds: [offset=" + bytesOff + ", len=" + len +
", bytes.length=" + bytes.length + ']';
final int addrSize = GridUnsafe.ADDR_SIZE;
// Align reads to address size.
int off = (int)(ptr % addrSize);
int align = addrSize - off;
if (align != addrSize) {
for (int i = 0, tmpOff = bytesOff; i < align && i < len; i++, tmpOff++, ptr++) {
if (GridUnsafe.getByte(ptr) != bytes[tmpOff])
return false;
}
}
else
align = 0;
if (len <= align)
return true;
assert ptr % addrSize == 0 : "Invalid alignment [ptr=" + ptr + ", addrSize=" + addrSize + ", mod=" +
(ptr % addrSize) + ']';
int words = (len - align) / addrSize;
int left = (len - align) % addrSize;
switch (addrSize) {
case 4:
for (int i = 0; i < words; i++) {
int step = i * addrSize + align;
int word = GridUnsafe.getInt(ptr);
int comp = GridUnsafe.getInt(bytes, GridUnsafe.BYTE_ARR_OFF + step + bytesOff);
if (word != comp)
return false;
ptr += GridUnsafe.ADDR_SIZE;
}
break;
default:
for (int i = 0; i < words; i++) {
int step = i * addrSize + align;
long word = GridUnsafe.getLong(ptr);
long comp = GridUnsafe.getLong(bytes, GridUnsafe.BYTE_ARR_OFF + step + bytesOff);
if (word != comp)
return false;
ptr += GridUnsafe.ADDR_SIZE;
}
break;
}
if (left != 0) {
// Compare left overs byte by byte.
for (int i = 0; i < left; i++)
if (GridUnsafe.getByte(ptr + i) != bytes[bytesOff + i + align + words * GridUnsafe.ADDR_SIZE])
return false;
}
return true;
}
/**
* @param ptr Pointer.
* @param cnt Count.
* @return Byte array.
*/
public byte[] readBytes(long ptr, int cnt) {
return readBytes(ptr, new byte[cnt]);
}
/**
* @param ptr Pointer.
* @param arr Array.
* @return The same array as passed in one.
*/
public byte[] readBytes(long ptr, byte[] arr) {
GridUnsafe.copyOffheapHeap(ptr, arr, GridUnsafe.BYTE_ARR_OFF, arr.length);
return arr;
}
/**
* @param ptr Pointer.
* @param arr Array.
* @param off Offset.
* @param len Length.
* @return The same array as passed in one.
*/
public byte[] readBytes(long ptr, byte[] arr, int off, int len) {
GridUnsafe.copyOffheapHeap(ptr, arr, GridUnsafe.BYTE_ARR_OFF + off, len);
return arr;
}
/**
* Writes byte array into memory location.
*
* @param ptr Pointer.
* @param arr Array.
*/
public void writeBytes(long ptr, byte[] arr) {
GridUnsafe.copyHeapOffheap(arr, GridUnsafe.BYTE_ARR_OFF, ptr, arr.length);
}
/**
* Writes part of byte array into memory location.
*
* @param ptr Pointer.
* @param arr Array.
* @param off Offset.
* @param len Length.
*/
public void writeBytes(long ptr, byte[] arr, int off, int len) {
GridUnsafe.copyHeapOffheap(arr, GridUnsafe.BYTE_ARR_OFF + off, ptr, len);
}
/**
* Copy memory.
*
* @param srcPtr Source pointer.
* @param destPtr Destination pointer.
* @param len Length in bytes.
*/
public void copyMemory(long srcPtr, long destPtr, long len) {
GridUnsafe.copyOffheapOffheap(srcPtr, destPtr, len);
}
/**
* Checks if direct memory allocation is limited to some value.
*
* @return {@code True} if memory allocation is limited.
*/
public boolean unlimited() {
return totalSize() == 0;
}
/**
* @return Total size.
*/
public long totalSize() {
return total;
}
/**
* @return Free size.
*/
public long freeSize() {
if (total == 0)
return 0;
long diff = total - allocated.get();
return diff < 0 ? 0 : diff;
}
/**
* @return Allocated size.
*/
public long allocatedSize() {
return allocated.get();
}
/**
* @return Size of memory allocated with {@link #allocateSystem(long, boolean)}.
*/
public long systemAllocatedSize() {
return sysAllocated.get();
}
/** {@inheritDoc} */
@Override public String toString() {
return S.toString(GridUnsafeMemory.class, this);
}
}