Skip to content

Commit ffe2fb3

Browse files
authored
Merge pull request #115 from kgibm/issue114
[Fix #114] Add leak suspect retained heap top consumers
2 parents f27747d + 7c19ae2 commit ffe2fb3

File tree

6 files changed

+216
-52
lines changed

6 files changed

+216
-52
lines changed

plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/FindLeaksQuery.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2008, 2023 SAP AG and IBM Corporation.
2+
* Copyright (c) 2008, 2025 SAP AG and IBM Corporation.
33
* All rights reserved. This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License 2.0
55
* which accompanies this distribution, and is available at
@@ -18,7 +18,6 @@
1818
import java.util.Collection;
1919
import java.util.Collections;
2020
import java.util.Comparator;
21-
import java.util.HashSet;
2221
import java.util.List;
2322
import java.util.Map;
2423
import java.util.Objects;

plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/LeakHunterQuery.java

Lines changed: 112 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2008, 2023 SAP AG, IBM Corporation and others.
2+
* Copyright (c) 2008, 2025 SAP AG, IBM Corporation and others.
33
* All rights reserved. This program and the accompanying materials
44
* are made available under the terms of the Eclipse Public License 2.0
55
* which accompanies this distribution, and is available at
@@ -23,6 +23,8 @@
2323
import java.util.LinkedList;
2424
import java.util.List;
2525
import java.util.Map;
26+
import java.util.Map.Entry;
27+
import java.util.PriorityQueue;
2628
import java.util.Set;
2729
import java.util.Stack;
2830
import java.util.logging.Level;
@@ -120,6 +122,9 @@ public class LeakHunterQuery implements IQuery
120122
@Argument(isMandatory = false)
121123
public int max_paths = 10000;
122124

125+
@Argument(isMandatory = false)
126+
public int top_min_retained_set_consumers = 3;
127+
123128
@Argument(isMandatory = false, advice = Advice.CLASS_NAME_PATTERN, flag = "skip")
124129
public Pattern skipPattern = Pattern.compile("java\\..*|javax\\..*|com\\.sun\\..*|sun\\..*|jdk\\..*"); //$NON-NLS-1$
125130

@@ -310,6 +315,8 @@ private CompositeResult getLeakDescriptionSingleObject(SuspectRecord suspect, IP
310315
Set<String> keywords = new LinkedHashSet<String>();
311316
List<IObject> objectsForTroubleTicketInfo = new ArrayList<IObject>(2);
312317
int suspectId = suspect.getSuspect().getObjectId();
318+
String retainedHeapTopConsumers = getRetainedHeapTopConsumersDescription(suspectId, listener,
319+
top_min_retained_set_consumers);
313320

314321
/* get dominator info */
315322
boolean isThreadRelated = isThreadRelated(suspect);
@@ -318,7 +325,7 @@ private CompositeResult getLeakDescriptionSingleObject(SuspectRecord suspect, IP
318325
overview.append("<p>"); //$NON-NLS-1$
319326
overview.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_Thread, //
320327
HTMLUtils.escapeText(suspect.getSuspect().getDisplayName()), //
321-
formatRetainedHeap(suspect.getSuspectRetained(), totalHeap)));
328+
formatRetainedHeap(suspect.getSuspectRetained(), totalHeap), retainedHeapTopConsumers));
322329
overview.append("</p>"); //$NON-NLS-1$
323330
}
324331
else if (snapshot.isClassLoader(suspectId))
@@ -329,7 +336,8 @@ else if (snapshot.isClassLoader(suspectId))
329336
String classloaderName = getClassLoaderName(suspectClassloader, keywords);
330337

331338
overview.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_ClassLoader, //
332-
classloaderName, formatRetainedHeap(suspect.getSuspectRetained(), totalHeap)));
339+
classloaderName, formatRetainedHeap(suspect.getSuspectRetained(), totalHeap),
340+
retainedHeapTopConsumers));
333341
}
334342
else if (snapshot.isClass(suspectId))
335343
{
@@ -344,7 +352,8 @@ else if (snapshot.isClass(suspectId))
344352
String classloaderName = getClassLoaderName(suspectClassloader, keywords);
345353

346354
overview.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_Class, //
347-
HTMLUtils.escapeText(className), classloaderName, formatRetainedHeap(suspect.getSuspectRetained(), totalHeap)));
355+
HTMLUtils.escapeText(className), classloaderName,
356+
formatRetainedHeap(suspect.getSuspectRetained(), totalHeap), retainedHeapTopConsumers));
348357
}
349358
else
350359
{
@@ -359,7 +368,8 @@ else if (snapshot.isClass(suspectId))
359368
String classloaderName = getClassLoaderName(suspectClassloader, keywords);
360369

361370
overview.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_Instance, //
362-
HTMLUtils.escapeText(className), classloaderName, formatRetainedHeap(suspect.getSuspectRetained(), totalHeap)));
371+
HTMLUtils.escapeText(className), classloaderName,
372+
formatRetainedHeap(suspect.getSuspectRetained(), totalHeap), retainedHeapTopConsumers));
363373

