blob: 7607f0f26a8354a759de5149e4b08e1b1117d98f [file] [log] [blame]
/*
* 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);
}
}