blob: ddb5b76e971f541f7138e9bfd5be120eeef6d8e4 [file] [log] [blame]
package com.gemstone.gemfire.internal.offheap;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
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.concurrent.atomic.AtomicReference;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import com.gemstone.gemfire.OutOfOffHeapMemoryException;
import com.gemstone.gemfire.internal.offheap.SimpleMemoryAllocatorImpl.Chunk;
import com.gemstone.gemfire.test.junit.categories.UnitTest;
@Category(UnitTest.class)
public class SimpleMemoryAllocatorJUnitTest {
private static int round(int multiple, int v) {
return ((v+multiple-1)/multiple)*multiple;
}
@Test
public void testBasics() {
int BATCH_SIZE = com.gemstone.gemfire.internal.offheap.SimpleMemoryAllocatorImpl.BATCH_SIZE;
int TINY_MULTIPLE = com.gemstone.gemfire.internal.offheap.SimpleMemoryAllocatorImpl.TINY_MULTIPLE;
// int BIG_MULTIPLE = com.gemstone.gemfire.internal.offheap.SimpleMemoryAllocatorImpl.FreeListManager.BIG_MULTIPLE;
int HUGE_MULTIPLE = com.gemstone.gemfire.internal.offheap.SimpleMemoryAllocatorImpl.HUGE_MULTIPLE;
int perObjectOverhead = com.gemstone.gemfire.internal.offheap.SimpleMemoryAllocatorImpl.Chunk.OFF_HEAP_HEADER_SIZE;
int maxTiny = com.gemstone.gemfire.internal.offheap.SimpleMemoryAllocatorImpl.MAX_TINY-perObjectOverhead;
// int MIN_BIG_SIZE = round(BIG_MULTIPLE, maxTiny+perObjectOverhead+1)-perObjectOverhead;
// int maxBig = com.gemstone.gemfire.internal.offheap.SimpleMemoryAllocatorImpl.FreeListManager.MAX_BIG-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);
UnsafeMemoryChunk slab = new UnsafeMemoryChunk(TOTAL_MEM);
try {
SimpleMemoryAllocatorImpl ma = SimpleMemoryAllocatorImpl.create(new NullOutOfOffHeapMemoryListener(), new NullOffHeapMemoryStats(), new UnsafeMemoryChunk[]{slab});
assertEquals(TOTAL_MEM, ma.getFreeMemory());
assertEquals(TOTAL_MEM, ma.freeList.getFreeFragmentMemory());
assertEquals(0, ma.freeList.getFreeTinyMemory());
// assertEquals(0, ma.freeList.getFreeBigMemory());
assertEquals(0, ma.freeList.getFreeHugeMemory());
MemoryChunk tinymc = ma.allocate(maxTiny, null);
assertEquals(TOTAL_MEM-round(TINY_MULTIPLE, maxTiny+perObjectOverhead), ma.getFreeMemory());
assertEquals(round(TINY_MULTIPLE, maxTiny+perObjectOverhead)*(BATCH_SIZE-1), ma.freeList.getFreeTinyMemory());
// MemoryChunk bigmc = ma.allocate(maxBig);
// assertEquals(TOTAL_MEM-round(BIG_MULTIPLE, maxBig+perObjectOverhead)-round(TINY_MULTIPLE, maxTiny+perObjectOverhead), ma.getFreeMemory());
// assertEquals(round(BIG_MULTIPLE, maxBig+perObjectOverhead)*(BATCH_SIZE-1), ma.getFreeList().getFreeBigMemory());
MemoryChunk hugemc = ma.allocate(minHuge, null);
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());
// long oldFreeBigMemory = ma.freeList.getFreeBigMemory();
// bigmc.free();
// assertEquals(round(BIG_MULTIPLE, maxBig+perObjectOverhead), ma.freeList.getFreeBigMemory()-oldFreeBigMemory);
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, null);
assertEquals(oldFreeTinyMemory, ma.freeList.getFreeTinyMemory());
assertEquals(TOTAL_MEM-round(TINY_MULTIPLE, maxTiny+perObjectOverhead), ma.getFreeMemory());
// bigmc = ma.allocate(maxBig);
// assertEquals(oldFreeBigMemory, ma.freeList.getFreeBigMemory());
// assertEquals(TOTAL_MEM-round(BIG_MULTIPLE, maxBig+perObjectOverhead)-round(TINY_MULTIPLE, maxTiny+perObjectOverhead), ma.getFreeMemory());
hugemc = ma.allocate(minHuge, null);
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());
// bigmc.free();
// assertEquals(round(BIG_MULTIPLE, maxBig+perObjectOverhead), ma.freeList.getFreeBigMemory()-oldFreeBigMemory);
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, null);
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);
// bigmc = ma.allocate(MIN_BIG_SIZE);
// assertEquals(MIN_BIG_SIZE+perObjectOverhead, bigmc.getSize());
// assertEquals(freeSlab-((MIN_BIG_SIZE+perObjectOverhead)*BATCH_SIZE), ma.freeList.getFreeFragmentMemory());
hugemc = ma.allocate(minHuge+1, null);
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, null);
assertEquals(round(TINY_MULTIPLE, minHuge+perObjectOverhead)*(BATCH_SIZE-1), ma.freeList.getFreeHugeMemory());
if (BATCH_SIZE > 1) {
MemoryChunk hugemc2 = ma.allocate(minHuge, null);
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 compaction the following allocate works.
hugemc = ma.allocate(minHuge + HUGE_MULTIPLE + HUGE_MULTIPLE-1, null);
// assertEquals(minHuge+minHuge+1, ma.freeList.getFreeHugeMemory());
// hugemc.free();
// assertEquals(minHuge+minHuge+1+minHuge + HUGE_MULTIPLE + HUGE_MULTIPLE-1, ma.freeList.getFreeHugeMemory());
// hugemc = ma.allocate(minHuge + HUGE_MULTIPLE);
// assertEquals(minHuge + HUGE_MULTIPLE + HUGE_MULTIPLE-1, hugemc.getSize());
// assertEquals(minHuge+minHuge+1, ma.freeList.getFreeHugeMemory());
} finally {
SimpleMemoryAllocatorImpl.freeOffHeapMemory();
}
}
@Test
public void testChunkCreateDirectByteBuffer() {
UnsafeMemoryChunk slab = new UnsafeMemoryChunk(1024*1024);
try {
SimpleMemoryAllocatorImpl ma = SimpleMemoryAllocatorImpl.create(new NullOutOfOffHeapMemoryListener(), new NullOffHeapMemoryStats(), new UnsafeMemoryChunk[]{slab});
ByteBuffer bb = ByteBuffer.allocate(1024);
for (int i=0; i < 1024; i++) {
bb.put((byte) i);
}
bb.position(0);
Chunk c = (Chunk) ma.allocateAndInitialize(bb.array(), false, false, null);
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 {
SimpleMemoryAllocatorImpl.freeOffHeapMemory();
}
}
@Test
public void testCompaction() {
final int perObjectOverhead = com.gemstone.gemfire.internal.offheap.SimpleMemoryAllocatorImpl.Chunk.OFF_HEAP_HEADER_SIZE;
final int BIG_ALLOC_SIZE = 150000;
final int SMALL_ALLOC_SIZE = BIG_ALLOC_SIZE/2;
final int TOTAL_MEM = BIG_ALLOC_SIZE;
UnsafeMemoryChunk slab = new UnsafeMemoryChunk(TOTAL_MEM);
try {
SimpleMemoryAllocatorImpl ma = SimpleMemoryAllocatorImpl.create(new NullOutOfOffHeapMemoryListener(), new NullOffHeapMemoryStats(), new UnsafeMemoryChunk[]{slab});
MemoryChunk bmc = ma.allocate(BIG_ALLOC_SIZE-perObjectOverhead, null);
try {
MemoryChunk smc = ma.allocate(SMALL_ALLOC_SIZE-perObjectOverhead, null);
fail("Expected out of memory");
} catch (OutOfOffHeapMemoryException expected) {
}
bmc.release();
assertEquals(TOTAL_MEM, ma.freeList.getFreeMemory());
MemoryChunk smc1 = ma.allocate(SMALL_ALLOC_SIZE-perObjectOverhead, null);
MemoryChunk smc2 = ma.allocate(SMALL_ALLOC_SIZE-perObjectOverhead, null);
smc2.release();
assertEquals(TOTAL_MEM-SMALL_ALLOC_SIZE, ma.freeList.getFreeMemory());
try {
bmc = ma.allocate(BIG_ALLOC_SIZE-perObjectOverhead, null);
fail("Expected out of memory");
} catch (OutOfOffHeapMemoryException expected) {
}
smc1.release();
assertEquals(TOTAL_MEM, ma.freeList.getFreeMemory());
bmc = ma.allocate(BIG_ALLOC_SIZE-perObjectOverhead, null);
bmc.release();
assertEquals(TOTAL_MEM, ma.freeList.getFreeMemory());
ArrayList<MemoryChunk> mcs = new ArrayList<MemoryChunk>();
for (int i=0; i < BIG_ALLOC_SIZE/(8+perObjectOverhead); i++) {
mcs.add(ma.allocate(8, null));
}
checkMcs(mcs);
assertEquals(0, ma.freeList.getFreeMemory());
try {
ma.allocate(8, null);
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, null).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, null).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, null);
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
MemoryChunk mc24 = ma.allocate(24, null);
checkMcs(mcs);
assertEquals((8+perObjectOverhead)*6 - (24+perObjectOverhead), ma.freeList.getFreeMemory());
// At this point I should have 8*3 + perObjectOverhead*5 of free memory
MemoryChunk mc16 = ma.allocate(16, null);
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, null));
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
MemoryChunk mcDO = ma.allocate(perObjectOverhead*2, null);
checkMcs(mcs);
// At this point I should have 8*0 + perObjectOverhead*0 of free memory
assertEquals(0, ma.freeList.getFreeMemory());
try {
ma.allocate(1, null);
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 (MemoryChunk 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, null);
bmc.release();
} finally {
SimpleMemoryAllocatorImpl.freeOffHeapMemory();
}
}
long expectedMemoryUsage;
boolean memoryUsageEventReceived;
@Test
public void testUsageEventListener() {
final int perObjectOverhead = com.gemstone.gemfire.internal.offheap.SimpleMemoryAllocatorImpl.Chunk.OFF_HEAP_HEADER_SIZE;
final int SMALL_ALLOC_SIZE = 1000;
UnsafeMemoryChunk slab = new UnsafeMemoryChunk(3000);
try {
SimpleMemoryAllocatorImpl ma = SimpleMemoryAllocatorImpl.create(new NullOutOfOffHeapMemoryListener(), new NullOffHeapMemoryStats(), new UnsafeMemoryChunk[]{slab});
MemoryUsageListener listener = new MemoryUsageListener() {
@Override
public void updateMemoryUsed(final long bytesUsed) {
SimpleMemoryAllocatorJUnitTest.this.memoryUsageEventReceived = true;
assertEquals(SimpleMemoryAllocatorJUnitTest.this.expectedMemoryUsage, bytesUsed);
}
};
ma.addMemoryUsageListener(listener);
this.expectedMemoryUsage = SMALL_ALLOC_SIZE;
this.memoryUsageEventReceived = false;
MemoryChunk smc = ma.allocate(SMALL_ALLOC_SIZE-perObjectOverhead, null);
assertEquals(true, this.memoryUsageEventReceived);
this.expectedMemoryUsage = SMALL_ALLOC_SIZE * 2;
this.memoryUsageEventReceived = false;
smc = ma.allocate(SMALL_ALLOC_SIZE-perObjectOverhead, null);
assertEquals(true, this.memoryUsageEventReceived);
ma.removeMemoryUsageListener(listener);
this.expectedMemoryUsage = SMALL_ALLOC_SIZE * 2;
this.memoryUsageEventReceived = false;
smc = ma.allocate(SMALL_ALLOC_SIZE-perObjectOverhead, null);
assertEquals(false, this.memoryUsageEventReceived);
} finally {
SimpleMemoryAllocatorImpl.freeOffHeapMemory();
}
}
private void checkMcs(ArrayList<MemoryChunk> mcs) {
for (MemoryChunk mc: mcs) {
assertEquals(8+8, mc.getSize());
}
}
@Test
public void testOutOfOffHeapMemory() {
final int perObjectOverhead = com.gemstone.gemfire.internal.offheap.SimpleMemoryAllocatorImpl.Chunk.OFF_HEAP_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 UnsafeMemoryChunk slab = new UnsafeMemoryChunk(TOTAL_MEM);
final AtomicReference<OutOfOffHeapMemoryException> ooom = new AtomicReference<OutOfOffHeapMemoryException>();
final OutOfOffHeapMemoryListener oooml = new OutOfOffHeapMemoryListener() {
@Override
public void outOfOffHeapMemory(OutOfOffHeapMemoryException cause) {
ooom.set(cause);
}
@Override
public void close() {
}
};
try {
SimpleMemoryAllocatorImpl ma = SimpleMemoryAllocatorImpl.create(oooml, new NullOffHeapMemoryStats(), new UnsafeMemoryChunk[]{slab});
// make a big allocation
MemoryChunk bmc = ma.allocate(BIG_ALLOC_SIZE-perObjectOverhead, null);
assertNull(ooom.get());
// drive the ma to ooom with small allocations
try {
MemoryChunk smc = ma.allocate(SMALL_ALLOC_SIZE-perObjectOverhead, null);
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 {
SimpleMemoryAllocatorImpl.freeOffHeapMemory();
}
}
}