/*
* Copyright (c) 2008-2017, Hazelcast, Inc. All Rights Reserved.
*
* Licensed 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 com.hazelcast.internal.memory.impl;
import com.hazelcast.internal.memory.MemoryAccessor;
import com.hazelcast.internal.memory.MemoryAllocator;
import com.hazelcast.internal.memory.MemoryManager;
import com.hazelcast.util.collection.Long2LongHashMap;
import junit.framework.AssertionFailedError;
import java.util.Arrays;
import static com.hazelcast.internal.memory.impl.EndiannessUtil.BYTE_ARRAY_ACCESS;
/**
* Manages memory in Java byte arrays. For testing only.
*/
public class HeapMemoryManager implements MemoryManager {
private static final int HEAP_BOTTOM = 8;
private final Allocator malloc = new Allocator();
private final Accessor mem = new Accessor();
private final Long2LongHashMap allocatedAddrs = new Long2LongHashMap(1024, 0.7, -1);
private byte[] storage;
private int heapTop = HEAP_BOTTOM;
private int lastAllocatedAddress;
private int usedMemory;
public HeapMemoryManager(int size) {
this.storage = new byte[size];
}
// Suports the testing of HashSlotArray#migrateTo()
public HeapMemoryManager(HeapMemoryManager that) {
this.storage = that.storage;
this.heapTop = storage.length / 2;
}
@Override
public MemoryAllocator getAllocator() {
return malloc;
}
@Override
public MemoryAccessor getAccessor() {
return mem;
}
public long getUsedMemory() {
return usedMemory;
}
@Override
public void dispose() {
storage = null;
}
class Allocator implements MemoryAllocator {
@Override
public long allocate(long size) {
assertFitsInt(size);
assureEnoughMemoryLeft(size);
final long addr = heapTop;
lastAllocatedAddress = heapTop;
heapTop += size;
usedMemory += size;
allocatedAddrs.put(addr, size);
return addr;
}
@Override
public long reallocate(long address, long currentSize, long newSize) {
assertFitsInt(address);
assertFitsInt(currentSize);
assertFitsInt(newSize);
validateBlock(address, currentSize);
if (address == lastAllocatedAddress) {
final long sizeDelta = newSize - currentSize;
assureEnoughMemoryLeft(sizeDelta);
allocatedAddrs.put(address, newSize);
heapTop += sizeDelta;
usedMemory += sizeDelta;
return address;
} else {
final long newAddress = allocate(newSize);
System.arraycopy(storage, (int) address, storage, (int) newAddress,
(int) Math.min(currentSize, newSize));
free0(address, currentSize);
return newAddress;
}
}
@Override
public void free(long address, long size) {
assertFitsInt(address);
assertFitsInt(size);
validateBlock(address, size);
free0(address, size);
}
@Override
public void dispose() {
storage = null;
}
private void assureEnoughMemoryLeft(long size) {
final long bytesFree = storage.length - toStorageIndex(heapTop);
if (size > bytesFree) {
throw new OutOfMemoryError(String.format("Asked to allocate %d, free bytes left %d", size, bytesFree));
}
}
private void validateBlock(long address, long size) {
final long allocatedSize = allocatedAddrs.get(address);
if (allocatedSize == -1) {
throw new AssertionFailedError(String.format("Address %d was not allocated", address));
}
if (allocatedSize != size) {
throw new AssertionFailedError(String.format(
"Allocated size at %d was %d, but tried to free %d bytes", address, allocatedSize, size));
}
}
private void free0(long address, long size) {
allocatedAddrs.remove(address);
usedMemory -= size;
}
}
static void assertFitsInt(long arg) {
assert arg > 0 && arg <= Integer.MAX_VALUE : "argument outside of legal range: " + arg;
}
static long toStorageIndex(long addr) {
return addr - HEAP_BOTTOM;
}
class Accessor implements MemoryAccessor {
@Override
public byte getByte(long address) {
assertAllocated(address);
return storage[(int) toStorageIndex(address)];
}
@Override
public void putByte(long address, byte x) {
assertAllocated(address);
storage[(int) toStorageIndex(address)] = x;
}
private void assertAllocated(long address) {
assert address >= HEAP_BOTTOM && address < heapTop : String.format(
"Attempted to access unallocated address %,d. Heap top is %,d", address, heapTop);
}
@Override
public int getInt(long address) {
return EndiannessUtil.readIntL(BYTE_ARRAY_ACCESS, storage, toStorageIndex(address));
}
@Override
public void putInt(long address, int x) {
EndiannessUtil.writeIntL(BYTE_ARRAY_ACCESS, storage, toStorageIndex(address), x);
}
@Override
public long getLong(long address) {
return EndiannessUtil.readLongL(BYTE_ARRAY_ACCESS, storage, toStorageIndex(address));
}
@Override
public void putLong(long address, long x) {
EndiannessUtil.writeLongL(BYTE_ARRAY_ACCESS, storage, toStorageIndex(address), x);
}
@Override
public void copyMemory(long srcAddress, long destAddress, long lengthBytes) {
System.arraycopy(storage, (int) toStorageIndex(srcAddress),
storage, (int) toStorageIndex(destAddress), (int) lengthBytes);
}
@Override
public void copyFromByteArray(byte[] source, int offset, long destAddress, int length) {
System.arraycopy(source, offset, storage, (int) toStorageIndex(destAddress), length);
}
@Override
public void copyToByteArray(long srcAddress, byte[] destination, int offset, int length) {
System.arraycopy(storage, (int) toStorageIndex(srcAddress), destination, offset, length);
}
@Override
public void setMemory(long address, long lengthBytes, byte value) {
final int fromIndex = (int) toStorageIndex(address);
Arrays.fill(storage, fromIndex, fromIndex + (int) lengthBytes, value);
}
@Override
public boolean isBigEndian() {
return false;
}
@Override
public boolean getBoolean(long address) {
throw new UnsupportedOperationException();
}
@Override
public void putBoolean(long address, boolean x) {
throw new UnsupportedOperationException();
}
@Override
public char getChar(long address) {
throw new UnsupportedOperationException();
}
@Override
public void putChar(long address, char x) {
throw new UnsupportedOperationException();
}
@Override
public short getShort(long address) {
throw new UnsupportedOperationException();
}
@Override
public void putShort(long address, short x) {
throw new UnsupportedOperationException();
}
@Override
public float getFloat(long address) {
throw new UnsupportedOperationException();
}
@Override
public void putFloat(long address, float x) {
throw new UnsupportedOperationException();
}
@Override
public double getDouble(long address) {
throw new UnsupportedOperationException();
}
@Override
public void putDouble(long address, double x) {
throw new UnsupportedOperationException();
}
}
}