blob: b080b7d14c823aa22a2b05f995007a33b82f48a1 [file] [log] [blame]
/*
* 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 static org.apache.geode.internal.offheap.MemoryAllocatorImpl.DummyNonRealTimeStatsUpdater;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.RestoreSystemProperties;
import org.apache.geode.OutOfOffHeapMemoryException;
import org.apache.geode.cache.CacheClosedException;
public class MemoryAllocatorJUnitTest {
private long expectedMemoryUsage;
private boolean memoryUsageEventReceived;
@Rule
public final RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties();
private static int round(int multiple, int v) {
return ((v + multiple - 1) / multiple) * multiple;
}
@Test
public void testNullGetAllocator() {
try {
MemoryAllocatorImpl.getAllocator();
fail("expected CacheClosedException");
} catch (CacheClosedException expected) {
}
}
@Test
public void testConstructor() {
try {
MemoryAllocatorImpl.createForUnitTest(null, null, null);
fail("expected IllegalArgumentException");
} catch (IllegalArgumentException expected) {
}
}
@Test
public void testCreate() {
System.setProperty(MemoryAllocatorImpl.FREE_OFF_HEAP_MEMORY_PROPERTY, "false");
{
NullOutOfOffHeapMemoryListener listener = new NullOutOfOffHeapMemoryListener();
NullOffHeapMemoryStats stats = new NullOffHeapMemoryStats();
try {
MemoryAllocatorImpl.create(listener, stats, 10, 950, 100, null, size -> {
throw new OutOfMemoryError("expected");
}, null, () -> new DummyNonRealTimeStatsUpdater());
} catch (OutOfMemoryError expected) {
}
assertTrue(listener.isClosed());
assertTrue(stats.isClosed());
}
{
NullOutOfOffHeapMemoryListener listener = new NullOutOfOffHeapMemoryListener();
NullOffHeapMemoryStats stats = new NullOffHeapMemoryStats();
int MAX_SLAB_SIZE = 100;
try {
SlabFactory factory = new SlabFactory() {
private int createCount = 0;
@Override
public Slab create(int size) {
createCount++;
if (createCount == 1) {
return new SlabImpl(size);
} else {
throw new OutOfMemoryError("expected");
}
}
};
MemoryAllocatorImpl.create(listener, stats, 10, 950, MAX_SLAB_SIZE, null, factory, null,
() -> new MemoryAllocatorImpl.DummyNonRealTimeStatsUpdater());
} catch (OutOfMemoryError expected) {
}
assertTrue(listener.isClosed());
assertTrue(stats.isClosed());
}
{
NullOutOfOffHeapMemoryListener listener = new NullOutOfOffHeapMemoryListener();
NullOffHeapMemoryStats stats = new NullOffHeapMemoryStats();
SlabFactory factory = SlabImpl::new;
MemoryAllocator ma =
MemoryAllocatorImpl.create(listener, stats, 10, 950, 100, null, factory, null,
() -> new DummyNonRealTimeStatsUpdater());
try {
assertFalse(listener.isClosed());
assertFalse(stats.isClosed());
ma.close();
assertTrue(listener.isClosed());
assertFalse(stats.isClosed());
listener = new NullOutOfOffHeapMemoryListener();
NullOffHeapMemoryStats stats2 = new NullOffHeapMemoryStats();
{
SlabImpl slab = new SlabImpl(1024);
try {
MemoryAllocatorImpl.createForUnitTest(listener, stats2, new SlabImpl[] {slab});
} catch (IllegalStateException expected) {
assertTrue("unexpected message: " + expected.getMessage(), expected.getMessage().equals(
"attempted to reuse existing off-heap memory even though new off-heap memory was allocated"));
} finally {
slab.free();
}
assertFalse(stats.isClosed());
assertTrue(listener.isClosed());
assertTrue(stats2.isClosed());
}
listener = new NullOutOfOffHeapMemoryListener();
stats2 = new NullOffHeapMemoryStats();
MemoryAllocator ma2 =
MemoryAllocatorImpl.create(listener, stats2, 10, 950, 100, null, factory, null,
() -> new DummyNonRealTimeStatsUpdater());
assertSame(ma, ma2);
assertTrue(stats.isClosed());
assertFalse(listener.isClosed());
assertFalse(stats2.isClosed());
stats = stats2;
ma.close();
assertTrue(listener.isClosed());
assertFalse(stats.isClosed());
} finally {
MemoryAllocatorImpl.freeOffHeapMemory();
}
assertTrue(stats.isClosed());
}
}
@Test
public void testBasics() {
int BATCH_SIZE = 1;
int TINY_MULTIPLE = FreeListManager.TINY_MULTIPLE;
int HUGE_MULTIPLE = FreeListManager.HUGE_MULTIPLE;
int perObjectOverhead = OffHeapStoredObject.HEADER_SIZE;
int maxTiny = FreeListManager.MAX_TINY - perObjectOverhead;
int minHuge = maxTiny + 1;
int TOTAL_MEM = (maxTiny + perObjectOverhead) * BATCH_SIZE
/* + (maxBig+perObjectOverhead)*BATCH_SIZE */ + round(TINY_MULTIPLE,
minHuge + 1 + perObjectOverhead) * BATCH_SIZE
+ (TINY_MULTIPLE + perObjectOverhead)
* BATCH_SIZE /* + (MIN_BIG_SIZE+perObjectOverhead)*BATCH_SIZE */
+ round(TINY_MULTIPLE, minHuge + perObjectOverhead + 1);
SlabImpl slab = new SlabImpl(TOTAL_MEM);
try {
MemoryAllocatorImpl ma =
MemoryAllocatorImpl.createForUnitTest(new NullOutOfOffHeapMemoryListener(),
new NullOffHeapMemoryStats(), new SlabImpl[] {slab});
assertEquals(TOTAL_MEM, ma.getFreeMemory());
assertEquals(TOTAL_MEM, ma.freeList.getFreeFragmentMemory());
assertEquals(0, ma.freeList.getFreeTinyMemory());
assertEquals(0, ma.freeList.getFreeHugeMemory());
StoredObject tinymc = ma.allocate(maxTiny);
assertEquals(TOTAL_MEM - round(TINY_MULTIPLE, maxTiny + perObjectOverhead),
ma.getFreeMemory());
assertEquals(round(TINY_MULTIPLE, maxTiny + perObjectOverhead) * (BATCH_SIZE - 1),
ma.freeList.getFreeTinyMemory());
StoredObject hugemc = ma.allocate(minHuge);
assertEquals(TOTAL_MEM
- round(TINY_MULTIPLE,
minHuge + perObjectOverhead)/*-round(BIG_MULTIPLE, maxBig+perObjectOverhead)*/
- round(TINY_MULTIPLE, maxTiny + perObjectOverhead), ma.getFreeMemory());
long freeSlab = ma.freeList.getFreeFragmentMemory();
long oldFreeHugeMemory = ma.freeList.getFreeHugeMemory();
assertEquals(round(TINY_MULTIPLE, minHuge + perObjectOverhead) * (BATCH_SIZE - 1),
oldFreeHugeMemory);
hugemc.release();
assertEquals(round(TINY_MULTIPLE, minHuge + perObjectOverhead),
ma.freeList.getFreeHugeMemory() - oldFreeHugeMemory);
assertEquals(TOTAL_MEM/*-round(BIG_MULTIPLE, maxBig+perObjectOverhead)*/ - round(
TINY_MULTIPLE, maxTiny + perObjectOverhead), ma.getFreeMemory());
assertEquals(TOTAL_MEM - round(TINY_MULTIPLE, maxTiny + perObjectOverhead),
ma.getFreeMemory());
long oldFreeTinyMemory = ma.freeList.getFreeTinyMemory();
tinymc.release();
assertEquals(round(TINY_MULTIPLE, maxTiny + perObjectOverhead),
ma.freeList.getFreeTinyMemory() - oldFreeTinyMemory);
assertEquals(TOTAL_MEM, ma.getFreeMemory());
// now lets reallocate from the free lists
tinymc = ma.allocate(maxTiny);
assertEquals(oldFreeTinyMemory, ma.freeList.getFreeTinyMemory());
assertEquals(TOTAL_MEM - round(TINY_MULTIPLE, maxTiny + perObjectOverhead),
ma.getFreeMemory());
hugemc = ma.allocate(minHuge);
assertEquals(oldFreeHugeMemory, ma.freeList.getFreeHugeMemory());
assertEquals(TOTAL_MEM
- round(TINY_MULTIPLE,
minHuge + perObjectOverhead)/*-round(BIG_MULTIPLE, maxBig+perObjectOverhead)*/
- round(TINY_MULTIPLE, maxTiny + perObjectOverhead), ma.getFreeMemory());
hugemc.release();
assertEquals(round(TINY_MULTIPLE, minHuge + perObjectOverhead),
ma.freeList.getFreeHugeMemory() - oldFreeHugeMemory);
assertEquals(TOTAL_MEM/*-round(BIG_MULTIPLE, maxBig+perObjectOverhead)*/ - round(
TINY_MULTIPLE, maxTiny + perObjectOverhead), ma.getFreeMemory());
assertEquals(TOTAL_MEM - round(TINY_MULTIPLE, maxTiny + perObjectOverhead),
ma.getFreeMemory());
tinymc.release();
assertEquals(round(TINY_MULTIPLE, maxTiny + perObjectOverhead),
ma.freeList.getFreeTinyMemory() - oldFreeTinyMemory);
assertEquals(TOTAL_MEM, ma.getFreeMemory());
// None of the reallocates should have come from the slab.
assertEquals(freeSlab, ma.freeList.getFreeFragmentMemory());
tinymc = ma.allocate(1);
assertEquals(round(TINY_MULTIPLE, 1 + perObjectOverhead), tinymc.getSize());
assertEquals(freeSlab - (round(TINY_MULTIPLE, 1 + perObjectOverhead) * BATCH_SIZE),
ma.freeList.getFreeFragmentMemory());
freeSlab = ma.freeList.getFreeFragmentMemory();
tinymc.release();
assertEquals(
round(TINY_MULTIPLE, maxTiny + perObjectOverhead)
+ (round(TINY_MULTIPLE, 1 + perObjectOverhead) * BATCH_SIZE),
ma.freeList.getFreeTinyMemory() - oldFreeTinyMemory);
hugemc = ma.allocate(minHuge + 1);
assertEquals(round(TINY_MULTIPLE, minHuge + 1 + perObjectOverhead), hugemc.getSize());
assertEquals(round(TINY_MULTIPLE, minHuge + perObjectOverhead) * (BATCH_SIZE - 1),
ma.freeList.getFreeHugeMemory());
hugemc.release();
assertEquals(round(TINY_MULTIPLE, minHuge + perObjectOverhead) * BATCH_SIZE,
ma.freeList.getFreeHugeMemory());
hugemc = ma.allocate(minHuge);
assertEquals(round(TINY_MULTIPLE, minHuge + perObjectOverhead) * (BATCH_SIZE - 1),
ma.freeList.getFreeHugeMemory());
if (BATCH_SIZE > 1) {
StoredObject hugemc2 = ma.allocate(minHuge);
assertEquals(round(TINY_MULTIPLE, minHuge + perObjectOverhead) * (BATCH_SIZE - 2),
ma.freeList.getFreeHugeMemory());
hugemc2.release();
assertEquals(round(TINY_MULTIPLE, minHuge + perObjectOverhead) * (BATCH_SIZE - 1),
ma.freeList.getFreeHugeMemory());
}
hugemc.release();
assertEquals(round(TINY_MULTIPLE, minHuge + perObjectOverhead) * BATCH_SIZE,
ma.freeList.getFreeHugeMemory());
// now that we do defragmentation the following allocate works.
hugemc = ma.allocate(minHuge + HUGE_MULTIPLE + HUGE_MULTIPLE - 1);
} finally {
MemoryAllocatorImpl.freeOffHeapMemory();
}
}
@Test
public void testChunkCreateDirectByteBuffer() {
SlabImpl slab = new SlabImpl(1024 * 1024);
try {
MemoryAllocatorImpl ma =
MemoryAllocatorImpl.createForUnitTest(new NullOutOfOffHeapMemoryListener(),
new NullOffHeapMemoryStats(), new SlabImpl[] {slab});
ByteBuffer bb = ByteBuffer.allocate(1024);
for (int i = 0; i < 1024; i++) {
bb.put((byte) i);
}
bb.position(0);
OffHeapStoredObject c =
(OffHeapStoredObject) ma.allocateAndInitialize(bb.array(), false, false);
assertEquals(1024, c.getDataSize());
if (!Arrays.equals(bb.array(), c.getRawBytes())) {
fail("arrays are not equal. Expected " + Arrays.toString(bb.array()) + " but found: "
+ Arrays.toString(c.getRawBytes()));
}
ByteBuffer dbb = c.createDirectByteBuffer();
assertEquals(true, dbb.isDirect());
assertEquals(bb, dbb);
} finally {
MemoryAllocatorImpl.freeOffHeapMemory();
}
}
@Test
public void testDebugLog() {
MemoryAllocatorImpl.debugLog("test debug log", false);
MemoryAllocatorImpl.debugLog("test debug log", true);
}
@Test
public void testGetLostChunks() {
SlabImpl slab = new SlabImpl(1024 * 1024);
try {
MemoryAllocatorImpl ma =
MemoryAllocatorImpl.createForUnitTest(new NullOutOfOffHeapMemoryListener(),
new NullOffHeapMemoryStats(), new SlabImpl[] {slab});
assertEquals(Collections.emptyList(), ma.getLostChunks(null));
} finally {
MemoryAllocatorImpl.freeOffHeapMemory();
}
}
@Test
public void testFindSlab() {
final int SLAB_SIZE = 1024 * 1024;
SlabImpl slab = new SlabImpl(SLAB_SIZE);
try {
MemoryAllocatorImpl ma =
MemoryAllocatorImpl.createForUnitTest(new NullOutOfOffHeapMemoryListener(),
new NullOffHeapMemoryStats(), new SlabImpl[] {slab});
assertEquals(0, ma.findSlab(slab.getMemoryAddress()));
assertEquals(0, ma.findSlab(slab.getMemoryAddress() + SLAB_SIZE - 1));
try {
ma.findSlab(slab.getMemoryAddress() - 1);
fail("expected IllegalStateException");
} catch (IllegalStateException expected) {
}
try {
ma.findSlab(slab.getMemoryAddress() + SLAB_SIZE);
fail("expected IllegalStateException");
} catch (IllegalStateException expected) {
}
} finally {
MemoryAllocatorImpl.freeOffHeapMemory();
}
}
@Test
public void testValidateAddressAndSize() {
final int SLAB_SIZE = 1024 * 1024;
SlabImpl slab = new SlabImpl(SLAB_SIZE);
try {
MemoryAllocatorImpl ma =
MemoryAllocatorImpl.createForUnitTest(new NullOutOfOffHeapMemoryListener(),
new NullOffHeapMemoryStats(), new SlabImpl[] {slab});
try {
MemoryAllocatorImpl.validateAddress(0L);
fail("expected IllegalStateException");
} catch (IllegalStateException expected) {
assertEquals("Unexpected exception message: " + expected.getMessage(), true,
expected.getMessage().contains("addr was smaller than expected"));
}
try {
MemoryAllocatorImpl.validateAddress(1L);
fail("expected IllegalStateException");
} catch (IllegalStateException expected) {
assertEquals("Unexpected exception message: " + expected.getMessage(), true, expected
.getMessage().contains("Valid addresses must be in one of the following ranges:"));
}
MemoryAllocatorImpl.validateAddressAndSizeWithinSlab(slab.getMemoryAddress(), SLAB_SIZE,
false);
MemoryAllocatorImpl.validateAddressAndSizeWithinSlab(slab.getMemoryAddress(), SLAB_SIZE,
true);
MemoryAllocatorImpl.validateAddressAndSizeWithinSlab(slab.getMemoryAddress(), -1, true);
try {
MemoryAllocatorImpl.validateAddressAndSizeWithinSlab(slab.getMemoryAddress() - 1, SLAB_SIZE,
true);
fail("expected IllegalStateException");
} catch (IllegalStateException expected) {
assertEquals("Unexpected exception message: " + expected.getMessage(), true,
expected.getMessage()
.equals(" address 0x" + Long.toString(slab.getMemoryAddress() - 1, 16)
+ " does not address the original slab memory"));
}
try {
MemoryAllocatorImpl.validateAddressAndSizeWithinSlab(slab.getMemoryAddress(), SLAB_SIZE + 1,
true);
fail("expected IllegalStateException");
} catch (IllegalStateException expected) {
assertEquals("Unexpected exception message: " + expected.getMessage(), true,
expected.getMessage()
.equals(" address 0x" + Long.toString(slab.getMemoryAddress() + SLAB_SIZE, 16)
+ " does not address the original slab memory"));
}
} finally {
MemoryAllocatorImpl.freeOffHeapMemory();
}
}
@Test
public void testMemoryInspection() {
final int SLAB_SIZE = 1024 * 1024;
SlabImpl slab = new SlabImpl(SLAB_SIZE);
try {
MemoryAllocatorImpl ma =
MemoryAllocatorImpl.createForUnitTest(new NullOutOfOffHeapMemoryListener(),
new NullOffHeapMemoryStats(), new SlabImpl[] {slab});
MemoryInspector inspector = ma.getMemoryInspector();
assertNotNull(inspector);
assertEquals(null, inspector.getFirstBlock());
assertEquals(Collections.emptyList(), inspector.getSnapshot());
assertEquals(Collections.emptyList(), inspector.getAllocatedBlocks());
assertEquals(null, inspector.getBlockAfter(null));
inspector.createSnapshot();
// call this twice for code coverage
inspector.createSnapshot();
try {
assertEquals(inspector.getAllBlocks(), inspector.getSnapshot());
MemoryBlock firstBlock = inspector.getFirstBlock();
assertNotNull(firstBlock);
assertEquals(1024 * 1024, firstBlock.getBlockSize());
assertEquals("N/A", firstBlock.getDataType());
assertEquals(-1, firstBlock.getFreeListId());
assertTrue(firstBlock.getAddress() > 0);
assertNull(firstBlock.getNextBlock());
assertEquals(0, firstBlock.getRefCount());
assertEquals(0, firstBlock.getSlabId());
assertEquals(MemoryBlock.State.UNUSED, firstBlock.getState());
assertFalse(firstBlock.isCompressed());
assertFalse(firstBlock.isSerialized());
assertEquals(null, inspector.getBlockAfter(firstBlock));
} finally {
inspector.clearSnapshot();
}
} finally {
MemoryAllocatorImpl.freeOffHeapMemory();
}
}
@Test
public void testClose() {
System.setProperty(MemoryAllocatorImpl.FREE_OFF_HEAP_MEMORY_PROPERTY, "false");
SlabImpl slab = new SlabImpl(1024 * 1024);
boolean freeSlab = true;
SlabImpl[] slabs = new SlabImpl[] {slab};
try {
MemoryAllocatorImpl ma = MemoryAllocatorImpl.createForUnitTest(
new NullOutOfOffHeapMemoryListener(), new NullOffHeapMemoryStats(), slabs);
ma.close();
ma.close();
System.setProperty(MemoryAllocatorImpl.FREE_OFF_HEAP_MEMORY_PROPERTY, "true");
try {
ma = MemoryAllocatorImpl.createForUnitTest(new NullOutOfOffHeapMemoryListener(),
new NullOffHeapMemoryStats(), slabs);
ma.close();
freeSlab = false;
ma.close();
} finally {
System.clearProperty(MemoryAllocatorImpl.FREE_OFF_HEAP_MEMORY_PROPERTY);
}
} finally {
if (freeSlab) {
MemoryAllocatorImpl.freeOffHeapMemory();
}
}
}
@Test
public void testDefragmentation() {
final int perObjectOverhead = OffHeapStoredObject.HEADER_SIZE;
final int BIG_ALLOC_SIZE = 150000;
final int SMALL_ALLOC_SIZE = BIG_ALLOC_SIZE / 2;
final int TOTAL_MEM = BIG_ALLOC_SIZE;
SlabImpl slab = new SlabImpl(TOTAL_MEM);
try {
MemoryAllocatorImpl ma =
MemoryAllocatorImpl.createForUnitTest(new NullOutOfOffHeapMemoryListener(),
new NullOffHeapMemoryStats(), new SlabImpl[] {slab});
StoredObject bmc = ma.allocate(BIG_ALLOC_SIZE - perObjectOverhead);
try {
StoredObject smc = ma.allocate(SMALL_ALLOC_SIZE - perObjectOverhead);
fail("Expected out of memory");
} catch (OutOfOffHeapMemoryException expected) {
}
bmc.release();
assertEquals(TOTAL_MEM, ma.freeList.getFreeMemory());
StoredObject smc1 = ma.allocate(SMALL_ALLOC_SIZE - perObjectOverhead);
StoredObject smc2 = ma.allocate(SMALL_ALLOC_SIZE - perObjectOverhead);
smc2.release();
assertEquals(TOTAL_MEM - SMALL_ALLOC_SIZE, ma.freeList.getFreeMemory());
try {
bmc = ma.allocate(BIG_ALLOC_SIZE - perObjectOverhead);
fail("Expected out of memory");
} catch (OutOfOffHeapMemoryException expected) {
}
smc1.release();
assertEquals(TOTAL_MEM, ma.freeList.getFreeMemory());
bmc = ma.allocate(BIG_ALLOC_SIZE - perObjectOverhead);
bmc.release();
assertEquals(TOTAL_MEM, ma.freeList.getFreeMemory());
ArrayList<StoredObject> mcs = new ArrayList<>();
for (int i = 0; i < BIG_ALLOC_SIZE / (8 + perObjectOverhead); i++) {
mcs.add(ma.allocate(8));
}
checkMcs(mcs);
assertEquals(0, ma.freeList.getFreeMemory());
try {
ma.allocate(8);
fail("expected out of memory");
} catch (OutOfOffHeapMemoryException expected) {
}
mcs.remove(0).release(); // frees 8+perObjectOverhead
assertEquals(8 + perObjectOverhead, ma.freeList.getFreeMemory());
mcs.remove(0).release(); // frees 8+perObjectOverhead
assertEquals((8 + perObjectOverhead) * 2, ma.freeList.getFreeMemory());
ma.allocate(16).release(); // allocates and frees 16+perObjectOverhead; still have
// perObjectOverhead
assertEquals((8 + perObjectOverhead) * 2, ma.freeList.getFreeMemory());
mcs.remove(0).release(); // frees 8+perObjectOverhead
assertEquals((8 + perObjectOverhead) * 3, ma.freeList.getFreeMemory());
mcs.remove(0).release(); // frees 8+perObjectOverhead
assertEquals((8 + perObjectOverhead) * 4, ma.freeList.getFreeMemory());
// At this point I should have 8*4 + perObjectOverhead*4 of free memory
ma.allocate(8 * 4 + perObjectOverhead * 3).release();
assertEquals((8 + perObjectOverhead) * 4, ma.freeList.getFreeMemory());
mcs.remove(0).release(); // frees 8+perObjectOverhead
assertEquals((8 + perObjectOverhead) * 5, ma.freeList.getFreeMemory());
// At this point I should have 8*5 + perObjectOverhead*5 of free memory
try {
ma.allocate((8 * 5 + perObjectOverhead * 4) + 1);
fail("expected out of memory");
} catch (OutOfOffHeapMemoryException expected) {
}
mcs.remove(0).release(); // frees 8+perObjectOverhead
assertEquals((8 + perObjectOverhead) * 6, ma.freeList.getFreeMemory());
checkMcs(mcs);
// At this point I should have 8*6 + perObjectOverhead*6 of free memory
StoredObject mc24 = ma.allocate(24);
checkMcs(mcs);
assertEquals((8 + perObjectOverhead) * 6 - (24 + perObjectOverhead),
ma.freeList.getFreeMemory());
// At this point I should have 8*3 + perObjectOverhead*5 of free memory
StoredObject mc16 = ma.allocate(16);
checkMcs(mcs);
assertEquals(
(8 + perObjectOverhead) * 6 - (24 + perObjectOverhead) - (16 + perObjectOverhead),
ma.freeList.getFreeMemory());
// At this point I should have 8*1 + perObjectOverhead*4 of free memory
mcs.add(ma.allocate(8));
checkMcs(mcs);
assertEquals((8 + perObjectOverhead) * 6 - (24 + perObjectOverhead) - (16 + perObjectOverhead)
- (8 + perObjectOverhead), ma.freeList.getFreeMemory());
// At this point I should have 8*0 + perObjectOverhead*3 of free memory
StoredObject mcDO = ma.allocate(perObjectOverhead * 2);
checkMcs(mcs);
// At this point I should have 8*0 + perObjectOverhead*0 of free memory
assertEquals(0, ma.freeList.getFreeMemory());
try {
ma.allocate(1);
fail("expected out of memory");
} catch (OutOfOffHeapMemoryException expected) {
}
checkMcs(mcs);
assertEquals(0, ma.freeList.getFreeMemory());
mcDO.release();
assertEquals((perObjectOverhead * 3), ma.freeList.getFreeMemory());
mcs.remove(mcs.size() - 1).release();
assertEquals((perObjectOverhead * 3) + (8 + perObjectOverhead), ma.freeList.getFreeMemory());
mc16.release();
assertEquals((perObjectOverhead * 3) + (8 + perObjectOverhead) + (16 + perObjectOverhead),
ma.freeList.getFreeMemory());
mc24.release();
assertEquals((perObjectOverhead * 3) + (8 + perObjectOverhead) + (16 + perObjectOverhead)
+ (24 + perObjectOverhead), ma.freeList.getFreeMemory());
long freeMem = ma.freeList.getFreeMemory();
for (StoredObject mc : mcs) {
mc.release();
assertEquals(freeMem + (8 + perObjectOverhead), ma.freeList.getFreeMemory());
freeMem += (8 + perObjectOverhead);
}
mcs.clear();
assertEquals(TOTAL_MEM, ma.freeList.getFreeMemory());
bmc = ma.allocate(BIG_ALLOC_SIZE - perObjectOverhead);
bmc.release();
} finally {
MemoryAllocatorImpl.freeOffHeapMemory();
}
}
@Test
public void testUsageEventListener() {
final int perObjectOverhead = OffHeapStoredObject.HEADER_SIZE;
final int SMALL_ALLOC_SIZE = 1000;
SlabImpl slab = new SlabImpl(3000);
try {
MemoryAllocatorImpl ma =
MemoryAllocatorImpl.createForUnitTest(new NullOutOfOffHeapMemoryListener(),
new NullOffHeapMemoryStats(), new SlabImpl[] {slab});
MemoryUsageListener listener = bytesUsed -> {
memoryUsageEventReceived = true;
assertEquals(expectedMemoryUsage, bytesUsed);
};
ma.addMemoryUsageListener(listener);
expectedMemoryUsage = SMALL_ALLOC_SIZE;
memoryUsageEventReceived = false;
StoredObject smc = ma.allocate(SMALL_ALLOC_SIZE - perObjectOverhead);
assertEquals(true, memoryUsageEventReceived);
expectedMemoryUsage = SMALL_ALLOC_SIZE * 2;
memoryUsageEventReceived = false;
smc = ma.allocate(SMALL_ALLOC_SIZE - perObjectOverhead);
assertEquals(true, memoryUsageEventReceived);
MemoryUsageListener unaddedListener = bytesUsed -> {
throw new IllegalStateException("Should never be called");
};
ma.removeMemoryUsageListener(unaddedListener);
ma.removeMemoryUsageListener(listener);
ma.removeMemoryUsageListener(unaddedListener);
expectedMemoryUsage = SMALL_ALLOC_SIZE * 2;
memoryUsageEventReceived = false;
smc = ma.allocate(SMALL_ALLOC_SIZE - perObjectOverhead);
assertEquals(false, memoryUsageEventReceived);
} finally {
MemoryAllocatorImpl.freeOffHeapMemory();
}
}
private void checkMcs(ArrayList<StoredObject> mcs) {
for (StoredObject mc : mcs) {
assertEquals(8 + 8, mc.getSize());
}
}
@Test
public void testOutOfOffHeapMemory() {
final int perObjectOverhead = OffHeapStoredObject.HEADER_SIZE;
final int BIG_ALLOC_SIZE = 150000;
final int SMALL_ALLOC_SIZE = BIG_ALLOC_SIZE / 2;
final int TOTAL_MEM = BIG_ALLOC_SIZE;
final SlabImpl slab = new SlabImpl(TOTAL_MEM);
final AtomicReference<OutOfOffHeapMemoryException> ooom =
new AtomicReference<>();
final OutOfOffHeapMemoryListener oooml = new OutOfOffHeapMemoryListener() {
@Override
public void outOfOffHeapMemory(OutOfOffHeapMemoryException cause) {
ooom.set(cause);
}
@Override
public void close() {}
};
try {
MemoryAllocatorImpl ma = MemoryAllocatorImpl.createForUnitTest(oooml,
new NullOffHeapMemoryStats(), new SlabImpl[] {slab});
// make a big allocation
StoredObject bmc = ma.allocate(BIG_ALLOC_SIZE - perObjectOverhead);
assertNull(ooom.get());
// drive the ma to ooom with small allocations
try {
StoredObject smc = ma.allocate(SMALL_ALLOC_SIZE - perObjectOverhead);
fail("Expected out of memory");
} catch (OutOfOffHeapMemoryException expected) {
}
assertNotNull(ooom.get());
assertTrue(
ooom.get().getMessage().contains("Out of off-heap memory. Could not allocate size of "));
} finally {
MemoryAllocatorImpl.freeOffHeapMemory();
}
}
}