/* * Copyright (C) 2014 The Android Open Source Project * * 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 com.android.tools.perflib.heap.io; import com.android.annotations.NonNull; import com.android.annotations.VisibleForTesting; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; import sun.nio.ch.DirectBuffer; public class MemoryMappedFileBuffer implements HprofBuffer { // Default chunk size is 1 << 30, or 1,073,741,824 bytes. private static final int DEFAULT_SIZE = 1 << 30; // Eliminate wrapped, multi-byte reads across chunks in most cases. private static final int DEFAULT_PADDING = 1024; private final int mBufferSize; private final int mPadding; @NonNull private final ByteBuffer[] mByteBuffers; private final long mLength; private long mCurrentPosition; @VisibleForTesting MemoryMappedFileBuffer(@NonNull File f, int bufferSize, int padding) throws IOException { mBufferSize = bufferSize; mPadding = padding; mLength = f.length(); int shards = (int) (mLength / mBufferSize) + 1; mByteBuffers = new ByteBuffer[shards]; FileInputStream inputStream = new FileInputStream(f); try { long offset = 0; for (int i = 0; i < shards; i++) { long size = Math.min(mLength - offset, mBufferSize + mPadding); mByteBuffers[i] = inputStream.getChannel() .map(FileChannel.MapMode.READ_ONLY, offset, size); mByteBuffers[i].order(HPROF_BYTE_ORDER); offset += mBufferSize; } mCurrentPosition = 0; } finally { inputStream.close(); } } /** * Creates a buffer by memory-mapping file {@param f}. * * It may be a good idea to dispose() the buffer if no longer needed. A garbage collection isn't * guaranteed to free up the resources, and in a long-running 32-bit JVM there's the risk of * exhausting the address space this way. On Windows, mmap locks the file, preventing it from * being deleted. See {@link http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4715154}. */ public MemoryMappedFileBuffer(@NonNull File f) throws IOException { this(f, DEFAULT_SIZE, DEFAULT_PADDING); } /** * Attempts to unmap the buffer. It is the caller's responsibility to ensure there are no other * accesses to this buffer, otherwise this can result in a crash and kill the JVM. */ public void dispose() { try { for (int i = 0; i < mByteBuffers.length; i++) { ((DirectBuffer) mByteBuffers[i]).cleaner().clean(); } } catch (Exception ex) { // ignore, this is a best effort attempt. } } @Override public byte readByte() { byte result = mByteBuffers[getIndex()].get(getOffset()); mCurrentPosition++; return result; } @Override public void read(@NonNull byte[] b) { int index = getIndex(); mByteBuffers[index].position(getOffset()); if (b.length <= mByteBuffers[index].remaining()) { mByteBuffers[index].get(b, 0, b.length); } else { // Wrapped read int split = mBufferSize - mByteBuffers[index].position(); mByteBuffers[index].get(b, 0, split); mByteBuffers[index + 1].position(0); mByteBuffers[index + 1].get(b, split, b.length - split); } mCurrentPosition += b.length; } @Override public void readSubSequence(@NonNull byte[] b, int sourceStart, int length) { assert length < mLength; mCurrentPosition += sourceStart; int index = getIndex(); mByteBuffers[index].position(getOffset()); if (b.length <= mByteBuffers[index].remaining()) { mByteBuffers[index].get(b, 0, b.length); } else { int split = mBufferSize - mByteBuffers[index].position(); mByteBuffers[index].get(b, 0, split); int start = split; int remainingMaxLength = Math.min(length - start, b.length - start); int remainingShardCount = (remainingMaxLength + mBufferSize - 1) / mBufferSize; for (int i = 0; i < remainingShardCount; ++i) { int maxToRead = Math.min(remainingMaxLength, mBufferSize); mByteBuffers[index + 1 + i].position(0); mByteBuffers[index + 1 + i].get(b, start, maxToRead); start += maxToRead; remainingMaxLength -= maxToRead; } } mCurrentPosition += Math.min(b.length, length); } @Override public char readChar() { char result = mByteBuffers[getIndex()].getChar(getOffset()); mCurrentPosition += 2; return result; } @Override public short readShort() { short result = mByteBuffers[getIndex()].getShort(getOffset()); mCurrentPosition += 2; return result; } @Override public int readInt() { int result = mByteBuffers[getIndex()].getInt(getOffset()); mCurrentPosition += 4; return result; } @Override public long readLong() { long result = mByteBuffers[getIndex()].getLong(getOffset()); mCurrentPosition += 8; return result; } @Override public float readFloat() { float result = mByteBuffers[getIndex()].getFloat(getOffset()); mCurrentPosition += 4; return result; } @Override public double readDouble() { double result = mByteBuffers[getIndex()].getDouble(getOffset()); mCurrentPosition += 8; return result; } @Override public void setPosition(long position) { mCurrentPosition = position; } @Override public long position() { return mCurrentPosition; } @Override public boolean hasRemaining() { return mCurrentPosition < mLength; } @Override public long remaining() { return mLength - mCurrentPosition; } private int getIndex() { return (int) (mCurrentPosition / mBufferSize); } private int getOffset() { return (int) (mCurrentPosition % mBufferSize); } }