blob: d9ef9a639e9ffe0e20b13ed7255c52df97bbc456 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2023 SAP AG, 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
* IBM Corporation - additional debug information
* Netflix (Jason Koch) - refactors for increased performance and concurrency
*******************************************************************************/
package org.eclipse.mat.hprof;
import java.io.EOFException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.core.runtime.Platform;
import org.eclipse.mat.SnapshotException;
import org.eclipse.mat.collect.HashMapLongObject;
import org.eclipse.mat.collect.SetLong;
import org.eclipse.mat.hprof.ui.HprofPreferences;
import org.eclipse.mat.parser.model.ClassImpl;
import org.eclipse.mat.snapshot.MultipleSnapshotsException;
import org.eclipse.mat.snapshot.model.Field;
import org.eclipse.mat.snapshot.model.FieldDescriptor;
import org.eclipse.mat.snapshot.model.GCRootInfo;
import org.eclipse.mat.snapshot.model.IClass;
import org.eclipse.mat.snapshot.model.IObject;
import org.eclipse.mat.snapshot.model.IObject.Type;
import org.eclipse.mat.snapshot.model.IPrimitiveArray;
import org.eclipse.mat.snapshot.model.ObjectReference;
import org.eclipse.mat.util.IProgressListener;
import org.eclipse.mat.util.IProgressListener.Severity;
import org.eclipse.mat.util.MessageUtil;
import org.eclipse.mat.util.SimpleMonitor;
public class Pass1Parser extends AbstractParser
{
private static final Pattern PATTERN_OBJ_ARRAY = Pattern.compile("^(\\[+)L(.*);$"); //$NON-NLS-1$
private static final Pattern PATTERN_PRIMITIVE_ARRAY = Pattern.compile("^(\\[+)(.)$"); //$NON-NLS-1$
// New size of classes including per-instance fields
private final boolean NEWCLASSSIZE = HprofPreferences.useAdditionalClassReferences();
private final String METHODSASCLASSES = HprofPreferences.methodsAsClasses();
private final boolean READFRAMES = HprofPreferences.FRAMES_ONLY.equals(METHODSASCLASSES)
|| HprofPreferences.RUNNING_METHODS_AS_CLASSES.equals(METHODSASCLASSES);
private HashMapLongObject<String> class2name = new HashMapLongObject<String>();
private HashMapLongObject<Long> thread2id = new HashMapLongObject<Long>();
private HashMapLongObject<StackFrame> id2frame = new HashMapLongObject<StackFrame>();
private HashMapLongObject<StackTrace> serNum2stackTrace = new HashMapLongObject<StackTrace>();
private HashMapLongObject<Long> classSerNum2id = new HashMapLongObject<Long>();
private HashMapLongObject<List<JavaLocal>> thread2locals = new HashMapLongObject<List<JavaLocal>>();
private HashMapLongObject<String> constantPool = new HashMapLongObject<String>();
private SetLong frameObjs;
private IHprofParserHandler handler;
private SimpleMonitor.Listener monitor;
private long previousArrayStart;
private long previousArrayUncompressedEnd;
private boolean foundCompressed;
private int biggestArrays[];
private long streamLength;
private final boolean verbose = Platform.inDebugMode() && HprofPlugin.getDefault().isDebugging()
&& Boolean.parseBoolean(Platform.getDebugOption("org.eclipse.mat.hprof/debug/parser")); //$NON-NLS-1$
private BufferingRafPositionInputStream in;
/** First stack frame class address */
private long stackFrameClassBase = 0x100;
/** Alignment of stack frame classes frames - should not be stricter than rest of heap */
private long stackFrameClassAlign = 0x100;
public Pass1Parser(IHprofParserHandler handler, SimpleMonitor.Listener monitor,
HprofPreferences.HprofStrictness strictnessPreference)
{
super(strictnessPreference);
this.handler = handler;
this.monitor = monitor;
this.biggestArrays = new int[Runtime.getRuntime().availableProcessors()];
}
public void read(File file, String prefix, String dumpNrToRead, long estimatedLength) throws SnapshotException, IOException
{
// See http://java.net/downloads/heap-snapshot/hprof-binary-format.html
// or https://hg.openjdk.org/jdk8/jdk8/jdk/raw-file/tip/src/share/demo/jvmti/hprof/manual.html
in = new BufferingRafPositionInputStream(file, prefix, 0, 8*1024, 0);
int currentDumpNr = 0;
List<MultipleSnapshotsException.Context> ctxs = new ArrayList<MultipleSnapshotsException.Context>();
boolean foundDump = false;
try
{
// header & version
version = readVersion(in);
handler.addProperty(IHprofParserHandler.VERSION, version.toString());
// identifierSize (32 or 64 bit)
idSize = in.readInt();
if (idSize != 4 && idSize != 8)
throw new SnapshotException(Messages.Pass1Parser_Error_SupportedDumps);
handler.addProperty(IHprofParserHandler.IDENTIFIER_SIZE, String.valueOf(idSize));
// creation date
long date = in.readLong();
long prevTimeOffset = 0;
long timeWrap = 0;
// Actual file size
long fileSize0 = file.length();
// Estimated stream length
long fileSize = estimatedLength;
long curPos = in.position();
recordLoop: while (true)
{
if (monitor.isProbablyCanceled())
throw new IProgressListener.OperationCanceledException();
monitor.totalWorkDone(in.workPosition() / 1000);
/*
* Use this instead of
* record = in.readUnsignedByte();
* so that we can detect the end of a zipped stream.
*/
int r = in.read();
if (r == -1)
break;
int record = r & 0xff;
long timeOffset = in.readUnsignedInt(); // time stamp in microseconds
if (timeOffset < prevTimeOffset)
{
// Wrap after 4294 seconds
timeWrap += 1L << 32;
}
prevTimeOffset = timeOffset;
long length = in.readUnsignedInt();
if (verbose)
System.out.println("Read record type " + record + ", length " + length + " at position 0x" + Long.toHexString(curPos)); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
if (curPos + 9 >= fileSize && fileSize > fileSize0 && curPos + 9 + length >= 0x100000000L)
{
/*
* Gzip has uncertain stream length though the lower 32-bits are correct,
* so make the estimated size grow.
*/
while (fileSize < curPos + 9)
{
fileSize += 0x100000000L;
}
}
length = updateLengthIfNecessary(fileSize, curPos, record, length, monitor);
if (length < 0)
throw new SnapshotException(MessageUtil.format(Messages.Pass1Parser_Error_IllegalRecordLength,
length, Long.toHexString(in.position() - 4), Integer.toHexString(record), Long.toHexString(curPos)));
if (curPos + 9 + length > fileSize)
{
switch (strictnessPreference)
{
case STRICTNESS_STOP:
// If we are sure about the file size
if (fileSize == fileSize0 || fileSize < 0x10000000L)
throw new SnapshotException(Messages.HPROFStrictness_Stopped, new SnapshotException(
MessageUtil.format(Messages.Pass1Parser_Error_invalidHPROFFile, length,
fileSize - curPos - 9, Integer.toHexString(record), Long.toHexString(curPos))));
case STRICTNESS_WARNING:
case STRICTNESS_PERMISSIVE:
monitor.sendUserMessage(Severity.WARNING,
MessageUtil.format(Messages.Pass1Parser_Error_invalidHPROFFile, length,
fileSize - curPos - 9, Integer.toHexString(record), Long.toHexString(curPos)), null);
break;
default:
throw new SnapshotException(Messages.HPROFStrictness_Unhandled_Preference);
}
}
switch (record)
{
case Constants.Record.STRING_IN_UTF8:
if (((int) (length - idSize) < 0))
throw new SnapshotException(MessageUtil.format(
Messages.Pass1Parser_Error_IllegalRecordLength, length, Long.toHexString(in.position() - 4),
Integer.toHexString(record), Long.toHexString(curPos)));
readString(length);
break;
case Constants.Record.LOAD_CLASS:
readLoadClass();
break;
case Constants.Record.UNLOAD_CLASS:
readUnloadClass();
break;
case Constants.Record.STACK_FRAME:
readStackFrame(curPos, length);
break;
case Constants.Record.STACK_TRACE:
readStackTrace(length);
break;
case Constants.Record.HEAP_DUMP:
case Constants.Record.HEAP_DUMP_SEGMENT:
long dumpTime = date + (timeWrap + timeOffset) / 1000;
if (dumpMatches(currentDumpNr, dumpNrToRead))
{
if (!foundDump)
{
handler.addProperty(IHprofParserHandler.CREATION_DATE, String.valueOf(dumpTime));
foundDump = true;
}
long posnext = readDumpSegments(length);
if (posnext < curPos + length)
{
// Truncated file, so could not read to end of segment
curPos = posnext;
break recordLoop;
}
}
else
checkSkipBytes(length);
if (ctxs.size() < currentDumpNr + 1)
{
MultipleSnapshotsException.Context ctx = new MultipleSnapshotsException.Context(dumpIdentifier(currentDumpNr));
ctx.setDescription(MessageUtil.format(Messages.Pass1Parser_HeapDumpCreated, new Date(dumpTime)));
ctxs.add(ctx);
}
if (record == Constants.Record.HEAP_DUMP)
currentDumpNr++;
break;
case Constants.Record.HEAP_DUMP_END:
currentDumpNr++;
checkSkipBytes(length);
break;
case Constants.Record.ALLOC_SITES:
case Constants.Record.HEAP_SUMMARY:
case Constants.Record.START_THREAD:
case Constants.Record.END_THREAD:
case Constants.Record.CPU_SAMPLES:
case Constants.Record.CONTROL_SETTINGS:
checkSkipBytes(length);
break;
default:
switch (strictnessPreference)
{
case STRICTNESS_STOP:
throw new SnapshotException(Messages.HPROFStrictness_Stopped, new SnapshotException(
MessageUtil.format(Messages.Pass1Parser_UnexpectedRecord,
Integer.toHexString(record), length, Long.toHexString(curPos))));
case STRICTNESS_WARNING:
case STRICTNESS_PERMISSIVE:
monitor.sendUserMessage(
Severity.WARNING,
MessageUtil.format(Messages.Pass1Parser_UnexpectedRecord,
Integer.toHexString(record), length, Long.toHexString(curPos)), null);
checkSkipBytes(length);
break;
default:
throw new SnapshotException(Messages.HPROFStrictness_Unhandled_Preference);
}
break;
}
curPos = in.position();
}
streamLength = curPos;
}
finally
{
try
{
in.close();
}
catch (IOException ignore)
{}
}
handler.addProperty(IHprofParserHandler.STREAM_LENGTH, Long.toString(streamLength()));
if (!foundDump)
throw new SnapshotException(MessageUtil.format(Messages.Pass1Parser_Error_NoHeapDumpIndexFound,
currentDumpNr, file.getName(), dumpNrToRead));
if (currentDumpNr > 1)
{
if (dumpNrToRead == null)
{
MultipleSnapshotsException mse = new MultipleSnapshotsException(MessageUtil.format(Messages.Pass1Parser_HeapDumpsFound, currentDumpNr));
for (MultipleSnapshotsException.Context runtime : ctxs)
{
mse.addContext(runtime);
}
throw mse;
}
monitor.sendUserMessage(IProgressListener.Severity.INFO, MessageUtil.format(
Messages.Pass1Parser_Info_UsingDumpIndex, currentDumpNr, file.getName(), dumpNrToRead),
null);
}
if (serNum2stackTrace.size() > 0)
dumpThreads();
}
/**
* Total of the size of the k biggest object arrays.
* k = number of processors
* Use to estimate how much more memory parallel parsing will use.
* @return size in bytes
*/
public long biggestArrays()
{
long total = 0;
for (int s : biggestArrays)
{
total += s;
}
return total;
}
/**
* Find the uncompressed stream length (in case the dump is compressed).
* @return the length in bytes
*/
public long streamLength()
{
return streamLength;
}
private void readString(long length) throws IOException
{
long id = in.readID(idSize);
byte[] chars = new byte[(int) (length - idSize)];
in.readFully(chars);
constantPool.put(id, new String(chars, "UTF-8")); //$NON-NLS-1$
}
private void readLoadClass() throws IOException
{
long classSerNum = in.readUnsignedInt(); // used in stacks frames
long classID = in.readID(idSize);
checkSkipBytes(4); // stack trace
long nameID = in.readID(idSize);
String className0 = getStringConstant(nameID);
int methodArgs = className0.indexOf('(');
String className;
if (methodArgs < 0)
className = className0.replace('/', '.');
else
className = className0.substring(0, methodArgs).replace('/', '.') + className0.substring(methodArgs);
class2name.put(classID, className);
classSerNum2id.put(classSerNum, classID);
}
private void readUnloadClass() throws IOException
{
long classSerNum = in.readUnsignedInt(); // used in stacks frames
if (classSerNum2id.containsKey(classSerNum))
{
long classID = classSerNum2id.get(classSerNum);
// class2name only holds active classes
class2name.remove(classID);
}
else
{
// For example: sun_jdk6_31_hprofagent_compressedOops.hprof
monitor.sendUserMessage(IProgressListener.Severity.WARNING, MessageUtil.format(Messages.Pass1Parser_UnloadClassNotFound, classSerNum, Long.toHexString(in.position() - 5)), null);
}
}
private void readStackFrame(long segmentStartPos, long length) throws IOException
{
long frameId = in.readID(idSize);
long methodName = in.readID(idSize);
long methodSig = in.readID(idSize);
long srcFile = in.readID(idSize);
long classSerNum = in.readUnsignedInt();
int lineNr = in.readInt(); // can be negative
StackFrame frame = new StackFrame(frameId, lineNr, getStringConstant(methodName), getStringConstant(methodSig),
getStringConstant(srcFile), classSerNum, segmentStartPos);
id2frame.put(frameId, frame);
}
private void readStackTrace(long length) throws IOException
{
long stackTraceNr = in.readUnsignedInt();
long threadNr = in.readUnsignedInt();
long frameCount = in.readUnsignedInt();
long[] frameIds = new long[(int) frameCount];
for (int i = 0; i < frameCount; i++)
{
frameIds[i] = in.readID(idSize);
}
StackTrace stackTrace = new StackTrace(stackTraceNr, threadNr, frameIds);
serNum2stackTrace.put(stackTraceNr, stackTrace);
}
private long readDumpSegments(long length) throws IOException, SnapshotException
{
long segmentStartPos = in.position();
long segmentsEndPos = segmentStartPos + length;
subrecordLoop: while (segmentStartPos < segmentsEndPos)
{
long workDone = segmentStartPos / 1000;
if (this.monitor.getWorkDone() < workDone)
{
if (this.monitor.isProbablyCanceled())
throw new IProgressListener.OperationCanceledException();
this.monitor.totalWorkDone(workDone);
}
int segmentType = -1;
try
{
segmentType = in.readUnsignedByte();
if (verbose)
System.out.println(" Read heap sub-record type " + segmentType + " at position 0x" + Long.toHexString(segmentStartPos)); //$NON-NLS-1$ //$NON-NLS-2$
switch (segmentType)
{
case Constants.DumpSegment.ROOT_UNKNOWN:
readGC(GCRootInfo.Type.UNKNOWN, 0);
break;
case Constants.DumpSegment.ROOT_THREAD_OBJECT:
readGCThreadObject(GCRootInfo.Type.THREAD_OBJ);
break;
case Constants.DumpSegment.ROOT_JNI_GLOBAL:
readGC(GCRootInfo.Type.NATIVE_STATIC, idSize);
break;
case Constants.DumpSegment.ROOT_JNI_LOCAL:
readGCWithThreadContext(GCRootInfo.Type.NATIVE_LOCAL, true);
break;
case Constants.DumpSegment.ROOT_JAVA_FRAME:
readGCWithThreadContext(GCRootInfo.Type.JAVA_LOCAL, true);
break;
case Constants.DumpSegment.ROOT_NATIVE_STACK:
readGCWithThreadContext(GCRootInfo.Type.NATIVE_STACK, false);
break;
case Constants.DumpSegment.ROOT_STICKY_CLASS:
readGC(GCRootInfo.Type.SYSTEM_CLASS, 0);
break;
case Constants.DumpSegment.ROOT_THREAD_BLOCK:
readGCWithThreadContext(GCRootInfo.Type.THREAD_BLOCK, false);
break;
case Constants.DumpSegment.ROOT_MONITOR_USED:
readGC(GCRootInfo.Type.BUSY_MONITOR, 0);
break;
case Constants.DumpSegment.CLASS_DUMP:
readClassDump(segmentStartPos);
break;
case Constants.DumpSegment.INSTANCE_DUMP:
readInstanceDump(segmentStartPos);
break;
case Constants.DumpSegment.OBJECT_ARRAY_DUMP:
readObjectArrayDump(segmentStartPos);
break;
case Constants.DumpSegment.PRIMITIVE_ARRAY_DUMP:
readPrimitiveArrayDump(segmentStartPos);
break;
default:
throw new SnapshotException(MessageUtil.format(Messages.Pass1Parser_Error_InvalidHeapDumpFile,
Integer.toHexString(segmentType), Long.toHexString(segmentStartPos)));
}
}
catch (EOFException e)
{
switch (strictnessPreference)
{
case STRICTNESS_STOP:
throw new SnapshotException(Messages.HPROFStrictness_Stopped, new SnapshotException(
MessageUtil.format(Messages.Pass1Parser_Error_invalidHPROFFile, segmentsEndPos - segmentStartPos,
in.position() - segmentStartPos, Integer.toHexString(segmentType), Long.toHexString(segmentStartPos)), e));
case STRICTNESS_WARNING:
case STRICTNESS_PERMISSIVE:
/*
* Recover from early end of file.
* The EOFException occurred in this record
* so the start of the record is an okay
* end point.
*/
monitor.sendUserMessage(Severity.WARNING, MessageUtil.format(
Messages.Pass1Parser_ExceptionReadingSubrecord,
Integer.toHexString(segmentType), Long.toHexString(in.position()), Long.toHexString(segmentStartPos), segmentStartPos - (segmentsEndPos - length)), e);
break subrecordLoop;
}
}
segmentStartPos = in.position();
}
if (verbose)
System.out.println(" Finished heap sub-records."); //$NON-NLS-1$
if (segmentStartPos != segmentsEndPos)
{
switch (strictnessPreference)
{
case STRICTNESS_STOP:
throw new SnapshotException(Messages.HPROFStrictness_Stopped,
new SnapshotException(
MessageUtil.format(Messages.Pass1Parser_UnexpectedEndPosition,
Long.toHexString(segmentsEndPos - length), length,
Long.toHexString(segmentStartPos),
Long.toHexString(segmentsEndPos))));
case STRICTNESS_WARNING:
case STRICTNESS_PERMISSIVE:
monitor.sendUserMessage(Severity.WARNING, MessageUtil.format(
Messages.Pass1Parser_UnexpectedEndPosition,
Long.toHexString(segmentsEndPos - length), length,
Long.toHexString(segmentStartPos), Long.toHexString(segmentsEndPos)), null);
break;
default:
throw new SnapshotException(Messages.HPROFStrictness_Unhandled_Preference);
}
}
return segmentStartPos;
}
/**
* Guaranteed skip of skips, and that
* we can read the last byte, so we haven't
* skipped beyond the end of the file
* @param s
* @return bytes skipped
* @throws IOException if unable to skip the bytes
* or read the last byte
*/
private long checkSkipBytes(long s) throws IOException
{
if (s > 0)
{
if (s > 1)
{
// Since skipBytes returns only an int, we might have to call
// it more than once.
long left = s - 1;
while (left > 0)
{
long toSkip = Math.min((long) Integer.MAX_VALUE, left);
int skipped = in.skipBytes(toSkip);
if (skipped < toSkip)
{
throw new EOFException();
}
left -= skipped;
}
}
in.readByte();
}
return s;
}
private void readGCThreadObject(int gcType) throws IOException
{
long id = in.readID(idSize);
int threadSerialNo = in.readInt();
thread2id.put(threadSerialNo, id);
handler.addGCRoot(id, 0, gcType);
checkSkipBytes(4);
}
private void readGC(int gcType, int skip) throws IOException
{
long id = in.readID(idSize);
handler.addGCRoot(id, 0, gcType);
if (skip > 0)
checkSkipBytes(skip);
}
private void readGCWithThreadContext(int gcType, boolean hasLineInfo) throws IOException
{
long id = in.readID(idSize);
int threadSerialNo = in.readInt();
Long tid = thread2id.get(threadSerialNo);
int lineNumber;
if (hasLineInfo)
lineNumber = in.readInt();
else
lineNumber = -1;
if (tid != null)
{
// With METHODSASCLASSES instead we add references from the stack
// frame to the local
if (!((HprofPreferences.FRAMES_ONLY.equals(METHODSASCLASSES)
|| HprofPreferences.RUNNING_METHODS_AS_CLASSES.equals(METHODSASCLASSES)) && hasLineInfo && lineNumber >= 0))
handler.addGCRoot(id, tid, gcType);
}
else
{
handler.addGCRoot(id, 0, gcType);
}
if (hasLineInfo)
{
List<JavaLocal> locals = thread2locals.get(threadSerialNo);
if (locals == null)
{
locals = new ArrayList<JavaLocal>();
thread2locals.put(threadSerialNo, locals);
}
locals.add(new JavaLocal(id, lineNumber, gcType));
}
}
private void readClassDump(long segmentStartPos) throws IOException
{
long address = in.readID(idSize);
checkSkipBytes(4); // stack trace serial number
long superClassObjectId = in.readID(idSize);
long classLoaderObjectId = in.readID(idSize);
// read signers, protection domain, reserved ids (2)
long signersId = in.readID(idSize);
long protectionDomainId = in.readID(idSize);
long reserved1Id = in.readID(idSize);
long reserved2Id = in.readID(idSize);
int extraDummyStatics = 0;
if (NEWCLASSSIZE)
{
// Always add signers / protectionDomain
extraDummyStatics += 2;
if (reserved1Id != 0)
++extraDummyStatics;
if (reserved2Id != 0)
++extraDummyStatics;
}
// instance size
int instsize = in.readInt();
// constant pool: u2 ( u2 u1 value )*
int constantPoolSize = in.readUnsignedShort();
Field[] constantPool = new Field[constantPoolSize];
for (int ii = 0; ii < constantPoolSize; ii++)
{
int index = in.readUnsignedShort(); // index
String name = "<constant pool["+index+"]>"; //$NON-NLS-1$ //$NON-NLS-2$
byte type = in.readByte();
Object value = readValue(in, null, type);
constantPool[ii] = new Field(name, type, value);
}
// static fields: u2 num ( name ID, u1 type, value)
int numStaticFields = in.readUnsignedShort();
Field[] statics = new Field[numStaticFields + extraDummyStatics];
for (int ii = 0; ii < numStaticFields; ii++)
{
long nameId = in.readID(idSize);
String name = getStringConstant(nameId);
byte type = in.readByte();
Object value = readValue(in, null, type);
statics[ii] = new Field(name, type, value);
}
if (NEWCLASSSIZE)
{
int si = numStaticFields;
statics[si++] = new Field("<signers>", Type.OBJECT, signersId == 0 ? null : new ObjectReference(null, signersId)); //$NON-NLS-1$
statics[si++] = new Field("<protectionDomain>", Type.OBJECT, protectionDomainId == 0 ? null : new ObjectReference(null, protectionDomainId)); //$NON-NLS-1$
if (reserved1Id != 0)
statics[si++] = new Field("<reserved1>", Type.OBJECT, reserved1Id == 0 ? null : new ObjectReference(null, reserved1Id)); //$NON-NLS-1$
if (reserved2Id != 0)
statics[si++] = new Field("<reserved2>", Type.OBJECT, reserved2Id == 0 ? null : new ObjectReference(null, reserved2Id)); //$NON-NLS-1$
Field all[] = new Field[statics.length + constantPool.length];
System.arraycopy(statics, 0, all, 0, statics.length);
System.arraycopy(constantPool, 0, all, statics.length, constantPool.length);
statics = all;
}
// instance fields: u2 num ( name ID, u1 type )
int numInstanceFields = in.readUnsignedShort();
FieldDescriptor[] fields = new FieldDescriptor[numInstanceFields];
for (int ii = 0; ii < numInstanceFields; ii++)
{
long nameId = in.readID(idSize);
String name = getStringConstant(nameId);
byte type = in.readByte();
fields[ii] = new FieldDescriptor(name, type);
}
// get name
String className = class2name.get(address);
if (className == null)
className = "unknown-name@0x" + Long.toHexString(address); //$NON-NLS-1$
if (className.charAt(0) == '[') // quick check if array at hand
{
// fix object class names
Matcher matcher = PATTERN_OBJ_ARRAY.matcher(className);
if (matcher.matches())
{
int l = matcher.group(1).length();
StringBuilder classNameBuilder = new StringBuilder(matcher.group(2));
for (int ii = 0; ii < l; ii++)
classNameBuilder.append("[]"); //$NON-NLS-1$
className = classNameBuilder.toString();
}
// primitive arrays
matcher = PATTERN_PRIMITIVE_ARRAY.matcher(className);
if (matcher.matches())
{
int count = matcher.group(1).length() - 1;
className = "unknown[]"; //$NON-NLS-1$
char signature = matcher.group(2).charAt(0);
for (int ii = 0; ii < IPrimitiveArray.SIGNATURES.length; ii++)
{
if (IPrimitiveArray.SIGNATURES[ii] == (byte) signature)
{
className = IPrimitiveArray.TYPE[ii];
break;
}
}
StringBuilder classNameBuilder = new StringBuilder(className);
for (int ii = 0; ii < count; ii++)
classNameBuilder.append("[]"); //$NON-NLS-1$
className = classNameBuilder.toString();
}
}
ClassImpl clazz = new ClassImpl(address, className, superClassObjectId, classLoaderObjectId, statics, fields);
// This will be replaced by a size calculated from the field sizes
clazz.setHeapSizePerInstance(instsize);
handler.addClass(clazz, segmentStartPos, idSize, instsize);
// TODO do we actually need this code?
// if so - move it to HprofParserHandlerImpl
// instanceaddress2classID was a HashMapLongObject<Long>
/*
// Just in case the superclass is missing
if (superClassObjectId != 0 && handler.lookupClass(superClassObjectId) == null)
{
// Try to calculate how big the superclass should be
int ownFieldsSize = 0;
for (FieldDescriptor field : clazz.getFieldDescriptors())
{
int type = field.getType();
if (type == IObject.Type.OBJECT)
ownFieldsSize += idSize;
else
ownFieldsSize += IPrimitiveArray.ELEMENT_SIZE[type];
}
int supersize = Math.max(instsize - ownFieldsSize, 0);
// A real size of an instance will override this
handler.reportRequiredClass(superClassObjectId, supersize);
}
// Check / set types of classes
if (instanceaddress2classID.containsKey(address))
{
// Already seen an instance dump for class type
long classId = instanceaddress2classID.get(address);
IClass type = handler.lookupClass(classId);
if (type instanceof ClassImpl)
{
clazz.setClassInstance((ClassImpl)type);
}
}
for (Iterator<Entry<Long>>it = instanceaddress2classID.entries(); it.hasNext(); )
{
Entry<Long>e = it.next();
if (e.getValue() == address)
{
// Existing class has this class as its type
IClass base = handler.lookupClass(e.getKey());
if (base instanceof ClassImpl)
{
ClassImpl baseCls = (ClassImpl)base;
baseCls.setClassInstance(clazz);
}
}
}
*/
}
private void readInstanceDump(long segmentStartPos) throws IOException
{
long address = in.readID(idSize);
checkSkipBytes(4); // stack trace serial
long classID = in.readID(idSize);
int payload = in.readInt();
checkSkipBytes(payload);
if (!skipFrameObject(address))
handler.reportInstanceWithClass(address, segmentStartPos, classID, payload);
}
private void readObjectArrayDump(long segmentStartPos) throws IOException
{
long address = in.readID(idSize);
if (!foundCompressed && idSize == 8 && address > previousArrayStart && address < previousArrayUncompressedEnd)
{
monitor.sendUserMessage(
Severity.INFO,
MessageUtil.format(Messages.Pass1Parser_DetectedCompressedReferences,
Long.toHexString(address), Long.toHexString(previousArrayStart)), null);
handler.addProperty(IHprofParserHandler.REFERENCE_SIZE, "4"); //$NON-NLS-1$
foundCompressed = true;
}
checkSkipBytes(4); // stack trace serial
int size = in.readInt();
long arrayClassObjectID = in.readID(idSize);
checkSkipBytes((long) size * idSize);
previousArrayStart = address;
previousArrayUncompressedEnd = address + 16 + (long)size * 8;
if (size > biggestArrays[0])
{
biggestArrays[0] = size;
Arrays.sort(biggestArrays);
}
handler.reportInstanceOfObjectArray(address, segmentStartPos, arrayClassObjectID);
}
private void readPrimitiveArrayDump(long segmentStartPos) throws SnapshotException, IOException
{
long address = in.readID(idSize);
checkSkipBytes(4);
int size = in.readInt();
byte elementType = in.readByte();
if ((elementType < IPrimitiveArray.Type.BOOLEAN) || (elementType > IPrimitiveArray.Type.LONG))
throw new SnapshotException(Messages.Pass1Parser_Error_IllegalType);
int elementSize = IPrimitiveArray.ELEMENT_SIZE[elementType];
checkSkipBytes((long) elementSize * size);
handler.reportInstanceOfPrimitiveArray(address, segmentStartPos, elementType);
}
private String getStringConstant(long address)
{
if (address == 0L)
return ""; //$NON-NLS-1$
String result = constantPool.get(address);
return result == null ? MessageUtil.format(Messages.Pass1Parser_Error_UnresolvedName, Long.toHexString(address)) : result;
}
private void dumpThreads() throws IOException
{
// noticed that one stack trace with empty stack is always reported,
// even if the dump has no call stacks info
if (serNum2stackTrace == null || serNum2stackTrace.size() <= 1)
return;
PrintWriter out = null;
String outputName = handler.getSnapshotInfo().getPrefix() + "threads"; //$NON-NLS-1$
try
{
out = new PrintWriter(new OutputStreamWriter(new FileOutputStream(outputName), "UTF-8")); //$NON-NLS-1$
Iterator<StackTrace> it = serNum2stackTrace.values();
while (it.hasNext())
{
StackTrace stack = it.next();
Long tid = thread2id.get(stack.threadSerialNr);
if (tid == null)
continue;
String threadId = tid == null ? "<unknown>" : "0x" + Long.toHexString(tid); //$NON-NLS-1$ //$NON-NLS-2$
out.println("Thread " + threadId); //$NON-NLS-1$
out.println(stack);
out.println(" locals:"); //$NON-NLS-1$
List<JavaLocal> locals = thread2locals.get(stack.threadSerialNr);
if (locals != null)
{
for (JavaLocal javaLocal : locals)
{
out.println(" objectId=0x" + Long.toHexString(javaLocal.objectId) + ", line=" + javaLocal.lineNumber); //$NON-NLS-1$ //$NON-NLS-2$
}
}
if (HprofPreferences.FRAMES_ONLY.equals(METHODSASCLASSES)
|| HprofPreferences.RUNNING_METHODS_AS_CLASSES.equals(METHODSASCLASSES))
{
// Add the stack frames as extra locals
for (int i = 0; i < stack.frameIds.length; ++i)
{
long frameI = stack.frameIds[i];
StackFrame frame = id2frame.get(frameI);
{
long frameAddress = frameIdToAddress(frame.frameId);
out.println(" objectId=0x" + Long.toHexString(frameAddress) + ", line=" + (i)); //$NON-NLS-1$ //$NON-NLS-2$
}
}
}
out.println();
}
out.flush();
this.monitor.sendUserMessage(Severity.INFO,
MessageUtil.format(Messages.Pass1Parser_Info_WroteThreadsTo, outputName), null);
}
catch (IOException e)
{
this.monitor.sendUserMessage(Severity.WARNING,
MessageUtil.format(Messages.Pass1Parser_Error_WritingThreadsInformation), e);
}
finally
{
if (out != null)
{
try
{
out.close();
}
catch (Exception ignore)
{
// $JL-EXC$
}
}
}
if (HprofPreferences.FRAMES_ONLY.equals(METHODSASCLASSES)
|| HprofPreferences.RUNNING_METHODS_AS_CLASSES.equals(METHODSASCLASSES))
{
stackFramesAsObjects();
}
else
{
fixupNativeClasses();
}
}
/**
* Fix up any special classes introduced in an exported HPROF file.
*/
private void fixupNativeClasses()
{
IClass nm = handler.lookupClassByName(NATIVE_MEMORY, false);
IClass nt = handler.lookupClassByName(NATIVE_MEMORY_TYPE, false);
IClass mt = handler.lookupClassByName(METHOD_TYPE, false);
IClass m = handler.lookupClassByName(METHOD, false);
IClass s = handler.lookupClassByName(STACK_FRAME, false);
if (nm instanceof ClassImpl && nt instanceof ClassImpl && (m instanceof ClassImpl || s instanceof ClassImpl))
{
((ClassImpl) nm).setClassInstance((ClassImpl) nt);
((ClassImpl) nt).setClassInstance((ClassImpl) nt);
if (mt instanceof ClassImpl)
{
((ClassImpl) mt).setClassInstance((ClassImpl) nt);
if (m instanceof ClassImpl)
((ClassImpl) m).setClassInstance((ClassImpl) mt);
}
if (s instanceof ClassImpl)
((ClassImpl) s).setClassInstance((ClassImpl) nt);
}
}
private ClassImpl genClass(long nextAddress, String className, long superClass, long classLoader,
Field flds[], FieldDescriptor desc[], IClass clsType) throws IOException
{
IClass existing = handler.lookupClassByName(className, false);
if (existing instanceof ClassImpl)
{
if (clsType instanceof ClassImpl)
((ClassImpl)existing).setClassInstance((ClassImpl) clsType);
return (ClassImpl)existing;
}
ClassImpl newClass = new ClassImpl(nextAddress, className, superClass, classLoader, flds, desc);
class2name.put(nextAddress, className);
// This will be replaced by a size calculated from the field sizes
newClass.setHeapSizePerInstance(0);
if (clsType instanceof ClassImpl)
newClass.setClassInstance((ClassImpl) clsType);
handler.addClass(newClass, -1, idSize, 0);
return newClass;
}
private long nextFreeAddr(long addr)
{
while (class2name.containsKey(addr))
{
addr += stackFrameClassAlign;
}
return addr;
}
private void initFrameToAddress()
{
// Find the biggest frameId
long maxFrameId = 0;
long ids[] = id2frame.getAllKeys().clone();
Arrays.sort(ids);
if (ids.length >= 1)
{
maxFrameId = ids[ids.length - 1];
long idBits = 0;
for (long id : ids)
{
idBits |= id;
}
if ((idBits & 0x3) != 0)
{
stackFrameAlign = 0x100;
stackFrameBase = 0x100;
}
else
{
stackFrameAlign = 1;
stackFrameBase = 0;
}
}
long next = frameIdToAddress(maxFrameId + 1);
stackFrameClassBase = next + Math.floorMod(next + stackFrameClassAlign, stackFrameClassAlign);
}
/**
* Converts a frame ID to a pseudo-object address.
* @param frameId the ID
* @return the address
*/
private long frameIdToAddress(long frameId)
{
return stackFrameBase + frameId * stackFrameAlign;
}
private boolean skipFrameObject(long addr)
{
if (frameObjs == null)
{
frameObjs = new SetLong();
if (READFRAMES)
{
initFrameToAddress();
long ids[] = id2frame.getAllKeys();
for (long id : ids)
{
frameObjs.add(frameIdToAddress(id));
}
}
}
return frameObjs.contains(addr);
}
private long systemClassLoader()
{
IClass c = handler.lookupClassByName(ClassImpl.JAVA_LANG_CLASS, false);
if (c == null)
return 0;
return c.getClassLoaderAddress();
}
private void stackFramesAsObjects() throws IOException
{
final long rootLoader = systemClassLoader();
long nextAddress = stackFrameClassBase;
// The method addresses come after the stack frames
nextAddress = nextFreeAddr(nextAddress);
// Equivalent to java.lang.Object
ClassImpl clazzNativeMemory = genClass(nextAddress, NATIVE_MEMORY, 0, rootLoader, new Field[0], new FieldDescriptor[0], null);
nextAddress = nextFreeAddr(nextAddress);
// Equivalent to java.lang.Class
ClassImpl clazzNativeMemoryType = genClass(nextAddress, NATIVE_MEMORY_TYPE, clazzNativeMemory.getObjectAddress(), rootLoader, new Field[0], new FieldDescriptor[0], null);
nextAddress = nextFreeAddr(nextAddress);
clazzNativeMemoryType.setClassInstance(clazzNativeMemoryType);
// Now we have a type we can set the type of NATIVE_MEMORY
clazzNativeMemory.setClassInstance(clazzNativeMemoryType);
ClassImpl clazzMethodType;
ClassImpl clazzMethod;
ClassImpl clazzStackFrame;
Field[] frameStatics = new Field[0];
if (HprofPreferences.RUNNING_METHODS_AS_CLASSES.equals(METHODSASCLASSES))
{
clazzMethodType = genClass(nextAddress, METHOD_TYPE, clazzNativeMemoryType.getObjectAddress(), rootLoader, new Field[0], new FieldDescriptor[0], clazzNativeMemoryType);
nextAddress = nextFreeAddr(nextAddress);
FieldDescriptor[] fldm = new FieldDescriptor[] { new FieldDescriptor(LINE_NUMBER, IObject.Type.INT),
new FieldDescriptor(COMPILATION_LEVEL, IObject.Type.INT),
new FieldDescriptor(NATIVE, IObject.Type.BOOLEAN),
new FieldDescriptor(LOCATION_ADDRESS, IObject.Type.LONG),
new FieldDescriptor(FRAME_NUMBER, IObject.Type.INT),
new FieldDescriptor(STACK_DEPTH, IObject.Type.INT),
};
clazzMethod = genClass(nextAddress, METHOD, clazzNativeMemory.getObjectAddress(), rootLoader, frameStatics, fldm, clazzMethodType);
nextAddress = nextFreeAddr(nextAddress);
clazzStackFrame = null;
}
else
{
clazzMethodType = null;
clazzMethod = null;
FieldDescriptor[] fld = new FieldDescriptor[] { new FieldDescriptor(LINE_NUMBER, IObject.Type.INT),
new FieldDescriptor(COMPILATION_LEVEL, IObject.Type.INT),
new FieldDescriptor(NATIVE, IObject.Type.BOOLEAN),
new FieldDescriptor(LOCATION_ADDRESS, IObject.Type.LONG),
new FieldDescriptor(FRAME_NUMBER, IObject.Type.INT),
new FieldDescriptor(STACK_DEPTH, IObject.Type.INT),
new FieldDescriptor(FILE_NAME, IObject.Type.OBJECT),
new FieldDescriptor(METHOD_NAME, IObject.Type.OBJECT),
};
clazzStackFrame = genClass(nextAddress, STACK_FRAME, clazzNativeMemory.getObjectAddress(), rootLoader, frameStatics, fld, clazzNativeMemoryType);
nextAddress = nextFreeAddr(nextAddress);
}
// Examine all the stack frames
for (Iterator<StackTrace> it = serNum2stackTrace.values(); it.hasNext(); )
{
StackTrace st = it.next();
Long tid = thread2id.get(st.threadSerialNr);
if (tid == null)
continue;
List<JavaLocal> locals = thread2locals.get(st.threadSerialNr);
for (int linenum = 0; linenum < st.frameIds.length; ++linenum)
{
long frameId = st.frameIds[linenum];
StackFrame frame = id2frame.get(frameId);
long frameAddress = frameIdToAddress(frame.frameId);
long frameType = 0;
ClassImpl frameClazz = null;
if (HprofPreferences.RUNNING_METHODS_AS_CLASSES.equals(METHODSASCLASSES))
{
String className2 = null;
Long classId = classSerNum2id.get(frame.classSerNum);
if (classId == null)
{
className2 = "<UNKNOWN CLASS>"; //$NON-NLS-1$
}
else
{
className2 = class2name.get(classId);
}
String fullMethodName = className2 + '.' + frame.method + frame.methodSignature;
for (Iterator <HashMapLongObject.Entry<String>>it2 = class2name.entries(); it2.hasNext();)
{
HashMapLongObject.Entry<String>e = it2.next();
if (e.getValue().equals(fullMethodName))
{
IClass cls = handler.lookupClass(e.getKey());
if (cls != null)
{
for (Field s1 : cls.getStaticFields())
{
if (s1.getType() == Type.OBJECT
&& s1.getName().equals(DECLARING_CLASS)
&& s1.getValue() instanceof ObjectReference
&& ((ObjectReference)s1.getValue()).getObjectAddress() == classId)
{
frameType = e.getKey();
if (cls instanceof ClassImpl)
{
frameClazz = (ClassImpl)cls;
frameClazz.setClassInstance(clazzMethodType);
}
break;
}
}
if (frameType != 0)
break;
}
}
}
if (frameType == 0)
{
// Method does not exist, so create it
frameType = nextAddress;
Field statics[] = {
new Field(DECLARING_CLASS, Type.OBJECT, new ObjectReference(null, classId)),
new Field(FILE_NAME, Type.OBJECT, frame.sourceFile),
};
IClass decClass = handler.lookupClass(classId);
frameClazz = genClass(frameType, fullMethodName, clazzMethod.getObjectAddress(), decClass.getClassLoaderAddress(), statics, new FieldDescriptor[0], clazzMethodType);
nextAddress = nextFreeAddr(nextAddress);
}
}
else
{
frameType = clazzStackFrame.getObjectAddress();
}
handler.reportInstanceWithClass(frameAddress, frame.filePos, frameType, 0);
handler.addGCRoot(frameAddress, tid, GCRootInfo.Type.JAVA_STACK_FRAME);
int localCount = 0;
for (JavaLocal loc : locals)
{
if (loc.lineNumber == linenum)
{
handler.addGCRoot(loc.objectId, frameAddress, loc.getType());
++localCount;
}
}
// Adjust size of frame based on number of locals
if (HprofPreferences.RUNNING_METHODS_AS_CLASSES.equals(METHODSASCLASSES))
{
if (frameClazz != null)
{
long frameSize = localCount * idSize;
if (frameSize > frameClazz.getHeapSizePerInstance())
frameClazz.setHeapSizePerInstance(frameSize);
}
}
}
}
}
private class StackFrame
{
long frameId;
String method;
String methodSignature;
String sourceFile;
long classSerNum;
long filePos;
/*
* > 0 line number 0 no line info -1 unknown location -2 compiled method
* -3 native method
*/
int lineNr;
public StackFrame(long frameId, int lineNr, String method, String methodSignature, String sourceFile,
long classSerNum, long filePos)
{
super();
this.frameId = frameId;
this.lineNr = lineNr;
this.method = method;
this.methodSignature = methodSignature;
this.sourceFile = sourceFile;
this.classSerNum = classSerNum;
this.filePos = filePos;
}
@Override
public String toString()
{
String className = null;
Long classId = classSerNum2id.get(classSerNum);
if (classId == null)
{
className = "<UNKNOWN CLASS>"; //$NON-NLS-1$
}
else
{
className = class2name.get(classId);
}
String sourceLocation = ""; //$NON-NLS-1$
if (lineNr > 0)
{
sourceLocation = sourceFile + ":" + String.valueOf(lineNr); //$NON-NLS-1$
}
else if (lineNr == 0 || lineNr == -1)
{
if (sourceFile != null)
sourceLocation = sourceFile;
else
sourceLocation = "Unknown Source"; //$NON-NLS-1$
}
else if (lineNr == -2)
{
if (sourceFile != null)
sourceLocation = sourceFile + "(Compiled Code)"; //$NON-NLS-1$
else
sourceLocation = "(Compiled Code)"; //$NON-NLS-1$
}
else if (lineNr == -3)
{
if (sourceFile != null)
sourceLocation = sourceFile + "(Native Method)"; //$NON-NLS-1$
else
sourceLocation = "(Native Method)"; //$NON-NLS-1$
}
return " at " + className + "." + method + methodSignature + " (" + sourceLocation + ")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
}
}
private class StackTrace
{
private long threadSerialNr;
private long[] frameIds;
public StackTrace(long serialNr, long threadSerialNr, long[] frameIds)
{
super();
this.frameIds = frameIds;
this.threadSerialNr = threadSerialNr;
}
@Override
public String toString()
{
StringBuilder b = new StringBuilder();
for (long frameId : frameIds)
{
StackFrame frame = id2frame.get(frameId);
if (frame != null)
{
b.append(frame.toString());
b.append("\r\n"); //$NON-NLS-1$
}
}
return b.toString();
}
}
private static class JavaLocal
{
private long objectId;
private int lineNumber;
private int type;
public JavaLocal(long objectId, int lineNumber, int type)
{
super();
this.lineNumber = lineNumber;
this.objectId = objectId;
this.type = type;
}
public int getType()
{
return type;
}
}
}