blob: 3622d0f1fdc95a1bfad6cbacb71bc4cd80d2e4ca [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2018 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 v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* SAP AG - initial API and implementation
* IBM Corporation - validation of indices
*******************************************************************************/
package org.eclipse.mat.parser.internal;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import org.eclipse.mat.SnapshotException;
import org.eclipse.mat.collect.ArrayInt;
import org.eclipse.mat.collect.ArrayIntBig;
import org.eclipse.mat.collect.BitField;
import org.eclipse.mat.collect.HashMapIntObject;
import org.eclipse.mat.collect.IteratorInt;
import org.eclipse.mat.collect.SetInt;
import org.eclipse.mat.parser.IObjectReader;
import org.eclipse.mat.parser.index.IIndexReader;
import org.eclipse.mat.parser.index.IIndexReader.IOne2OneIndex;
import org.eclipse.mat.parser.index.IIndexReader.IOne2SizeIndex;
import org.eclipse.mat.parser.index.IndexManager;
import org.eclipse.mat.parser.index.IndexManager.Index;
import org.eclipse.mat.parser.internal.snapshot.HistogramBuilder;
import org.eclipse.mat.parser.internal.snapshot.MultiplePathsFromGCRootsComputerImpl;
import org.eclipse.mat.parser.internal.snapshot.ObjectCache;
import org.eclipse.mat.parser.internal.snapshot.ObjectMarker;
import org.eclipse.mat.parser.internal.snapshot.PathsFromGCRootsTreeBuilder;
import org.eclipse.mat.parser.internal.snapshot.RetainedSizeCache;
import org.eclipse.mat.parser.internal.util.IntStack;
import org.eclipse.mat.parser.internal.util.ParserRegistry;
import org.eclipse.mat.parser.internal.util.ParserRegistry.Parser;
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.XClassHistogramRecord;
import org.eclipse.mat.parser.model.XGCRootInfo;
import org.eclipse.mat.parser.model.XSnapshotInfo;
import org.eclipse.mat.snapshot.DominatorsSummary;
import org.eclipse.mat.snapshot.DominatorsSummary.ClassDominatorRecord;
import org.eclipse.mat.snapshot.ExcludedReferencesDescriptor;
import org.eclipse.mat.snapshot.Histogram;
import org.eclipse.mat.snapshot.IMultiplePathsFromGCRootsComputer;
import org.eclipse.mat.snapshot.IPathsFromGCRootsComputer;
import org.eclipse.mat.snapshot.ISnapshot;
import org.eclipse.mat.snapshot.PathsFromGCRootsTree;
import org.eclipse.mat.snapshot.UnreachableObjectsHistogram;
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.IThreadStack;
import org.eclipse.mat.snapshot.model.NamedReference;
import org.eclipse.mat.util.IProgressListener;
import org.eclipse.mat.util.IProgressListener.OperationCanceledException;
import org.eclipse.mat.util.MessageUtil;
import org.eclipse.mat.util.VoidProgressListener;
public final class SnapshotImpl implements ISnapshot
{
// //////////////////////////////////////////////////////////////
// factory methods
// //////////////////////////////////////////////////////////////
private static final String VERSION = "MAT_01";//$NON-NLS-1$
@SuppressWarnings("unchecked")
public static SnapshotImpl readFromFile(File file, String prefix, IProgressListener listener)
throws SnapshotException, IOException
{
FileInputStream fis = null;
listener.beginTask(Messages.SnapshotImpl_ReopeningParsedHeapDumpFile, 9);
try
{
File indexFile = new File(prefix + "index"); //$NON-NLS-1$
fis = new FileInputStream(indexFile);
listener.worked(1);
ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(fis));
String version = in.readUTF();
if (!VERSION.equals(version))
throw new IOException(MessageUtil.format(Messages.SnapshotImpl_Error_UnknownVersion, version));
String objectReaderUniqueIdentifier = in.readUTF();
Parser parser = ParserPlugin.getDefault().getParserRegistry().lookupParser(objectReaderUniqueIdentifier);
if (parser == null)
throw new IOException(Messages.SnapshotImpl_Error_ParserNotFound + objectReaderUniqueIdentifier);
listener.worked(1);
IObjectReader heapObjectReader = parser.create(IObjectReader.class, ParserRegistry.OBJECT_READER);
if (heapObjectReader == null)
throw new SnapshotException(MessageUtil.format(Messages.SnapshotFactoryImpl_Error_OpeningHeapDump, file));
XSnapshotInfo snapshotInfo = (XSnapshotInfo) in.readObject();
snapshotInfo.setProperty("$heapFormat", parser.getId()); //$NON-NLS-1$
HashMapIntObject<ClassImpl> classCache = (HashMapIntObject<ClassImpl>) in.readObject();
if (listener.isCanceled())
throw new IProgressListener.OperationCanceledException();
HashMapIntObject<XGCRootInfo[]> roots = (HashMapIntObject<XGCRootInfo[]>) in.readObject();
HashMapIntObject<HashMapIntObject<XGCRootInfo[]>> rootsPerThread = (HashMapIntObject<HashMapIntObject<XGCRootInfo[]>>) in
.readObject();
listener.worked(1);
if (listener.isCanceled())
throw new IProgressListener.OperationCanceledException();
HashMapIntObject<String> loaderLabels = (HashMapIntObject<String>) in.readObject();
BitField arrayObjects = (BitField) in.readObject();
listener.worked(3);
snapshotInfo.setPrefix(prefix);
// Allow a dump to be opened via the index file
if (file.equals(indexFile))
{
// Previous location of the heap dump
file = new File(snapshotInfo.getPath());
if (!file.exists())
{
// Perhaps files were moved, so try in same directory as index
file = new File(indexFile.getParentFile(), file.getName());
snapshotInfo.setPath(file.getAbsolutePath());
}
}
else
{
snapshotInfo.setPath(file.getAbsolutePath());
}
IndexManager indexManager = new IndexManager();
indexManager.init(prefix);
SnapshotImpl ret = new SnapshotImpl(snapshotInfo, heapObjectReader, classCache, roots, rootsPerThread, loaderLabels,
arrayObjects, indexManager);
listener.worked(3);
return ret;
}
catch (ClassNotFoundException e)
{
IOException ioe = new IOException(e.getMessage());
ioe.initCause(e);
throw ioe;
}
catch (ClassCastException e)
{
IOException ioe = new IOException(e.getMessage());
ioe.initCause(e);
throw ioe;
}
finally
{
if (fis != null)
fis.close();
listener.done();
}
}
public static SnapshotImpl create(XSnapshotInfo snapshotInfo, //
String objectReaderUniqueIdentifier, //
IObjectReader heapObjectReader, //
HashMapIntObject<ClassImpl> classCache, //
HashMapIntObject<XGCRootInfo[]> roots, //
HashMapIntObject<HashMapIntObject<XGCRootInfo[]>> rootsPerThread, //
BitField arrayObjects, //
IndexManager indexManager, //
IProgressListener listener) throws IOException, SnapshotException
{
SnapshotImpl answer = new SnapshotImpl(snapshotInfo, heapObjectReader, classCache, roots, rootsPerThread, null,
arrayObjects, indexManager);
answer.calculateLoaderLabels();
FileOutputStream fos = null;
ObjectOutputStream out = null;
try
{
fos = new FileOutputStream(snapshotInfo.getPrefix() + "index");//$NON-NLS-1$
out = new ObjectOutputStream(new BufferedOutputStream(fos));
out.writeUTF(VERSION);
out.writeUTF(objectReaderUniqueIdentifier);
out.writeObject(answer.snapshotInfo);
out.writeObject(answer.classCache);
if (listener.isCanceled())
throw new IProgressListener.OperationCanceledException();
out.writeObject(answer.roots);
out.writeObject(answer.rootsPerThread);
if (listener.isCanceled())
throw new IProgressListener.OperationCanceledException();
out.writeObject(answer.loaderLabels);
out.writeObject(answer.arrayObjects);
}
finally
{
if (out != null)
out.close();
if (fos != null)
fos.close();
}
return answer;
}
// //////////////////////////////////////////////////////////////
// member variables
// //////////////////////////////////////////////////////////////
// serialized data
private XSnapshotInfo snapshotInfo;
private HashMapIntObject<ClassImpl> classCache;
private HashMapIntObject<XGCRootInfo[]> roots;
private HashMapIntObject<HashMapIntObject<XGCRootInfo[]>> rootsPerThread;
private HashMapIntObject<String> loaderLabels;
private BitField arrayObjects;
// stored in separate files
private IndexManager indexManager;
// runtime data
private IObjectReader heapObjectReader;
private boolean dominatorTreeCalculated;
private Map<String, List<IClass>> classCacheByName;
private ObjectCache<IObject> objectCache;
private boolean parsedThreads = false;
HashMapIntObject<IThreadStack> threadId2stack;
// //////////////////////////////////////////////////////////////
// constructor
// //////////////////////////////////////////////////////////////
private SnapshotImpl(XSnapshotInfo snapshotInfo, //
IObjectReader heapObjectReader, //
HashMapIntObject<ClassImpl> classCache, //
HashMapIntObject<XGCRootInfo[]> roots, //
HashMapIntObject<HashMapIntObject<XGCRootInfo[]>> rootsPerThread, //
HashMapIntObject<String> loaderLabels, //
BitField arrayObjects, //
IndexManager indexManager) throws SnapshotException, IOException
{
this.snapshotInfo = snapshotInfo;
this.heapObjectReader = heapObjectReader;
this.classCache = classCache;
this.roots = roots;
this.rootsPerThread = rootsPerThread;
this.loaderLabels = loaderLabels;
this.arrayObjects = arrayObjects;
this.indexManager = indexManager;
// initialize data
if (indexManager.i2sv2 == null)
{
// If the class+classloader retained size cache doesn't exist then create it
indexManager.setReader(Index.I2RETAINED, new RetainedSizeCache(snapshotInfo));
}
this.classCacheByName = new HashMap<String, List<IClass>>(this.classCache.size());
for (Iterator<ClassImpl> iter = this.classCache.values(); iter.hasNext();)
{
ClassImpl clasz = iter.next();
clasz.setSnapshot(this);
List<IClass> list = classCacheByName.get(clasz.getName());
if (list == null)
classCacheByName.put(clasz.getName(), list = new ArrayList<IClass>());
list.add(clasz);
}
this.dominatorTreeCalculated = indexManager.dominated() != null && indexManager.o2retained() != null
&& indexManager.dominator() != null;
this.objectCache = new HeapObjectCache(this, 1000);
this.heapObjectReader.open(this);
Object unreach = snapshotInfo.getProperty(UnreachableObjectsHistogram.class.getName());
if (unreach instanceof UnreachableObjectsHistogram)
((UnreachableObjectsHistogram)unreach).setSnapshot(this);
}
private void calculateLoaderLabels() throws SnapshotException
{
loaderLabels = new HashMapIntObject<String>();
long usedHeapSize = 0;
int systemClassLoaderId = indexManager.o2address().reverse(0);
Object[] classes = classCache.getAllValues();
for (int i = 0; i < classes.length; i++)
{
ClassImpl clasz = (ClassImpl) classes[i];
usedHeapSize += clasz.getTotalSize();
int classLoaderId = clasz.getClassLoaderId();
String label = loaderLabels.get(classLoaderId);
if (label != null)
continue;
if (classLoaderId == systemClassLoaderId)
{
label = "<system class loader>";//$NON-NLS-1$
}
else
{
IObject classLoader = getObject(classLoaderId);
label = classLoader.getClassSpecificName();
if (label == null)
label = ClassLoaderImpl.NO_LABEL;
}
loaderLabels.put(classLoaderId, label);
}
// now, let's go through all instances of all sub classes to attach
// labels
Collection<IClass> loaderClasses = getClassesByName(IClass.JAVA_LANG_CLASSLOADER, true);
if (loaderClasses != null)
{
for (IClass clazz : loaderClasses)
{
for (int classLoaderId : clazz.getObjectIds())
{
String label = loaderLabels.get(classLoaderId);
if (label != null)
continue;
if (classLoaderId == systemClassLoaderId)
{
label = "<system class loader>";//$NON-NLS-1$
}
else
{
IObject classLoader = getObject(classLoaderId);
label = classLoader.getClassSpecificName();
if (label == null)
label = ClassLoaderImpl.NO_LABEL;
}
loaderLabels.put(classLoaderId, label);
}
}
}
snapshotInfo.setUsedHeapSize(usedHeapSize);
// numberOfObjects was previously calculated by summing getNumberOfObjects() for
// each class. Sometimes there was a mismatch. See bug 294311
snapshotInfo.setNumberOfObjects(indexManager.idx.size());
snapshotInfo.setNumberOfClassLoaders(loaderLabels.size());
snapshotInfo.setNumberOfGCRoots(roots.size());
snapshotInfo.setNumberOfClasses(classCache.size());
// important: refresh object cache. To calculate the loader labels, the
// class loader instances are loaded. however, the object cache uses the
// loader label to determine the implementation class. hence, cached
// instances from creating the labels have the wrong implementation
// class. hence, the refresh.
objectCache.clear();
}
// //////////////////////////////////////////////////////////////
// interface implementation
// //////////////////////////////////////////////////////////////
public XSnapshotInfo getSnapshotInfo()
{
return snapshotInfo;
}
public int[] getGCRoots() throws SnapshotException
{
return roots.getAllKeys();
// return Arrays.asList((GCRootInfo[]) roots.getAllValues(new
// GCRootInfo[roots.size()]));
}
public Collection<IClass> getClasses() throws SnapshotException
{
return Arrays.asList(classCache.getAllValues(new IClass[classCache.size()]));
}
public Collection<IClass> getClassesByName(String name, boolean includeSubClasses) throws SnapshotException
{
List<IClass> list = this.classCacheByName.get(name);
if (list == null)
return null;
if (!includeSubClasses)
return Collections.unmodifiableCollection(list);
// use set to filter out duplicate subclasses
Set<IClass> answer = new HashSet<IClass>();
answer.addAll(list);
for (IClass clazz : list)
answer.addAll(clazz.getAllSubclasses());
return answer;
}
public Collection<IClass> getClassesByName(Pattern namePattern, boolean includeSubClasses) throws SnapshotException
{
Set<IClass> result = new HashSet<IClass>();
Object[] classes = classCache.getAllValues();
for (int i = 0; i < classes.length; i++)
{
IClass clazz = (IClass) classes[i];
if (namePattern.matcher(clazz.getName()).matches())
{
result.add(clazz);
if (includeSubClasses)
{
result.addAll(clazz.getAllSubclasses());
}
}
}
return result;
}
public Histogram getHistogram(IProgressListener listener) throws SnapshotException
{
if (listener == null)
listener = new VoidProgressListener();
HistogramBuilder histogramBuilder = new HistogramBuilder(Messages.SnapshotImpl_Histogram);
Object[] classes = classCache.getAllValues();
for (int i = 0; i < classes.length; i++)
{
histogramBuilder.put(new XClassHistogramRecord((ClassImpl) classes[i]));
}
if (listener.isCanceled())
throw new IProgressListener.OperationCanceledException();
return histogramBuilder.toHistogram(this, true);
}
public Histogram getHistogram(int[] objectIds, IProgressListener progressMonitor) throws SnapshotException
{
if (progressMonitor == null)
progressMonitor = new VoidProgressListener();
HistogramBuilder histogramBuilder = new HistogramBuilder(Messages.SnapshotImpl_Histogram);
progressMonitor.beginTask(Messages.SnapshotImpl_BuildingHistogram, objectIds.length >>> 8);
// Arrays.sort(objectIds);
// int[] classIds = indexManager.o2class().getAll(objectIds);
IOne2OneIndex o2class = indexManager.o2class();
int classId;
for (int ii = 0; ii < objectIds.length; ii++)
{
classId = o2class.get(objectIds[ii]);
histogramBuilder.add(classId, objectIds[ii], getHeapSize(objectIds[ii]));
if ((ii & 0xff) == 0)
{
if (progressMonitor.isCanceled())
return null;
progressMonitor.worked(1);
}
}
progressMonitor.done();
return histogramBuilder.toHistogram(this, false);
}
public int[] getInboundRefererIds(int objectId) throws SnapshotException
{
return indexManager.inbound().get(objectId);
}
public int[] getOutboundReferentIds(int objectId) throws SnapshotException
{
return indexManager.outbound().get(objectId);
}
public int[] getInboundRefererIds(int[] objectIds, IProgressListener progressMonitor) throws SnapshotException
{
if (progressMonitor == null)
progressMonitor = new VoidProgressListener();
IIndexReader.IOne2ManyIndex inbound = indexManager.inbound();
SetInt result = new SetInt();
progressMonitor.beginTask(Messages.SnapshotImpl_ReadingInboundReferrers, objectIds.length / 100);
for (int ii = 0; ii < objectIds.length; ii++)
{
int[] referees = inbound.get(objectIds[ii]);
for (int refereeId : referees)
result.add(refereeId);
if (ii % 100 == 0)
{
if (progressMonitor.isCanceled())
return null;
progressMonitor.worked(1);
}
}
int[] endResult = result.toArray();
// It used to be sorted before (TreeSet<Integer>) but I don't
// remember if this is needed
// Arrays.sort(endResult);
progressMonitor.done();
return endResult;
}
public int[] getOutboundReferentIds(int[] objectIds, IProgressListener progressMonitor) throws SnapshotException
{
if (progressMonitor == null)
progressMonitor = new VoidProgressListener();
IIndexReader.IOne2ManyIndex outbound = indexManager.outbound();
SetInt result = new SetInt();
progressMonitor.beginTask(Messages.SnapshotImpl_ReadingOutboundReferrers, objectIds.length / 100);
for (int ii = 0; ii < objectIds.length; ii++)
{
int[] referees = outbound.get(objectIds[ii]);
for (int refereeId : referees)
result.add(refereeId);
if (ii % 100 == 0)
{
if (progressMonitor.isCanceled())
return null;
progressMonitor.worked(1);
}
}
int[] endResult = result.toArray();
progressMonitor.done();
return endResult;
}
public IPathsFromGCRootsComputer getPathsFromGCRoots(int objectId, Map<IClass, Set<String>> excludeList)
throws SnapshotException
{
return new PathsFromGCRootsComputerImpl(objectId, excludeList);
}
public IMultiplePathsFromGCRootsComputer getMultiplePathsFromGCRoots(int[] objectIds,
Map<IClass, Set<String>> excludeList) throws SnapshotException
{
return new MultiplePathsFromGCRootsComputerImpl(objectIds, excludeList, this);
}
int[] getRetainedSetSingleThreaded(int[] objectIds, IProgressListener progressMonitor) throws SnapshotException
{
/* for empty initial set - return immediately an empty retained set */
if (objectIds.length == 0) { return new int[0]; }
/*
* take the retained set of a single object out of the dominator tree -
* it's faster
*/
if (objectIds.length == 1) { return getSingleObjectRetainedSet(objectIds[0]); }
int numberOfObjects = snapshotInfo.getNumberOfObjects();
if (progressMonitor == null)
progressMonitor = new VoidProgressListener();
/* a bit field to mark all reached objects */
boolean[] reachable = new boolean[numberOfObjects];
/*
* Initially mark all the objects whose retained set is to be calculated
* Thus the dfs will not go through this objects, and all objects
* retained from them will stay unmarked (the bits will be clear)
*/
for (int objId : objectIds)
{
reachable[objId] = true;
}
/*
* The dfs() will start from the GC roots, follow the outbound
* references, and mark all unmarked objects. The retained set will
* contain the unmarked objects
*/
ObjectMarker marker = new ObjectMarker(roots.getAllKeys(), reachable, indexManager.outbound(),
IndexManager.Index.OUTBOUND.getFile(getSnapshotInfo().getPrefix()).length(),
progressMonitor);
int numReached;
try
{
numReached = marker.markSingleThreaded();
}
catch (OperationCanceledException e)
{
// $JL-EXC$
return null;
}
// int numReached = dfs(reachable);
int[] retained = new int[numberOfObjects - numReached];
/*
* Unmark also the initial objects, as we want them to be included in
* the retained set
*/
for (int objId : objectIds)
{
reachable[objId] = false;
}
/* Put each unmarked bit into the retained set */
int j = 0;
for (int i = 0; i < numberOfObjects; i++)
{
if (!reachable[i])
{
retained[j++] = i;
}
}
return retained;
}
private int[] getRetainedSetMultiThreaded(int[] objectIds, int availableProcessors,
IProgressListener progressMonitor) throws SnapshotException
{
/* for empty initial set - return immediately an empty retained set */
if (objectIds.length == 0) { return new int[0]; }
/*
* take the retained set of a single object out of the dominator tree -
* it's faster
*/
if (objectIds.length == 1) { return getSingleObjectRetainedSet(objectIds[0]); }
int numberOfObjects = snapshotInfo.getNumberOfObjects();
if (progressMonitor == null)
progressMonitor = new VoidProgressListener();
/* a boolean[] to mark all reached objects */
boolean[] reachable = new boolean[numberOfObjects];
/*
* Initially mark all the objects whose retained set is to be calculated
* Thus the dfs will not go through this objects, and all objects
* retained from them will stay unmarked (the bits will be clear)
*/
for (int objId : objectIds)
{
reachable[objId] = true;
}
/*
* Mark all the GC roots, and keep them in a stack. The worker threads
* are going to pop() one by one the gc roots and do the marking from
* them
*/
int[] gcRoots = roots.getAllKeys();
ObjectMarker marker = new ObjectMarker(gcRoots, reachable, indexManager.outbound(),
IndexManager.Index.OUTBOUND.getFile(getSnapshotInfo().getPrefix()).length(),
progressMonitor);
try
{
marker.markMultiThreaded(availableProcessors);
}
catch (InterruptedException e)
{
throw new SnapshotException(e);
}
/*
* Unmark also the initial objects, as we want them to be included in
* the retained set
*/
for (int objId : objectIds)
{
reachable[objId] = false;
}
/*
* build the result in an IntArray - the exact number of marked is not
* known
*/
ArrayIntBig retained = new ArrayIntBig();
/* Put each unmarked object into the retained set */
for (int i = 0; i < numberOfObjects; i++)
{
if (!reachable[i])
{
retained.add(i);
}
}
return retained.toArray();
}
public int[] getRetainedSet(int[] objectIds, IProgressListener progressMonitor) throws SnapshotException
{
int availableProcessors = Runtime.getRuntime().availableProcessors();
if (availableProcessors > 1)
{
return getRetainedSetMultiThreaded(objectIds, availableProcessors, progressMonitor);
}
else
{
return getRetainedSetSingleThreaded(objectIds, progressMonitor);
}
}
public int[] getRetainedSet(int[] objectIds, String[] fieldNames, IProgressListener listener)
throws SnapshotException
{
if (objectIds.length == 0) { return new int[0]; }
int numberOfObjects = indexManager.o2address().size();
if (listener == null)
listener = new VoidProgressListener();
BitField initialSet = new BitField(numberOfObjects);
for (int objId : objectIds)
initialSet.set(objId);
if (listener.isCanceled())
return null;
BitField reachable = new BitField(numberOfObjects);
int markedObjects = dfs2(reachable, initialSet, fieldNames);
int[] retained = new int[numberOfObjects - markedObjects];
int j = 0;
for (int i = 0; i < numberOfObjects; i++)
{
if (!reachable.get(i))
{
retained[j++] = i;
}
}
return retained;
}
public int[] getRetainedSet(int[] objectIds, ExcludedReferencesDescriptor[] excludedReferences,
IProgressListener progressMonitor) throws SnapshotException
{
if (progressMonitor == null)
progressMonitor = new VoidProgressListener();
/*
* first pass - mark starting from the GC roots, avoiding
* excludedReferences, until initial are reached. The non-marked objects
* will be a common retained set from the excluded and initial objects
*/
boolean[] firstPass = new boolean[getSnapshotInfo().getNumberOfObjects()];
// mark all initial
for (int objId : objectIds)
{
firstPass[objId] = true;
}
ObjectMarker marker = new ObjectMarker(getGCRoots(), firstPass, getIndexManager().outbound,
IndexManager.Index.OUTBOUND.getFile(getSnapshotInfo().getPrefix()).length(),
progressMonitor);
marker.markSingleThreaded(excludedReferences, this);
// un-mark initial - they have to go into the retained set
for (int objId : objectIds)
{
firstPass[objId] = false;
}
/*
* Second pass - from the non-marked objects mark the ones starting from
* the initial set (objectIds)
*/
boolean[] secondPass = new boolean[firstPass.length];
System.arraycopy(firstPass, 0, secondPass, 0, firstPass.length);
ObjectMarker secondMarker = new ObjectMarker(objectIds, secondPass, getIndexManager().outbound,
progressMonitor);
secondMarker.markSingleThreaded();
/*
* Have to merge the results of the two markings here
*/
int numObjects = getSnapshotInfo().getNumberOfObjects();
ArrayIntBig retainedSet = new ArrayIntBig();
for (int i = 0; i < numObjects; i++)
{
if (!firstPass[i] && secondPass[i])
{
retainedSet.add(i);
}
}
return retainedSet.toArray();
}
public long getMinRetainedSize(int[] objectIds, IProgressListener progressMonitor)
throws UnsupportedOperationException, SnapshotException
{
if (objectIds.length == 1) { return getRetainedHeapSize(objectIds[0]); }
if (objectIds.length == 0) { return 0; }
// to get the min.retained size we do not need to find the min.retain
// set at all
// all one needs is the distinct objects in the dominator tree. The
// sum of their retained sizes is the correct value then
int[] topAncestors = getTopAncestorsInDominatorTree(objectIds, progressMonitor);
long result = 0;
for (int topAncestorId : topAncestors)
{
result += getRetainedHeapSize(topAncestorId);
}
return result;
}
public int[] getMinRetainedSet(int[] objectIds, IProgressListener progressMonitor)
throws UnsupportedOperationException, SnapshotException
{
if (objectIds.length == 1) { return getSingleObjectRetainedSet(objectIds[0]); }
SetInt retainedSet = new SetInt(2 * objectIds.length);
for (int i : objectIds)
{
retainedSet.add(i);
}
/*
* objects on the path from a top-ancestor to the <root> will be saved
* here to avoid walking the same path many times
*/
SetInt negativeCache = new SetInt(2 * objectIds.length);
// used to temporarily keep the walked-through objects before we decide
// in which cache to store them
// inline the stack functionality for performance reasons
// IntStack temp = new IntStack();
int tempSize = 0;
int tempCapacity = 10 * 1024;
int[] temp = new int[tempCapacity];
IIndexReader.IOne2OneIndex dominatorIdx = indexManager.dominator();
IIndexReader.IOne2ManyIndex dominated = indexManager.dominated();
int size = 0;
int capacity = 10 * 1024;
int[] stack = new int[capacity];
int iterations = 0;
for (int objectId : objectIds)
{
iterations++;
if ((iterations & 0xffff) == 0)
{
if (progressMonitor.isCanceled()) { throw new IProgressListener.OperationCanceledException(); }
}
int dominatorId = dominatorIdx.get(objectId) - 2;
boolean save = true;
/*
* For each object walk up the dominator tree until either the
* <root> is reached or an object which is already in the retained
* set
*/
while (dominatorId > -1)
{
// temp.push(dominatorId); // save all objects on the path
if (tempSize == tempCapacity)
{
int newCapacity = tempCapacity << 1;
int[] newArr = new int[newCapacity];
System.arraycopy(temp, 0, newArr, 0, tempCapacity);
temp = newArr;
tempCapacity = newCapacity;
}
temp[tempSize++] = dominatorId;
// end of push()
// check if the dominator is in the retained set (i.e. there
// is another object from the initial set dominating it)
if (retainedSet.contains(dominatorId))
{
save = false;
break;
}
// check if the dominator is in the negative cache - i.e. there
// are no objects from the initial set on the way to the <root>
if (negativeCache.contains(dominatorId))
{
// save is true, so simply break and let the result be saved
break;
}
dominatorId = dominatorIdx.get(dominatorId) - 2;
}
if (save)
{
// add the path from the object up to the <root> to the negative
// cache
while (tempSize > 0)
{
// negativeCache.add(temp.pop());
negativeCache.add(temp[--tempSize]); // pop
}
/*
* Add the the objects retained by objectId to the whole
* retained set. Always use one and the same stack, it is empty
* at the end of this block
*/
stack[size++] = objectId; // push
int current;
while (size > 0) // are there elements in the stack?
{
current = stack[--size]; // pop
retainedSet.add(current);
int[] next = dominated.get(current + 1);
for (int i : next)
{
// push, check capacity first
if (size == capacity)
{
int newCapacity = capacity << 1;
int[] newArr = new int[newCapacity];
System.arraycopy(stack, 0, newArr, 0, capacity);
stack = newArr;
capacity = newCapacity;
}
stack[size++] = i;
}
}
}
}
return retainedSet.toArray();
}
public int[] getTopAncestorsInDominatorTree(int[] objectIds, IProgressListener listener) throws SnapshotException
{
// Used by the dominator_tree query to allow a missing dominator tree to be built
if (!isDominatorTreeCalculated() && listener != null && objectIds.length == 0)
calculateDominatorTree(listener);
if (!isDominatorTreeCalculated())
throw new SnapshotException(Messages.SnapshotImpl_Error_DomTreeNotAvailable);
if (listener == null)
listener = new VoidProgressListener();
/*
* For big objects sets use a boolean[] instead of SetInt to mark
* processed objects SetInt is too memory expensive and on huge sets may
* lead to an OOMError Using the boolean[] is also faster on bigger
* sets.
*/
if (objectIds.length > 1000000)
return getTopAncestorsWithBooleanCache(objectIds, listener);
/*
* objects on the path from a top-ancestor to the <root> will be saved
* here to avoid walking the same path many times
*/
SetInt negativeCache = new SetInt(objectIds.length);
/*
* objects on the path to a top-ancestor will be cached here, to avoid
* walking the same path multiple times.
*/
SetInt positiveCache = new SetInt(2 * objectIds.length);
for (int i : objectIds)
{
positiveCache.add(i);
}
/*
* an array where all top-ancestors will be saved
*/
ArrayInt result = new ArrayInt();
// used to temporarily keep the walked-through objects before we decide
// in which cache to store them
// inline the stack functionality for performance reasons
// IntStack temp = new IntStack();
int tempSize = 0;
int tempCapacity = 10 * 1024;
int[] temp = new int[tempCapacity];
IIndexReader.IOne2OneIndex dominatorIdx = indexManager.dominator();
int iterations = 0;
for (int objectId : objectIds)
{
iterations++;
if ((iterations & 0xffff) == 0)
{
if (listener.isCanceled()) { throw new IProgressListener.OperationCanceledException(); }
}
int dominatorId = dominatorIdx.get(objectId) - 2;
boolean save = true;
/*
* For each object walk up the dominator tree until either the
* <root> is reached or an object which is already in the retained
* set
*/
while (dominatorId > -1)
{
// temp.push(dominatorId); // save all objects on the path
if (tempSize == tempCapacity)
{
int newCapacity = tempCapacity << 1;
int[] newArr = new int[newCapacity];
System.arraycopy(temp, 0, newArr, 0, tempCapacity);
temp = newArr;
tempCapacity = newCapacity;
}
temp[tempSize++] = dominatorId;
// check if the dominator is in the positive cache (i.e. there
// is another object from the initial set dominating it)
if (positiveCache.contains(dominatorId))
{
save = false;
// add the marked objects to the positiveCache
while (tempSize > 0)
{
// positiveCahce.add(temp.pop());
positiveCache.add(temp[--tempSize]); // pop
}
break;
}
// check if the dominator is in the negative cache - i.e. there
// are no objects from the initial set on the way to the <root>
if (negativeCache.contains(dominatorId))
{
// save is true, so simply break and let the result be saved
break;
}
dominatorId = dominatorIdx.get(dominatorId) - 2;
}
if (save)
{
result.add(objectId);
while (tempSize > 0)
{
// negativeCache.add(temp.pop());
negativeCache.add(temp[--tempSize]); // pop
}
}
}
return result.toArray();
}
private int[] getTopAncestorsWithBooleanCache(int[] objectIds, IProgressListener listener)
{
/*
* objects on the path from a top-ancestor to the <root> will be saved
* here to avoid walking the same path many times
*/
boolean[] negativeCache = new boolean[snapshotInfo.getNumberOfObjects()];
/*
* objects on the path to a top-ancestor will be cached here, to avoid
* walking the same path multiple times.
*/
boolean[] positiveCache = new boolean[snapshotInfo.getNumberOfObjects()];
for (int i : objectIds)
{
positiveCache[i] = true;
}
/*
* an array where all top-ancestors will be saved
*/
ArrayInt result = new ArrayInt();
// used to temporarily keep the walked-through objects before we decide
// in which cache to store them
// inline the stack functionality for performance reasons
// IntStack temp = new IntStack();
int tempSize = 0;
int tempCapacity = 10 * 1024;
int[] temp = new int[tempCapacity];
IIndexReader.IOne2OneIndex dominatorIdx = indexManager.dominator();
int iterations = 0;
for (int objectId : objectIds)
{
iterations++;
if ((iterations & 0xffff) == 0)
{
if (listener.isCanceled()) { throw new IProgressListener.OperationCanceledException(); }
}
int dominatorId = dominatorIdx.get(objectId) - 2;
boolean save = true;
/*
* For each object walk up the dominator tree until either the
* <root> is reached or an object which is already in the retained
* set
*/
while (dominatorId > -1)
{
// temp.push(dominatorId); // save all objects on the path
if (tempSize == tempCapacity)
{
int newCapacity = tempCapacity << 1;
int[] newArr = new int[newCapacity];
System.arraycopy(temp, 0, newArr, 0, tempCapacity);
temp = newArr;
tempCapacity = newCapacity;
}
temp[tempSize++] = dominatorId;
// check if the dominator is in the positive cache (i.e. there
// is another object from the initial set dominating it)
if (positiveCache[dominatorId])
{
save = false;
// add the marked objects to the positiveCache
while (tempSize > 0)
{
// positiveCahce.add(temp.pop());
positiveCache[temp[--tempSize]] = true; // pop
}
break;
}
// check if the dominator is in the negative cache - i.e. there
// are no objects from the initial set on the way to the <root>
if (negativeCache[dominatorId])
{
// save is true, so simply break and let the result be saved
break;
}
dominatorId = dominatorIdx.get(dominatorId) - 2;
}
if (save)
{
result.add(objectId);
while (tempSize > 0)
{
// negativeCache.add(temp.pop());
negativeCache[temp[--tempSize]] = true; // pop
}
}
}
return result.toArray();
}
private boolean isDominatorTreeCalculated()
{
return dominatorTreeCalculated;
}
public void calculateDominatorTree(IProgressListener listener) throws SnapshotException,
IProgressListener.OperationCanceledException
{
try
{
DominatorTree.calculate(this, listener);
dominatorTreeCalculated = indexManager.dominated() != null && indexManager.o2retained() != null
&& indexManager.dominator() != null;
}
catch (IOException e)
{
throw new SnapshotException(e);
}
}
public int[] getImmediateDominatedIds(int objectId) throws SnapshotException
{
if (!isDominatorTreeCalculated())
throw new SnapshotException(Messages.SnapshotImpl_Error_DomTreeNotAvailable);
return indexManager.dominated().get(objectId + 1);
}
public int getImmediateDominatorId(int objectId) throws SnapshotException
{
if (!isDominatorTreeCalculated())
throw new SnapshotException(Messages.SnapshotImpl_Error_DomTreeNotAvailable);
return indexManager.dominator().get(objectId) - 2;
}
public DominatorsSummary getDominatorsOf(int[] objectIds, Pattern excludePattern, IProgressListener progressListener)
throws SnapshotException
{
if (!isDominatorTreeCalculated())
throw new SnapshotException(Messages.SnapshotImpl_Error_DomTreeNotAvailable);
if (progressListener == null)
progressListener = new VoidProgressListener();
IIndexReader.IOne2OneIndex dominatorIndex = indexManager.dominator();
IIndexReader.IOne2OneIndex o2classIndex = indexManager.o2class();
SetInt excludeSet = new SetInt();
SetInt includeSet = new SetInt();
progressListener.beginTask(Messages.SnapshotImpl_RetrievingDominators, objectIds.length / 10);
Map<IClass, DominatorsSummary.ClassDominatorRecord> map = new HashMap<IClass, DominatorsSummary.ClassDominatorRecord>();
for (int ii = 0; ii < objectIds.length; ii++)
{
int objectId = objectIds[ii];
int domClassId;
IClass clasz;
String domClassName;
// the values in the index are 2+the real value
int dominatorId = dominatorIndex.get(objectId) - 2;
if (dominatorId == -1)
{
clasz = null;
domClassName = "<ROOT>";//$NON-NLS-1$
domClassId = -1;
}
else
{
domClassId = o2classIndex.get(dominatorId);
clasz = classCache.get(domClassId);
domClassName = clasz.getName();
}
if (excludePattern != null && dominatorId >= 0)
{
boolean exclude = true;
while (exclude)
{
if (progressListener.isCanceled())
throw new IProgressListener.OperationCanceledException();
// check the negative cache first
if (excludeSet.contains(domClassId))
{
// the values in the index are 2+the real value
dominatorId = dominatorIndex.get(dominatorId) - 2;
if (dominatorId == -1)
{
clasz = null;
domClassName = "<ROOT>";//$NON-NLS-1$
domClassId = -1;
}
else
{
domClassId = o2classIndex.get(dominatorId);
clasz = classCache.get(domClassId);
domClassName = clasz.getName();
}
}
// then check the positive cache
else if (includeSet.contains(domClassId))
{
exclude = false;
}
// the class has not been processed so far
else
{
if (excludePattern.matcher(domClassName).matches() && dominatorId >= 0)
{
// just add the classId to the exclude cache
// the next iteration will get the next dominator
excludeSet.add(domClassId);
}
else
{
includeSet.add(domClassId);
exclude = false;
}
}
}
}
DominatorsSummary.ClassDominatorRecord record = map.get(clasz);
if (record == null)
{
record = new DominatorsSummary.ClassDominatorRecord();
map.put(clasz, record);
record.setClassName(domClassName);
record.setClassId(domClassId);
record.setClassloaderId(dominatorId == -1 || clasz == null ? -1 : clasz.getClassLoaderId());
}
if (record.addDominator(dominatorId) && dominatorId != -1)
record.addDominatorNetSize(getHeapSize(dominatorId));
if (record.addDominated(objectId))
record.addDominatedNetSize(getHeapSize(objectId));
if (ii % 10 == 0)
{
if (progressListener.isCanceled())
throw new IProgressListener.OperationCanceledException();
progressListener.worked(1);
}
}
ClassDominatorRecord[] records = map.values().toArray(new DominatorsSummary.ClassDominatorRecord[0]);
progressListener.done();
return new DominatorsSummary(records, this);
}
public IObject getObject(int objectId) throws SnapshotException
{
IObject answer = this.classCache.get(objectId);
if (answer != null)
return answer;
return this.objectCache.get(objectId);
}
public GCRootInfo[] getGCRootInfo(int objectId) throws SnapshotException
{
return roots.get(objectId);
}
public IClass getClassOf(int objectId) throws SnapshotException
{
if (isClass(objectId))
return getObject(objectId).getClazz();
else
return (IClass) getObject(indexManager.o2class().get(objectId));
}
public long mapIdToAddress(int objectId) throws SnapshotException
{
return indexManager.o2address().get(objectId);
}
public long getHeapSize(int objectId) throws SnapshotException
{
if (arrayObjects.get(objectId))
{
return indexManager.a2size().getSize(objectId);
}
else
{
IClass clazz = classCache.get(objectId);
if (clazz != null)
{
// it is a class
return clazz.getUsedHeapSize();
}
else
{
// it is an instance
clazz = classCache.get(indexManager.o2class().get(objectId));
return clazz.getHeapSizePerInstance();
}
}
}
public long getHeapSize(int[] objectIds) throws UnsupportedOperationException, SnapshotException
{
long total = 0;
IOne2OneIndex o2class = indexManager.o2class();
IOne2SizeIndex a2size = indexManager.a2size();
for (int objectId : objectIds)
{
if (arrayObjects.get(objectId)) // take array sizes from another
// index
{
total += a2size.getSize(objectId);
}
else
{
IClass clazz = classCache.get(objectId);
if (clazz != null)
{
// it is a class
total += clazz.getUsedHeapSize();
}
else
{
// it is an instance
clazz = classCache.get(o2class.get(objectId));
total += clazz.getHeapSizePerInstance();
}
}
}
return total;
}
public long getRetainedHeapSize(int objectId) throws SnapshotException
{
if (this.isDominatorTreeCalculated())
return indexManager.o2retained().get(objectId);
else
return 0;
}
public boolean isArray(int objectId)
{
if (arrayObjects.get(objectId))
{
// Variable size, so see if actually an array
IClass clazz = classCache.get(indexManager.o2class().get(objectId));
if (clazz.isArrayType())
{
return true;
} else {
return false;
}
}
return false;
}
public boolean isClass(int objectId)
{
return classCache.containsKey(objectId);
}
public boolean isGCRoot(int objectId)
{
return roots.containsKey(objectId);
}
public int mapAddressToId(long objectAddress) throws SnapshotException
{
int objectId = indexManager.o2address().reverse(objectAddress);
if (objectId < 0)
throw new SnapshotException(MessageUtil.format(Messages.SnapshotImpl_Error_ObjectNotFound,
new Object[] { "0x" //$NON-NLS-1$
+ Long.toHexString(objectAddress) }));
return objectId;
}
public void dispose()
{
IOException error = null;
try
{
heapObjectReader.close();
}
catch (IOException e1)
{
error = e1;
}
try
{
indexManager.close();
}
catch (IOException e1)
{
error = e1;
}
classCacheByName.clear();
if (error != null)
throw new RuntimeException(error);
}
// //////////////////////////////////////////////////////////////
// internal stuff
// //////////////////////////////////////////////////////////////
public List<IClass> resolveClassHierarchy(int classIndex)
{
IClass clazz = classCache.get(classIndex);
if (clazz == null)
return null;
List<IClass> answer = new ArrayList<IClass>();
answer.add(clazz);
while (clazz.hasSuperClass())
{
clazz = classCache.get(clazz.getSuperClassId());
if (clazz == null)
return null;
answer.add(clazz);
}
return answer;
}
/** performance improved check if the object is a class loader */
public boolean isClassLoader(int objectId)
{
return loaderLabels.containsKey(objectId);
}
public String getClassLoaderLabel(int objectId)
{
return loaderLabels.get(objectId);
}
public void setClassLoaderLabel(int objectId, String label)
{
if (label == null)
throw new NullPointerException(Messages.SnapshotImpl_Label);
String old = loaderLabels.put(objectId, label);
if (old == null)
throw new RuntimeException(Messages.SnapshotImpl_Error_ReplacingNonExistentClassLoader);
}
private int dfs2(BitField bits, BitField exclude, String[] fieldNames) throws SnapshotException
{
int count = 0;
HashSet<String> fieldNamesSet = new HashSet<String>(fieldNames.length);
for (int i = 0; i < fieldNames.length; i++)
{
fieldNamesSet.add(fieldNames[i]);
}
IIndexReader.IOne2ManyIndex outbound = indexManager.outbound();
IntStack stack = new IntStack();
for (IteratorInt en = roots.keys(); en.hasNext();)
{
int i = en.next();
stack.push(i);
bits.set(i);
count++;
}
int current;
while (stack.size() > 0)
{
current = stack.pop();
if (exclude.get(current))
{
// check for objects which are only referenced by the desired
// fields
for (int child : outbound.get(current))
{
// well, we must load the obj
IObject obj = getObject(current);
long childAddress = mapIdToAddress(child);
List<NamedReference> refs = obj.getOutboundReferences();
for (NamedReference reference : refs)
{
// if there is a ref from a not-specified field - put
// the child
// on the list. This means it's not part of the desired
// retained set
if (!bits.get(child) && reference.getObjectAddress() == childAddress
&& !fieldNamesSet.contains(reference.getName()))
{
stack.push(child);
bits.set(child);
count++;
}
}
}
}
else
{
for (int child : outbound.get(current))
{
if (!bits.get(child))
{
stack.push(child);
bits.set(child);
count++;
}
}
}
}
return count;
}
private int[] getSingleObjectRetainedSet(int objectId) throws SnapshotException
{
ArrayIntBig result = new ArrayIntBig();
IntStack stack = new IntStack();
stack.push(objectId);
int current;
while (stack.size() > 0)
{
current = stack.pop();
result.add(current);
int[] next = getImmediateDominatedIds(current);
for (int i : next)
{
stack.push(i);
}
}
return result.toArray();
}
private static class Path
{
int index;
Path next;
public Path(int index, Path next)
{
this.index = index;
this.next = next;
}
public Path getNext()
{
return next;
}
public int getIndex()
{
return index;
}
public boolean contains(long id)
{
Path p = this;
while (p != null)
{
if (p.index == id)
return true;
p = p.next;
}
return false;
}
}
private class PathsFromGCRootsComputerImpl implements IPathsFromGCRootsComputer
{
/*
* special state of the path computer 0 initial; 1 final; 2 processing a
* GC root; 3 normal processing
*/
private int state;
private int nextState;
int objectId;
LinkedList<Path> fifo = new LinkedList<Path>();
BitField visited = new BitField(indexManager.o2address().size());
BitField excludeInstances;
IIndexReader.IOne2ManyIndex inboundIndex; // to avoid method calls to
int currentId;
Path currentPath;
int[] currentReferrers;
int lastReadReferrer;
int[] referringThreads;
int currentReferringThread;
int[] foundPath;
Map<IClass, Set<String>> excludeMap;
public PathsFromGCRootsComputerImpl(int objectId, Map<IClass, Set<String>> excludeMap) throws SnapshotException
{
this.objectId = objectId;
this.excludeMap = excludeMap;
inboundIndex = indexManager.inbound();
if (excludeMap != null)
{
initExcludeInstances();
}
currentId = objectId;
visited.set(objectId);
if (roots.get(objectId) != null)
{
// leave the fifo empty
}
else
{
fifo.add(new Path(objectId, null));
}
}
private void initExcludeInstances() throws SnapshotException
{
excludeInstances = new BitField(indexManager.o2address().size());
for (IClass clazz : excludeMap.keySet())
{
int[] objects = clazz.getObjectIds();
for (int objId : objects)
{
excludeInstances.set(objId);
}
}
}
private boolean refersOnlyThroughExcluded(int referrerId, int referentId) throws SnapshotException
{
if (!excludeInstances.get(referrerId))
return false;
IObject referrerObject = getObject(referrerId);
Set<String> excludeFields = excludeMap.get(referrerObject.getClazz());
if (excludeFields == null)
return true; // treat null as all fields
long referentAddr = mapIdToAddress(referentId);
List<NamedReference> refs = referrerObject.getOutboundReferences();
for (NamedReference reference : refs)
{
if (referentAddr == reference.getObjectAddress() && !excludeFields.contains(reference.getName())) { return false; }
}
return true;
}
public int[] getNextShortestPath() throws SnapshotException
{
switch (state)
{
case 0: // INITIAL
{
/*
* some special check if the initial object itself is a GC
* root usually the GC roots are found among the referrers
*/
if (roots.containsKey(currentId))
{
referringThreads = null;
state = 2; // PROCESSING GC ROOT
nextState = 1; // FINAL
foundPath = new int[] { currentId };
return getNextShortestPath();
}
else
{
state = 3; // NORMAL
return getNextShortestPath();
}
}
case 1: // FINAL
return null;
case 2: // PROCESSING GC ROOT
{
if (referringThreads == null)
{
referringThreads = getReferringTreads(getGCRootInfo(foundPath[foundPath.length - 1]));
currentReferringThread = 0;
if (referringThreads.length == 0)
{
// there were no threads found to refer to this GC
// root
state = nextState;
return foundPath;
}
}
if (currentReferringThread < referringThreads.length)
{
int[] result = new int[foundPath.length + 1];
System.arraycopy(foundPath, 0, result, 0, foundPath.length);
result[result.length - 1] = referringThreads[currentReferringThread];
currentReferringThread++;
return result;
}
else
{
state = nextState;
return getNextShortestPath();
}
}
case 3: // NORMAL PROCESSING
{
int[] res;
// finish processing the current entry
if (currentReferrers != null)
{
res = processCurrentReferrefs(lastReadReferrer + 1);
if (res != null)
return res;
}
// Continue with the FIFO
while (fifo.size() > 0)
{
currentPath = fifo.getFirst();
fifo.removeFirst();
currentId = currentPath.getIndex();
currentReferrers = inboundIndex.get(currentId);
if (currentReferrers != null)
{
res = processCurrentReferrefs(0);
if (res != null)
return res;
}
}
return null;
}
default:
throw new RuntimeException(Messages.SnapshotImpl_Error_UnrecognizedState + state);
}
}
private int[] getReferringTreads(GCRootInfo[] rootInfos)
{
SetInt threads = new SetInt();
for (GCRootInfo info : rootInfos)
{
// add only threads different from the current GC root
if (info.getContextAddress() != 0 && info.getObjectAddress() != info.getContextAddress())
{
threads.add(info.getContextId());
}
}
return threads.toArray();
}
public PathsFromGCRootsTree getTree(Collection<int[]> paths)
{
PathsFromGCRootsTreeBuilder rootBuilder = new PathsFromGCRootsTreeBuilder(objectId);
for (int[] path : paths)
{
PathsFromGCRootsTreeBuilder current = rootBuilder;
/*
* now add the path as a branch start from 1, as path[0] is the
* starting object
*/
for (int k = 1; k < path.length; k++)
{
int childId = path[k];
PathsFromGCRootsTreeBuilder child = current.getObjectReferers().get(childId);
if (child == null)
{
child = new PathsFromGCRootsTreeBuilder(childId);
current.addObjectReferer(child);
}
current = child;
}
}
return rootBuilder.toPathsFromGCRootsTree();
}
private int[] path2Int(Path p)
{
IntStack s = new IntStack();
while (p != null)
{
s.push(p.getIndex());
p = p.getNext();
}
int res[] = new int[s.size()];
for (int i = 0; i < res.length; i++)
{
res[i] = s.pop();
}
return res;
}
private int[] processCurrentReferrefs(int fromIndex) throws SnapshotException
{
GCRootInfo[] rootInfo = null;
for (int i = fromIndex; i < currentReferrers.length; i++)
{
rootInfo = roots.get(currentReferrers[i]);
if (rootInfo != null)
{
if (excludeMap == null)
{
// save state
lastReadReferrer = i;
Path p = new Path(currentReferrers[i], currentPath);
referringThreads = null;
state = 2; // FOUND GC ROOT
nextState = 3; // NORMAL PROCESSING
foundPath = path2Int(p);
return getNextShortestPath();
}
else
{
if (!refersOnlyThroughExcluded(currentReferrers[i], currentId))
{
// save state
lastReadReferrer = i;
Path p = new Path(currentReferrers[i], currentPath);
referringThreads = null;
state = 2; // FOUND GC ROOT
nextState = 3; // NORMAL PROCESSING
foundPath = path2Int(p);
return getNextShortestPath();
}
}
}
}
for (int referrer : currentReferrers)
{
if (referrer >= 0 && !visited.get(referrer) && !roots.containsKey(referrer))
{
if (excludeMap == null)
{
fifo.add(new Path(referrer, currentPath));
visited.set(referrer);
}
else
{
if (!refersOnlyThroughExcluded(referrer, currentId))
{
fifo.add(new Path(referrer, currentPath));
visited.set(referrer);
}
}
}
}
return null;
}
}
public IndexManager getIndexManager()
{
return indexManager;
}
public IObjectReader getHeapObjectReader()
{
return heapObjectReader;
}
public RetainedSizeCache getRetainedSizeCache()
{
return indexManager.i2sv2;
}
public HashMapIntObject<HashMapIntObject<XGCRootInfo[]>> getRootsPerThread()
{
return rootsPerThread;
}
/**
* Get additional JVM information, if available.
* <p>
* A known type is {@link UnreachableObjectsHistogram}.
* Extra information can be obtained from an implementation of {@link IObjectReader#getAddon(Class)}.
* @param addon the type of the data. For example, {@link UnreachableObjectsHistogram}.class
* @return the extra data
*/
@SuppressWarnings("unchecked")
public <A> A getSnapshotAddons(Class<A> addon) throws SnapshotException
{
if (addon == UnreachableObjectsHistogram.class)
{
return (A) this.getSnapshotInfo().getProperty(UnreachableObjectsHistogram.class.getName());
}
else
{
return heapObjectReader.getAddon(addon);
}
}
public IThreadStack getThreadStack(int objectId) throws SnapshotException
{
if (!parsedThreads)
{
threadId2stack = ThreadStackHelper.loadThreadsData(this);
parsedThreads = true;
}
if (threadId2stack != null)
{
return threadId2stack.get(objectId);
}
return null;
}
// //////////////////////////////////////////////////////////////
// private classes
// //////////////////////////////////////////////////////////////
private static final class HeapObjectCache extends ObjectCache<IObject>
{
SnapshotImpl snapshot;
private HeapObjectCache(SnapshotImpl snapshot, int maxSize)
{
super(maxSize);
this.snapshot = snapshot;
}
@Override
protected IObject load(int objectId)
{
try
{
IObject answer = null;
// check if the object is an array (no index needed)
if (snapshot.isArray(objectId))
{
answer = snapshot.heapObjectReader.read(objectId, snapshot);
}
else
{
ClassImpl classImpl = (ClassImpl) snapshot.getObject(snapshot.indexManager.o2class().get(objectId));
if (snapshot.isClassLoader(objectId))
answer = new ClassLoaderImpl(objectId, Long.MIN_VALUE, classImpl, null);
else
answer = new InstanceImpl(objectId, Long.MIN_VALUE, classImpl, null);
}
((AbstractObjectImpl) answer).setSnapshot(snapshot);
return answer;
}
catch (IOException e)
{
throw new RuntimeException(e);
}
catch (SnapshotException e)
{
throw new RuntimeException(e);
}
}
}
}