| /* |
| * 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.geode.internal.offheap; |
| |
| import java.io.DataOutput; |
| import java.io.IOException; |
| import java.nio.ByteBuffer; |
| |
| import org.apache.geode.cache.Region; |
| import org.apache.geode.internal.HeapDataOutputStream; |
| import org.apache.geode.internal.InternalDataSerializer; |
| import org.apache.geode.internal.cache.BytesAndBitsForCompactor; |
| import org.apache.geode.internal.cache.EntryBits; |
| import org.apache.geode.internal.cache.EntryEventImpl; |
| import org.apache.geode.internal.cache.RegionEntry; |
| import org.apache.geode.internal.cache.RegionEntryContext; |
| import org.apache.geode.internal.offheap.annotations.Unretained; |
| import org.apache.geode.internal.serialization.DSCODE; |
| |
| /** |
| * A class that stores a Java object in off-heap memory. See {@link AddressableMemoryManager} for |
| * how off-heap memory can be allocated, accessed, modified, and freed. Currently the object stored |
| * in this class is always an entry value of a Region. Note: this class has a natural ordering that |
| * is inconsistent with equals. Instances of this class should have a short lifetime. We do not |
| * store references to it in the cache. Instead the memoryAddress is stored in a primitive field in |
| * the cache and if used it will then, if needed, create an instance of this class. |
| */ |
| public class OffHeapStoredObject extends AbstractStoredObject |
| implements Comparable<OffHeapStoredObject>, MemoryBlock { |
| /** |
| * The memory address of the first byte of addressable memory that belongs to this object |
| */ |
| private final long memoryAddress; |
| |
| /** |
| * The useCount, chunkSize, dataSizeDelta, isSerialized, and isCompressed are all stored in |
| * addressable memory in a HEADER. This saves heap memory by using off heap. |
| */ |
| public static final int HEADER_SIZE = 4 + 4; |
| /** |
| * We need to smallest chunk to at least have enough room for a hdr and for an off heap ref (which |
| * is a long). |
| */ |
| public static final int MIN_CHUNK_SIZE = HEADER_SIZE + 8; |
| /** |
| * int field. The number of bytes in this chunk. |
| */ |
| private static final int CHUNK_SIZE_OFFSET = 0; |
| /** |
| * Volatile int field The upper two bits are used for the isSerialized and isCompressed flags. The |
| * next three bits are unused. The lower 3 bits of the most significant byte contains a magic |
| * number to help us detect if we are changing the ref count of an object that has been released. |
| * The next byte contains the dataSizeDelta. The number of bytes of logical data in this chunk. |
| * Since the number of bytes of logical data is always <= chunkSize and since chunkSize never |
| * changes, we have dataSize be a delta whose max value would be HUGE_MULTIPLE-1. The lower two |
| * bytes contains the use count. |
| */ |
| static final int REF_COUNT_OFFSET = 4; |
| /** |
| * The upper two bits are used for the isSerialized and isCompressed flags. |
| */ |
| static final int IS_SERIALIZED_BIT = 0x80000000; |
| static final int IS_COMPRESSED_BIT = 0x40000000; |
| // UNUSED 0x38000000 |
| static final int MAGIC_MASK = 0x07000000; |
| static final int MAGIC_NUMBER = 0x05000000; |
| static final int DATA_SIZE_DELTA_MASK = 0x00ff0000; |
| static final int DATA_SIZE_SHIFT = 16; |
| static final int REF_COUNT_MASK = 0x0000ffff; |
| static final int MAX_REF_COUNT = 0xFFFF; |
| static final long FILL_PATTERN = 0x3c3c3c3c3c3c3c3cL; |
| static final byte FILL_BYTE = 0x3c; |
| |
| protected OffHeapStoredObject(long memoryAddress, int chunkSize) { |
| MemoryAllocatorImpl.validateAddressAndSize(memoryAddress, chunkSize); |
| this.memoryAddress = memoryAddress; |
| setSize(chunkSize); |
| AddressableMemoryManager.writeIntVolatile(getAddress() + REF_COUNT_OFFSET, MAGIC_NUMBER); |
| } |
| |
| public void readyForFree() { |
| AddressableMemoryManager.writeIntVolatile(getAddress() + REF_COUNT_OFFSET, 0); |
| } |
| |
| public void readyForAllocation() { |
| if (!AddressableMemoryManager.writeIntVolatile(getAddress() + REF_COUNT_OFFSET, 0, |
| MAGIC_NUMBER)) { |
| throw new IllegalStateException("Expected 0 but found " + Integer |
| .toHexString(AddressableMemoryManager.readIntVolatile(getAddress() + REF_COUNT_OFFSET))); |
| } |
| } |
| |
| /** |
| * Should only be used by FakeChunk subclass |
| */ |
| protected OffHeapStoredObject() { |
| this.memoryAddress = 0L; |
| } |
| |
| /** |
| * Used to create a Chunk given an existing, already allocated, memoryAddress. The off heap header |
| * has already been initialized. |
| */ |
| protected OffHeapStoredObject(long memoryAddress) { |
| MemoryAllocatorImpl.validateAddress(memoryAddress); |
| this.memoryAddress = memoryAddress; |
| } |
| |
| protected OffHeapStoredObject(OffHeapStoredObject chunk) { |
| this.memoryAddress = chunk.memoryAddress; |
| } |
| |
| @Override |
| public void fillSerializedValue(BytesAndBitsForCompactor wrapper, byte userBits) { |
| if (isSerialized()) { |
| userBits = EntryBits.setSerialized(userBits, true); |
| } |
| wrapper.setOffHeapData(this, userBits); |
| } |
| |
| String getShortClassName() { |
| String cname = getClass().getName(); |
| return cname.substring(getClass().getPackage().getName().length() + 1); |
| } |
| |
| @Override |
| public boolean checkDataEquals(@Unretained StoredObject so) { |
| if (this == so) { |
| return true; |
| } |
| if (isSerialized() != so.isSerialized()) { |
| return false; |
| } |
| int mySize = getValueSizeInBytes(); |
| if (mySize != so.getValueSizeInBytes()) { |
| return false; |
| } |
| if (!(so instanceof OffHeapStoredObject)) { |
| return false; |
| } |
| OffHeapStoredObject other = (OffHeapStoredObject) so; |
| if (getAddress() == other.getAddress()) { |
| return true; |
| } |
| // We want to be able to do this operation without copying any of the data into the heap. |
| // Hopefully the jvm is smart enough to use our stack for this short lived array. |
| final byte[] dataCache1 = new byte[1024]; |
| final byte[] dataCache2 = new byte[dataCache1.length]; |
| int i; |
| // inc it twice since we are reading two different objects |
| MemoryAllocatorImpl.getAllocator().getStats().incReads(); |
| MemoryAllocatorImpl.getAllocator().getStats().incReads(); |
| for (i = 0; i < mySize - (dataCache1.length - 1); i += dataCache1.length) { |
| this.readDataBytes(i, dataCache1); |
| other.readDataBytes(i, dataCache2); |
| for (int j = 0; j < dataCache1.length; j++) { |
| if (dataCache1[j] != dataCache2[j]) { |
| return false; |
| } |
| } |
| } |
| int bytesToRead = mySize - i; |
| if (bytesToRead > 0) { |
| // need to do one more read which will be less than dataCache.length |
| this.readDataBytes(i, dataCache1, 0, bytesToRead); |
| other.readDataBytes(i, dataCache2, 0, bytesToRead); |
| for (int j = 0; j < bytesToRead; j++) { |
| if (dataCache1[j] != dataCache2[j]) { |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| @Override |
| public boolean checkDataEquals(byte[] serializedObj) { |
| // caller was responsible for checking isSerialized |
| int mySize = getValueSizeInBytes(); |
| if (mySize != serializedObj.length) { |
| return false; |
| } |
| // We want to be able to do this operation without copying any of the data into the heap. |
| // Hopefully the jvm is smart enough to use our stack for this short lived array. |
| final byte[] dataCache = new byte[1024]; |
| int idx = 0; |
| int i; |
| MemoryAllocatorImpl.getAllocator().getStats().incReads(); |
| for (i = 0; i < mySize - (dataCache.length - 1); i += dataCache.length) { |
| this.readDataBytes(i, dataCache); |
| for (int j = 0; j < dataCache.length; j++) { |
| if (dataCache[j] != serializedObj[idx++]) { |
| return false; |
| } |
| } |
| } |
| int bytesToRead = mySize - i; |
| if (bytesToRead > 0) { |
| // need to do one more read which will be less than dataCache.length |
| this.readDataBytes(i, dataCache, 0, bytesToRead); |
| for (int j = 0; j < bytesToRead; j++) { |
| if (dataCache[j] != serializedObj[idx++]) { |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| |
| /** |
| * Throw an exception if this chunk is not allocated |
| */ |
| public void checkIsAllocated() { |
| int originalBits = |
| AddressableMemoryManager.readIntVolatile(this.memoryAddress + REF_COUNT_OFFSET); |
| if ((originalBits & MAGIC_MASK) != MAGIC_NUMBER) { |
| throw new IllegalStateException( |
| "It looks like this off heap memory was already freed. rawBits=" |
| + Integer.toHexString(originalBits)); |
| } |
| } |
| |
| public void incSize(int inc) { |
| setSize(getSize() + inc); |
| } |
| |
| protected void beforeReturningToAllocator() { |
| |
| } |
| |
| @Override |
| public int getSize() { |
| return getSize(this.memoryAddress); |
| } |
| |
| public void setSize(int size) { |
| setSize(this.memoryAddress, size); |
| } |
| |
| @Override |
| public long getAddress() { |
| return this.memoryAddress; |
| } |
| |
| @Override |
| public int getDataSize() { |
| return getDataSize(this.memoryAddress); |
| } |
| |
| protected static int getDataSize(long memoryAdress) { |
| int dataSizeDelta = AddressableMemoryManager.readInt(memoryAdress + REF_COUNT_OFFSET); |
| dataSizeDelta &= DATA_SIZE_DELTA_MASK; |
| dataSizeDelta >>= DATA_SIZE_SHIFT; |
| return getSize(memoryAdress) - dataSizeDelta; |
| } |
| |
| protected long getBaseDataAddress() { |
| return this.memoryAddress + HEADER_SIZE; |
| } |
| |
| protected int getBaseDataOffset() { |
| return 0; |
| } |
| |
| @Override |
| @Unretained |
| public ByteBuffer createDirectByteBuffer() { |
| return AddressableMemoryManager.createDirectByteBuffer(getBaseDataAddress(), getDataSize()); |
| } |
| |
| @Override |
| public void sendTo(DataOutput out) throws IOException { |
| if (!this.isCompressed() && out instanceof HeapDataOutputStream) { |
| ByteBuffer bb = createDirectByteBuffer(); |
| if (bb != null) { |
| HeapDataOutputStream hdos = (HeapDataOutputStream) out; |
| if (this.isSerialized()) { |
| hdos.write(bb); |
| } else { |
| hdos.writeByte(DSCODE.BYTE_ARRAY.toByte()); |
| InternalDataSerializer.writeArrayLength(bb.remaining(), hdos); |
| hdos.write(bb); |
| } |
| return; |
| } |
| } |
| super.sendTo(out); |
| } |
| |
| @Override |
| public void sendAsByteArray(DataOutput out) throws IOException { |
| if (!isCompressed() && out instanceof HeapDataOutputStream) { |
| ByteBuffer bb = createDirectByteBuffer(); |
| if (bb != null) { |
| HeapDataOutputStream hdos = (HeapDataOutputStream) out; |
| InternalDataSerializer.writeArrayLength(bb.remaining(), hdos); |
| hdos.write(bb); |
| return; |
| } |
| } |
| super.sendAsByteArray(out); |
| } |
| |
| /** |
| * Returns an address that can be used with AddressableMemoryManager to access this object's data. |
| * |
| * @param offset the offset from this chunk's first byte of the byte the returned address should |
| * point to. Must be >= 0. |
| * @param size the number of bytes that will be read using the returned address. Assertion will |
| * use this to verify that all the memory accessed belongs to this chunk. Must be > 0. |
| * @return a memory address that can be used to access this object's data |
| */ |
| @Override |
| public long getAddressForReadingData(int offset, int size) { |
| assert offset >= 0 && offset + size <= getDataSize() : "Offset=" + offset + ",size=" + size |
| + ",dataSize=" + getDataSize() + ", chunkSize=" + getSize() |
| + ", but offset + size must be <= " + getDataSize(); |
| assert size > 0; |
| long result = getBaseDataAddress() + offset; |
| // validateAddressAndSizeWithinSlab(result, size); |
| return result; |
| } |
| |
| @Override |
| public byte readDataByte(int offset) { |
| assert offset < getDataSize(); |
| return AddressableMemoryManager.readByte(getBaseDataAddress() + offset); |
| } |
| |
| @Override |
| public void writeDataByte(int offset, byte value) { |
| assert offset < getDataSize(); |
| AddressableMemoryManager.writeByte(getBaseDataAddress() + offset, value); |
| } |
| |
| @Override |
| public void readDataBytes(int offset, byte[] bytes) { |
| readDataBytes(offset, bytes, 0, bytes.length); |
| } |
| |
| @Override |
| public void writeDataBytes(int offset, byte[] bytes) { |
| writeDataBytes(offset, bytes, 0, bytes.length); |
| } |
| |
| @Override |
| public void readDataBytes(int offset, byte[] bytes, int bytesOffset, int size) { |
| assert offset + size <= getDataSize(); |
| AddressableMemoryManager.readBytes(getBaseDataAddress() + offset, bytes, bytesOffset, size); |
| } |
| |
| @Override |
| public void writeDataBytes(int offset, byte[] bytes, int bytesOffset, int size) { |
| assert offset + size <= getDataSize(); |
| AddressableMemoryManager.writeBytes(getBaseDataAddress() + offset, bytes, bytesOffset, size); |
| } |
| |
| @Override |
| public void release() { |
| release(this.memoryAddress); |
| } |
| |
| @Override |
| public int compareTo(OffHeapStoredObject o) { |
| int result = Integer.signum(getSize() - o.getSize()); |
| if (result == 0) { |
| // For the same sized chunks we really don't care about their order |
| // but we need compareTo to only return 0 if the two chunks are identical |
| result = Long.signum(getAddress() - o.getAddress()); |
| } |
| return result; |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (o instanceof OffHeapStoredObject) { |
| return getAddress() == ((OffHeapStoredObject) o).getAddress(); |
| } |
| return false; |
| } |
| |
| @Override |
| public int hashCode() { |
| long value = this.getAddress(); |
| return (int) (value ^ (value >>> 32)); |
| } |
| |
| public void setSerializedValue(byte[] value) { |
| writeDataBytes(0, value); |
| } |
| |
| public byte[] getDecompressedBytes(RegionEntryContext context) { |
| byte[] result = getCompressedBytes(); |
| long time = context.getCachePerfStats().startDecompression(); |
| result = context.getCompressor().decompress(result); |
| context.getCachePerfStats().endDecompression(time); |
| return result; |
| } |
| |
| /** |
| * Returns the raw possibly compressed bytes of this chunk |
| */ |
| public byte[] getCompressedBytes() { |
| byte[] result = new byte[getDataSize()]; |
| readDataBytes(0, result); |
| // debugLog("reading", true); |
| MemoryAllocatorImpl.getAllocator().getStats().incReads(); |
| return result; |
| } |
| |
| /** |
| * This method should only be called on uncompressed objects |
| * |
| * @return byte array of the StoredObject value. |
| */ |
| protected byte[] getRawBytes() { |
| assert !isCompressed(); |
| return getCompressedBytes(); |
| } |
| |
| @Override |
| public byte[] getSerializedValue() { |
| byte[] result = getRawBytes(); |
| if (!isSerialized()) { |
| // The object is a byte[]. So we need to make it look like a serialized byte[] in our result |
| result = EntryEventImpl.serialize(result); |
| } |
| return result; |
| } |
| |
| @Override |
| public Object getDeserializedValue(Region r, RegionEntry re) { |
| if (isSerialized()) { |
| return EntryEventImpl.deserialize(getRawBytes()); |
| } else { |
| return getRawBytes(); |
| } |
| } |
| |
| /** |
| * We want this to include memory overhead so use getSize() instead of getDataSize(). |
| */ |
| @Override |
| public int getSizeInBytes() { |
| // Calling getSize includes the off heap header size. |
| // We do not add anything to this since the size of the reference belongs to the region entry |
| // size |
| // not the size of this object. |
| return getSize(); |
| } |
| |
| @Override |
| public int getValueSizeInBytes() { |
| return getDataSize(); |
| } |
| |
| @Override |
| public boolean isSerialized() { |
| return (AddressableMemoryManager.readInt(this.memoryAddress + REF_COUNT_OFFSET) |
| & IS_SERIALIZED_BIT) != 0; |
| } |
| |
| @Override |
| public boolean isCompressed() { |
| return (AddressableMemoryManager.readInt(this.memoryAddress + REF_COUNT_OFFSET) |
| & IS_COMPRESSED_BIT) != 0; |
| } |
| |
| @Override |
| public boolean retain() { |
| return retain(this.memoryAddress); |
| } |
| |
| @Override |
| public int getRefCount() { |
| return getRefCount(this.memoryAddress); |
| } |
| |
| public static int getSize(long memAddr) { |
| MemoryAllocatorImpl.validateAddress(memAddr); |
| return AddressableMemoryManager.readInt(memAddr + CHUNK_SIZE_OFFSET); |
| } |
| |
| public static void setSize(long memAddr, int size) { |
| MemoryAllocatorImpl.validateAddressAndSize(memAddr, size); |
| AddressableMemoryManager.writeInt(memAddr + CHUNK_SIZE_OFFSET, size); |
| } |
| |
| public static long getNext(long memAddr) { |
| MemoryAllocatorImpl.validateAddress(memAddr); |
| return AddressableMemoryManager.readLong(memAddr + HEADER_SIZE); |
| } |
| |
| public static void setNext(long memAddr, long next) { |
| MemoryAllocatorImpl.validateAddress(memAddr); |
| AddressableMemoryManager.writeLong(memAddr + HEADER_SIZE, next); |
| } |
| |
| /** |
| * Fills the chunk with a repeated byte fill pattern. |
| * |
| * @param baseAddress the starting address for a {@link OffHeapStoredObject}. |
| */ |
| public static void fill(long baseAddress) { |
| long startAddress = baseAddress + MIN_CHUNK_SIZE; |
| int size = getSize(baseAddress) - MIN_CHUNK_SIZE; |
| |
| AddressableMemoryManager.fill(startAddress, size, FILL_BYTE); |
| } |
| |
| /** |
| * Validates that the fill pattern for this chunk has not been disturbed. This method assumes the |
| * TINY_MULTIPLE is 8 bytes. |
| * |
| * @throws IllegalStateException when the pattern has been violated. |
| */ |
| public void validateFill() { |
| assert FreeListManager.TINY_MULTIPLE == 8; |
| |
| long startAddress = getAddress() + MIN_CHUNK_SIZE; |
| int size = getSize() - MIN_CHUNK_SIZE; |
| |
| for (int i = 0; i < size; i += FreeListManager.TINY_MULTIPLE) { |
| if (AddressableMemoryManager.readLong(startAddress + i) != FILL_PATTERN) { |
| throw new IllegalStateException( |
| "Fill pattern violated for chunk " + getAddress() + " with size " + getSize()); |
| } |
| } |
| } |
| |
| public void setSerialized(boolean isSerialized) { |
| if (isSerialized) { |
| int bits; |
| int originalBits; |
| do { |
| originalBits = |
| AddressableMemoryManager.readIntVolatile(this.memoryAddress + REF_COUNT_OFFSET); |
| if ((originalBits & MAGIC_MASK) != MAGIC_NUMBER) { |
| throw new IllegalStateException( |
| "It looks like this off heap memory was already freed. rawBits=" |
| + Integer.toHexString(originalBits)); |
| } |
| bits = originalBits | IS_SERIALIZED_BIT; |
| } while (!AddressableMemoryManager.writeIntVolatile(this.memoryAddress + REF_COUNT_OFFSET, |
| originalBits, bits)); |
| } |
| } |
| |
| public void setCompressed(boolean isCompressed) { |
| if (isCompressed) { |
| int bits; |
| int originalBits; |
| do { |
| originalBits = |
| AddressableMemoryManager.readIntVolatile(this.memoryAddress + REF_COUNT_OFFSET); |
| if ((originalBits & MAGIC_MASK) != MAGIC_NUMBER) { |
| throw new IllegalStateException( |
| "It looks like this off heap memory was already freed. rawBits=" |
| + Integer.toHexString(originalBits)); |
| } |
| bits = originalBits | IS_COMPRESSED_BIT; |
| } while (!AddressableMemoryManager.writeIntVolatile(this.memoryAddress + REF_COUNT_OFFSET, |
| originalBits, bits)); |
| } |
| } |
| |
| public void setDataSize(int dataSize) { |
| assert dataSize <= getSize(); |
| int delta = getSize() - dataSize; |
| assert delta <= (DATA_SIZE_DELTA_MASK >> DATA_SIZE_SHIFT); |
| delta <<= DATA_SIZE_SHIFT; |
| int bits; |
| int originalBits; |
| do { |
| originalBits = |
| AddressableMemoryManager.readIntVolatile(this.memoryAddress + REF_COUNT_OFFSET); |
| if ((originalBits & MAGIC_MASK) != MAGIC_NUMBER) { |
| throw new IllegalStateException( |
| "It looks like this off heap memory was already freed. rawBits=" |
| + Integer.toHexString(originalBits)); |
| } |
| bits = originalBits; |
| bits &= ~DATA_SIZE_DELTA_MASK; // clear the old dataSizeDelta bits |
| bits |= delta; // set the dataSizeDelta bits to the new delta value |
| } while (!AddressableMemoryManager.writeIntVolatile(this.memoryAddress + REF_COUNT_OFFSET, |
| originalBits, bits)); |
| } |
| |
| public void initializeUseCount() { |
| int rawBits; |
| do { |
| rawBits = AddressableMemoryManager.readIntVolatile(this.memoryAddress + REF_COUNT_OFFSET); |
| if ((rawBits & MAGIC_MASK) != MAGIC_NUMBER) { |
| throw new IllegalStateException( |
| "It looks like this off heap memory was already freed. rawBits=" |
| + Integer.toHexString(rawBits)); |
| } |
| int uc = rawBits & REF_COUNT_MASK; |
| if (uc != 0) { |
| throw new IllegalStateException("Expected use count to be zero but it was: " + uc |
| + " rawBits=0x" + Integer.toHexString(rawBits)); |
| } |
| } while (!AddressableMemoryManager.writeIntVolatile(this.memoryAddress + REF_COUNT_OFFSET, |
| rawBits, rawBits + 1)); |
| } |
| |
| public static int getRefCount(long memAddr) { |
| return AddressableMemoryManager.readInt(memAddr + REF_COUNT_OFFSET) & REF_COUNT_MASK; |
| } |
| |
| public static boolean retain(long memAddr) { |
| MemoryAllocatorImpl.validateAddress(memAddr); |
| int uc; |
| int rawBits; |
| int retryCount = 0; |
| do { |
| rawBits = AddressableMemoryManager.readIntVolatile(memAddr + REF_COUNT_OFFSET); |
| if ((rawBits & MAGIC_MASK) != MAGIC_NUMBER) { |
| // same as uc == 0 |
| // TODO MAGIC_NUMBER rethink its use and interaction with compactor fragments |
| return false; |
| } |
| uc = rawBits & REF_COUNT_MASK; |
| if (uc == MAX_REF_COUNT) { |
| throw new IllegalStateException( |
| "Maximum use count exceeded. rawBits=" + Integer.toHexString(rawBits)); |
| } else if (uc == 0) { |
| return false; |
| } |
| retryCount++; |
| if (retryCount > 1000) { |
| throw new IllegalStateException("tried to write " + (rawBits + 1) + " to @" |
| + Long.toHexString(memAddr) + " 1,000 times."); |
| } |
| } while (!AddressableMemoryManager.writeIntVolatile(memAddr + REF_COUNT_OFFSET, rawBits, |
| rawBits + 1)); |
| // debugLog("use inced ref count " + (uc+1) + " @" + Long.toHexString(memAddr), true); |
| if (ReferenceCountHelper.trackReferenceCounts()) { |
| ReferenceCountHelper.refCountChanged(memAddr, false, uc + 1); |
| } |
| |
| return true; |
| } |
| |
| public static void release(final long memAddr) { |
| release(memAddr, null); |
| } |
| |
| static void release(final long memAddr, FreeListManager freeListManager) { |
| MemoryAllocatorImpl.validateAddress(memAddr); |
| int newCount; |
| int rawBits; |
| boolean returnToAllocator; |
| do { |
| returnToAllocator = false; |
| rawBits = AddressableMemoryManager.readIntVolatile(memAddr + REF_COUNT_OFFSET); |
| if ((rawBits & MAGIC_MASK) != MAGIC_NUMBER) { |
| String msg = "It looks like off heap memory @" + Long.toHexString(memAddr) |
| + " was already freed. rawBits=" + Integer.toHexString(rawBits) + " history=" |
| + ReferenceCountHelper.getFreeRefCountInfo(memAddr); |
| // debugLog(msg, true); |
| throw new IllegalStateException(msg); |
| } |
| int curCount = rawBits & REF_COUNT_MASK; |
| if ((curCount) == 0) { |
| // debugLog("too many frees @" + Long.toHexString(memAddr), true); |
| throw new IllegalStateException( |
| "Memory has already been freed." + " history=" + ReferenceCountHelper |
| .getFreeRefCountInfo(memAddr) /* + System.identityHashCode(this) */); |
| } |
| if (curCount == 1) { |
| newCount = 0; // clear the use count, bits, and the delta size since it will be freed. |
| returnToAllocator = true; |
| } else { |
| newCount = rawBits - 1; |
| } |
| } while (!AddressableMemoryManager.writeIntVolatile(memAddr + REF_COUNT_OFFSET, rawBits, |
| newCount)); |
| // debugLog("free deced ref count " + (newCount&USE_COUNT_MASK) + " @" + |
| // Long.toHexString(memAddr), true); |
| if (returnToAllocator) { |
| if (ReferenceCountHelper.trackReferenceCounts()) { |
| if (ReferenceCountHelper.trackFreedReferenceCounts()) { |
| ReferenceCountHelper.refCountChanged(memAddr, true, newCount & REF_COUNT_MASK); |
| } |
| ReferenceCountHelper.freeRefCountInfo(memAddr); |
| } |
| if (freeListManager == null) { |
| freeListManager = MemoryAllocatorImpl.getAllocator().getFreeListManager(); |
| } |
| freeListManager.free(memAddr); |
| } else { |
| if (ReferenceCountHelper.trackReferenceCounts()) { |
| ReferenceCountHelper.refCountChanged(memAddr, true, newCount & REF_COUNT_MASK); |
| } |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return super.toString() + ":<dataSize=" + getDataSize() + " refCount=" + getRefCount() |
| + " addr=" + Long.toHexString(getAddress()) + ">"; |
| } |
| |
| @Override |
| public State getState() { |
| if (getRefCount() > 0) { |
| return State.ALLOCATED; |
| } else { |
| return State.DEALLOCATED; |
| } |
| } |
| |
| @Override |
| public MemoryBlock getNextBlock() { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public int getBlockSize() { |
| return getSize(); |
| } |
| |
| @Override |
| public int getSlabId() { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| public int getFreeListId() { |
| return -1; |
| } |
| |
| @Override |
| public String getDataType() { |
| return null; |
| } |
| |
| @Override |
| public Object getDataValue() { |
| return null; |
| } |
| |
| @Override |
| public StoredObject slice(int position, int limit) { |
| return new OffHeapStoredObjectSlice(this, position, limit); |
| } |
| |
| @Override |
| public boolean hasRefCount() { |
| return true; |
| } |
| } |