
org.gridkit.jvmtool.heapdump.io.PagedVirtualMemory Maven / Gradle / Ivy
package org.gridkit.jvmtool.heapdump.io;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import org.gridkit.jvmtool.heapdump.io.PagePool.NoMorePagesException;
/**
*
* This class manages pages and their mapping
* to virtual address space.
*
*
* It offers basic memory access utilities along side
* with page fault management and hot page accounting.
*
*
* @author Alexey Ragozin ([email protected])
*/
public abstract class PagedVirtualMemory {
private static final long SANITY_LIMIT = 1l << 40; // one terra byte
private static final int LOAD_BUST = 16;
private static final int MAP_BUST = 4;
private final PageComparator cmp = new PageComparator();
protected final PagePool pagePool;
protected final int pageSize;
protected final int pageBits;
protected final long pageMask;
// array below are indexed by virtual pageId
// buffers and pageMap are sparse
private ByteBuffer[] bufferMap = new ByteBuffer[0];
private int[] hitCounts = new int[0];
private PageInfo[] pageMap = new PageInfo[0];
private long pageFaults = 0;
private long eof = SANITY_LIMIT;
// this is dense collection of allocated pages
private List pages = new ArrayList();
private int mappedPageCount = 0;
private int mappedPageLimit = 0;
public PagedVirtualMemory(PagePool pagePool) {
this.pagePool = pagePool;
this.pageSize = pagePool.getPageSize();
if (Integer.bitCount(pageSize) != 1) {
throw new IllegalArgumentException("Page size should be power of 2 (" + pageSize + ")");
}
pageMask = pageSize - 1;
pageBits = Long.bitCount(pageMask);
}
public void setLimit(long vsize) {
eof = vsize;
int n = (int) (vsize >> pageBits);
if (n >= bufferMap.length) {
int nsize = Math.max(n + 1, (3 * bufferMap.length) / 4);
bufferMap = Arrays.copyOf(bufferMap, nsize);
hitCounts = Arrays.copyOf(hitCounts, nsize);
pageMap = Arrays.copyOf(pageMap, nsize);
}
}
public char readChar(long index) {
try {
ByteBuffer buf = ensureBuffer(index);
return buf.getChar((int)(index & pageMask));
} catch (IndexOutOfBoundsException e) {
byte[] sb = new byte[2];
readSafe(index, sb, sb.length);
return ByteBuffer.wrap(sb).getChar();
}
}
public double readDouble(long index) {
try {
ByteBuffer buf = ensureBuffer(index);
return buf.getDouble((int)(index & pageMask));
} catch (IndexOutOfBoundsException e) {
byte[] sb = new byte[8];
readSafe(index, sb, sb.length);
return ByteBuffer.wrap(sb).getDouble();
}
}
public float readFloat(long index) {
try {
ByteBuffer buf = ensureBuffer(index);
return buf.getFloat((int)(index & pageMask));
} catch (IndexOutOfBoundsException e) {
byte[] sb = new byte[4];
readSafe(index, sb, sb.length);
return ByteBuffer.wrap(sb).getFloat();
}
}
public int readInt(long index) {
try {
ByteBuffer buf = ensureBuffer(index);
return buf.getInt((int)(index & pageMask));
} catch (IndexOutOfBoundsException e) {
byte[] sb = new byte[4];
readSafe(index, sb, sb.length);
return ByteBuffer.wrap(sb).getInt();
}
// catch (BufferUnderflowException e) {
// byte[] sb = new byte[4];
// readSafe(index, sb, sb.length);
// return ByteBuffer.wrap(sb).getInt();
// }
}
public long readLong(long index) {
try {
ByteBuffer buf = ensureBuffer(index);
return buf.getLong((int)(index & pageMask));
} catch (IndexOutOfBoundsException e) {
byte[] sb = new byte[8];
readSafe(index, sb, sb.length);
return ByteBuffer.wrap(sb).getLong();
}
}
public short readShort(long index) {
try {
ByteBuffer buf = ensureBuffer(index);
return buf.getShort((int)(index & pageMask));
} catch (IndexOutOfBoundsException e) {
byte[] sb = new byte[2];
readSafe(index, sb, sb.length);
return ByteBuffer.wrap(sb).getShort();
}
}
public byte readByte(long index) {
try {
ByteBuffer buf = ensureBuffer(index);
return buf.get((int)(index & pageMask));
} catch(IndexOutOfBoundsException e) {
if (index >= eof) {
throw new RuntimeException("Read beyond end of file: " + index);
}
else {
throw e;
}
}
}
public void readBytes(long position, byte[] chars) {
try {
ByteBuffer buf = ensureBuffer(position);
buf.position((int)(position & pageMask));
buf.get(chars);
buf.position(0);
} catch (IndexOutOfBoundsException e) {
readSafe(position, chars, chars.length);
} catch (BufferUnderflowException e) {
readSafe(position, chars, chars.length);
}
}
private void readSafe(long index, byte[] buffer, int len) {
// TODO may be very slow
for(int i = 0; i != len; ++i) {
buffer[i] = readByte(index + i);
}
}
private ByteBuffer ensureBuffer(long index) {
if (index > SANITY_LIMIT) {
throw new IllegalArgumentException("Offset beyond sanity: " + index);
}
int n = (int) (index >> pageBits);
if (n >= bufferMap.length) {
int nsize = Math.max(n + 1, (3 * bufferMap.length) / 4);
bufferMap = Arrays.copyOf(bufferMap, nsize);
hitCounts = Arrays.copyOf(hitCounts, nsize);
pageMap = Arrays.copyOf(pageMap, nsize);
}
ByteBuffer b = bufferMap[n];
if (b == null) {
pageFault(n);
b = bufferMap[n];
if (b == null) {
throw new IllegalArgumentException("Failed to load page #" + n);
}
}
return b;
}
protected PageInfo allocPage() {
ByteBuffer buf = allocBuffer();
if (buf != null) {
PageInfo pi = new PageInfo(pages.size());
pi.buffer = buf;
pages.add(pi);
mappedPageLimit = pages.size() / 2; // why?
return pi;
}
else {
return null;
}
}
protected ByteBuffer allocBuffer() {
if (pagePool.hasFreePages()) {
try {
ByteBuffer buf = pagePool.accurePage();
return buf;
} catch (NoMorePagesException e) {
return null;
}
}
else {
return null;
}
}
protected void reclaimPages(PageInfo[] pages) {
EvictBuffer buffer = new EvictBuffer(pages);
while(buffer.size() < pages.length) {
PageInfo pi = allocPage();
if (pi != null) {
buffer.push(pi);
}
else {
break;
}
}
if (buffer.size() < pages.length) {
for(PageInfo pi: this.pages) {
buffer.push(pi);
}
}
if (buffer.size() < pages.length) {
throw new RuntimeException("Cannot reclaim " + pages.length);
}
// unlink reclaimed pages
for(PageInfo pi: pages) {
if (pi.pageIndex >= 0) {
bufferMap[pi.pageIndex] = null;
pageMap[pi.pageIndex] = null;
pi.pageIndex = -1;
}
pi.age = -1;
}
// done
}
protected abstract void loadPage(int pageId);
protected boolean isPageMapped(int pageId) {
return pageId < pageMap.length && pageMap[pageId] != null;
}
protected void mapPage(int pageId, PageInfo info) {
if (pageMap[pageId] != null) {
throw new IllegalArgumentException("Page is already mapped");
}
info.age = pageFaults;
info.pageIndex = pageId;
pageMap[pageId] = info;
hitCounts[pageId] += MAP_BUST; // fake hit
}
protected void pageFault(int pageId) {
++hitCounts[pageId];
if (pageMap[pageId] != null && mappedPageCount < mappedPageLimit) {
// soft fall - restore mapping
bufferMap[pageId] = pageMap[pageId].buffer;
return;
}
// otherwise process hard fall
++pageFaults;
if (pageFaults % (1 << 10) == 0) {
fadeHitCounts();
}
// we hit mapping threshold need to unmap pages
if (mappedPageCount >= mappedPageLimit) {
// unmap all pages to free limit
Arrays.fill(bufferMap, null);
mappedPageCount = 0;
}
if (pageMap[pageId] != null) {
bufferMap[pageId] = pageMap[pageId].buffer;
++mappedPageCount;
return;
}
loadPage(pageId);
hitCounts[pageId] += LOAD_BUST; // bust loaded page
if (pageMap[pageId] != null) {
bufferMap[pageId] = pageMap[pageId].buffer;
++mappedPageCount;
return;
}
}
private void fadeHitCounts() {
for(int i = 0; i != hitCounts.length; ++i) {
hitCounts[i] = hitCounts[i] / 2;
}
}
// private PageInfo compare(PageInfo page1, PageInfo page2) {
// if (page1 == null) {
// return page2;
// }
// else if (page2 == null) {
// return page1;
// }
// int h1 = page1.pageIndex < 0 ? 0 : hitCounts[page1.pageIndex];
// int h2 = page2.pageIndex < 0 ? 0 : hitCounts[page2.pageIndex];
// if (h1 > h2) {
// return page2;
// }
// else if (h2 > h1) {
// return page1;
// }
// if (page1.age < page2.age) {
// return page1;
// }
// else if (page2.age < page1.age) {
// return page2;
// }
// return page1.pageIndex < page2.pageIndex ? page1 : page2;
// }
protected static class PageInfo {
protected ByteBuffer buffer;
int pageNo;
int pageIndex = -1;
long age = -1;
public PageInfo(int pageNo) {
this.pageNo = pageNo;
}
public String toString() {
return "@" + pageIndex + " A" + age;
}
}
class EvictBuffer {
PageInfo[] array;
int size;
EvictBuffer(PageInfo[] array) {
this.array = array;
}
public int size() {
return size;
}
public void push(PageInfo pi) {
if (size == 0) {
array[0] = pi;
++size;
}
else {
int n;
for(n = size - 1;n >= 0; --n) {
if (cmp.compare(pi, array[n]) >= 0) {
break;
}
}
n = n + 1;
if (n < array.length) {
insert(n, pi);
}
}
}
private void insert(int pos, PageInfo pi) {
if (pos < array.length - 1) {
System.arraycopy(array, pos, array, pos + 1, array.length - pos - 1);
}
array[pos] = pi;
if (size < array.length) {
++size;
}
}
}
private class PageComparator implements Comparator {
@Override
public int compare(PageInfo page1, PageInfo page2) {
int h1 = page1.pageIndex < 0 ? 0 : hitCounts[page1.pageIndex];
int h2 = page2.pageIndex < 0 ? 0 : hitCounts[page2.pageIndex];
if (h1 > h2) {
return 1;
}
else if (h2 > h1) {
return -1;
}
if (page1.age < page2.age) {
return 1;
}
else if (page2.age < page1.age) {
return -1;
}
return page1.pageIndex < page2.pageIndex ? 1
: page1.pageIndex == page2.pageIndex ? 0
: -1;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy