@Override public void reset(int size) { // reset allocations/frees count first, as it's not used // for any functionality but to show information metrics.reset(); // --- used try { usedLock.writeLock().lock(); used.clear(); usedMemorySize.set(0); } finally { usedLock.writeLock().unlock(); } metrics.mark("vmtable.usedSize", usedMemorySize.longValue()); metrics.mark("vmtable.usedBlocksCount", used.size()); // --- free try { freeLock.writeLock().lock(); free.clear(); free.add(new TableBlock(0, size)); freeMemorySize.set(size); } finally { freeLock.writeLock().unlock(); } metrics.mark("vmtable.freeSize", freeMemorySize.longValue()); metrics.mark("vmtable.freeBlocksCount", free.size()); }
@Override public boolean free(Block block) { if (block == null) { return false; } metrics.increment("vmtable.totalFrees"); TimeContext timer = metrics.getTimer("vmtable.freeTime"); timer.start(); TableBlock tableBlock = getSimilarBlock(used, block, usedLock); if (tableBlock != null) { if (removeBlock(used, tableBlock, usedLock)) { int size = tableBlock.getSize(); usedMemorySize.addAndGet(-size); addFreeBlock(new TableBlock(tableBlock.getAddress(), tableBlock.getSize())); freeMemorySize.addAndGet(size); tableBlock.resize(0, 0); tableBlock.unlock(); timer.stop(); metrics.mark("vmtable.freeSize", freeMemorySize.longValue()); metrics.mark("vmtable.usedSize", usedMemorySize.longValue()); metrics.mark("vmtable.freeBlocksCount", free.size()); metrics.mark("vmtable.usedBlocksCount", used.size()); return true; } } timer.stop(); metrics.increment("vmtable.failedFrees"); return false; }
@Override public void increaseSize(int size) { final int freeSize = freeMemorySize.get(); final int usedSize = usedMemorySize.get(); final int totalSize = freeSize + usedSize; // increase memory size int incSize = size - totalSize; addFreeBlock(new TableBlock(totalSize, incSize)); freeMemorySize.addAndGet(incSize); metrics.increment("vmtable.increases"); }
@Override public Block allocate(int size) { if (size <= 0) { throw new OutOfBoundException("Size can't be negative or be zero: " + size); } metrics.increment("vmtable.totalAllocations"); TimeContext timer = metrics.getTimer("vmtable.allocationTime"); timer.start(); final TableBlock freeBlock = findBlockToAllocateFrom(size); if (freeBlock != null) { final TableBlock result; try { result = new TableBlock(freeBlock.getAddress(), size); if (freeBlock.getSize() == size) { freeBlock.resize(0, 0); removeBlock(free, freeBlock, freeLock); metrics.decrement("vmtable.fragmentation"); } else { freeBlock.resize(freeBlock.getAddress() + size, freeBlock.getSize() - size); metrics.increment("vmtable.fragmentation"); } freeMemorySize.addAndGet(-size); } finally { // unlock asap freeBlock.unlock(); } insertBlock(used, result, usedLock); usedMemorySize.addAndGet(size); timer.stop(); metrics.mark("vmtable.freeSize", freeMemorySize.longValue()); metrics.mark("vmtable.usedSize", usedMemorySize.longValue()); metrics.mark("vmtable.freeBlocksCount", free.size()); metrics.mark("vmtable.usedBlocksCount", used.size()); return result; } timer.stop(); metrics.increment("vmtable.failedAllocations"); return null; }
protected TableBlock findBlockToAllocateFrom(int size) { // TODO - do we need this loop restriction? int loop = 0; boolean repeat; do { repeat = false; try { freeLock.readLock().lock(); for (TableBlock each : free) { if (each.getSize() >= size) { if (each.lock()) { if (each.getSize() >= size) { return each; } each.unlock(); } else { // looks like there was a block that was enough to allocate from // but now it's locked, so need to loop the list of blocks again. repeat = true; } } } } finally { freeLock.readLock().unlock(); } metrics.increment("vmtable.loopsToFindFitBlock"); // avoid ever looping, instead prefer to wait or exit the loop loop++; if (loop > MAX_ALLOC_LOOPS) { break; } else if (loop == MAX_ALLOC_LOOPS) { sleep(); } else if (loop >= YIELD_MARGIN) { Thread.yield(); } } while (repeat); return null; }
public LinkedVirtualMemoryTable(int size) { free.add(new TableBlock(0, size)); usedMemorySize = new AtomicInteger(0); freeMemorySize = new AtomicInteger(size); // add metrics metrics = new Metrics(); metrics.addValueMetric("vmtable.totalAllocations"); metrics.addValueMetric("vmtable.failedAllocations"); metrics.addValueMetric("vmtable.totalFrees"); metrics.addValueMetric("vmtable.failedFrees"); metrics.addValueMetric("vmtable.increases"); metrics.addValueMetric("vmtable.loopsToFindFitBlock"); metrics.addValueMetric("vmtable.fragmentation"); metrics.addValueMetric("vmtable.freeSize"); metrics.addValueMetric("vmtable.usedSize"); metrics.addValueMetric("vmtable.freeBlocksCount"); metrics.addValueMetric("vmtable.usedBlocksCount"); metrics.addTimerMetric("vmtable.allocationTime"); metrics.addTimerMetric("vmtable.freeTime"); }
/** * Tries to find and extend a free memory with specified block. If memory can't be extended with * such block, then returns false. * * @param block the memory block to extend free memory with. * @return true if memory was extended, otherwise false. */ protected boolean extendFreeMemory(TableBlock block) { final int blockAddress = block.getAddress(); final int blockEnd = block.getEnd(); TableBlock head = null; TableBlock tail = null; boolean repeat; int loop = 0; do { // TODO - metrics for these loops repeat = false; try { freeLock.readLock().lock(); for (TableBlock each : free) { if (head == null && each.getAddress() == blockEnd) { if (each.lock()) { if (each.getAddress() != blockEnd) { each.unlock(); } else { head = each; } } else { repeat = true; } } else if (tail == null && each.getEnd() == blockAddress) { if (each.lock()) { if (each.getEnd() != blockAddress) { each.unlock(); } else { tail = each; } } else { repeat = true; } } if (head != null && tail != null) { repeat = false; break; } } } finally { freeLock.readLock().unlock(); } loop++; if (loop > MAX_ALLOC_LOOPS) { break; } else if (loop == MAX_ALLOC_LOOPS) { sleep(); } else if (loop >= YIELD_MARGIN) { Thread.yield(); } } while (repeat); // if there is a head or tail - then resize and return true if (tail != null) { if (head == null) { // only tail is found tail.setSize(block.getSize() + tail.getSize()); } else { // head is found, so we just resize tail and remove head tail.setSize(block.getSize() + tail.getSize() + head.getSize()); removeBlock(free, head, freeLock); head.unlock(); // we want to decrease fragmentation twice for this case, because we merged 3 blocks into 1 // so this is decrement #1 and the next is at the end of method // TODO - investigate why double decrement is needed?! metrics.decrement("vmtable.fragmentation", 2); } tail.unlock(); } else if (head != null) { head.resize(blockAddress, block.getSize() + head.getSize()); head.unlock(); } else { // nothing was changed return false; } metrics.decrement("vmtable.fragmentation"); return true; }
/** * Add a block to the free memory list. * * @param block the block that need to be added to the free memory list. */ protected void addFreeBlock(TableBlock block) { if (!extendFreeMemory(block)) { insertBlock(free, block, freeLock); metrics.increment("vmtable.fragmentation"); } }