blob: b5af743981e0ad87b9483bd3f513b3bf35a2121a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2021 SAP AG, Netflix, IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* SAP AG - initial API and implementation
* Netflix (Jason Koch) - refactors for increased performance and concurrency
* IBM Corporation (Andrew Johnson) - compressed dumps
*******************************************************************************/
package org.eclipse.mat.hprof;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.eclipse.mat.SnapshotException;
import org.eclipse.mat.hprof.AbstractParser.Constants.Record;
import org.eclipse.mat.hprof.describer.Version;
import org.eclipse.mat.hprof.ui.HprofPreferences;
import org.eclipse.mat.parser.index.IIndexReader.IOne2LongIndex;
import org.eclipse.mat.parser.io.BufferedRandomAccessInputStream;
import org.eclipse.mat.parser.model.AbstractObjectImpl;
import org.eclipse.mat.parser.model.ClassImpl;
import org.eclipse.mat.parser.model.ClassLoaderImpl;
import org.eclipse.mat.parser.model.InstanceImpl;
import org.eclipse.mat.parser.model.ObjectArrayImpl;
import org.eclipse.mat.parser.model.PrimitiveArrayImpl;
import org.eclipse.mat.snapshot.ISnapshot;
import org.eclipse.mat.snapshot.model.Field;
import org.eclipse.mat.snapshot.model.FieldDescriptor;
import org.eclipse.mat.snapshot.model.IArray;
import org.eclipse.mat.snapshot.model.IClass;
import org.eclipse.mat.snapshot.model.IObject;
import org.eclipse.mat.snapshot.model.IPrimitiveArray;
import org.eclipse.mat.snapshot.model.ObjectReference;
import org.eclipse.mat.util.MessageUtil;
public class HprofRandomAccessParser extends AbstractParser
{
public static final int LAZY_LOADING_LIMIT = 256;
private final IPositionInputStream in;
public HprofRandomAccessParser(File file, Version version, int identifierSize, long len,
HprofPreferences.HprofStrictness strictnessPreference) throws IOException
{
super(strictnessPreference);
RandomAccessFile raf = new RandomAccessFile(file, "r"); //$NON-NLS-1$
boolean gzip = CompressedRandomAccessFile.isGZIP(raf);
if (gzip)
{
ChunkedGZIPRandomAccessFile cgraf = ChunkedGZIPRandomAccessFile.get(raf, file);
raf.close();
if (cgraf != null)
{
raf = cgraf;
}
else
{
long requested = len / 10;
long maxFree = CompressedRandomAccessFile.checkMemSpace(requested);
// If we are very memory constrained use a file cache
if (requested > maxFree && FileCacheCompressedRandomAccessFile.isDiskSpace(file, len))
raf = new FileCacheCompressedRandomAccessFile(file);
else
raf = new CompressedRandomAccessFile(file, true, len);
}
}
this.in = new DefaultPositionInputStream(new BufferedRandomAccessInputStream(raf, 512));
this.version = version;
this.idSize = identifierSize;
}
public synchronized void close() throws IOException
{
in.close();
}
public synchronized IObject read(int objectId, long position, ISnapshot dump, IOne2LongIndex o2hprof) throws IOException, SnapshotException
{
in.seek(position);
int segmentType = in.readUnsignedByte();
if (objectId == -1)
{
segmentType = skipRecords(segmentType);
}
switch (segmentType)
{
case Constants.DumpSegment.INSTANCE_DUMP:
return readInstanceDump(objectId, dump);
case Constants.DumpSegment.OBJECT_ARRAY_DUMP:
return readObjectArrayDump(objectId, dump);
case Constants.DumpSegment.PRIMITIVE_ARRAY_DUMP:
return readPrimitiveArrayDump(objectId, dump);
default:
throw new IOException(MessageUtil.format(Messages.HprofRandomAccessParser_Error_IllegalDumpSegment,
segmentType, Long.toHexString(position)));
}
}
public List<IClass> resolveClassHierarchy(ISnapshot snapshot, IClass clazz) throws SnapshotException
{
List<IClass> answer = new ArrayList<IClass>();
answer.add(clazz);
while (clazz.hasSuperClass())
{
clazz = (IClass) snapshot.getObject(clazz.getSuperClassId());
if (clazz == null)
return null;
answer.add(clazz);
}
return answer;
}
/**
* A reference to an object via its address
* which can handle the address not being in the index.
*/
static class ObjectAddressReference extends ObjectReference
{
private static final long serialVersionUID = 1L;
transient IOne2LongIndex o2hprof;
transient ISnapshot snapshot;
transient HprofRandomAccessParser parser;
/**
* Construct an {@link ObjectAddressReference} which can be used to retrieve an
* object by address even if it has not been indexed.
* Used as a proxy by {@link ObjectReference} which can change the {@link ObjectReference#address}
* private field.
* @param snapshot the snapshot
* @param parser the HPROF parser used to build the IObject if required
* @param o2hprof the index from object ID to HPROF file position
* @param address the address of the object
*/
public ObjectAddressReference(ISnapshot snapshot, HprofRandomAccessParser parser, IOne2LongIndex o2hprof, long address)
{
super(snapshot, address);
this.snapshot = snapshot;
this.o2hprof = o2hprof;
this.parser = parser;
}
@Override
public int getObjectId() throws SnapshotException
{
try
{
int id = super.getObjectId();
return id;
}
catch (SnapshotException e)
{
return -1;
}
}
/**
* Actually construct an IObject.
* First try constructing by object ID.
* If that fails then construct by address.
* Find the indexed objects before and after this.
* Parse the HPROF file between these two points looking for an
* object of the correct address.
*/
@Override
public IObject getObject() throws SnapshotException
{
SnapshotException e1 = null;
try
{
int id = super.getObjectId();
if (id >= 0)
{
IObject o = super.getObject();
return o;
}
}
catch (SnapshotException e)
{
e1 = e;
}
// Find the object IDs before and after this object
int low = 0;
int high = o2hprof.size() - 1;
for (;low + 1 < high;)
{
int mid = (low + high) >>> 1;
long midAddr = snapshot.mapIdToAddress(mid);
if (getObjectAddress() < midAddr)
{
high = mid;
}
else if (getObjectAddress() > midAddr)
{
low = mid;
}
else
{
low = mid;
high = mid;
}
}
// Some objects don't have a position (classes?), so find valid file positions
long pos = 0;
while ((pos = o2hprof.get(low)) == 0 && low > 0)
--low;
if (pos == 0)
{
// There might be discarded objects before the first
Long lpos = (Long)snapshot.getSnapshotInfo().getProperty(HprofHeapObjectReader.HPROF_HEAP_START);
if (lpos != null)
pos = lpos;
else while ((pos = o2hprof.get(low)) == 0 && low < o2hprof.size() - 1)
++low;
}
long posHigh = pos;
while (high >= 0 && (posHigh = o2hprof.get(high)) == 0 && high < o2hprof.size() - 1)
++high;
if (posHigh == 0)
{
// There might be discarded objects after the last
Long olen = (Long)snapshot.getSnapshotInfo().getProperty(HprofHeapObjectReader.HPROF_LENGTH_PROPERTY);
posHigh = (olen != null) ? olen : Long.MAX_VALUE;
}
// Reparse the HPROF file looking for an object of the right address
try
{
do
{
IObject o;
synchronized (parser)
{
// Need final position without interference
o = parser.read(-1, pos, snapshot, o2hprof);
pos = parser.in.position();
}
if (o.getObjectAddress() == getObjectAddress())
{
// Class was discarded from the snapshot
if (o.getClazz() == null)
throw new IOException();
((AbstractObjectImpl)o).setSnapshot(snapshot);
return o;
}
// Gone past the object address we want
if (o.getObjectAddress() > getObjectAddress())
break;
}
while (pos < posHigh);
}
catch (IOException e2)
{
if (e2 instanceof EOFException)
throw e1;
throw new SnapshotException(e2);
}
// Not found, so throw the original exception
throw e1;
}
}
private IObject readInstanceDump(int objectId, ISnapshot dump) throws IOException, SnapshotException
{
long address = in.readID(idSize);
IClass oclazz;
if (objectId >= 0)
{
// Skip serial number, class ID, length
if (in.skipBytes(8 + idSize) != 8 + idSize)
throw new IOException();
oclazz = dump.getClassOf(objectId);
}
else
{
// skip serial number
if (in.skipBytes(4) != 4)
throw new IOException();
// class ID
long classAddr = in.readID(idSize);
// length
long len = in.readUnsignedInt();
try
{
int classID = dump.mapAddressToId(classAddr);
IObject io = dump.getObject(classID);
if (io instanceof IClass)
oclazz = (IClass)io;
else
throw new IOException();
}
catch (SnapshotException e)
{
// move to end of object
if (in.skipBytes(len) != len)
throw new IOException();
// Invalid object, but might be good enough for skipping over
return new InstanceImpl(objectId, address, null, null);
}
}
// check if we need to defer reading the class
List<IClass> hierarchy = resolveClassHierarchy(dump, oclazz);
if (hierarchy == null)
{
throw new IOException(Messages.HprofRandomAccessParser_Error_DumpIncomplete);
}
else
{
List<Field> instanceFields = new ArrayList<Field>();
for (IClass clazz : hierarchy)
{
List<FieldDescriptor> fields = clazz.getFieldDescriptors();
for (int ii = 0; ii < fields.size(); ii++)
{
FieldDescriptor field = fields.get(ii);
int type = field.getType();
Object value = readValue(in, dump, type);
instanceFields.add(new Field(field.getName(), field.getType(), value));
}
}
ClassImpl classImpl = (ClassImpl) hierarchy.get(0);
if (dump.isClassLoader(objectId))
return new ClassLoaderImpl(objectId, address, classImpl, instanceFields);
else
return new InstanceImpl(objectId, address, classImpl, instanceFields);
}
}
private IArray readObjectArrayDump(int objectId, ISnapshot dump) throws IOException, SnapshotException
{
long id = in.readID(idSize);
in.skipBytes(4);
int size = in.readInt();
long len = (long)size * idSize;
long arrayClassObjectID = in.readID(idSize);
int typeId;
if (objectId == -1)
{
try
{
typeId = dump.mapAddressToId(arrayClassObjectID);
}
catch (SnapshotException e)
{
ObjectArrayImpl array = new ObjectArrayImpl(objectId, id, null, size);
// Move to end of object
if (in.skipBytes(len) != len)
throw new IOException();
return array;
}
}
else
{
typeId = dump.mapAddressToId(arrayClassObjectID);
}
IClass arrayType = (IClass) dump.getObject(typeId);
if (arrayType == null)
throw new RuntimeException(Messages.HprofRandomAccessParser_Error_MissingFakeClass);
Object content = null;
if (len < LAZY_LOADING_LIMIT)
{
long[] data = new long[size];
for (int ii = 0; ii < data.length; ii++)
data[ii] = in.readID(idSize);
content = data;
}
else
{
content = new ArrayDescription.Offline(false, in.position(), 0, size);
if (objectId == -1)
{
// Move to end of object
if (in.skipBytes(len) != len)
throw new IOException();
}
}
ObjectArrayImpl array = new ObjectArrayImpl(objectId, id, (ClassImpl) arrayType, size);
array.setInfo(content);
return array;
}
private IArray readPrimitiveArrayDump(int objectId, ISnapshot dump) throws IOException, SnapshotException
{
long id = in.readID(idSize);
in.skipBytes(4);
int arraySize = in.readInt();
long elementType = in.readByte();
if ((elementType < IPrimitiveArray.Type.BOOLEAN) || (elementType > IPrimitiveArray.Type.LONG))
throw new IOException(Messages.Pass1Parser_Error_IllegalType);
int elementSize = IPrimitiveArray.ELEMENT_SIZE[(int) elementType];
long len = elementSize * (long)arraySize;
Object content = null;
if (len < LAZY_LOADING_LIMIT)
{
byte[] data = new byte[(int)len];
in.readFully(data);
content = elementType == IObject.Type.BYTE ? data : new ArrayDescription.Raw(data);
}
else
{
content = new ArrayDescription.Offline(true, in.position(), elementSize, arraySize);
if (objectId == -1)
{
// Move to end of object
if (in.skipBytes(len) != len)
throw new IOException();
}
}
// lookup class by name
IClass clazz = null;
String name = IPrimitiveArray.TYPE[(int) elementType];
Collection<IClass> classes = dump.getClassesByName(name, false);
if (classes == null || classes.isEmpty())
{
if (objectId == -1)
{
// Return a dummy array which can be ignored
PrimitiveArrayImpl array = new PrimitiveArrayImpl(objectId, id, (ClassImpl) clazz, arraySize, (int) elementType);
return array;
}
throw new IOException(MessageUtil.format(Messages.HprofRandomAccessParser_Error_MissingClass, name));
}
else if (classes.size() > 1)
throw new IOException(MessageUtil.format(Messages.HprofRandomAccessParser_Error_DuplicateClass, name));
else
clazz = classes.iterator().next();
PrimitiveArrayImpl array = new PrimitiveArrayImpl(objectId, id, (ClassImpl) clazz, arraySize, (int) elementType);
array.setInfo(content);
return array;
}
public synchronized long[] readObjectArray(ArrayDescription.Offline descriptor, int offset, int length)
throws IOException
{
int elementSize = this.idSize;
in.seek(descriptor.getPosition() + ((long)offset * elementSize));
long[] data = new long[length];
for (int ii = 0; ii < data.length; ii++)
data[ii] = in.readID(idSize);
return data;
}
public synchronized byte[] readPrimitiveArray(ArrayDescription.Offline descriptor, int offset, int length)
throws IOException
{
int elementSize = descriptor.getElementSize();
in.seek(descriptor.getPosition() + ((long)offset * elementSize));
byte[] data = new byte[length * elementSize];
in.readFully(data);
return data;
}
private int skipRecords(int segmentType) throws IOException
{
boolean again = true;
do
{
int skip = -1;
switch (segmentType)
{
case Record.HEAP_DUMP_SEGMENT:
skip = 8;
break;
case Constants.DumpSegment.ROOT_UNKNOWN:
case Constants.DumpSegment.ROOT_STICKY_CLASS:
case Constants.DumpSegment.ROOT_MONITOR_USED:
skip = idSize;
break;
case Constants.DumpSegment.ROOT_JNI_GLOBAL:
skip = idSize * 2;
break;
case Constants.DumpSegment.ROOT_JNI_LOCAL:
case Constants.DumpSegment.ROOT_JAVA_FRAME:
case Constants.DumpSegment.ROOT_THREAD_OBJECT:
skip = idSize + 8;
break;
case Constants.DumpSegment.ROOT_NATIVE_STACK:
case Constants.DumpSegment.ROOT_THREAD_BLOCK:
skip = idSize + 4;
break;
case Constants.DumpSegment.CLASS_DUMP:
skipClassDump();
// Already skipped enough, so just reread
skip = 0;
break;
default:
again = false;
break;
}
if (skip >= 0)
{
// Skip over new segment header etc.
in.skipBytes(skip);
segmentType = in.readUnsignedByte();
}
}
while (again);
return segmentType;
}
private void skipClassDump() throws IOException
{
in.skipBytes(7 * idSize + 8);
int constantPoolSize = in.readUnsignedShort();
for (int ii = 0; ii < constantPoolSize; ii++)
{
in.skipBytes(2);
skipValue(in);
}
int numStaticFields = in.readUnsignedShort();
for (int i = 0; i < numStaticFields; i++)
{
in.skipBytes(idSize);
skipValue(in);
}
int numInstanceFields = in.readUnsignedShort();
in.skipBytes((idSize + 1) * numInstanceFields);
}
}