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
2323import java .util .LinkedList ;
2424import java .util .List ;
2525import java .util .Map ;
26+ import java .util .Map .Entry ;
27+ import java .util .PriorityQueue ;
2628import java .util .Set ;
2729import java .util .Stack ;
2830import 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 )
0 commit comments