1 /*
   2  * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
   3  *
   4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   5  *
   6  * The contents of this file are subject to the terms of either the Universal Permissive License
   7  * v 1.0 as shown at http://oss.oracle.com/licenses/upl
   8  *
   9  * or the following license:
  10  *
  11  * Redistribution and use in source and binary forms, with or without modification, are permitted
  12  * provided that the following conditions are met:
  13  *
  14  * 1. Redistributions of source code must retain the above copyright notice, this list of conditions
  15  * and the following disclaimer.
  16  *
  17  * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
  18  * conditions and the following disclaimer in the documentation and/or other materials provided with
  19  * the distribution.
  20  *
  21  * 3. Neither the name of the copyright holder nor the names of its contributors may be used to
  22  * endorse or promote products derived from this software without specific prior written permission.
  23  *
  24  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
  25  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
  26  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
  27  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  29  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  30  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
  31  * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  32  */
  33 package org.openjdk.jmc.flightrecorder.memleak;
  34 
  35 import java.lang.reflect.Modifier;
  36 import java.util.ArrayList;
  37 import java.util.Collections;
  38 import java.util.HashMap;
  39 import java.util.HashSet;
  40 import java.util.List;
  41 import java.util.Map;
  42 import java.util.Set;
  43 
  44 import org.openjdk.jmc.common.IMCOldObject;
  45 import org.openjdk.jmc.common.IMCOldObjectArray;
  46 import org.openjdk.jmc.common.IMCOldObjectField;
  47 import org.openjdk.jmc.common.IMCOldObjectGcRoot;
  48 import org.openjdk.jmc.common.IMCType;
  49 import org.openjdk.jmc.common.item.IItem;
  50 import org.openjdk.jmc.common.unit.IQuantity;
  51 
  52 /**
  53  * A data type representing alive objects in a Java heap used in a {@link ReferenceTreeModel}.
  54  */
  55 public class ReferenceTreeObject implements IMCOldObject {
  56 
  57         public enum ReferenceTreeObjectType {
  58                 Array, InstanceField, JavaObject, LeakObject
  59         }
  60 
  61         private final List<ReferenceTreeObject> children = new ArrayList<>();
  62         private final Set<IItem> items = new HashSet<>();
  63         private final Map<IQuantity, ReferenceTreeObject> leafNodes = new HashMap<>();
  64 
  65         private String rootDescription;
  66         private IMCOldObject object;
  67         private int objectsKeptAliveCount;
  68         private ReferenceTreeObject parent;
  69         private IQuantity timestamp;
  70         private double leakRelevance;
  71         private int distanceFromRoot;
  72         public static final int FORMAT_PACKAGE = 0b00001;
  73         public static final int FORMAT_FIELD = 0b00010;
  74         public static final int FORMAT_STATIC_MODIFIER = 0b00100;
  75         public static final int FORMAT_OTHER_MODIFIERS = 0b01000;
  76         public static final int FORMAT_ARRAY_INFO = 0b10000;
  77 
  78         /**
  79          * @param timestamp
  80          *            a timestamp representing when the object was allocated
  81          * @param object
  82          *            the object itself
  83          */
  84         ReferenceTreeObject(IQuantity timestamp, IMCOldObject object) {
  85                 this.timestamp = timestamp;
  86                 this.object = object;
  87                 leakRelevance = -1;
  88                 distanceFromRoot = 0;
  89         }
  90 
  91         /**
  92          * @param distance
  93          *            the distance from the root object
  94          */
  95         void setDistanceFromRoot(int distance) {
  96                 this.distanceFromRoot = distance;
  97         }
  98 
  99         /**
 100          * @return the number of steps in a referral chain from the root object
 101          */
 102         public int getDistanceFromRoot() {
 103                 return distanceFromRoot;
 104         }
 105 
 106         /**
 107          * @param relevance
 108          *            how relevant this object is as a leak candidate
 109          */
 110         void setLeakRelevance(double relevance) {
 111                 this.leakRelevance = relevance;
 112         }
 113 
 114         /**
 115          * @return the relevance of this object for memory leak detection
 116          */
 117         public double getLeakRelevance() {
 118                 return this.leakRelevance;
 119         }
 120 
 121         /**
 122          * @param node
 123          *            a child to be added to this object
 124          */
 125         void addChild(ReferenceTreeObject node) {
 126                 if (!children.contains(node)) {
 127                         children.add(node);
 128                         node.setParent(this);
 129                 }
 130         }
 131 
 132         /**
 133          * @param item
 134          *            an item this object keeps alive
 135          */
 136         void addItem(IItem item) {
 137                 items.add(item);
 138         }
 139 
 140         /**
 141          * This method is used when it is necessary to get information about all objects this object
 142          * keeps alive in the {@link ReferenceTreeModel}. E.g. the Mission Control GUI uses this when a
 143          * user selects a row in the tree to show everything below it as well in the properties view.
 144          *
 145          * @return a set representing all {@link IItem} objects this object keeps alive, including
 146          *         itself
 147          */
 148         public Set<IItem> getItems() {
 149                 return items;
 150         }
 151 
 152         /**
 153          * @param root
 154          *            a GC root description
 155          */
 156         void addRoot(IMCOldObjectGcRoot root) {
 157                 if (root != null) {
 158                         rootDescription = root.toString();
 159                 }
 160         }
 161 
 162         /**
 163          * @return the GC root description
 164          */
 165         public String getRootDescription() {
 166                 return rootDescription;
 167         }
 168 
 169         @Override
 170         public IQuantity getAddress() {
 171                 return object.getAddress();
 172         }
 173 
 174         @Override
 175         public IMCOldObjectArray getReferrerArray() {
 176                 return object.getReferrerArray();
 177         }
 178 
 179         /**
 180          * @return if this object is an array, gets information representing that array, {@code null}
 181          *         otherwise
 182          */
 183         public IMCOldObjectArray getArray() {
 184                 if (getChildren().size() > 0) {
 185                         return getChildren().get(0).getReferrerArray();
 186                 }
 187                 return null;
 188         }
 189 
 190         /**
 191          * @return the children of this object
 192          */
 193         public List<ReferenceTreeObject> getChildren() {
 194                 return children;
 195         }
 196 
 197         @Override
 198         public IMCOldObjectField getReferrerField() {
 199                 return object.getReferrerField();
 200         }
 201 
 202         /**
 203          * @return if this object is a field, gets information representing that field, {@code null}
 204          *         otherwise
 205          */
 206         public IMCOldObjectField getField() {
 207                 if (getChildren().size() > 0) {
 208                         return getChildren().get(0).getReferrerField();
 209                 }
 210                 return null;
 211         }
 212 
 213         @Override
 214         public String getDescription() {
 215                 return object.getDescription();
 216         }
 217 
 218         /**
 219          * @return the number of objects this object keeps alive
 220          */
 221         public int getObjectsKeptAliveCount() {
 222                 return objectsKeptAliveCount;
 223         }
 224 
 225         /**
 226          * This method returns an object that is keeping this object alive.
 227          *
 228          * @return the object linking to this object from the direction of the gc root
 229          */
 230         public ReferenceTreeObject getParent() {
 231                 return parent;
 232         }
 233 
 234         @Override
 235         public IMCOldObject getReferrer() {
 236                 return object.getReferrer();
 237         }
 238 
 239         /**
 240          * @return the timestamp this object was allocated
 241          */
 242         public IQuantity getTimestamp() {
 243                 return timestamp;
 244         }
 245 
 246         @Override
 247         public IMCType getType() {
 248                 return object.getType();
 249         }
 250 
 251         @Override
 252         public int hashCode() {
 253                 return object.hashCode();
 254         }
 255 
 256         @Override
 257         public boolean equals(Object o) {
 258                 if (o instanceof ReferenceTreeObject) {
 259                         ReferenceTreeObject that = (ReferenceTreeObject) o;
 260                         return that.getAddress().equals(this.getAddress());
 261                 }
 262                 return false;
 263         }
 264 
 265         /**
 266          * Increments the number of objects this object keeps alive.
 267          */
 268         void incrementObjectsKeptAliveCount() {
 269                 objectsKeptAliveCount++;
 270         }
 271 
 272         /**
 273          * @param parent
 274          *            the parent of this object
 275          */
 276         public void setParent(ReferenceTreeObject parent) {
 277                 this.parent = parent;
 278         }
 279 
 280         /**
 281          * @return the Root object
 282          */
 283         public ReferenceTreeObject getRootObject() {
 284                 if (this.parent == null) {
 285                         return this;
 286                 } else {
 287                         ReferenceTreeObject rto = this.parent;
 288                         while (rto.getParent() != null) {
 289                                 rto = rto.getParent();
 290                         }
 291                         return rto;
 292                 }
 293         }
 294 
 295         @Override
 296         public int getReferrerSkip() {
 297                 return object.getReferrerSkip();
 298         }
 299 
 300         /**
 301          * @return Map containing allocation time and its leafnode object
 302          */
 303         public Map<IQuantity, ReferenceTreeObject> getLeafNodes() {
 304                 return Collections.unmodifiableMap(leafNodes);
 305         }
 306 
 307         /**
 308          * @param leafNode
 309          *            Object which is leaked
 310          */
 311         public void updateLeafNode(ReferenceTreeObject leafNode) {
 312                 leafNodes.put(leafNode.getTimestamp(), leafNode);
 313         }
 314 
 315         /**
 316          * Returns a string representation of this object.
 317          *
 318          * @param displayFormatting
 319          *            an int describing how this object is to be represented, using bitwise masking of
 320          *            constants defined in {@link ReferenceTreeObject}
 321          * @return a human readable string representation of this object
 322          */
 323         public String toString(int displayFormatting) {
 324                 String text = getType().getTypeName();
 325                 if ((displayFormatting & ReferenceTreeObject.FORMAT_PACKAGE) != 0) {
 326                         text = getType().getFullName();
 327                 }
 328                 if (this.getChildren().size() > 0 && getField() != null) {
 329                         if ((displayFormatting & ReferenceTreeObject.FORMAT_FIELD) != 0) {
 330                                 text = text + "." + getField().getName(); //$NON-NLS-1$
 331                         }
 332                         if (getField().getModifier() != null) {
 333                                 String modifiers = Modifier.toString(getField().getModifier());
 334                                 if ((displayFormatting & ReferenceTreeObject.FORMAT_STATIC_MODIFIER) != 0
 335                                                 && (displayFormatting & ReferenceTreeObject.FORMAT_OTHER_MODIFIERS) == 0) {
 336                                         if (modifiers.contains("static")) { //$NON-NLS-1$
 337                                                 text = "static " + text; //$NON-NLS-1$
 338                                         }
 339                                 } else if ((displayFormatting & ReferenceTreeObject.FORMAT_OTHER_MODIFIERS) != 0
 340                                                 && (displayFormatting & ReferenceTreeObject.FORMAT_STATIC_MODIFIER) == 0) {
 341                                         String nonStaticModifiers = modifiers.replaceAll("static ", ""); //$NON-NLS-1$ //$NON-NLS-2$
 342                                         if (!"".equals(nonStaticModifiers)) { //$NON-NLS-1$
 343                                                 text = nonStaticModifiers + " " + text; //$NON-NLS-1$
 344                                         }
 345                                 } else if ((displayFormatting & ReferenceTreeObject.FORMAT_STATIC_MODIFIER) != 0
 346                                                 && (displayFormatting & ReferenceTreeObject.FORMAT_OTHER_MODIFIERS) != 0) {
 347                                         if (!"".equals(modifiers)) { //$NON-NLS-1$
 348                                                 text = modifiers + " " + text; //$NON-NLS-1$
 349                                         }
 350                                 }
 351                         }
 352                 }
 353                 if ((displayFormatting & ReferenceTreeObject.FORMAT_ARRAY_INFO) != 0 && this.getArray() != null) {
 354                         if (text.endsWith("[]")) { //$NON-NLS-1$
 355                                 text = text.substring(0, text.length() - 1) + getArray().getIndex() + "/" + getArray().getSize() + "]"; //$NON-NLS-1$ //$NON-NLS-2$
 356                         } else {
 357                                 text = text + getArray().getIndex() + "/" + getArray().getSize(); //$NON-NLS-1$
 358                         }
 359                 }
 360                 return text.trim();
 361         }
 362 
 363 }