/* * Copyright 2015 Odnoklassniki Ltd, Mail.Ru Group * * 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 one.nio.mem; import one.nio.mgt.Management; import static one.nio.util.JavaInternals.unsafe; /** * The simplified implementation of Doug Lea's Memory Allocator. * See http://g.oswego.edu/dl/html/malloc.html */ public class Malloc implements MallocMXBean { static final long SIGNATURE = 0x3230636f6c6c614dL; static final int SIGNATURE_OFFSET = 0; static final int CAPACITY_OFFSET = 8; static final int BASE_OFFSET = 16; static final int HEADER_SIZE = 8; static final int SIZE_OFFSET = 0; static final int LEFT_OFFSET = 4; static final int NEXT_OFFSET = 8; static final int PREV_OFFSET = 16; static final int BIN_COUNT = 120; static final int BIN_SIZE = 8; static final int BIN_SPACE = BIN_COUNT * BIN_SIZE + 64; static final int MAX_CHUNK = HEADER_SIZE + 1024 * 1024 * 1024; static final int MIN_CHUNK = HEADER_SIZE + 16; static final int OCCUPIED_MASK = 0x80000000; static final int FREE_MASK = 0x7ffffff8; static final int MAX_SCAN_COUNT = 127; final long base; final long capacity; long freeMemory; Malloc next; int mask = OCCUPIED_MASK; public Malloc(long capacity) { this.capacity = capacity & ~7; this.base = DirectMemory.allocateAndFill(this.capacity, this, (byte) 0); init(); } public Malloc(long base, long capacity) { this.base = base; this.capacity = capacity & ~7; init(); } public Malloc(MappedFile mmap) { this.base = mmap.getAddr(); this.capacity = mmap.getSize(); init(); } public long base() { return base; } public long getTotalMemory() { return capacity; } public long getFreeMemory() { return freeMemory; } public long getUsedMemory() { return getTotalMemory() - getFreeMemory(); } public long calloc(int size) { long address = malloc(size); unsafe.setMemory(address, size, (byte) 0); return address; } public long malloc(int size) { int alignedSize = (Math.max(size, 16) + (HEADER_SIZE + 7)) & ~7; long address = mallocImpl(getBin(alignedSize), alignedSize); if (address != 0) { return address; } throw new OutOfMemoryException("Failed to allocate " + size + " bytes"); } final synchronized long mallocImpl(int bin, int size) { do { long address = findChunk(base + bin * BIN_SIZE, size); if (address != 0) { return address + HEADER_SIZE; } } while (++bin < BIN_COUNT); return 0; } public synchronized void free(long address) { address -= HEADER_SIZE; // Calculate the addresses of the neighbour chunks int size = unsafe.getInt(address + SIZE_OFFSET) & FREE_MASK; long leftChunk = address - unsafe.getInt(address + LEFT_OFFSET); long rightChunk = address + size; int leftSize = unsafe.getInt(leftChunk + SIZE_OFFSET); int rightSize = unsafe.getInt(rightChunk + SIZE_OFFSET); freeMemory += size; // Coalesce with left neighbour chunk if it is free if (leftSize > 0) { size += leftSize; removeFreeChunk(leftChunk); address = leftChunk; } // Coalesce with right neighbour chunk if it is free if (rightSize > 0) { size += rightSize; removeFreeChunk(rightChunk); } // Return the combined chunk to the bin addFreeChunk(address, size); } // Get the actual size of the allocated block or 0 if the given address is not allocated public int allocatedSize(long address) { address -= HEADER_SIZE; if (address >= base + BIN_SPACE && address < base + capacity - HEADER_SIZE * 2) { int size = unsafe.getInt(address + SIZE_OFFSET); if ((size & OCCUPIED_MASK) != 0) { return (size & FREE_MASK) - HEADER_SIZE; } } return 0; } // Verify the layout of the heap. Expensive operation, used only for debug purposes public synchronized void verify() { long start = base + BIN_SPACE; long end = base + capacity - HEADER_SIZE * 2; long actualFree = 0; for (int prevSize = 0; start < end; start += prevSize) { if (unsafe.getInt(start + LEFT_OFFSET) != prevSize) { throw new AssertionError("Corrupted chunk at address 0x" + Long.toHexString(start)); } int size = unsafe.getInt(start + SIZE_OFFSET); if (size > 0) { actualFree += size; } prevSize = size & FREE_MASK; } if (freeMemory != actualFree) { throw new AssertionError("Corrupted freeMemory: stored=" + freeMemory +", actual=" + actualFree); } } // Initial setup of the empty heap void init() { long signature = unsafe.getLong(base + SIGNATURE_OFFSET); // If the heap already contains data (e.g. backed by an existing file), do relocation instead of initialization if (signature != 0) { if (signature != SIGNATURE) { throw new IllegalArgumentException("Incompatible Malloc image"); } else if (unsafe.getLong(base + CAPACITY_OFFSET) != capacity) { throw new IllegalArgumentException("Malloc capacity mismatch"); } long oldBase = unsafe.getLong(base + BASE_OFFSET); unsafe.putLong(base + BASE_OFFSET, base); relocate(base - oldBase); } else { unsafe.putLong(base + SIGNATURE_OFFSET, SIGNATURE); unsafe.putLong(base + CAPACITY_OFFSET, capacity); unsafe.putLong(base + BASE_OFFSET, base); long start = base + BIN_SPACE; long end = base + capacity - HEADER_SIZE * 2; if (end - start < MIN_CHUNK) { throw new IllegalArgumentException("Malloc area too small"); } // Initialize the bins with the chunks of the maximum possible size do { int size = (int) Math.min(end - start, MAX_CHUNK); addFreeChunk(start, size); addBoundary(start + size); freeMemory += size; start += size + HEADER_SIZE; } while (end - start >= MIN_CHUNK); } Management.registerMXBean(this, "one.nio.mem:type=Malloc,base=" + Long.toHexString(base)); } // Relocate absolute pointers when the heap is loaded from a snapshot private void relocate(long delta) { for (int bin = getBin(MIN_CHUNK); bin < BIN_COUNT; bin++) { long prev = base + bin * BIN_SIZE; for (long chunk; (chunk = unsafe.getLong(prev + NEXT_OFFSET)) != 0; prev = chunk) { chunk += delta; freeMemory += unsafe.getInt(chunk + SIZE_OFFSET); unsafe.putLong(prev + NEXT_OFFSET, chunk); unsafe.putLong(chunk + PREV_OFFSET, prev); } } } // Separate large chunks by occupied boundaries to prevent coalescing private void addBoundary(long address) { unsafe.putInt(address + SIZE_OFFSET, HEADER_SIZE | mask); unsafe.putInt(address + HEADER_SIZE + LEFT_OFFSET, HEADER_SIZE); } // Find a suitable chunk in the given bin using best-fit strategy private long findChunk(long binAddress, int size) { int bestFitSize = Integer.MAX_VALUE; long bestFitChunk = 0; int scanCount = MAX_SCAN_COUNT; for (long chunk = binAddress; (chunk = unsafe.getLong(chunk + NEXT_OFFSET)) != 0; ) { int chunkSize = unsafe.getInt(chunk + SIZE_OFFSET); int leftoverSize = chunkSize - size; if (leftoverSize < 0) { // Continue search } else if (leftoverSize < MIN_CHUNK) { // Allocated memory perfectly fits the chunk unsafe.putInt(chunk + SIZE_OFFSET, chunkSize | mask); freeMemory -= chunkSize; removeFreeChunk(chunk); return chunk; } else if (leftoverSize < bestFitSize) { // Search for a chunk with the minimum leftover size bestFitSize = leftoverSize; bestFitChunk = chunk; } else if (--scanCount <= 0 && bestFitChunk != 0) { // Do not let scan for too long break; } } if (bestFitChunk != 0) { // Allocate memory from the best-sized chunk unsafe.putInt(bestFitChunk + SIZE_OFFSET, size | mask); freeMemory -= size; removeFreeChunk(bestFitChunk); // Cut off the remaining tail and return it to the bin as a smaller chunk long leftoverChunk = bestFitChunk + size; addFreeChunk(leftoverChunk, bestFitSize); unsafe.putInt(leftoverChunk + LEFT_OFFSET, size); } return bestFitChunk; } // Insert a new chunk in the head of the linked list of free chunks of a suitable bin private void addFreeChunk(long address, int size) { unsafe.putInt(address + SIZE_OFFSET, size); unsafe.putInt(address + size + LEFT_OFFSET, size); long binAddress = base + getBin(size) * BIN_SIZE; long head = unsafe.getLong(binAddress + NEXT_OFFSET); unsafe.putLong(address + NEXT_OFFSET, head); unsafe.putLong(address + PREV_OFFSET, binAddress); unsafe.putLong(binAddress + NEXT_OFFSET, address); if (head != 0) { unsafe.putLong(head + PREV_OFFSET, address); } } // Remove a chunk from the linked list of free chunks private void removeFreeChunk(long address) { long next = unsafe.getLong(address + NEXT_OFFSET); long prev = unsafe.getLong(address + PREV_OFFSET); unsafe.putLong(prev + NEXT_OFFSET, next); if (next != 0) { unsafe.putLong(next + PREV_OFFSET, prev); } } // Calculate the address of the smallest bin which holds chunks of the given size. // Bins grow somewhat logarithmically: 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192 ... static int getBin(int size) { size -= HEADER_SIZE + 1; int index = 29 - Integer.numberOfLeadingZeros(size); return (index << 2) + ((size >>> index) & 3); } static int binSize(int bin) { bin++; return (4 + (bin & 3)) << (bin >>> 2); } }