blob: 59aa629cdf90b95c28903ead5109798f0d846834 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2020 IBM Corporation.
* 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:
* Andrew Johnson (IBM Corporation) - initial API and implementation
*******************************************************************************/
package org.eclipse.mat.inspections;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.mat.collect.ArrayInt;
import org.eclipse.mat.inspections.util.ObjectTreeFactory;
import org.eclipse.mat.internal.Messages;
import org.eclipse.mat.internal.snapshot.inspections.MultiplePath2GCRootsQuery;
import org.eclipse.mat.query.IQuery;
import org.eclipse.mat.query.IQueryContext;
import org.eclipse.mat.query.IResult;
import org.eclipse.mat.query.IResultTree;
import org.eclipse.mat.query.annotations.Argument;
import org.eclipse.mat.query.annotations.CommandName;
import org.eclipse.mat.query.annotations.Icon;
import org.eclipse.mat.query.results.CompositeResult;
import org.eclipse.mat.query.results.TextResult;
import org.eclipse.mat.snapshot.IMultiplePathsFromGCRootsComputer;
import org.eclipse.mat.snapshot.IPathsFromGCRootsComputer;
import org.eclipse.mat.snapshot.ISnapshot;
import org.eclipse.mat.snapshot.MultiplePathsFromGCRootsClassRecord;
import org.eclipse.mat.snapshot.model.IClass;
import org.eclipse.mat.snapshot.model.IInstance;
import org.eclipse.mat.snapshot.model.IObject;
import org.eclipse.mat.snapshot.model.ObjectReference;
import org.eclipse.mat.snapshot.query.IHeapObjectArgument;
import org.eclipse.mat.util.IProgressListener;
import org.eclipse.mat.util.MessageUtil;
import org.eclipse.mat.util.SimpleMonitor;
import com.ibm.icu.text.MessageFormat;
/**
* Extract information about objects referenced by java.lang.ref.Reference, e.g.
* weak and soft references, and Finalizer which are also strongly held by
* the reference, so causing a possible leak.
*/
@CommandName("reference_leak")
@Icon("/META-INF/icons/reference.gif")
public class ReferenceLeakQuery implements IQuery
{
@Argument
public ISnapshot snapshot;
@Argument
public IQueryContext context;
@Argument(flag = Argument.UNFLAGGED)
public IHeapObjectArgument objects;
static final String DEFAULT_REFERENT = "referent"; //$NON-NLS-1$
@Argument(isMandatory = false)
public String referent_attribute = DEFAULT_REFERENT;
@Argument(isMandatory = false)
public int maxresults = 5;
@Argument(isMandatory = false)
public int maxpaths = 100;
@Argument(isMandatory = false)
public int maxobjs = 0;
@Argument(isMandatory = false)
public double factor = 0.8;
public IResult execute(IProgressListener listener) throws Exception
{
CompositeResult results = new CompositeResult();
Set<String> fields = Collections.singleton(referent_attribute);
Map<IClass, Set<String>> allExcludeMap = new HashMap<IClass, Set<String>>();
// Add all the suspect referents
ArrayInt ai = new ArrayInt();
int commonpath[] = null;
MultiplePathsFromGCRootsClassRecord dummy = new MultiplePathsFromGCRootsClassRecord(null, -1, true, snapshot);
int nobjs = 0;
for (int[] objs : objects)
{
nobjs += objs.length;
}
SimpleMonitor sm = new SimpleMonitor(Messages.ReferenceLeakQuery_ComputingReferentLeaks, listener, new int[] {400, 100});
listener = sm.nextMonitor();
listener.beginTask(Messages.ReferenceLeakQuery_ExaminingReferenceObjects, maxobjs > 0 ? maxobjs : nobjs);
int iobjs = 0;
int eobjs = 0;
for (int[] objs : objects)
{
for (int ii = 0; ii < objs.length; ii++, iobjs++)
{
// Choose a random selection
if (maxobjs > 0 && Math.random() * (nobjs - iobjs) >= (maxobjs - eobjs))
continue;
++eobjs;
IObject o = snapshot.getObject(objs[ii]);
if (o instanceof IInstance)
{
IInstance obj = (IInstance) o;
ObjectReference ref = ReferenceQuery.getReferent(obj, referent_attribute);
if (ref != null)
{
int suspect = ref.getObjectId();
Map<IClass, Set<String>> excludeMap = new HashMap<IClass, Set<String>>();
excludeMap.put(obj.getClazz(), fields);
allExcludeMap.put(obj.getClazz(), fields);
IPathsFromGCRootsComputer path = snapshot.getPathsFromGCRoots(suspect, excludeMap);
int count = 0;
lx: for (int p1[] = path.getNextShortestPath(); p1 != null && count < maxpaths; p1 = path.getNextShortestPath(), ++count)
{
for (int ip = 0; ip < p1.length; ++ip)
{
int p = p1[ip];
if (listener.isCanceled())
throw new IProgressListener.OperationCanceledException();
if (p == obj.getObjectId())
{
// Good path and suspect
ai.add(suspect);
ObjectTreeFactory.TreePathBuilder builder = new ObjectTreeFactory.TreePathBuilder();
builder.setIsOutgoing();
builder.addBranch(p1[p1.length - 1]);
for (int j = p1.length - 2; j >= 0 ; --j)
{
if (j + 1 == ip)
builder.addSibling(suspect, true);
builder.addChild(p1[j], j == ip || j == 0);
}
if (commonpath == null)
{
commonpath = new int[p1.length];
for (int i = 0; i < p1.length; ++i)
{
commonpath[i] = p1[p1.length - 1 - i];
}
}
else
{
for (int i = 0; i < commonpath.length; ++i)
{
if (p1.length - 1 - i < 0 || commonpath[i] != p1[p1.length - 1 - i])
{
commonpath = Arrays.copyOf(commonpath, i);
}
}
}
// for classes
dummy.addPath(p1);
// Show some example exact paths
if (ai.size() <= maxresults)
{
IResultTree t1 = builder.build(snapshot);
String title = MessageFormat.format(Messages.ReferenceLeakQuery_TwoPaths, ref.getObject().getDisplayName(), o.getDisplayName());
results.addResult(title, t1);
}
break lx;
}
}
}
}
}
listener.worked(1);
if (listener.isCanceled())
throw new IProgressListener.OperationCanceledException();
}
}
listener.done();
listener = sm.nextMonitor();
// Combine the results
if (ai.size() == 0)
{
TextResult child = new TextResult(MessageFormat.format(Messages.ReferenceLeakQuery_NoStrongPaths, eobjs));
results.addResult(child);
}
if (ai.size() == 1)
{
// calculate the shortest path for each object
IMultiplePathsFromGCRootsComputer computer = snapshot.getMultiplePathsFromGCRoots(ai.toArray(), allExcludeMap);
// force a path as sometimes the multiple paths GC root choice doesn't match paths from GC roots
Object allpaths[] = computer.getAllPaths(listener);
if (allpaths.length >= 1 && allpaths[0] instanceof int[])
{
int path1[] = (int[])allpaths[0];
int path2[] = new int[path1.length];
for (int i = 0; i < path1.length; ++i)
{
path2[i] = path1[path1.length - 1 - i];
}
commonpath = path2;
}
IResultTree rt = MultiplePath2GCRootsQuery.create(snapshot, computer, commonpath, listener);
results.addResult(Messages.ReferenceLeakQuery_PathToReferent, rt);
}
if (ai.size() > 1)
{
// Display the common paths by class - borrowed from LeakHunterQuery.findReferencePattern()
// calculate the shortest path for each object
IMultiplePathsFromGCRootsComputer computer = snapshot.getMultiplePathsFromGCRoots(ai.toArray(), allExcludeMap);
/*
* Using the paths from getPathsFromGCRoots doesn't always match getMultiplePathsFromGCRoots
* so redo.
*/
dummy = new MultiplePathsFromGCRootsClassRecord(null, -1, true, snapshot);
Object[] allPaths = computer.getAllPaths(listener);
for (Object path : allPaths)
dummy.addPath((int[]) path);
int numPaths = dummy.getCount();
MultiplePathsFromGCRootsClassRecord[] classRecords = dummy.nextLevel();
double threshold = numPaths * factor;
List<IClass> referencePattern = new ArrayList<IClass>();
Arrays.sort(classRecords, MultiplePathsFromGCRootsClassRecord.getComparatorByNumberOfReferencedObjects());
MultiplePathsFromGCRootsClassRecord r = classRecords[0];
while (r.getCount() > threshold)
{
threshold = r.getCount() * factor;
referencePattern.add(r.getClazz());
classRecords = r.nextLevel();
if (classRecords == null || classRecords.length == 0)
break;
Arrays.sort(classRecords, MultiplePathsFromGCRootsClassRecord.getComparatorByNumberOfReferencedObjects());
r = classRecords[0];
}
/*
* build the tree
*/
int expandedClasses[] = new int[referencePattern.size()];
for (int i = 0; i < referencePattern.size(); ++i)
{
expandedClasses[i] = referencePattern.get(i).getObjectId();
}
IResultTree rt = MultiplePath2GCRootsQuery.create(snapshot, computer, expandedClasses, true, listener);
String message;
if (eobjs < nobjs)
message = MessageUtil.format(Messages.ReferenceLeakQuery_CommonPathsLimit, eobjs);
else
message = Messages.ReferenceLeakQuery_CommonPaths;
results.addResult(message, rt);
}
listener.done();
return results;
}
}