364374
/*
365375
* if the class name matches the skip pattern, try to find the first
@@ -398,7 +408,12 @@ else if (snapshot.isClass(referrerId))
398408
isThreadRelated = true;
399409
suspectId = referrerId;
400410
IObject suspectObject = snapshot.getObject(suspectId);
401-
suspect = new SuspectRecord(suspectObject, suspectObject.getRetainedHeapSize(), suspect.getAccumulationPoint());
411+
suspect = new SuspectRecord(suspectObject, suspectObject.getRetainedHeapSize(),
412+
suspect.getAccumulationPoint());
413+
414+
retainedHeapTopConsumers = getRetainedHeapTopConsumersDescription(
415+
suspect.getSuspect().getObjectId(), listener,
416+
top_min_retained_set_consumers);
402417
}
403418
className = referrer.getClazz().getName();
404419
keywords.add(className);
@@ -413,7 +428,8 @@ else if (snapshot.isClass(referrerId))
413428
overview.append("<p>"); //$NON-NLS-1$
414429
overview.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_Thread, //
415430
HTMLUtils.escapeText(suspect.getSuspect().getDisplayName()), //
416-
formatRetainedHeap(suspect.getSuspectRetained(), totalHeap)));
431+
formatRetainedHeap(suspect.getSuspectRetained(), totalHeap),
432+
retainedHeapTopConsumers));
417433
overview.append("</p>"); //$NON-NLS-1$
418434
}
419435
}
@@ -526,7 +542,8 @@ else if (snapshot.isClass(accumulationPointId))
526542
overview.append(" "); //$NON-NLS-1$
527543
overview.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_Thread, //
528544
HTMLUtils.escapeText(suspect2.getSuspect().getDisplayName()), //
529-
formatRetainedHeap(suspect2.getSuspectRetained(), totalHeap)));
545+
formatRetainedHeap(suspect2.getSuspectRetained(), totalHeap),
546+
retainedHeapTopConsumers));
530547
overview.append("</p>"); //$NON-NLS-1$
531548
threadDetails = extractThreadData(suspect2, keywords, objectsForTroubleTicketInfo, overview, overviewResult, monitor.nextMonitor());
532549
threadObj = suspect2.getSuspect();
@@ -590,6 +607,61 @@ else if (snapshot.isClass(accumulationPointId))
590607
return composite;
591608
}
592609

610+
private String getRetainedHeapTopConsumersDescription(int object, IProgressListener listener, int topItems)
611+
throws SnapshotException
612+
{
613+
return getRetainedHeapTopConsumersDescription(new int[] { object }, listener, topItems);
614+
}
615+
616+
private String getRetainedHeapTopConsumersDescription(int[] objects, IProgressListener listener, int topItems)
617+
throws SnapshotException
618+
{
619+
if (objects.length == 0)
620+
{ return Messages.LeakHunterQuery_RetainedHeapComponentEmpty; }
621+
int[] minRetainedSet = snapshot.getMinRetainedSet(objects, listener);
622+
623+
Map<String, Long> sumHeapSizes = new HashMap<>();
624+
Map<String, Integer> counts = new HashMap<>();
625+
for (int objectId : minRetainedSet)
626+
{
627+
IObject object = snapshot.getObject(objectId);
628+
String name = snapshot.isClass(objectId) ? ((IClass) object).getName() : object.getClazz().getName();
629+
sumHeapSizes.merge(name, snapshot.getHeapSize(objectId), Long::sum);
630+
counts.merge(name, 1, Integer::sum);
631+
}
632+
StringBuilder result = new StringBuilder();
633+
Set<Entry<String, Long>> entries = sumHeapSizes.entrySet();
634+
boolean multipleItems = entries.size() > 1 && topItems > 1;
635+
PriorityQueue<Entry<String, Long>> pq = new PriorityQueue<Entry<String, Long>>(entries.size(),
636+
(e1, e2) -> e2.getValue().compareTo(e1.getValue()));
637+
pq.addAll(entries);
638+
int maxItems = Math.min(pq.size(), topItems);
639+
while (maxItems-- > 0 && pq.size() > 0)
640+
{
641+
Entry<String, Long> entry = pq.poll();
642+
if (result.length() > 0)
643+
{
644+
result.append(", "); //$NON-NLS-1$
645+
}
646+
if (maxItems == 0 && multipleItems)
647+
{
648+
result.append(Messages.LeakHunterQuery_RetainedHeapComponentAnd);
649+
}
650+
int count = counts.get(entry.getKey());
651+
if (count == 1)
652+
{
653+
result.append(MessageUtil.format(Messages.LeakHunterQuery_RetainedHeapComponentInstance,
654+
HTMLUtils.escapeText(entry.getKey()), count, bytesFormatter.format(entry.getValue())));
655+
}
656+
else
657+
{
658+
result.append(MessageUtil.format(Messages.LeakHunterQuery_RetainedHeapComponentInstances,
659+
HTMLUtils.escapeText(entry.getKey()), count, bytesFormatter.format(entry.getValue())));
660+
}
661+
}
662+
return result.toString();
663+
}
664+
593665
private void addCommand(QuerySpec spec, String command, int suspects[])
594666
{
595667
if (suspects.length > 0)
@@ -665,11 +737,16 @@ private CompositeResult getLeakDescriptionGroupOfObjects(SuspectRecordGroupOfObj
665737

666738
String classloaderName = getClassLoaderName(classloader, keywords);
667739

668-
String numberOfInstances = numberFormatter.format(suspect.getSuspectInstances().length);
669-
builder.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_InstancesOccupy, numberOfInstances, HTMLUtils.escapeText(className),
670-
classloaderName, formatRetainedHeap(suspect.getSuspectRetained(), totalHeap)));
671-
672740
int[] suspectInstances = suspect.getSuspectInstances();
741+
String numberOfInstances = numberFormatter.format(suspectInstances.length);
742+
743+
String retainedHeapTopConsumers = getRetainedHeapTopConsumersDescription(suspectInstances, listener,
744+
top_min_retained_set_consumers);
745+
746+
builder.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_InstancesOccupy, numberOfInstances,
747+
HTMLUtils.escapeText(className), classloaderName,
748+
formatRetainedHeap(suspect.getSuspectRetained(), totalHeap), retainedHeapTopConsumers));
749+
673750
List<IObject> bigSuspectInstances = new ArrayList<IObject>();
674751
for (int j = 0; j < suspectInstances.length; j++)
675752
{
@@ -777,6 +854,9 @@ else if (snapshot.isClass(referrerId))
777854
int suspectId = referrerId;
778855
IObject suspectObject = snapshot.getObject(suspectId);
779856
suspect2 = new SuspectRecord(suspectObject, suspectObject.getRetainedHeapSize(), suspect.getAccumulationPoint());
857+
retainedHeapTopConsumers = getRetainedHeapTopConsumersDescription(
858+
suspect2.getSuspect().getObjectId(), listener,
859+
top_min_retained_set_consumers);
780860
}
781861
className = referrer.getClazz().getName();
782862
keywords.add(className);
@@ -791,7 +871,8 @@ else if (snapshot.isClass(referrerId))
791871
builder.append("<p>"); //$NON-NLS-1$
792872
builder.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_Thread, //
793873
HTMLUtils.escapeText(suspect2.getSuspect().getDisplayName()), //
794-
formatRetainedHeap(suspect2.getSuspectRetained(), totalHeap)));
874+
formatRetainedHeap(suspect2.getSuspectRetained(), totalHeap),
875+
retainedHeapTopConsumers));
795876
builder.append("</p>"); //$NON-NLS-1$
796877
}
797878
}
@@ -952,6 +1033,9 @@ else if (path.length > 1)
9521033
}
9531034
IObject suspectObject = snapshot.getObject(path[0]);
9541035
SuspectRecord suspect2 = new SuspectRecord(suspectObject, suspectObject.getRetainedHeapSize(), ap);
1036+
retainedHeapTopConsumers = getRetainedHeapTopConsumersDescription(
1037+
suspect2.getSuspect().getObjectId(), listener, top_min_retained_set_consumers);
1038+
9551039
objectsForTroubleTicketInfo.add(suspect2.getSuspect());
9561040
// Description
9571041
builder.append("<p>"); //$NON-NLS-1$
@@ -971,7 +1055,8 @@ else if (path.length > 1)
9711055
builder.append(" "); //$NON-NLS-1$
9721056
builder.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_Thread, //
9731057
HTMLUtils.escapeText(suspect2.getSuspect().getDisplayName()), //
974-
formatRetainedHeap(suspect2.getSuspectRetained(), totalHeap)));
1058+
formatRetainedHeap(suspect2.getSuspectRetained(), totalHeap),
1059+
retainedHeapTopConsumers));
9751060
builder.append("</p>"); //$NON-NLS-1$
9761061
threadDetails = extractThreadData(suspect2, keywords, objectsForTroubleTicketInfo, builder, overviewResult, monitor.nextMonitor());
9771062
threadObj = suspect2.getSuspect();
@@ -1303,13 +1388,19 @@ public String getOQL()
13031388

13041389
private void appendKeywords(Set<String> keywords, StringBuilder builder)
13051390
{
1306-
String title = Messages.LeakHunterQuery_Keywords;
1307-
builder.append("<p><strong>").append(title).append("</strong>"); //$NON-NLS-1$ //$NON-NLS-2$
1308-
builder.append("</p>"); //$NON-NLS-1$
1309-
builder.append("<ul style=\"list-style-type:none;\" title=\"").append(escapeHTMLAttribute(title)).append("\">"); //$NON-NLS-1$ //$NON-NLS-2$
1310-
for (String s : keywords)
1311-
builder.append("<li>").append(HTMLUtils.escapeText(s)).append("</li>"); //$NON-NLS-1$ //$NON-NLS-2$
1312-
builder.append("</ul>"); //$NON-NLS-1$
1391+
boolean useKeywords = Boolean.parseBoolean(System
1392+
.getProperty("org.eclipse.mat.inspections.LeakHunterQuery.keywords", Boolean.TRUE.toString())); //$NON-NLS-1$
1393+
if (useKeywords)
1394+
{
1395+
String title = Messages.LeakHunterQuery_Keywords;
1396+
builder.append("<p><strong>").append(title).append("</strong>"); //$NON-NLS-1$ //$NON-NLS-2$
1397+
builder.append("</p>"); //$NON-NLS-1$
1398+
builder.append("<ul style=\"list-style-type:none;\" title=\"").append(escapeHTMLAttribute(title)) //$NON-NLS-1$
1399+
.append("\">"); //$NON-NLS-1$
1400+
for (String s : keywords)
1401+
builder.append("<li>").append(HTMLUtils.escapeText(s)).append("</li>"); //$NON-NLS-1$ //$NON-NLS-2$
1402+
builder.append("</ul>"); //$NON-NLS-1$
1403+
}
13131404
}
13141405

13151406
private void appendTroubleTicketInformation(List<IObject> classloaders, StringBuilder builder, IProgressListener listener)

plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/annotations.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ LeakHunterQuery.threshold_percent.help = Big memory chunk size in percent of the
174174
LeakHunterQuery.max_paths.help = Number of paths to garbage collection roots. (Default value is 10000.)
175175
LeakHunterQuery.excludes.help = Fields of certain classes which should be ignored when finding paths. \
176176
For example this allows paths through Weak or Soft Reference referents to be ignored.
177+
LeakHunterQuery.top_min_retained_set_consumers.help = Number of top consumers of a suspect's minimum retained set to report. (Default value is 3.)
177178

178179
LeakHunterQuery2.name = Find Leaks between Snapshots
179180
LeakHunterQuery2.category = Leak Identification

plugins/org.eclipse.mat.api/src/org/eclipse/mat/internal/Messages.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,10 @@ public class Messages extends NLS
481481
public static String LeakHunterQuery_ReferencePattern;
482482
public static String LeakHunterQuery_ReferencePatternFor;
483483
public static String LeakHunterQuery_RequestDetails;
484+
public static String LeakHunterQuery_RetainedHeapComponentAnd;
485+
public static String LeakHunterQuery_RetainedHeapComponentEmpty;
486+
public static String LeakHunterQuery_RetainedHeapComponentInstance;
487+
public static String LeakHunterQuery_RetainedHeapComponentInstances;
484488
public static String LeakHunterQuery_Retains;
485489
public static String LeakHunterQuery_SeeStackstrace;
486490
public static String LeakHunterQuery_SeeStackstraceVars;

0 commit comments

Comments
 (0)