blob: 7ea0a82f621ca89d08db0e6d080e39adf04a1b9d [file] [log] [blame]
/*-
* Copyright (C) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
*
* This file was distributed by Oracle as part of a version of Oracle Berkeley
* DB Java Edition made available at:
*
* http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html
*
* Please see the LICENSE file included in the top-level directory of the
* appropriate version of Oracle Berkeley DB Java Edition for a copy of the
* license and additional information.
*/
package com.sleepycat.je.evictor;
import java.lang.reflect.Field;
import java.util.concurrent.atomic.AtomicLong;
import sun.misc.Unsafe;
/**
* The default implementation of the off-heap allocator.
*
* Uses the sun.misc.Unsafe class to call the native 'malloc' and 'free'
* functions to allocate memory from the 'C' runtime heap.
*
* This class should not be referenced symbolically by any other other class.
* This is necessary to avoid a linkage error if JE is run on a JVM without the
* Unsafe class. The {@link OffHeapAllocatorFactory} loads this class by name,
* using reflection.
*/
class CHeapAllocator implements OffHeapAllocator {
/*
* We should probably always perform bounds checking, since going out of
* bounds is likely to crash the JVM.
*/
private static final boolean CHECK_BOUNDS = true;
/* Number of bytes for storing the int block size. */
private static final int SIZE_BYTES = 4;
private final Unsafe unsafe;
private final AtomicLong usedBytes = new AtomicLong(0);
public CHeapAllocator() {
/*
* We cannot call Unsafe.getUnsafe because it throws
* SecurityException when called from a non-bootstrap class. Getting
* the static field (that would be returned by Unsafe.getUnsafe) is
* better than calling the Unsafe private constructor, since Unsafe
* is intended to have a singleton instance.
*/
try {
final Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
} catch (Throwable e) {
throw new UnsupportedOperationException(
"Unable to get Unsafe object", e);
}
if (unsafe == null) {
throw new UnsupportedOperationException(
"Unsafe singleton is null");
}
/*
* Check for seemingly obvious byte and int sizes, to ensure that the
* JVM isn't doing something strange.
*/
if (Unsafe.ARRAY_BYTE_INDEX_SCALE != 1) {
throw new UnsupportedOperationException(
"Unexpected Unsafe.ARRAY_BYTE_INDEX_SCALE: " +
Unsafe.ARRAY_BYTE_INDEX_SCALE);
}
if (Unsafe.ARRAY_INT_INDEX_SCALE != SIZE_BYTES) {
throw new UnsupportedOperationException(
"Unexpected Unsafe.ARRAY_INT_INDEX_SCALE: " +
Unsafe.ARRAY_INT_INDEX_SCALE);
}
}
@Override
public void setMaxBytes(long maxBytes) {
}
@Override
public long getUsedBytes() {
return usedBytes.get();
}
@Override
public long allocate(int size) {
final int allocSize = size + SIZE_BYTES;
final long memId = unsafe.allocateMemory(allocSize);
unsafe.putInt(memId, size);
unsafe.setMemory(memId + SIZE_BYTES, size, (byte) 0);
usedBytes.addAndGet(addOverhead(allocSize));
return memId;
}
@Override
public int free(long memId) {
final int totalSize = addOverhead(size(memId) + SIZE_BYTES);
unsafe.freeMemory(memId);
usedBytes.addAndGet(0 - totalSize);
return totalSize;
}
private int addOverhead(int allocSize) {
/* TODO: There is 70 bytes added overhead when using the IBM JDK. */
/* Blocks are aligned on 8 byte boundaries with a 16 byte header. */
allocSize += (allocSize % 8) + 16;
/* The minimum block size is 24 bytes. */
return (allocSize < 24) ? 24 : allocSize;
}
@Override
public int size(long memId) {
return unsafe.getInt(memId);
}
@Override
public int totalSize(long memId) {
return addOverhead(size(memId) + SIZE_BYTES);
}
@Override
public void copy(long memId, int memOff, byte[] buf, int bufOff, int len) {
if (CHECK_BOUNDS) {
if (memId == 0) {
throw new NullPointerException("memId is 0");
}
if (buf == null) {
throw new NullPointerException("buf is null");
}
if (memOff < 0 || memOff + len > size(memId)) {
throw new IndexOutOfBoundsException(
"memOff=" + memOff +
" memSize=" + size(memId) +
" copyLen=" + len);
}
if (bufOff < 0 || bufOff + len > buf.length) {
throw new IndexOutOfBoundsException(
"bufOff=" + bufOff +
" bufSize=" + buf.length +
" copyLen=" + len);
}
}
unsafe.copyMemory(
null, memId + SIZE_BYTES + memOff,
buf, Unsafe.ARRAY_BYTE_BASE_OFFSET + bufOff,
len);
}
@Override
public void copy(byte[] buf, int bufOff, long memId, int memOff, int len) {
if (CHECK_BOUNDS) {
if (memId == 0) {
throw new NullPointerException("memId is 0");
}
if (buf == null) {
throw new NullPointerException("buf is null");
}
if (memOff < 0 || memOff + len > size(memId)) {
throw new IndexOutOfBoundsException(
"memOff=" + memOff +
" memSize=" + size(memId) +
" copyLen=" + len);
}
if (bufOff < 0 || bufOff + len > buf.length) {
throw new IndexOutOfBoundsException(
"bufOff=" + bufOff +
" bufSize=" + buf.length +
" copyLen=" + len);
}
}
unsafe.copyMemory(
buf, Unsafe.ARRAY_BYTE_BASE_OFFSET + bufOff,
null, memId + SIZE_BYTES + memOff,
len);
}
@Override
public void copy(long fromMemId,
int fromMemOff,
long toMemId,
int toMemOff,
int len) {
if (CHECK_BOUNDS) {
if (fromMemId == 0 || toMemId == 0) {
throw new NullPointerException("memId is 0");
}
if (fromMemOff < 0 || fromMemOff + len > size(fromMemId)) {
throw new IndexOutOfBoundsException(
"memOff=" + fromMemOff +
" memSize=" + size(fromMemId) +
" copyLen=" + len);
}
if (toMemOff < 0 || toMemOff + len > size(toMemId)) {
throw new IndexOutOfBoundsException(
"memOff=" + toMemOff +
" memSize=" + size(toMemId) +
" copyLen=" + len);
}
}
unsafe.copyMemory(
null, fromMemId + SIZE_BYTES + fromMemOff,
null, toMemId + SIZE_BYTES + toMemOff,
len);
}
}