/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved. * * Oracle and Java are registered trademarks of Oracle and/or its affiliates. * Other names may be trademarks of their respective owners. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common * Development and Distribution License("CDDL") (collectively, the * "License"). You may not use this file except in compliance with the * License. You can obtain a copy of the License at * http://www.netbeans.org/cddl-gplv2.html * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the * specific language governing permissions and limitations under the * License. When distributing the software, include this License Header * Notice in each file and include the License file at * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the GPL Version 2 section of the License file that * accompanied this code. If applicable, add the following below the * License Header, with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * Contributor(s): * The Original Software is NetBeans. The Initial Developer of the Original * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun * Microsystems, Inc. All Rights Reserved. * * If you wish your version of this file to be governed by only the CDDL * or only the GPL Version 2, indicate your decision by adding * "[Contributor] elects to include this software in this distribution * under the [CDDL or GPL Version 2] license." If you do not indicate a * single choice of license, a recipient has the option to distribute * your version of this file under either the CDDL, the GPL Version 2 or * to extend the choice of license to its licensees as provided above. * However, if you add GPL Version 2 code and therefore, elected the GPL * Version 2 license, then the option applies only if the new code is * made subject to such option by the copyright holder. */ package org.netbeans.lib.profiler.heap; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * @author Tomas Hurka */ class NumberList { private static final int NUMBERS_IN_BLOCK = 3; private final File dataFile; private final RandomAccessFile data; private final int numberSize; private final int blockSize; private final Map<Long/*offset*/, byte[]/*block*/> blockCache; private final Set<Long> dirtyBlocks; private long blocks; private MappedByteBuffer buf; private long mappedSize; NumberList(long dumpFileSize) throws IOException { this(bytes(dumpFileSize)); } NumberList(int elSize) throws IOException { dataFile = File.createTempFile("NBProfiler", ".ref"); // NOI18N data = new RandomAccessFile(dataFile, "rw"); // NOI18N numberSize = elSize; blockCache = new BlockLRUCache<Long, byte[]>(); dirtyBlocks = new HashSet<Long>(100000); blockSize = (NUMBERS_IN_BLOCK + 1) * numberSize; dataFile.deleteOnExit(); addBlock(); // first block is unused, since it starts at offset 0 } private static int bytes(long number) { if ((number & ~0xFFL) == 0L) { return 1; } if ((number & ~0xFFFFL) == 0L) { return 2; } if ((number & ~0xFFFFFFL) == 0L) { return 3; } if ((number & ~0xFFFFFFFFL) == 0L) { return 4; } if ((number & ~0xFFFFFFFFFFL) == 0L) { return 5; } if ((number & ~0xFFFFFFFFFFFFL) == 0L) { return 6; } if ((number & ~0xFFFFFFFFFFFFFFL) == 0L) { return 7; } return 8; } protected void finalize() throws Throwable { dataFile.delete(); super.finalize(); } long addNumber(long startOffset,long number) throws IOException { int slot; byte[] block = getBlock(startOffset); for (slot=0;slot<NUMBERS_IN_BLOCK;slot++) { long el = readNumber(block,slot); if (el == 0L) { writeNumber(startOffset,block,slot,number); return startOffset; } if (el == number) { // number is already in the list return startOffset; // do nothing } } long nextBlock = addBlock(); // create next blok block = getBlock(nextBlock); writeNumber(nextBlock,block,slot,startOffset); // put next block in front of old block writeNumber(nextBlock,block,0,number); // write number to first position in the new block return nextBlock; } long addFirstNumber(long number1,long number2) throws IOException { long blockOffset = addBlock(); byte[] block = getBlock(blockOffset); writeNumber(blockOffset,block,0,number1); writeNumber(blockOffset,block,1,number2); return blockOffset; } void putFirst(long startOffset,long number) throws IOException { int slot; long offset = startOffset; long movedNumber = 0; for(;;) { byte[] block = getBlock(offset); for (slot=0;slot<NUMBERS_IN_BLOCK;slot++) { long el = readNumber(block,slot); if (offset == startOffset && slot == 0) { // first block if (number == el) { // already first element return; } movedNumber = el; writeNumber(offset,block,slot,number); } else if (el == 0L) { // end of the block, move to next one break; } else if (el == number) { // number is already in the list writeNumber(offset,block,slot,movedNumber); // replace number and return return; } } offset = getOffsetToNextBlock(block); if (offset == 0L) { System.out.println("Error - number not found at end"); return; } } } long getFirstNumber(long startOffset) throws IOException { byte[] block = getBlock(startOffset); return readNumber(block,0); } List<Long> getNumbers(long startOffset) throws IOException { int slot; List<Long> numbers = new ArrayList<Long>(); for(;;) { byte[] block = getBlock(startOffset); for (slot=0;slot<NUMBERS_IN_BLOCK;slot++) { long el = readNumber(block,slot); if (el == 0L) { // end of the block, move to next one break; } numbers.add(new Long(el)); } long nextBlock = getOffsetToNextBlock(block); if (nextBlock == 0L) { return numbers; } startOffset = nextBlock; } } private void mmapData() { if (buf == null && blockSize*blocks < Integer.MAX_VALUE) { try { buf = data.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, data.length()); mappedSize = blockSize*blocks; } catch (IOException ex) { ex.printStackTrace(); } } } void flush() { try { flushDirtyBlocks(); blockCache.clear(); mmapData(); } catch (IOException ex) { ex.printStackTrace(); } } private long getOffsetToNextBlock(byte[] block) { return readNumber(block,NUMBERS_IN_BLOCK); } private long readNumber(byte[] block,int slot) { int offset = slot*numberSize; long el = 0; // for (int i=0;i<numberSize;i++) { // el <<= 8; // el |= ((int)block[offset+i]) & 0xFF; // } if (numberSize == 4) { return ((long)getInt(block,offset)) & 0xFFFFFFFFL; } else if (numberSize == 8) { return getLong(block,offset); } return el; } private int getInt(byte[] buf, int i) { int ch1 = ((int) buf[i++]) & 0xFF; int ch2 = ((int) buf[i++]) & 0xFF; int ch3 = ((int) buf[i++]) & 0xFF; int ch4 = ((int) buf[i]) & 0xFF; return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0)); } private long getLong(byte[] buf, int i) { return (((long)buf[i++] << 56) + ((long)(buf[i++] & 255) << 48) + ((long)(buf[i++] & 255) << 40) + ((long)(buf[i++] & 255) << 32) + ((long)(buf[i++] & 255) << 24) + ((buf[i++] & 255) << 16) + ((buf[i++] & 255) << 8) + ((buf[i++] & 255) << 0)); } private void writeNumber(long blockOffset,byte[] block,int slot,long element) throws IOException { if (blockOffset < mappedSize) { long offset = blockOffset+slot*numberSize; buf.position((int)offset); for (int i=numberSize-1;i>=0;i--) { byte el = (byte)(element >> (i*8)); buf.put(el); } } else { Long offsetObj = new Long(blockOffset); int offset = slot*numberSize; for (int i=numberSize-1;i>=0;i--) { byte el = (byte)(element >> (i*8)); block[offset++]=el; } dirtyBlocks.add(offsetObj); if (dirtyBlocks.size()>10000) { flushDirtyBlocks(); } } } private byte[] getBlock(long offset) throws IOException { byte[] block; if (offset < mappedSize) { block = new byte[blockSize]; buf.position((int)offset); buf.get(block); return block; } else { Long offsetObj = new Long(offset); block = (byte[]) blockCache.get(offsetObj); if (block == null) { block = new byte[blockSize]; data.seek(offset); data.readFully(block); blockCache.put(offsetObj,block); } return block; } } private long addBlock() throws IOException { long offset=blocks*blockSize; blockCache.put(new Long(offset),new byte[blockSize]); blocks++; return offset; } private void flushDirtyBlocks() throws IOException { if (dirtyBlocks.isEmpty()) { return; } Long[] dirty=new Long[dirtyBlocks.size()]; dirtyBlocks.toArray(dirty); Arrays.sort(dirty); byte blocks[] = new byte[1024*blockSize]; int dataOffset = 0; long lastBlockOffset = 0; for(int i=0;i<dirty.length;i++) { Long blockOffsetLong = dirty[i]; byte[] block = (byte[]) blockCache.get(blockOffsetLong); long blockOffset = blockOffsetLong.longValue(); if (lastBlockOffset+dataOffset==blockOffset && dataOffset <= blocks.length - blockSize) { System.arraycopy(block,0,blocks,dataOffset,blockSize); dataOffset+=blockSize; } else { data.seek(lastBlockOffset); data.write(blocks,0,dataOffset); dataOffset = 0; System.arraycopy(block,0,blocks,dataOffset,blockSize); dataOffset+=blockSize; lastBlockOffset = blockOffset; } } data.seek(lastBlockOffset); data.write(blocks,0,dataOffset); dirtyBlocks.clear(); } @SuppressWarnings("serial") private class BlockLRUCache<K, V> extends LinkedHashMap<K, V> { private static final int MAX_CAPACITY = 10000; private BlockLRUCache() { super(MAX_CAPACITY,0.75f,true); } protected boolean removeEldestEntry(Map.Entry<K, V> eldest) { if (size()>MAX_CAPACITY) { Object key = eldest.getKey(); if (!dirtyBlocks.contains(key)) { return true; } get(key); } return false; } } }